mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
fix: Controller wraps handler with try/catch (#909)
By having the controller perform try/catch around the handler function allows us to add extra safety to all our controllers and safeguards that we will always catch exceptions thrown by a controller method.
This commit is contained in:
parent
0faa0cd075
commit
2bcdb5ec31
@ -46,3 +46,15 @@ When you're done making changes and you'd like to propose them for review by ope
|
||||
Congratulations! The whole Unleash community thanks you. :sparkles:
|
||||
|
||||
Once your PR is merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/unleash/Unleash/graphs/contributors).
|
||||
|
||||
## Nice to know
|
||||
|
||||
### Controllers
|
||||
|
||||
In order to handle HTTP requests we have an abstraction called [Controller](https://github.com/Unleash/unleash/blob/master/src/lib/routes/controller.ts). If you want to introduce a new route handler for a specific path (and sub pats) you should implement a controller class which extends the base Controller. An example to follow is the [routes/admin-api/feature.ts](https://github.com/Unleash/unleash/blob/master/src/lib/routes/admin-api/feature.ts) implementation.
|
||||
|
||||
The controller takes care of the following:
|
||||
- try/catch RequestHandler method
|
||||
- error handling with proper response code if they fail
|
||||
- `await` the RequestHandler method if it returns a promise (so you don't have to)
|
||||
- access control so that you can just list the required permission for a RequestHandler and the base Controller will make sure the user have these permissions.
|
@ -1,5 +1,5 @@
|
||||
import joi from 'joi';
|
||||
import { nameType } from '../routes/admin-api/util';
|
||||
import { nameType } from '../routes/util';
|
||||
import { tagTypeSchema } from '../services/tag-type-schema';
|
||||
|
||||
export const addonDefinitionSchema = joi.object().keys({
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { IAuthRequest } from 'lib/routes/unleash-types';
|
||||
import supertest from 'supertest';
|
||||
import express from 'express';
|
||||
import noAuthentication from './no-authentication';
|
||||
import { IUserRequest } from '../routes/admin-api/user';
|
||||
|
||||
test('should add dummy user object to all requests', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const app = express();
|
||||
noAuthentication('', app);
|
||||
app.get('/api/admin/test', (req: IUserRequest<any, any, any, any>, res) => {
|
||||
app.get('/api/admin/test', (req: IAuthRequest<any, any, any, any>, res) => {
|
||||
const user = { ...req.user };
|
||||
|
||||
return res.status(200).json(user).end();
|
||||
|
@ -6,7 +6,6 @@ import { Logger } from '../../logger';
|
||||
import AddonService from '../../services/addon-service';
|
||||
|
||||
import extractUser from '../../extract-user';
|
||||
import { handleErrors } from './util';
|
||||
import {
|
||||
CREATE_ADDON,
|
||||
UPDATE_ADDON,
|
||||
@ -34,13 +33,9 @@ class AddonController extends Controller {
|
||||
}
|
||||
|
||||
async getAddons(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const addons = await this.addonService.getAddons();
|
||||
const providers = this.addonService.getProviderDefinitions();
|
||||
res.json({ addons, providers });
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
const addons = await this.addonService.getAddons();
|
||||
const providers = this.addonService.getProviderDefinitions();
|
||||
res.json({ addons, providers });
|
||||
}
|
||||
|
||||
async getAddon(
|
||||
@ -48,12 +43,8 @@ class AddonController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
const addon = await this.addonService.getAddon(id);
|
||||
res.json(addon);
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
const addon = await this.addonService.getAddon(id);
|
||||
res.json(addon);
|
||||
}
|
||||
|
||||
async updateAddon(
|
||||
@ -64,27 +55,15 @@ class AddonController extends Controller {
|
||||
const createdBy = extractUser(req);
|
||||
const data = req.body;
|
||||
|
||||
try {
|
||||
const addon = await this.addonService.updateAddon(
|
||||
id,
|
||||
data,
|
||||
createdBy,
|
||||
);
|
||||
res.status(200).json(addon);
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
const addon = await this.addonService.updateAddon(id, data, createdBy);
|
||||
res.status(200).json(addon);
|
||||
}
|
||||
|
||||
async createAddon(req: Request, res: Response): Promise<void> {
|
||||
const createdBy = extractUser(req);
|
||||
const data = req.body;
|
||||
try {
|
||||
const addon = await this.addonService.createAddon(data, createdBy);
|
||||
res.status(201).json(addon);
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
const addon = await this.addonService.createAddon(data, createdBy);
|
||||
res.status(201).json(addon);
|
||||
}
|
||||
|
||||
async deleteAddon(
|
||||
@ -93,12 +72,8 @@ class AddonController extends Controller {
|
||||
): Promise<void> {
|
||||
const { id } = req.params;
|
||||
const username = extractUser(req);
|
||||
try {
|
||||
await this.addonService.removeAddon(id, username);
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.addonService.removeAddon(id, username);
|
||||
res.status(200).end();
|
||||
}
|
||||
}
|
||||
export default AddonController;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { handleErrors } from './util';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
import { Logger } from '../../logger';
|
||||
@ -36,13 +35,10 @@ export default class ArchiveController extends Controller {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async getArchivedFeatures(req, res): Promise<void> {
|
||||
try {
|
||||
const features =
|
||||
await this.featureService.getMetadataForAllFeatures(true);
|
||||
res.json({ version: 2, features });
|
||||
} catch (err) {
|
||||
handleErrors(res, this.logger, err);
|
||||
}
|
||||
const features = await this.featureService.getMetadataForAllFeatures(
|
||||
true,
|
||||
);
|
||||
res.json({ version: 2, features });
|
||||
}
|
||||
|
||||
async deleteFeature(
|
||||
@ -51,25 +47,16 @@ export default class ArchiveController extends Controller {
|
||||
): Promise<void> {
|
||||
const { featureName } = req.params;
|
||||
const user = extractUser(req);
|
||||
try {
|
||||
await this.featureService.deleteFeature(featureName, user);
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.featureService.deleteFeature(featureName, user);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async reviveFeatureToggle(req, res): Promise<void> {
|
||||
const userName = extractUser(req);
|
||||
const { featureName } = req.params;
|
||||
|
||||
try {
|
||||
await this.featureService.reviveToggle(featureName, userName);
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.featureService.reviveToggle(featureName, userName);
|
||||
res.status(200).end();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ import { Request, Response } from 'express';
|
||||
|
||||
import Controller from '../controller';
|
||||
|
||||
import { handleErrors } from './util';
|
||||
import extractUser from '../../extract-user';
|
||||
|
||||
import {
|
||||
@ -45,12 +44,8 @@ class ContextController extends Controller {
|
||||
}
|
||||
|
||||
async getContextFields(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const fields = await this.contextService.getAll();
|
||||
res.status(200).json(fields).end();
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const fields = await this.contextService.getAll();
|
||||
res.status(200).json(fields).end();
|
||||
}
|
||||
|
||||
async getContextField(req: Request, res: Response): Promise<void> {
|
||||
@ -69,12 +64,8 @@ class ContextController extends Controller {
|
||||
const value = req.body;
|
||||
const userName = extractUser(req);
|
||||
|
||||
try {
|
||||
await this.contextService.createContextField(value, userName);
|
||||
res.status(201).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.contextService.createContextField(value, userName);
|
||||
res.status(201).end();
|
||||
}
|
||||
|
||||
async updateContextField(req: Request, res: Response): Promise<void> {
|
||||
@ -84,38 +75,23 @@ class ContextController extends Controller {
|
||||
|
||||
contextField.name = name;
|
||||
|
||||
try {
|
||||
await this.contextService.updateContextField(
|
||||
contextField,
|
||||
userName,
|
||||
);
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.contextService.updateContextField(contextField, userName);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async deleteContextField(req: Request, res: Response): Promise<void> {
|
||||
const name = req.params.contextField;
|
||||
const userName = extractUser(req);
|
||||
|
||||
try {
|
||||
await this.contextService.deleteContextField(name, userName);
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.contextService.deleteContextField(name, userName);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async validate(req: Request, res: Response): Promise<void> {
|
||||
const { name } = req.body;
|
||||
|
||||
try {
|
||||
await this.contextService.validateName(name);
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.contextService.validateName(name);
|
||||
res.status(200).end();
|
||||
}
|
||||
}
|
||||
export default ContextController;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ADMIN } from '../../types/permissions';
|
||||
import { TemplateFormat } from '../../services/email-service';
|
||||
import { handleErrors } from './util';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
|
||||
@ -20,40 +19,32 @@ export default class EmailController extends Controller {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async getHtmlPreview(req, res): Promise<void> {
|
||||
try {
|
||||
const { template } = req.params;
|
||||
const ctx = req.query;
|
||||
const data = await this.emailService.compileTemplate(
|
||||
template,
|
||||
TemplateFormat.HTML,
|
||||
ctx,
|
||||
);
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.status(200);
|
||||
res.send(data);
|
||||
res.end();
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const { template } = req.params;
|
||||
const ctx = req.query;
|
||||
const data = await this.emailService.compileTemplate(
|
||||
template,
|
||||
TemplateFormat.HTML,
|
||||
ctx,
|
||||
);
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.status(200);
|
||||
res.send(data);
|
||||
res.end();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async getTextPreview(req, res) {
|
||||
try {
|
||||
const { template } = req.params;
|
||||
const ctx = req.query;
|
||||
const data = await this.emailService.compileTemplate(
|
||||
template,
|
||||
TemplateFormat.PLAIN,
|
||||
ctx,
|
||||
);
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.status(200);
|
||||
res.send(data);
|
||||
res.end();
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const { template } = req.params;
|
||||
const ctx = req.query;
|
||||
const data = await this.emailService.compileTemplate(
|
||||
template,
|
||||
TemplateFormat.PLAIN,
|
||||
ctx,
|
||||
);
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.status(200);
|
||||
res.send(data);
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
module.exports = EmailController;
|
||||
|
@ -5,7 +5,7 @@ import { IUnleashConfig } from '../../types/option';
|
||||
import { IEnvironment } from '../../types/model';
|
||||
import EnvironmentService from '../../services/environment-service';
|
||||
import { Logger } from '../../logger';
|
||||
import { handleErrors } from './util';
|
||||
import { handleErrors } from '../util';
|
||||
import { ADMIN } from '../../types/permissions';
|
||||
|
||||
interface EnvironmentParam {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { handleErrors } from './util';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
import EventService from '../../services/event-service';
|
||||
@ -25,31 +24,21 @@ export default class EventController extends Controller {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async getEvents(req, res): Promise<void> {
|
||||
try {
|
||||
const events = await this.eventService.getEvents();
|
||||
eventDiffer.addDiffs(events);
|
||||
res.json({ version, events });
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const events = await this.eventService.getEvents();
|
||||
eventDiffer.addDiffs(events);
|
||||
res.json({ version, events });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async getEventsForToggle(req, res): Promise<void> {
|
||||
const toggleName = req.params.name;
|
||||
try {
|
||||
const events = await this.eventService.getEventsForToggle(
|
||||
toggleName,
|
||||
);
|
||||
const events = await this.eventService.getEventsForToggle(toggleName);
|
||||
|
||||
if (events) {
|
||||
eventDiffer.addDiffs(events);
|
||||
res.json({ toggleName, events });
|
||||
} else {
|
||||
res.status(404).json({ error: 'Could not find events' });
|
||||
}
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
if (events) {
|
||||
eventDiffer.addDiffs(events);
|
||||
res.json({ toggleName, events });
|
||||
} else {
|
||||
res.status(404).json({ error: 'Could not find events' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { handleErrors } from './util';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
import FeatureTypeService from '../../services/feature-type-service';
|
||||
import { Logger } from '../../logger';
|
||||
@ -26,12 +25,8 @@ export default class FeatureTypeController extends Controller {
|
||||
}
|
||||
|
||||
async getAllFeatureTypes(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const types = await this.featureTypeService.getAll();
|
||||
res.json({ version, types });
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const types = await this.featureTypeService.getAll();
|
||||
res.json({ version, types });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ import { Request, Response } from 'express';
|
||||
|
||||
import Controller from '../controller';
|
||||
|
||||
import { handleErrors } from './util';
|
||||
import extractUser from '../../extract-user';
|
||||
import {
|
||||
UPDATE_FEATURE,
|
||||
@ -94,129 +93,95 @@ class FeatureController extends Controller {
|
||||
|
||||
async getAllToggles(req: Request, res: Response): Promise<void> {
|
||||
const query = await this.prepQuery(req.query);
|
||||
try {
|
||||
const features = await this.featureService2.getFeatureToggles(
|
||||
query,
|
||||
);
|
||||
const features = await this.featureService2.getFeatureToggles(query);
|
||||
|
||||
res.json({ version, features });
|
||||
} catch (err) {
|
||||
handleErrors(res, this.logger, err);
|
||||
}
|
||||
res.json({ version, features });
|
||||
}
|
||||
|
||||
async getToggle(
|
||||
req: Request<{ featureName: string }, any, any, any>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const name = req.params.featureName;
|
||||
const feature = await this.featureService2.getFeatureToggle(name);
|
||||
const strategies =
|
||||
feature.environments.find((e) => e.name === GLOBAL_ENV)
|
||||
?.strategies || [];
|
||||
res.json({
|
||||
...feature,
|
||||
strategies,
|
||||
}).end();
|
||||
} catch (err) {
|
||||
handleErrors(res, this.logger, err);
|
||||
}
|
||||
const name = req.params.featureName;
|
||||
const feature = await this.featureService2.getFeatureToggle(name);
|
||||
const strategies =
|
||||
feature.environments.find((e) => e.name === GLOBAL_ENV)
|
||||
?.strategies || [];
|
||||
res.json({
|
||||
...feature,
|
||||
strategies,
|
||||
}).end();
|
||||
}
|
||||
|
||||
// TODO
|
||||
async listTags(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tags = await this.featureTagService.listTags(
|
||||
req.params.featureName,
|
||||
);
|
||||
res.json({ version, tags });
|
||||
} catch (err) {
|
||||
handleErrors(res, this.logger, err);
|
||||
}
|
||||
const tags = await this.featureTagService.listTags(
|
||||
req.params.featureName,
|
||||
);
|
||||
res.json({ version, tags });
|
||||
}
|
||||
|
||||
// TODO
|
||||
async addTag(req: Request, res: Response): Promise<void> {
|
||||
const { featureName } = req.params;
|
||||
const userName = extractUser(req);
|
||||
try {
|
||||
const tag = await this.featureTagService.addTag(
|
||||
featureName,
|
||||
req.body,
|
||||
userName,
|
||||
);
|
||||
res.status(201).json(tag);
|
||||
} catch (err) {
|
||||
handleErrors(res, this.logger, err);
|
||||
}
|
||||
const tag = await this.featureTagService.addTag(
|
||||
featureName,
|
||||
req.body,
|
||||
userName,
|
||||
);
|
||||
res.status(201).json(tag);
|
||||
}
|
||||
|
||||
// TODO
|
||||
async removeTag(req: Request, res: Response): Promise<void> {
|
||||
const { featureName, type, value } = req.params;
|
||||
const userName = extractUser(req);
|
||||
try {
|
||||
await this.featureTagService.removeTag(
|
||||
featureName,
|
||||
{ type, value },
|
||||
userName,
|
||||
);
|
||||
res.status(200).end();
|
||||
} catch (err) {
|
||||
handleErrors(res, this.logger, err);
|
||||
}
|
||||
await this.featureTagService.removeTag(
|
||||
featureName,
|
||||
{ type, value },
|
||||
userName,
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async validate(req: Request, res: Response): Promise<void> {
|
||||
const { name } = req.body;
|
||||
|
||||
try {
|
||||
await this.featureService2.validateName(name);
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.featureService2.validateName(name);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async createToggle(req: Request, res: Response): Promise<void> {
|
||||
const userName = extractUser(req);
|
||||
const toggle = req.body;
|
||||
|
||||
try {
|
||||
const validatedToggle = await featureSchema.validateAsync(toggle);
|
||||
const { enabled } = validatedToggle;
|
||||
const createdFeature =
|
||||
await this.featureService2.createFeatureToggle(
|
||||
validatedToggle.project,
|
||||
validatedToggle,
|
||||
userName,
|
||||
);
|
||||
const strategies = await Promise.all(
|
||||
toggle.strategies.map(async (s) =>
|
||||
this.featureService2.createStrategy(
|
||||
s,
|
||||
createdFeature.project,
|
||||
createdFeature.name,
|
||||
),
|
||||
const validatedToggle = await featureSchema.validateAsync(toggle);
|
||||
const { enabled } = validatedToggle;
|
||||
const createdFeature = await this.featureService2.createFeatureToggle(
|
||||
validatedToggle.project,
|
||||
validatedToggle,
|
||||
userName,
|
||||
);
|
||||
const strategies = await Promise.all(
|
||||
toggle.strategies.map(async (s) =>
|
||||
this.featureService2.createStrategy(
|
||||
s,
|
||||
createdFeature.project,
|
||||
createdFeature.name,
|
||||
),
|
||||
);
|
||||
await this.featureService2.updateEnabled(
|
||||
validatedToggle.name,
|
||||
GLOBAL_ENV,
|
||||
enabled,
|
||||
userName,
|
||||
);
|
||||
),
|
||||
);
|
||||
await this.featureService2.updateEnabled(
|
||||
validatedToggle.name,
|
||||
GLOBAL_ENV,
|
||||
enabled,
|
||||
userName,
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
...createdFeature,
|
||||
enabled,
|
||||
strategies,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.warn(error);
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
res.status(201).json({
|
||||
...createdFeature,
|
||||
enabled,
|
||||
strategies,
|
||||
});
|
||||
}
|
||||
|
||||
async updateToggle(req: Request, res: Response): Promise<void> {
|
||||
@ -230,48 +195,42 @@ class FeatureController extends Controller {
|
||||
featureName,
|
||||
);
|
||||
if (featureToggleExists) {
|
||||
try {
|
||||
await this.featureService2.getFeature(featureName);
|
||||
const projectId = await this.featureService2.getProjectId(
|
||||
updatedFeature.name,
|
||||
);
|
||||
const value = await featureSchema.validateAsync(updatedFeature);
|
||||
const { enabled } = value;
|
||||
const updatedToggle = this.featureService2.updateFeatureToggle(
|
||||
projectId,
|
||||
value,
|
||||
userName,
|
||||
);
|
||||
await this.featureService2.getFeature(featureName);
|
||||
const projectId = await this.featureService2.getProjectId(
|
||||
updatedFeature.name,
|
||||
);
|
||||
const value = await featureSchema.validateAsync(updatedFeature);
|
||||
const { enabled } = value;
|
||||
const updatedToggle = this.featureService2.updateFeatureToggle(
|
||||
projectId,
|
||||
value,
|
||||
userName,
|
||||
);
|
||||
|
||||
await this.featureService2.removeAllStrategiesForEnv(
|
||||
featureName,
|
||||
);
|
||||
let strategies;
|
||||
if (updatedFeature.strategies) {
|
||||
strategies = await Promise.all(
|
||||
updatedFeature.strategies.map(async (s) =>
|
||||
this.featureService2.createStrategy(
|
||||
s,
|
||||
projectId,
|
||||
featureName,
|
||||
),
|
||||
await this.featureService2.removeAllStrategiesForEnv(featureName);
|
||||
let strategies;
|
||||
if (updatedFeature.strategies) {
|
||||
strategies = await Promise.all(
|
||||
updatedFeature.strategies.map(async (s) =>
|
||||
this.featureService2.createStrategy(
|
||||
s,
|
||||
projectId,
|
||||
featureName,
|
||||
),
|
||||
);
|
||||
}
|
||||
await this.featureService2.updateEnabled(
|
||||
updatedFeature.name,
|
||||
GLOBAL_ENV,
|
||||
updatedFeature.enabled,
|
||||
userName,
|
||||
),
|
||||
);
|
||||
res.status(200).json({
|
||||
...updatedToggle,
|
||||
enabled,
|
||||
strategies: strategies || [],
|
||||
});
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.featureService2.updateEnabled(
|
||||
updatedFeature.name,
|
||||
GLOBAL_ENV,
|
||||
updatedFeature.enabled,
|
||||
userName,
|
||||
);
|
||||
res.status(200).json({
|
||||
...updatedToggle,
|
||||
enabled,
|
||||
strategies: strategies || [],
|
||||
});
|
||||
} else {
|
||||
res.status(404)
|
||||
.json({
|
||||
@ -285,91 +244,67 @@ class FeatureController extends Controller {
|
||||
// Kept to keep backward compatibility
|
||||
async toggle(req: Request, res: Response): Promise<void> {
|
||||
const userName = extractUser(req);
|
||||
try {
|
||||
const name = req.params.featureName;
|
||||
const feature = await this.featureService2.toggle(
|
||||
name,
|
||||
GLOBAL_ENV,
|
||||
userName,
|
||||
);
|
||||
res.status(200).json(feature);
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
const name = req.params.featureName;
|
||||
const feature = await this.featureService2.toggle(
|
||||
name,
|
||||
GLOBAL_ENV,
|
||||
userName,
|
||||
);
|
||||
res.status(200).json(feature);
|
||||
}
|
||||
|
||||
async toggleOn(req: Request, res: Response): Promise<void> {
|
||||
const { featureName } = req.params;
|
||||
const userName = extractUser(req);
|
||||
try {
|
||||
const feature = await this.featureService2.updateEnabled(
|
||||
featureName,
|
||||
GLOBAL_ENV,
|
||||
true,
|
||||
userName,
|
||||
);
|
||||
res.json(feature);
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
const feature = await this.featureService2.updateEnabled(
|
||||
featureName,
|
||||
GLOBAL_ENV,
|
||||
true,
|
||||
userName,
|
||||
);
|
||||
res.json(feature);
|
||||
}
|
||||
|
||||
async toggleOff(req: Request, res: Response): Promise<void> {
|
||||
const { featureName } = req.params;
|
||||
const userName = extractUser(req);
|
||||
try {
|
||||
const feature = await this.featureService2.updateEnabled(
|
||||
featureName,
|
||||
GLOBAL_ENV,
|
||||
false,
|
||||
userName,
|
||||
);
|
||||
res.json(feature);
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
const feature = await this.featureService2.updateEnabled(
|
||||
featureName,
|
||||
GLOBAL_ENV,
|
||||
false,
|
||||
userName,
|
||||
);
|
||||
res.json(feature);
|
||||
}
|
||||
|
||||
async staleOn(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { featureName } = req.params;
|
||||
const userName = extractUser(req);
|
||||
const feature = await this.featureService2.updateStale(
|
||||
featureName,
|
||||
true,
|
||||
userName,
|
||||
);
|
||||
res.json(feature).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
const { featureName } = req.params;
|
||||
const userName = extractUser(req);
|
||||
const feature = await this.featureService2.updateStale(
|
||||
featureName,
|
||||
true,
|
||||
userName,
|
||||
);
|
||||
res.json(feature).end();
|
||||
}
|
||||
|
||||
async staleOff(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { featureName } = req.params;
|
||||
const userName = extractUser(req);
|
||||
const feature = await this.featureService2.updateStale(
|
||||
featureName,
|
||||
false,
|
||||
userName,
|
||||
);
|
||||
res.json(feature).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
const { featureName } = req.params;
|
||||
const userName = extractUser(req);
|
||||
const feature = await this.featureService2.updateStale(
|
||||
featureName,
|
||||
false,
|
||||
userName,
|
||||
);
|
||||
res.json(feature).end();
|
||||
}
|
||||
|
||||
async archiveToggle(req: Request, res: Response): Promise<void> {
|
||||
const { featureName } = req.params;
|
||||
const userName = extractUser(req);
|
||||
|
||||
try {
|
||||
await this.featureService2.archiveToggle(featureName, userName);
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.featureService2.archiveToggle(featureName, userName);
|
||||
res.status(200).end();
|
||||
}
|
||||
}
|
||||
export default FeatureController;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import Controller from '../controller';
|
||||
import { handleErrors } from './util';
|
||||
import { handleErrors } from '../util';
|
||||
import { UPDATE_APPLICATION } from '../../types/permissions';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
|
@ -4,7 +4,7 @@ import { IUnleashConfig } from '../../../types/option';
|
||||
import { IUnleashServices } from '../../../types/services';
|
||||
import { Logger } from '../../../logger';
|
||||
import EnvironmentService from '../../../services/environment-service';
|
||||
import { handleErrors } from '../util';
|
||||
import { handleErrors } from '../../util';
|
||||
import { UPDATE_PROJECT } from '../../../types/permissions';
|
||||
|
||||
const PREFIX = '/:projectId/environments';
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
IConstraint,
|
||||
IStrategyConfig,
|
||||
} from '../../../types/model';
|
||||
import { handleErrors } from '../util';
|
||||
import extractUsername from '../../../extract-user';
|
||||
import ProjectHealthService from '../../../services/project-health-service';
|
||||
|
||||
@ -111,14 +110,10 @@ export default class ProjectFeaturesController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { projectId } = req.params;
|
||||
try {
|
||||
const features = await this.featureService.getFeatureToggles({
|
||||
project: [projectId],
|
||||
});
|
||||
res.json({ version: 1, features });
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const features = await this.featureService.getFeatureToggles({
|
||||
project: [projectId],
|
||||
});
|
||||
res.json({ version: 1, features });
|
||||
}
|
||||
|
||||
async createFeatureToggle(
|
||||
@ -126,17 +121,13 @@ export default class ProjectFeaturesController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { projectId } = req.params;
|
||||
try {
|
||||
const userName = extractUsername(req);
|
||||
const created = await this.featureService.createFeatureToggle(
|
||||
projectId,
|
||||
req.body,
|
||||
userName,
|
||||
);
|
||||
res.status(201).json(created);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const userName = extractUsername(req);
|
||||
const created = await this.featureService.createFeatureToggle(
|
||||
projectId,
|
||||
req.body,
|
||||
userName,
|
||||
);
|
||||
res.status(201).json(created);
|
||||
}
|
||||
|
||||
async getEnvironment(
|
||||
@ -144,17 +135,12 @@ export default class ProjectFeaturesController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { environment, featureName, projectId } = req.params;
|
||||
try {
|
||||
const environmentInfo =
|
||||
await this.featureService.getEnvironmentInfo(
|
||||
projectId,
|
||||
environment,
|
||||
featureName,
|
||||
);
|
||||
res.status(200).json(environmentInfo);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const environmentInfo = await this.featureService.getEnvironmentInfo(
|
||||
projectId,
|
||||
environment,
|
||||
featureName,
|
||||
);
|
||||
res.status(200).json(environmentInfo);
|
||||
}
|
||||
|
||||
async getFeature(
|
||||
@ -162,12 +148,8 @@ export default class ProjectFeaturesController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { featureName } = req.params;
|
||||
try {
|
||||
const feature = await this.featureService.getFeature(featureName);
|
||||
res.status(200).json(feature);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const feature = await this.featureService.getFeature(featureName);
|
||||
res.status(200).json(feature);
|
||||
}
|
||||
|
||||
async toggleEnvironmentOn(
|
||||
@ -175,17 +157,13 @@ export default class ProjectFeaturesController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { featureName, environment } = req.params;
|
||||
try {
|
||||
await this.featureService.updateEnabled(
|
||||
featureName,
|
||||
environment,
|
||||
true,
|
||||
extractUsername(req),
|
||||
);
|
||||
res.status(200).end();
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
await this.featureService.updateEnabled(
|
||||
featureName,
|
||||
environment,
|
||||
true,
|
||||
extractUsername(req),
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async toggleEnvironmentOff(
|
||||
@ -193,17 +171,13 @@ export default class ProjectFeaturesController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { featureName, environment } = req.params;
|
||||
try {
|
||||
await this.featureService.updateEnabled(
|
||||
featureName,
|
||||
environment,
|
||||
false,
|
||||
extractUsername(req),
|
||||
);
|
||||
res.status(200).end();
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
await this.featureService.updateEnabled(
|
||||
featureName,
|
||||
environment,
|
||||
false,
|
||||
extractUsername(req),
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async createFeatureStrategy(
|
||||
@ -211,17 +185,13 @@ export default class ProjectFeaturesController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { projectId, featureName, environment } = req.params;
|
||||
try {
|
||||
const featureStrategy = await this.featureService.createStrategy(
|
||||
req.body,
|
||||
projectId,
|
||||
featureName,
|
||||
environment,
|
||||
);
|
||||
res.status(200).json(featureStrategy);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const featureStrategy = await this.featureService.createStrategy(
|
||||
req.body,
|
||||
projectId,
|
||||
featureName,
|
||||
environment,
|
||||
);
|
||||
res.status(200).json(featureStrategy);
|
||||
}
|
||||
|
||||
async getFeatureStrategies(
|
||||
@ -229,17 +199,13 @@ export default class ProjectFeaturesController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { projectId, featureName, environment } = req.params;
|
||||
try {
|
||||
const featureStrategies =
|
||||
await this.featureService.getStrategiesForEnvironment(
|
||||
projectId,
|
||||
featureName,
|
||||
environment,
|
||||
);
|
||||
res.status(200).json(featureStrategies);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const featureStrategies =
|
||||
await this.featureService.getStrategiesForEnvironment(
|
||||
projectId,
|
||||
featureName,
|
||||
environment,
|
||||
);
|
||||
res.status(200).json(featureStrategies);
|
||||
}
|
||||
|
||||
async updateStrategy(
|
||||
@ -247,15 +213,11 @@ export default class ProjectFeaturesController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { strategyId } = req.params;
|
||||
try {
|
||||
const updatedStrategy = await this.featureService.updateStrategy(
|
||||
strategyId,
|
||||
req.body,
|
||||
);
|
||||
res.status(200).json(updatedStrategy);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const updatedStrategy = await this.featureService.updateStrategy(
|
||||
strategyId,
|
||||
req.body,
|
||||
);
|
||||
res.status(200).json(updatedStrategy);
|
||||
}
|
||||
|
||||
async getStrategy(
|
||||
@ -265,11 +227,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
this.logger.info('Getting strategy');
|
||||
const { strategyId } = req.params;
|
||||
this.logger.info(strategyId);
|
||||
try {
|
||||
const strategy = await this.featureService.getStrategy(strategyId);
|
||||
res.status(200).json(strategy);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const strategy = await this.featureService.getStrategy(strategyId);
|
||||
res.status(200).json(strategy);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { IUnleashConfig } from '../../../types/option';
|
||||
import ProjectHealthService from '../../../services/project-health-service';
|
||||
import { Logger } from '../../../logger';
|
||||
import { IArchivedQuery, IProjectParam } from '../../../types/model';
|
||||
import { handleErrors } from '../util';
|
||||
import { handleErrors } from '../../util';
|
||||
|
||||
export default class ProjectHealthReport extends Controller {
|
||||
private projectHealthService: ProjectHealthService;
|
||||
|
@ -6,7 +6,6 @@ import { Request, Response } from 'express';
|
||||
import Controller from '../controller';
|
||||
import { ADMIN } from '../../types/permissions';
|
||||
import extractUser from '../../extract-user';
|
||||
import { handleErrors } from './util';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
import { Logger } from '../../logger';
|
||||
@ -43,32 +42,28 @@ class StateController extends Controller {
|
||||
const userName = extractUser(req);
|
||||
const { drop, keep } = req.query;
|
||||
// TODO: Should override request type so file is a type on request
|
||||
try {
|
||||
let data;
|
||||
let data;
|
||||
// @ts-ignore
|
||||
if (req.file) {
|
||||
// @ts-ignore
|
||||
if (req.file) {
|
||||
if (mime.getType(req.file.originalname) === 'text/yaml') {
|
||||
// @ts-ignore
|
||||
if (mime.getType(req.file.originalname) === 'text/yaml') {
|
||||
// @ts-ignore
|
||||
data = YAML.safeLoad(req.file.buffer);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
data = JSON.parse(req.file.buffer);
|
||||
}
|
||||
data = YAML.safeLoad(req.file.buffer);
|
||||
} else {
|
||||
data = req.body;
|
||||
// @ts-ignore
|
||||
data = JSON.parse(req.file.buffer);
|
||||
}
|
||||
|
||||
await this.stateService.import({
|
||||
data,
|
||||
userName,
|
||||
dropBeforeImport: paramToBool(drop, false),
|
||||
keepExisting: paramToBool(keep, true),
|
||||
});
|
||||
res.sendStatus(202);
|
||||
} catch (err) {
|
||||
handleErrors(res, this.logger, err);
|
||||
} else {
|
||||
data = req.body;
|
||||
}
|
||||
|
||||
await this.stateService.import({
|
||||
data,
|
||||
userName,
|
||||
dropBeforeImport: paramToBool(drop, false),
|
||||
keepExisting: paramToBool(keep, true),
|
||||
});
|
||||
res.sendStatus(202);
|
||||
}
|
||||
|
||||
async export(req: Request, res: Response): Promise<void> {
|
||||
@ -84,30 +79,24 @@ class StateController extends Controller {
|
||||
const includeTags = paramToBool(req.query.tags, true);
|
||||
const includeEnvironments = paramToBool(req.query.environments, true);
|
||||
|
||||
try {
|
||||
const data = await this.stateService.export({
|
||||
includeStrategies,
|
||||
includeFeatureToggles,
|
||||
includeProjects,
|
||||
includeTags,
|
||||
includeEnvironments,
|
||||
});
|
||||
const timestamp = moment().format('YYYY-MM-DD_HH-mm-ss');
|
||||
if (format === 'yaml') {
|
||||
if (downloadFile) {
|
||||
res.attachment(`export-${timestamp}.yml`);
|
||||
}
|
||||
res.type('yaml').send(
|
||||
YAML.safeDump(data, { skipInvalid: true }),
|
||||
);
|
||||
} else {
|
||||
if (downloadFile) {
|
||||
res.attachment(`export-${timestamp}.json`);
|
||||
}
|
||||
res.json(data);
|
||||
const data = await this.stateService.export({
|
||||
includeStrategies,
|
||||
includeFeatureToggles,
|
||||
includeProjects,
|
||||
includeTags,
|
||||
includeEnvironments,
|
||||
});
|
||||
const timestamp = moment().format('YYYY-MM-DD_HH-mm-ss');
|
||||
if (format === 'yaml') {
|
||||
if (downloadFile) {
|
||||
res.attachment(`export-${timestamp}.yml`);
|
||||
}
|
||||
} catch (err) {
|
||||
handleErrors(res, this.logger, err);
|
||||
res.type('yaml').send(YAML.safeDump(data, { skipInvalid: true }));
|
||||
} else {
|
||||
if (downloadFile) {
|
||||
res.attachment(`export-${timestamp}.json`);
|
||||
}
|
||||
res.json(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { Logger } from '../../logger';
|
||||
const Controller = require('../controller');
|
||||
|
||||
const extractUser = require('../../extract-user');
|
||||
const { handleErrors } = require('./util');
|
||||
const { handleErrors } = require('../util');
|
||||
const {
|
||||
DELETE_STRATEGY,
|
||||
CREATE_STRATEGY,
|
||||
|
@ -2,7 +2,7 @@ import { Request, Response } from 'express';
|
||||
import Controller from '../controller';
|
||||
|
||||
import { UPDATE_FEATURE } from '../../types/permissions';
|
||||
import { handleErrors } from './util';
|
||||
import { handleErrors } from '../util';
|
||||
import extractUsername from '../../extract-user';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
|
@ -7,7 +7,6 @@ import { Logger } from '../../logger';
|
||||
import Controller from '../controller';
|
||||
|
||||
import { UPDATE_FEATURE } from '../../types/permissions';
|
||||
import { handleErrors } from './util';
|
||||
import extractUsername from '../../extract-user';
|
||||
|
||||
const version = 1;
|
||||
@ -33,52 +32,32 @@ class TagController extends Controller {
|
||||
}
|
||||
|
||||
async getTags(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tags = await this.tagService.getTags();
|
||||
res.json({ version, tags });
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const tags = await this.tagService.getTags();
|
||||
res.json({ version, tags });
|
||||
}
|
||||
|
||||
async getTagsByType(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tags = await this.tagService.getTagsByType(req.params.type);
|
||||
res.json({ version, tags });
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const tags = await this.tagService.getTagsByType(req.params.type);
|
||||
res.json({ version, tags });
|
||||
}
|
||||
|
||||
async getTag(req: Request, res: Response): Promise<void> {
|
||||
const { type, value } = req.params;
|
||||
try {
|
||||
const tag = await this.tagService.getTag({ type, value });
|
||||
res.json({ version, tag });
|
||||
} catch (err) {
|
||||
handleErrors(res, this.logger, err);
|
||||
}
|
||||
const tag = await this.tagService.getTag({ type, value });
|
||||
res.json({ version, tag });
|
||||
}
|
||||
|
||||
async createTag(req: Request, res: Response): Promise<void> {
|
||||
const userName = extractUsername(req);
|
||||
try {
|
||||
await this.tagService.createTag(req.body, userName);
|
||||
res.status(201).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.tagService.createTag(req.body, userName);
|
||||
res.status(201).end();
|
||||
}
|
||||
|
||||
async deleteTag(req: Request, res: Response): Promise<void> {
|
||||
const { type, value } = req.params;
|
||||
const userName = extractUsername(req);
|
||||
try {
|
||||
await this.tagService.deleteTag({ type, value }, userName);
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
await this.tagService.deleteTag({ type, value }, userName);
|
||||
res.status(200).end();
|
||||
}
|
||||
}
|
||||
export default TagController;
|
||||
|
@ -4,7 +4,7 @@ import { ADMIN } from '../../types/permissions';
|
||||
import UserService from '../../services/user-service';
|
||||
import { AccessService } from '../../services/access-service';
|
||||
import { Logger } from '../../logger';
|
||||
import { handleErrors } from './util';
|
||||
import { handleErrors } from '../util';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { EmailService } from '../../services/email-service';
|
||||
import ResetTokenService from '../../services/reset-token-service';
|
||||
|
@ -2,11 +2,10 @@ import { Response } from 'express';
|
||||
|
||||
import Controller from '../controller';
|
||||
import { Logger } from '../../logger';
|
||||
import { IUserRequest } from './user';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
import UserFeedbackService from '../../services/user-feedback-service';
|
||||
import { handleErrors } from './util';
|
||||
import { IAuthRequest } from '../unleash-types';
|
||||
|
||||
interface IFeedbackBody {
|
||||
neverShow?: boolean;
|
||||
@ -32,7 +31,7 @@ class UserFeedbackController extends Controller {
|
||||
}
|
||||
|
||||
private async recordFeedback(
|
||||
req: IUserRequest<any, any, IFeedbackBody, any>,
|
||||
req: IAuthRequest<any, any, IFeedbackBody, any>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const BAD_REQUEST = 400;
|
||||
@ -54,18 +53,12 @@ class UserFeedbackController extends Controller {
|
||||
neverShow: req.body.neverShow || false,
|
||||
};
|
||||
|
||||
try {
|
||||
const updated = await this.userFeedbackService.updateFeedback(
|
||||
feedback,
|
||||
);
|
||||
res.json(updated);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const updated = await this.userFeedbackService.updateFeedback(feedback);
|
||||
res.json(updated);
|
||||
}
|
||||
|
||||
private async updateFeedbackSettings(
|
||||
req: IUserRequest<any, any, IFeedbackBody, any>,
|
||||
req: IAuthRequest<any, any, IFeedbackBody, any>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { user } = req;
|
||||
@ -78,14 +71,8 @@ class UserFeedbackController extends Controller {
|
||||
neverShow: req.body.neverShow || false,
|
||||
};
|
||||
|
||||
try {
|
||||
const updated = await this.userFeedbackService.updateFeedback(
|
||||
feedback,
|
||||
);
|
||||
res.json(updated);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const updated = await this.userFeedbackService.updateFeedback(feedback);
|
||||
res.json(updated);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Response } from 'express';
|
||||
import { IAuthRequest } from '../unleash-types';
|
||||
import Controller from '../controller';
|
||||
import { AccessService } from '../../services/access-service';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
import UserService from '../../services/user-service';
|
||||
import User from '../../types/user';
|
||||
import { Logger } from '../../logger';
|
||||
import { handleErrors } from './util';
|
||||
import SessionService from '../../services/session-service';
|
||||
import UserFeedbackService from '../../services/user-feedback-service';
|
||||
|
||||
@ -16,11 +13,6 @@ interface IChangeUserRequest {
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
export interface IUserRequest<PARAM, QUERY, BODY, RESPONSE>
|
||||
extends Request<PARAM, QUERY, BODY, RESPONSE> {
|
||||
user: User;
|
||||
}
|
||||
|
||||
class UserController extends Controller {
|
||||
private accessService: AccessService;
|
||||
|
||||
@ -30,8 +22,6 @@ class UserController extends Controller {
|
||||
|
||||
private sessionService: SessionService;
|
||||
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
config: IUnleashConfig,
|
||||
{
|
||||
@ -52,7 +42,6 @@ class UserController extends Controller {
|
||||
this.userService = userService;
|
||||
this.sessionService = sessionService;
|
||||
this.userFeedbackService = userFeedbackService;
|
||||
this.logger = config.getLogger('lib/routes/admin-api/user.ts');
|
||||
|
||||
this.get('/', this.getUser);
|
||||
this.post('/change-password', this.updateUserPass);
|
||||
@ -76,34 +65,24 @@ class UserController extends Controller {
|
||||
}
|
||||
|
||||
async updateUserPass(
|
||||
req: IUserRequest<any, any, IChangeUserRequest, any>,
|
||||
req: IAuthRequest<any, any, IChangeUserRequest, any>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { user } = req;
|
||||
const { password, confirmPassword } = req.body;
|
||||
try {
|
||||
if (password === confirmPassword) {
|
||||
this.userService.validatePassword(password);
|
||||
await this.userService.changePassword(user.id, password);
|
||||
res.status(200).end();
|
||||
} else {
|
||||
res.status(400).end();
|
||||
}
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
if (password === confirmPassword) {
|
||||
this.userService.validatePassword(password);
|
||||
await this.userService.changePassword(user.id, password);
|
||||
res.status(200).end();
|
||||
} else {
|
||||
res.status(400).end();
|
||||
}
|
||||
}
|
||||
|
||||
async mySessions(req: IAuthRequest, res: Response): Promise<void> {
|
||||
const { user } = req;
|
||||
try {
|
||||
const sessions = await this.sessionService.getSessionsForUser(
|
||||
user.id,
|
||||
);
|
||||
res.json(sessions);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const sessions = await this.sessionService.getSessionsForUser(user.id);
|
||||
res.json(sessions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ import { Request, Response } from 'express';
|
||||
import Controller from '../controller';
|
||||
import UserService from '../../services/user-service';
|
||||
import { Logger } from '../../logger';
|
||||
import { handleErrors } from '../admin-api/util';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
|
||||
@ -40,23 +39,15 @@ class ResetPasswordController extends Controller {
|
||||
async sendResetPasswordEmail(req: Request, res: Response): Promise<void> {
|
||||
const { email } = req.body;
|
||||
|
||||
try {
|
||||
await this.userService.createResetPasswordEmail(email);
|
||||
res.status(200).end();
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
await this.userService.createResetPasswordEmail(email);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async validatePassword(req: Request, res: Response): Promise<void> {
|
||||
const { password } = req.body;
|
||||
|
||||
try {
|
||||
this.userService.validatePassword(password);
|
||||
res.status(200).end();
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
this.userService.validatePassword(password);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async validateToken(
|
||||
@ -64,13 +55,9 @@ class ResetPasswordController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { token } = req.query;
|
||||
try {
|
||||
const user = await this.userService.getUserForToken(token);
|
||||
await this.logout(req);
|
||||
res.status(200).json(user);
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
const user = await this.userService.getUserForToken(token);
|
||||
await this.logout(req);
|
||||
res.status(200).json(user);
|
||||
}
|
||||
|
||||
async changePassword(
|
||||
@ -79,12 +66,8 @@ class ResetPasswordController extends Controller {
|
||||
): Promise<void> {
|
||||
await this.logout(req);
|
||||
const { token, password } = req.body;
|
||||
try {
|
||||
await this.userService.resetPassword(token, password);
|
||||
res.status(200).end();
|
||||
} catch (e) {
|
||||
handleErrors(res, this.logger, e);
|
||||
}
|
||||
await this.userService.resetPassword(token, password);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
private async logout(req: SessionRequest<any, any, any, any>) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
const Controller = require('../controller');
|
||||
|
||||
class PasswordProvider extends Controller {
|
||||
constructor({ getLogger }, { userService }) {
|
||||
super();
|
||||
this.logger = getLogger('/auth/password-provider.js');
|
||||
constructor(config, { userService }) {
|
||||
super(config);
|
||||
this.logger = config.getLogger('/auth/password-provider.js');
|
||||
this.userService = userService;
|
||||
|
||||
this.post('/login', this.login);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import memoizee from 'memoizee';
|
||||
import { Request, Response } from 'express';
|
||||
import { handleErrors } from '../admin-api/util';
|
||||
import { handleErrors } from '../util';
|
||||
import Controller from '../controller';
|
||||
import { IUnleashServices } from '../../types/services';
|
||||
import { IUnleashConfig } from '../../types/option';
|
||||
|
@ -1,10 +1,24 @@
|
||||
import { IRouter } from 'express';
|
||||
import { IRouter, Request, Response } from 'express';
|
||||
import { Logger } from 'lib/logger';
|
||||
import { IUnleashConfig } from '../types/option';
|
||||
import { handleErrors } from './util';
|
||||
|
||||
const { Router } = require('express');
|
||||
const NoAccessError = require('../error/no-access-error');
|
||||
const requireContentType = require('../middleware/content_type_checker');
|
||||
|
||||
interface IRequestHandler<
|
||||
P = any,
|
||||
ResBody = any,
|
||||
ReqBody = any,
|
||||
ReqQuery = any,
|
||||
> {
|
||||
(
|
||||
req: Request<P, ResBody, ReqBody, ReqQuery>,
|
||||
res: Response<ResBody>,
|
||||
): Promise<void> | void;
|
||||
}
|
||||
|
||||
const checkPermission = (permission) => async (req, res, next) => {
|
||||
if (!permission) {
|
||||
return next();
|
||||
@ -17,24 +31,48 @@ const checkPermission = (permission) => async (req, res, next) => {
|
||||
|
||||
/**
|
||||
* Base class for Controllers to standardize binding to express Router.
|
||||
*
|
||||
* This class will take care of the following:
|
||||
* - try/catch inside RequestHandler
|
||||
* - await if the RequestHandler returns a promise.
|
||||
* - access control
|
||||
*/
|
||||
export default class Controller {
|
||||
private ownLogger: Logger;
|
||||
|
||||
app: IRouter;
|
||||
|
||||
config: IUnleashConfig;
|
||||
|
||||
constructor(config: IUnleashConfig) {
|
||||
this.ownLogger = config.getLogger(
|
||||
`controller/${this.constructor.name}`,
|
||||
);
|
||||
this.app = Router();
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
get(path: string, handler: Function, permission?: string): void {
|
||||
this.app.get(path, checkPermission(permission), handler.bind(this));
|
||||
wrap(handler: IRequestHandler): IRequestHandler {
|
||||
return async (req: Request, res: Response) => {
|
||||
try {
|
||||
await handler(req, res);
|
||||
} catch (error) {
|
||||
handleErrors(res, this.ownLogger, error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get(path: string, handler: IRequestHandler, permission?: string): void {
|
||||
this.app.get(
|
||||
path,
|
||||
checkPermission(permission),
|
||||
this.wrap(handler.bind(this)),
|
||||
);
|
||||
}
|
||||
|
||||
post(
|
||||
path: string,
|
||||
handler: Function,
|
||||
handler: IRequestHandler,
|
||||
permission?: string,
|
||||
...acceptedContentTypes: string[]
|
||||
): void {
|
||||
@ -42,13 +80,13 @@ export default class Controller {
|
||||
path,
|
||||
checkPermission(permission),
|
||||
requireContentType(...acceptedContentTypes),
|
||||
handler.bind(this),
|
||||
this.wrap(handler.bind(this)),
|
||||
);
|
||||
}
|
||||
|
||||
put(
|
||||
path: string,
|
||||
handler: Function,
|
||||
handler: IRequestHandler,
|
||||
permission?: string,
|
||||
...acceptedContentTypes: string[]
|
||||
): void {
|
||||
@ -56,17 +94,21 @@ export default class Controller {
|
||||
path,
|
||||
checkPermission(permission),
|
||||
requireContentType(...acceptedContentTypes),
|
||||
handler.bind(this),
|
||||
this.wrap(handler.bind(this)),
|
||||
);
|
||||
}
|
||||
|
||||
delete(path: string, handler: Function, permission?: string): void {
|
||||
this.app.delete(path, checkPermission(permission), handler.bind(this));
|
||||
delete(path: string, handler: IRequestHandler, permission?: string): void {
|
||||
this.app.delete(
|
||||
path,
|
||||
checkPermission(permission),
|
||||
this.wrap(handler.bind(this)),
|
||||
);
|
||||
}
|
||||
|
||||
fileupload(
|
||||
path: string,
|
||||
filehandler: Function,
|
||||
filehandler: IRequestHandler,
|
||||
handler: Function,
|
||||
permission?: string,
|
||||
): void {
|
||||
@ -74,11 +116,10 @@ export default class Controller {
|
||||
path,
|
||||
checkPermission(permission),
|
||||
filehandler.bind(this),
|
||||
handler.bind(this),
|
||||
this.wrap(handler.bind(this)),
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
use(path: string, router: IRouter): void {
|
||||
this.app.use(path, router);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class HealthCheckController extends Controller {
|
||||
config: IUnleashConfig,
|
||||
{ healthService }: Pick<IUnleashServices, 'healthService'>,
|
||||
) {
|
||||
super();
|
||||
super(config);
|
||||
this.logger = config.getLogger('health-check.js');
|
||||
this.healthService = healthService;
|
||||
this.get('/', (req, res) => this.index(req, res));
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { Request } from 'express';
|
||||
import * as core from 'express-serve-static-core';
|
||||
import User from '../types/user';
|
||||
|
||||
export interface IAuthRequest extends Request {
|
||||
export interface IAuthRequest<
|
||||
PARAM = core.ParamsDictionary,
|
||||
ResBody = any,
|
||||
ReqBody = any,
|
||||
ReqQuery = core.Query,
|
||||
> extends Request<PARAM, ResBody, ReqBody, ReqQuery> {
|
||||
user: User;
|
||||
logout: () => void;
|
||||
session: any;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import joi from 'joi';
|
||||
import { Response } from 'express';
|
||||
import { Logger } from '../../logger';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export const customJoi = joi.extend((j) => ({
|
||||
type: 'isUrlFriendly',
|
@ -1,5 +1,5 @@
|
||||
import joi from 'joi';
|
||||
import { nameType } from '../routes/admin-api/util';
|
||||
import { nameType } from '../routes/util';
|
||||
|
||||
export const nameSchema = joi
|
||||
.object()
|
||||
|
@ -14,6 +14,7 @@ import User from './types/user';
|
||||
|
||||
import * as permissions from './types/permissions';
|
||||
import AuthenticationRequired from './types/authentication-required';
|
||||
import Controller from './routes/controller';
|
||||
import * as eventType from './types/events';
|
||||
import { addEventHook } from './event-hook';
|
||||
import registerGracefulShutdown from './util/graceful-shutdown';
|
||||
@ -145,6 +146,7 @@ const serverImpl = {
|
||||
create,
|
||||
User,
|
||||
AuthenticationRequired,
|
||||
Controller,
|
||||
permissions,
|
||||
eventType,
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import joi from 'joi';
|
||||
import { nameType } from '../routes/admin-api/util';
|
||||
import { nameType } from '../routes/util';
|
||||
|
||||
export const addonSchema = joi
|
||||
.object()
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const joi = require('joi');
|
||||
const { nameType } = require('../routes/admin-api/util');
|
||||
const { nameType } = require('../routes/util');
|
||||
|
||||
const nameSchema = joi.object().keys({ name: nameType });
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
const joi = require('joi');
|
||||
const { nameType } = require('../routes/admin-api/util');
|
||||
const { nameType } = require('../routes/util');
|
||||
|
||||
const projectSchema = joi
|
||||
.object()
|
||||
|
@ -2,7 +2,7 @@ import User from '../types/user';
|
||||
import { AccessService } from './access-service';
|
||||
import NameExistsError from '../error/name-exists-error';
|
||||
import InvalidOperationError from '../error/invalid-operation-error';
|
||||
import { nameType } from '../routes/admin-api/util';
|
||||
import { nameType } from '../routes/util';
|
||||
import schema from './project-schema';
|
||||
import NotFoundError from '../error/notfound-error';
|
||||
import {
|
||||
|
@ -4,7 +4,7 @@ import strategySchema from './strategy-schema';
|
||||
import { tagSchema } from './tag-schema';
|
||||
import { tagTypeSchema } from './tag-type-schema';
|
||||
import projectSchema from './project-schema';
|
||||
import { nameType } from '../routes/admin-api/util';
|
||||
import { nameType } from '../routes/util';
|
||||
|
||||
export const featureStrategySchema = joi
|
||||
.object()
|
||||
|
@ -1,5 +1,5 @@
|
||||
const joi = require('joi');
|
||||
const { nameType } = require('../routes/admin-api/util');
|
||||
const { nameType } = require('../routes/util');
|
||||
|
||||
const strategySchema = joi
|
||||
.object()
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Joi from 'joi';
|
||||
|
||||
import { customJoi } from '../routes/admin-api/util';
|
||||
import { customJoi } from '../routes/util';
|
||||
|
||||
export const tagSchema = Joi.object()
|
||||
.keys({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Joi from 'joi';
|
||||
import { customJoi } from '../routes/admin-api/util';
|
||||
import { customJoi } from '../routes/util';
|
||||
|
||||
export const tagTypeSchema = Joi.object()
|
||||
.keys({
|
||||
|
Loading…
Reference in New Issue
Block a user