1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-19 01:17:18 +02: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:
Ivar Conradi Østhus 2021-08-13 10:36:19 +02:00 committed by GitHub
parent 0faa0cd075
commit 2bcdb5ec31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 411 additions and 627 deletions

View File

@ -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: 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). 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.

View File

@ -1,5 +1,5 @@
import joi from 'joi'; import joi from 'joi';
import { nameType } from '../routes/admin-api/util'; import { nameType } from '../routes/util';
import { tagTypeSchema } from '../services/tag-type-schema'; import { tagTypeSchema } from '../services/tag-type-schema';
export const addonDefinitionSchema = joi.object().keys({ export const addonDefinitionSchema = joi.object().keys({

View File

@ -1,14 +1,14 @@
import { IAuthRequest } from 'lib/routes/unleash-types';
import supertest from 'supertest'; import supertest from 'supertest';
import express from 'express'; import express from 'express';
import noAuthentication from './no-authentication'; import noAuthentication from './no-authentication';
import { IUserRequest } from '../routes/admin-api/user';
test('should add dummy user object to all requests', () => { test('should add dummy user object to all requests', () => {
expect.assertions(1); expect.assertions(1);
const app = express(); const app = express();
noAuthentication('', app); 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 }; const user = { ...req.user };
return res.status(200).json(user).end(); return res.status(200).json(user).end();

View File

@ -6,7 +6,6 @@ import { Logger } from '../../logger';
import AddonService from '../../services/addon-service'; import AddonService from '../../services/addon-service';
import extractUser from '../../extract-user'; import extractUser from '../../extract-user';
import { handleErrors } from './util';
import { import {
CREATE_ADDON, CREATE_ADDON,
UPDATE_ADDON, UPDATE_ADDON,
@ -34,13 +33,9 @@ class AddonController extends Controller {
} }
async getAddons(req: Request, res: Response): Promise<void> { async getAddons(req: Request, res: Response): Promise<void> {
try { const addons = await this.addonService.getAddons();
const addons = await this.addonService.getAddons(); const providers = this.addonService.getProviderDefinitions();
const providers = this.addonService.getProviderDefinitions(); res.json({ addons, providers });
res.json({ addons, providers });
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async getAddon( async getAddon(
@ -48,12 +43,8 @@ class AddonController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { id } = req.params; const { id } = req.params;
try { const addon = await this.addonService.getAddon(id);
const addon = await this.addonService.getAddon(id); res.json(addon);
res.json(addon);
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async updateAddon( async updateAddon(
@ -64,27 +55,15 @@ class AddonController extends Controller {
const createdBy = extractUser(req); const createdBy = extractUser(req);
const data = req.body; const data = req.body;
try { const addon = await this.addonService.updateAddon(id, data, createdBy);
const addon = await this.addonService.updateAddon( res.status(200).json(addon);
id,
data,
createdBy,
);
res.status(200).json(addon);
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async createAddon(req: Request, res: Response): Promise<void> { async createAddon(req: Request, res: Response): Promise<void> {
const createdBy = extractUser(req); const createdBy = extractUser(req);
const data = req.body; const data = req.body;
try { const addon = await this.addonService.createAddon(data, createdBy);
const addon = await this.addonService.createAddon(data, createdBy); res.status(201).json(addon);
res.status(201).json(addon);
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async deleteAddon( async deleteAddon(
@ -93,12 +72,8 @@ class AddonController extends Controller {
): Promise<void> { ): Promise<void> {
const { id } = req.params; const { id } = req.params;
const username = extractUser(req); const username = extractUser(req);
try { await this.addonService.removeAddon(id, username);
await this.addonService.removeAddon(id, username); res.status(200).end();
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
} }
export default AddonController; export default AddonController;

View File

@ -1,5 +1,4 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { handleErrors } from './util';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
@ -36,13 +35,10 @@ export default class ArchiveController extends Controller {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async getArchivedFeatures(req, res): Promise<void> { async getArchivedFeatures(req, res): Promise<void> {
try { const features = await this.featureService.getMetadataForAllFeatures(
const features = true,
await this.featureService.getMetadataForAllFeatures(true); );
res.json({ version: 2, features }); res.json({ version: 2, features });
} catch (err) {
handleErrors(res, this.logger, err);
}
} }
async deleteFeature( async deleteFeature(
@ -51,25 +47,16 @@ export default class ArchiveController extends Controller {
): Promise<void> { ): Promise<void> {
const { featureName } = req.params; const { featureName } = req.params;
const user = extractUser(req); const user = extractUser(req);
try { await this.featureService.deleteFeature(featureName, user);
await this.featureService.deleteFeature(featureName, user); res.status(200).end();
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async reviveFeatureToggle(req, res): Promise<void> { async reviveFeatureToggle(req, res): Promise<void> {
const userName = extractUser(req); const userName = extractUser(req);
const { featureName } = req.params; const { featureName } = req.params;
await this.featureService.reviveToggle(featureName, userName);
try { res.status(200).end();
await this.featureService.reviveToggle(featureName, userName);
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
} }

View File

@ -2,7 +2,6 @@ import { Request, Response } from 'express';
import Controller from '../controller'; import Controller from '../controller';
import { handleErrors } from './util';
import extractUser from '../../extract-user'; import extractUser from '../../extract-user';
import { import {
@ -45,12 +44,8 @@ class ContextController extends Controller {
} }
async getContextFields(req: Request, res: Response): Promise<void> { async getContextFields(req: Request, res: Response): Promise<void> {
try { const fields = await this.contextService.getAll();
const fields = await this.contextService.getAll(); res.status(200).json(fields).end();
res.status(200).json(fields).end();
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async getContextField(req: Request, res: Response): Promise<void> { async getContextField(req: Request, res: Response): Promise<void> {
@ -69,12 +64,8 @@ class ContextController extends Controller {
const value = req.body; const value = req.body;
const userName = extractUser(req); const userName = extractUser(req);
try { await this.contextService.createContextField(value, userName);
await this.contextService.createContextField(value, userName); res.status(201).end();
res.status(201).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async updateContextField(req: Request, res: Response): Promise<void> { async updateContextField(req: Request, res: Response): Promise<void> {
@ -84,38 +75,23 @@ class ContextController extends Controller {
contextField.name = name; contextField.name = name;
try { await this.contextService.updateContextField(contextField, userName);
await this.contextService.updateContextField( res.status(200).end();
contextField,
userName,
);
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async deleteContextField(req: Request, res: Response): Promise<void> { async deleteContextField(req: Request, res: Response): Promise<void> {
const name = req.params.contextField; const name = req.params.contextField;
const userName = extractUser(req); const userName = extractUser(req);
try { await this.contextService.deleteContextField(name, userName);
await this.contextService.deleteContextField(name, userName); res.status(200).end();
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async validate(req: Request, res: Response): Promise<void> { async validate(req: Request, res: Response): Promise<void> {
const { name } = req.body; const { name } = req.body;
try { await this.contextService.validateName(name);
await this.contextService.validateName(name); res.status(200).end();
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
} }
export default ContextController; export default ContextController;

View File

@ -1,6 +1,5 @@
import { ADMIN } from '../../types/permissions'; import { ADMIN } from '../../types/permissions';
import { TemplateFormat } from '../../services/email-service'; import { TemplateFormat } from '../../services/email-service';
import { handleErrors } from './util';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; 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 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async getHtmlPreview(req, res): Promise<void> { async getHtmlPreview(req, res): Promise<void> {
try { const { template } = req.params;
const { template } = req.params; const ctx = req.query;
const ctx = req.query; const data = await this.emailService.compileTemplate(
const data = await this.emailService.compileTemplate( template,
template, TemplateFormat.HTML,
TemplateFormat.HTML, ctx,
ctx, );
); res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Type', 'text/html'); res.status(200);
res.status(200); res.send(data);
res.send(data); res.end();
res.end();
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async getTextPreview(req, res) { async getTextPreview(req, res) {
try { const { template } = req.params;
const { template } = req.params; const ctx = req.query;
const ctx = req.query; const data = await this.emailService.compileTemplate(
const data = await this.emailService.compileTemplate( template,
template, TemplateFormat.PLAIN,
TemplateFormat.PLAIN, ctx,
ctx, );
); res.setHeader('Content-Type', 'text/plain');
res.setHeader('Content-Type', 'text/plain'); res.status(200);
res.status(200); res.send(data);
res.send(data); res.end();
res.end();
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
} }
module.exports = EmailController; module.exports = EmailController;

View File

@ -5,7 +5,7 @@ import { IUnleashConfig } from '../../types/option';
import { IEnvironment } from '../../types/model'; import { IEnvironment } from '../../types/model';
import EnvironmentService from '../../services/environment-service'; import EnvironmentService from '../../services/environment-service';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { handleErrors } from './util'; import { handleErrors } from '../util';
import { ADMIN } from '../../types/permissions'; import { ADMIN } from '../../types/permissions';
interface EnvironmentParam { interface EnvironmentParam {

View File

@ -1,4 +1,3 @@
import { handleErrors } from './util';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';
import EventService from '../../services/event-service'; 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 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async getEvents(req, res): Promise<void> { async getEvents(req, res): Promise<void> {
try { const events = await this.eventService.getEvents();
const events = await this.eventService.getEvents(); eventDiffer.addDiffs(events);
eventDiffer.addDiffs(events); res.json({ version, events });
res.json({ version, events });
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async getEventsForToggle(req, res): Promise<void> { async getEventsForToggle(req, res): Promise<void> {
const toggleName = req.params.name; const toggleName = req.params.name;
try { const events = await this.eventService.getEventsForToggle(toggleName);
const events = await this.eventService.getEventsForToggle(
toggleName,
);
if (events) { if (events) {
eventDiffer.addDiffs(events); eventDiffer.addDiffs(events);
res.json({ toggleName, events }); res.json({ toggleName, events });
} else { } else {
res.status(404).json({ error: 'Could not find events' }); res.status(404).json({ error: 'Could not find events' });
}
} catch (e) {
handleErrors(res, this.logger, e);
} }
} }
} }

View File

@ -1,5 +1,4 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { handleErrors } from './util';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';
import FeatureTypeService from '../../services/feature-type-service'; import FeatureTypeService from '../../services/feature-type-service';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
@ -26,12 +25,8 @@ export default class FeatureTypeController extends Controller {
} }
async getAllFeatureTypes(req: Request, res: Response): Promise<void> { async getAllFeatureTypes(req: Request, res: Response): Promise<void> {
try { const types = await this.featureTypeService.getAll();
const types = await this.featureTypeService.getAll(); res.json({ version, types });
res.json({ version, types });
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
} }

View File

@ -3,7 +3,6 @@ import { Request, Response } from 'express';
import Controller from '../controller'; import Controller from '../controller';
import { handleErrors } from './util';
import extractUser from '../../extract-user'; import extractUser from '../../extract-user';
import { import {
UPDATE_FEATURE, UPDATE_FEATURE,
@ -94,129 +93,95 @@ class FeatureController extends Controller {
async getAllToggles(req: Request, res: Response): Promise<void> { async getAllToggles(req: Request, res: Response): Promise<void> {
const query = await this.prepQuery(req.query); 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 }); res.json({ version, features });
} catch (err) {
handleErrors(res, this.logger, err);
}
} }
async getToggle( async getToggle(
req: Request<{ featureName: string }, any, any, any>, req: Request<{ featureName: string }, any, any, any>,
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
try { const name = req.params.featureName;
const name = req.params.featureName; const feature = await this.featureService2.getFeatureToggle(name);
const feature = await this.featureService2.getFeatureToggle(name); const strategies =
const strategies = feature.environments.find((e) => e.name === GLOBAL_ENV)
feature.environments.find((e) => e.name === GLOBAL_ENV) ?.strategies || [];
?.strategies || []; res.json({
res.json({ ...feature,
...feature, strategies,
strategies, }).end();
}).end();
} catch (err) {
handleErrors(res, this.logger, err);
}
} }
// TODO
async listTags(req: Request, res: Response): Promise<void> { async listTags(req: Request, res: Response): Promise<void> {
try { const tags = await this.featureTagService.listTags(
const tags = await this.featureTagService.listTags( req.params.featureName,
req.params.featureName, );
); res.json({ version, tags });
res.json({ version, tags });
} catch (err) {
handleErrors(res, this.logger, err);
}
} }
// TODO
async addTag(req: Request, res: Response): Promise<void> { async addTag(req: Request, res: Response): Promise<void> {
const { featureName } = req.params; const { featureName } = req.params;
const userName = extractUser(req); const userName = extractUser(req);
try { const tag = await this.featureTagService.addTag(
const tag = await this.featureTagService.addTag( featureName,
featureName, req.body,
req.body, userName,
userName, );
); res.status(201).json(tag);
res.status(201).json(tag);
} catch (err) {
handleErrors(res, this.logger, err);
}
} }
// TODO // TODO
async removeTag(req: Request, res: Response): Promise<void> { async removeTag(req: Request, res: Response): Promise<void> {
const { featureName, type, value } = req.params; const { featureName, type, value } = req.params;
const userName = extractUser(req); const userName = extractUser(req);
try { await this.featureTagService.removeTag(
await this.featureTagService.removeTag( featureName,
featureName, { type, value },
{ type, value }, userName,
userName, );
); res.status(200).end();
res.status(200).end();
} catch (err) {
handleErrors(res, this.logger, err);
}
} }
async validate(req: Request, res: Response): Promise<void> { async validate(req: Request, res: Response): Promise<void> {
const { name } = req.body; const { name } = req.body;
try { await this.featureService2.validateName(name);
await this.featureService2.validateName(name); res.status(200).end();
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async createToggle(req: Request, res: Response): Promise<void> { async createToggle(req: Request, res: Response): Promise<void> {
const userName = extractUser(req); const userName = extractUser(req);
const toggle = req.body; const toggle = req.body;
try { const validatedToggle = await featureSchema.validateAsync(toggle);
const validatedToggle = await featureSchema.validateAsync(toggle); const { enabled } = validatedToggle;
const { enabled } = validatedToggle; const createdFeature = await this.featureService2.createFeatureToggle(
const createdFeature = validatedToggle.project,
await this.featureService2.createFeatureToggle( validatedToggle,
validatedToggle.project, userName,
validatedToggle, );
userName, const strategies = await Promise.all(
); toggle.strategies.map(async (s) =>
const strategies = await Promise.all( this.featureService2.createStrategy(
toggle.strategies.map(async (s) => s,
this.featureService2.createStrategy( createdFeature.project,
s, createdFeature.name,
createdFeature.project,
createdFeature.name,
),
), ),
); ),
await this.featureService2.updateEnabled( );
validatedToggle.name, await this.featureService2.updateEnabled(
GLOBAL_ENV, validatedToggle.name,
enabled, GLOBAL_ENV,
userName, enabled,
); userName,
);
res.status(201).json({ res.status(201).json({
...createdFeature, ...createdFeature,
enabled, enabled,
strategies, strategies,
}); });
} catch (error) {
this.logger.warn(error);
handleErrors(res, this.logger, error);
}
} }
async updateToggle(req: Request, res: Response): Promise<void> { async updateToggle(req: Request, res: Response): Promise<void> {
@ -230,48 +195,42 @@ class FeatureController extends Controller {
featureName, featureName,
); );
if (featureToggleExists) { if (featureToggleExists) {
try { await this.featureService2.getFeature(featureName);
await this.featureService2.getFeature(featureName); const projectId = await this.featureService2.getProjectId(
const projectId = await this.featureService2.getProjectId( updatedFeature.name,
updatedFeature.name, );
); const value = await featureSchema.validateAsync(updatedFeature);
const value = await featureSchema.validateAsync(updatedFeature); const { enabled } = value;
const { enabled } = value; const updatedToggle = this.featureService2.updateFeatureToggle(
const updatedToggle = this.featureService2.updateFeatureToggle( projectId,
projectId, value,
value, userName,
userName, );
);
await this.featureService2.removeAllStrategiesForEnv( await this.featureService2.removeAllStrategiesForEnv(featureName);
featureName, let strategies;
); if (updatedFeature.strategies) {
let strategies; strategies = await Promise.all(
if (updatedFeature.strategies) { updatedFeature.strategies.map(async (s) =>
strategies = await Promise.all( this.featureService2.createStrategy(
updatedFeature.strategies.map(async (s) => s,
this.featureService2.createStrategy( projectId,
s, featureName,
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 { } else {
res.status(404) res.status(404)
.json({ .json({
@ -285,91 +244,67 @@ class FeatureController extends Controller {
// Kept to keep backward compatibility // Kept to keep backward compatibility
async toggle(req: Request, res: Response): Promise<void> { async toggle(req: Request, res: Response): Promise<void> {
const userName = extractUser(req); const userName = extractUser(req);
try { const name = req.params.featureName;
const name = req.params.featureName; const feature = await this.featureService2.toggle(
const feature = await this.featureService2.toggle( name,
name, GLOBAL_ENV,
GLOBAL_ENV, userName,
userName, );
); res.status(200).json(feature);
res.status(200).json(feature);
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async toggleOn(req: Request, res: Response): Promise<void> { async toggleOn(req: Request, res: Response): Promise<void> {
const { featureName } = req.params; const { featureName } = req.params;
const userName = extractUser(req); const userName = extractUser(req);
try { const feature = await this.featureService2.updateEnabled(
const feature = await this.featureService2.updateEnabled( featureName,
featureName, GLOBAL_ENV,
GLOBAL_ENV, true,
true, userName,
userName, );
); res.json(feature);
res.json(feature);
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async toggleOff(req: Request, res: Response): Promise<void> { async toggleOff(req: Request, res: Response): Promise<void> {
const { featureName } = req.params; const { featureName } = req.params;
const userName = extractUser(req); const userName = extractUser(req);
try { const feature = await this.featureService2.updateEnabled(
const feature = await this.featureService2.updateEnabled( featureName,
featureName, GLOBAL_ENV,
GLOBAL_ENV, false,
false, userName,
userName, );
); res.json(feature);
res.json(feature);
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async staleOn(req: Request, res: Response): Promise<void> { async staleOn(req: Request, res: Response): Promise<void> {
try { const { featureName } = req.params;
const { featureName } = req.params; const userName = extractUser(req);
const userName = extractUser(req); const feature = await this.featureService2.updateStale(
const feature = await this.featureService2.updateStale( featureName,
featureName, true,
true, userName,
userName, );
); res.json(feature).end();
res.json(feature).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async staleOff(req: Request, res: Response): Promise<void> { async staleOff(req: Request, res: Response): Promise<void> {
try { const { featureName } = req.params;
const { featureName } = req.params; const userName = extractUser(req);
const userName = extractUser(req); const feature = await this.featureService2.updateStale(
const feature = await this.featureService2.updateStale( featureName,
featureName, false,
false, userName,
userName, );
); res.json(feature).end();
res.json(feature).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async archiveToggle(req: Request, res: Response): Promise<void> { async archiveToggle(req: Request, res: Response): Promise<void> {
const { featureName } = req.params; const { featureName } = req.params;
const userName = extractUser(req); const userName = extractUser(req);
try { await this.featureService2.archiveToggle(featureName, userName);
await this.featureService2.archiveToggle(featureName, userName); res.status(200).end();
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
} }
export default FeatureController; export default FeatureController;

View File

@ -1,6 +1,6 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import Controller from '../controller'; import Controller from '../controller';
import { handleErrors } from './util'; import { handleErrors } from '../util';
import { UPDATE_APPLICATION } from '../../types/permissions'; import { UPDATE_APPLICATION } from '../../types/permissions';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';

View File

@ -4,7 +4,7 @@ import { IUnleashConfig } from '../../../types/option';
import { IUnleashServices } from '../../../types/services'; import { IUnleashServices } from '../../../types/services';
import { Logger } from '../../../logger'; import { Logger } from '../../../logger';
import EnvironmentService from '../../../services/environment-service'; import EnvironmentService from '../../../services/environment-service';
import { handleErrors } from '../util'; import { handleErrors } from '../../util';
import { UPDATE_PROJECT } from '../../../types/permissions'; import { UPDATE_PROJECT } from '../../../types/permissions';
const PREFIX = '/:projectId/environments'; const PREFIX = '/:projectId/environments';

View File

@ -10,7 +10,6 @@ import {
IConstraint, IConstraint,
IStrategyConfig, IStrategyConfig,
} from '../../../types/model'; } from '../../../types/model';
import { handleErrors } from '../util';
import extractUsername from '../../../extract-user'; import extractUsername from '../../../extract-user';
import ProjectHealthService from '../../../services/project-health-service'; import ProjectHealthService from '../../../services/project-health-service';
@ -111,14 +110,10 @@ export default class ProjectFeaturesController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { projectId } = req.params; const { projectId } = req.params;
try { const features = await this.featureService.getFeatureToggles({
const features = await this.featureService.getFeatureToggles({ project: [projectId],
project: [projectId], });
}); res.json({ version: 1, features });
res.json({ version: 1, features });
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async createFeatureToggle( async createFeatureToggle(
@ -126,17 +121,13 @@ export default class ProjectFeaturesController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { projectId } = req.params; const { projectId } = req.params;
try { const userName = extractUsername(req);
const userName = extractUsername(req); const created = await this.featureService.createFeatureToggle(
const created = await this.featureService.createFeatureToggle( projectId,
projectId, req.body,
req.body, userName,
userName, );
); res.status(201).json(created);
res.status(201).json(created);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async getEnvironment( async getEnvironment(
@ -144,17 +135,12 @@ export default class ProjectFeaturesController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { environment, featureName, projectId } = req.params; const { environment, featureName, projectId } = req.params;
try { const environmentInfo = await this.featureService.getEnvironmentInfo(
const environmentInfo = projectId,
await this.featureService.getEnvironmentInfo( environment,
projectId, featureName,
environment, );
featureName, res.status(200).json(environmentInfo);
);
res.status(200).json(environmentInfo);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async getFeature( async getFeature(
@ -162,12 +148,8 @@ export default class ProjectFeaturesController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { featureName } = req.params; const { featureName } = req.params;
try { const feature = await this.featureService.getFeature(featureName);
const feature = await this.featureService.getFeature(featureName); res.status(200).json(feature);
res.status(200).json(feature);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async toggleEnvironmentOn( async toggleEnvironmentOn(
@ -175,17 +157,13 @@ export default class ProjectFeaturesController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { featureName, environment } = req.params; const { featureName, environment } = req.params;
try { await this.featureService.updateEnabled(
await this.featureService.updateEnabled( featureName,
featureName, environment,
environment, true,
true, extractUsername(req),
extractUsername(req), );
); res.status(200).end();
res.status(200).end();
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async toggleEnvironmentOff( async toggleEnvironmentOff(
@ -193,17 +171,13 @@ export default class ProjectFeaturesController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { featureName, environment } = req.params; const { featureName, environment } = req.params;
try { await this.featureService.updateEnabled(
await this.featureService.updateEnabled( featureName,
featureName, environment,
environment, false,
false, extractUsername(req),
extractUsername(req), );
); res.status(200).end();
res.status(200).end();
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async createFeatureStrategy( async createFeatureStrategy(
@ -211,17 +185,13 @@ export default class ProjectFeaturesController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { projectId, featureName, environment } = req.params; const { projectId, featureName, environment } = req.params;
try { const featureStrategy = await this.featureService.createStrategy(
const featureStrategy = await this.featureService.createStrategy( req.body,
req.body, projectId,
projectId, featureName,
featureName, environment,
environment, );
); res.status(200).json(featureStrategy);
res.status(200).json(featureStrategy);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async getFeatureStrategies( async getFeatureStrategies(
@ -229,17 +199,13 @@ export default class ProjectFeaturesController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { projectId, featureName, environment } = req.params; const { projectId, featureName, environment } = req.params;
try { const featureStrategies =
const featureStrategies = await this.featureService.getStrategiesForEnvironment(
await this.featureService.getStrategiesForEnvironment( projectId,
projectId, featureName,
featureName, environment,
environment, );
); res.status(200).json(featureStrategies);
res.status(200).json(featureStrategies);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async updateStrategy( async updateStrategy(
@ -247,15 +213,11 @@ export default class ProjectFeaturesController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { strategyId } = req.params; const { strategyId } = req.params;
try { const updatedStrategy = await this.featureService.updateStrategy(
const updatedStrategy = await this.featureService.updateStrategy( strategyId,
strategyId, req.body,
req.body, );
); res.status(200).json(updatedStrategy);
res.status(200).json(updatedStrategy);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async getStrategy( async getStrategy(
@ -265,11 +227,7 @@ export default class ProjectFeaturesController extends Controller {
this.logger.info('Getting strategy'); this.logger.info('Getting strategy');
const { strategyId } = req.params; const { strategyId } = req.params;
this.logger.info(strategyId); this.logger.info(strategyId);
try { const strategy = await this.featureService.getStrategy(strategyId);
const strategy = await this.featureService.getStrategy(strategyId); res.status(200).json(strategy);
res.status(200).json(strategy);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
} }

View File

@ -5,7 +5,7 @@ import { IUnleashConfig } from '../../../types/option';
import ProjectHealthService from '../../../services/project-health-service'; import ProjectHealthService from '../../../services/project-health-service';
import { Logger } from '../../../logger'; import { Logger } from '../../../logger';
import { IArchivedQuery, IProjectParam } from '../../../types/model'; import { IArchivedQuery, IProjectParam } from '../../../types/model';
import { handleErrors } from '../util'; import { handleErrors } from '../../util';
export default class ProjectHealthReport extends Controller { export default class ProjectHealthReport extends Controller {
private projectHealthService: ProjectHealthService; private projectHealthService: ProjectHealthService;

View File

@ -6,7 +6,6 @@ import { Request, Response } from 'express';
import Controller from '../controller'; import Controller from '../controller';
import { ADMIN } from '../../types/permissions'; import { ADMIN } from '../../types/permissions';
import extractUser from '../../extract-user'; import extractUser from '../../extract-user';
import { handleErrors } from './util';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
@ -43,32 +42,28 @@ class StateController extends Controller {
const userName = extractUser(req); const userName = extractUser(req);
const { drop, keep } = req.query; const { drop, keep } = req.query;
// TODO: Should override request type so file is a type on request // TODO: Should override request type so file is a type on request
try { let data;
let data; // @ts-ignore
if (req.file) {
// @ts-ignore // @ts-ignore
if (req.file) { if (mime.getType(req.file.originalname) === 'text/yaml') {
// @ts-ignore // @ts-ignore
if (mime.getType(req.file.originalname) === 'text/yaml') { data = YAML.safeLoad(req.file.buffer);
// @ts-ignore
data = YAML.safeLoad(req.file.buffer);
} else {
// @ts-ignore
data = JSON.parse(req.file.buffer);
}
} else { } else {
data = req.body; // @ts-ignore
data = JSON.parse(req.file.buffer);
} }
} else {
await this.stateService.import({ data = req.body;
data,
userName,
dropBeforeImport: paramToBool(drop, false),
keepExisting: paramToBool(keep, true),
});
res.sendStatus(202);
} catch (err) {
handleErrors(res, this.logger, err);
} }
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> { async export(req: Request, res: Response): Promise<void> {
@ -84,30 +79,24 @@ class StateController extends Controller {
const includeTags = paramToBool(req.query.tags, true); const includeTags = paramToBool(req.query.tags, true);
const includeEnvironments = paramToBool(req.query.environments, true); const includeEnvironments = paramToBool(req.query.environments, true);
try { const data = await this.stateService.export({
const data = await this.stateService.export({ includeStrategies,
includeStrategies, includeFeatureToggles,
includeFeatureToggles, includeProjects,
includeProjects, includeTags,
includeTags, includeEnvironments,
includeEnvironments, });
}); const timestamp = moment().format('YYYY-MM-DD_HH-mm-ss');
const timestamp = moment().format('YYYY-MM-DD_HH-mm-ss'); if (format === 'yaml') {
if (format === 'yaml') { if (downloadFile) {
if (downloadFile) { res.attachment(`export-${timestamp}.yml`);
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);
} }
} catch (err) { res.type('yaml').send(YAML.safeDump(data, { skipInvalid: true }));
handleErrors(res, this.logger, err); } else {
if (downloadFile) {
res.attachment(`export-${timestamp}.json`);
}
res.json(data);
} }
} }
} }

View File

@ -6,7 +6,7 @@ import { Logger } from '../../logger';
const Controller = require('../controller'); const Controller = require('../controller');
const extractUser = require('../../extract-user'); const extractUser = require('../../extract-user');
const { handleErrors } = require('./util'); const { handleErrors } = require('../util');
const { const {
DELETE_STRATEGY, DELETE_STRATEGY,
CREATE_STRATEGY, CREATE_STRATEGY,

View File

@ -2,7 +2,7 @@ import { Request, Response } from 'express';
import Controller from '../controller'; import Controller from '../controller';
import { UPDATE_FEATURE } from '../../types/permissions'; import { UPDATE_FEATURE } from '../../types/permissions';
import { handleErrors } from './util'; import { handleErrors } from '../util';
import extractUsername from '../../extract-user'; import extractUsername from '../../extract-user';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';

View File

@ -7,7 +7,6 @@ import { Logger } from '../../logger';
import Controller from '../controller'; import Controller from '../controller';
import { UPDATE_FEATURE } from '../../types/permissions'; import { UPDATE_FEATURE } from '../../types/permissions';
import { handleErrors } from './util';
import extractUsername from '../../extract-user'; import extractUsername from '../../extract-user';
const version = 1; const version = 1;
@ -33,52 +32,32 @@ class TagController extends Controller {
} }
async getTags(req: Request, res: Response): Promise<void> { async getTags(req: Request, res: Response): Promise<void> {
try { const tags = await this.tagService.getTags();
const tags = await this.tagService.getTags(); res.json({ version, tags });
res.json({ version, tags });
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async getTagsByType(req: Request, res: Response): Promise<void> { async getTagsByType(req: Request, res: Response): Promise<void> {
try { const tags = await this.tagService.getTagsByType(req.params.type);
const tags = await this.tagService.getTagsByType(req.params.type); res.json({ version, tags });
res.json({ version, tags });
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async getTag(req: Request, res: Response): Promise<void> { async getTag(req: Request, res: Response): Promise<void> {
const { type, value } = req.params; const { type, value } = req.params;
try { const tag = await this.tagService.getTag({ type, value });
const tag = await this.tagService.getTag({ type, value }); res.json({ version, tag });
res.json({ version, tag });
} catch (err) {
handleErrors(res, this.logger, err);
}
} }
async createTag(req: Request, res: Response): Promise<void> { async createTag(req: Request, res: Response): Promise<void> {
const userName = extractUsername(req); const userName = extractUsername(req);
try { await this.tagService.createTag(req.body, userName);
await this.tagService.createTag(req.body, userName); res.status(201).end();
res.status(201).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
async deleteTag(req: Request, res: Response): Promise<void> { async deleteTag(req: Request, res: Response): Promise<void> {
const { type, value } = req.params; const { type, value } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);
try { await this.tagService.deleteTag({ type, value }, userName);
await this.tagService.deleteTag({ type, value }, userName); res.status(200).end();
res.status(200).end();
} catch (error) {
handleErrors(res, this.logger, error);
}
} }
} }
export default TagController; export default TagController;

View File

@ -4,7 +4,7 @@ import { ADMIN } from '../../types/permissions';
import UserService from '../../services/user-service'; import UserService from '../../services/user-service';
import { AccessService } from '../../services/access-service'; import { AccessService } from '../../services/access-service';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { handleErrors } from './util'; import { handleErrors } from '../util';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { EmailService } from '../../services/email-service'; import { EmailService } from '../../services/email-service';
import ResetTokenService from '../../services/reset-token-service'; import ResetTokenService from '../../services/reset-token-service';

View File

@ -2,11 +2,10 @@ import { Response } from 'express';
import Controller from '../controller'; import Controller from '../controller';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { IUserRequest } from './user';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';
import UserFeedbackService from '../../services/user-feedback-service'; import UserFeedbackService from '../../services/user-feedback-service';
import { handleErrors } from './util'; import { IAuthRequest } from '../unleash-types';
interface IFeedbackBody { interface IFeedbackBody {
neverShow?: boolean; neverShow?: boolean;
@ -32,7 +31,7 @@ class UserFeedbackController extends Controller {
} }
private async recordFeedback( private async recordFeedback(
req: IUserRequest<any, any, IFeedbackBody, any>, req: IAuthRequest<any, any, IFeedbackBody, any>,
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const BAD_REQUEST = 400; const BAD_REQUEST = 400;
@ -54,18 +53,12 @@ class UserFeedbackController extends Controller {
neverShow: req.body.neverShow || false, neverShow: req.body.neverShow || false,
}; };
try { const updated = await this.userFeedbackService.updateFeedback(feedback);
const updated = await this.userFeedbackService.updateFeedback( res.json(updated);
feedback,
);
res.json(updated);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
private async updateFeedbackSettings( private async updateFeedbackSettings(
req: IUserRequest<any, any, IFeedbackBody, any>, req: IAuthRequest<any, any, IFeedbackBody, any>,
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { user } = req; const { user } = req;
@ -78,14 +71,8 @@ class UserFeedbackController extends Controller {
neverShow: req.body.neverShow || false, neverShow: req.body.neverShow || false,
}; };
try { const updated = await this.userFeedbackService.updateFeedback(feedback);
const updated = await this.userFeedbackService.updateFeedback( res.json(updated);
feedback,
);
res.json(updated);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
} }

View File

@ -1,13 +1,10 @@
import { Request, Response } from 'express'; import { Response } from 'express';
import { IAuthRequest } from '../unleash-types'; import { IAuthRequest } from '../unleash-types';
import Controller from '../controller'; import Controller from '../controller';
import { AccessService } from '../../services/access-service'; import { AccessService } from '../../services/access-service';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';
import UserService from '../../services/user-service'; 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 SessionService from '../../services/session-service';
import UserFeedbackService from '../../services/user-feedback-service'; import UserFeedbackService from '../../services/user-feedback-service';
@ -16,11 +13,6 @@ interface IChangeUserRequest {
confirmPassword: string; confirmPassword: string;
} }
export interface IUserRequest<PARAM, QUERY, BODY, RESPONSE>
extends Request<PARAM, QUERY, BODY, RESPONSE> {
user: User;
}
class UserController extends Controller { class UserController extends Controller {
private accessService: AccessService; private accessService: AccessService;
@ -30,8 +22,6 @@ class UserController extends Controller {
private sessionService: SessionService; private sessionService: SessionService;
private logger: Logger;
constructor( constructor(
config: IUnleashConfig, config: IUnleashConfig,
{ {
@ -52,7 +42,6 @@ class UserController extends Controller {
this.userService = userService; this.userService = userService;
this.sessionService = sessionService; this.sessionService = sessionService;
this.userFeedbackService = userFeedbackService; this.userFeedbackService = userFeedbackService;
this.logger = config.getLogger('lib/routes/admin-api/user.ts');
this.get('/', this.getUser); this.get('/', this.getUser);
this.post('/change-password', this.updateUserPass); this.post('/change-password', this.updateUserPass);
@ -76,34 +65,24 @@ class UserController extends Controller {
} }
async updateUserPass( async updateUserPass(
req: IUserRequest<any, any, IChangeUserRequest, any>, req: IAuthRequest<any, any, IChangeUserRequest, any>,
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { user } = req; const { user } = req;
const { password, confirmPassword } = req.body; const { password, confirmPassword } = req.body;
try { if (password === confirmPassword) {
if (password === confirmPassword) { this.userService.validatePassword(password);
this.userService.validatePassword(password); await this.userService.changePassword(user.id, password);
await this.userService.changePassword(user.id, password); res.status(200).end();
res.status(200).end(); } else {
} else { res.status(400).end();
res.status(400).end();
}
} catch (e) {
handleErrors(res, this.logger, e);
} }
} }
async mySessions(req: IAuthRequest, res: Response): Promise<void> { async mySessions(req: IAuthRequest, res: Response): Promise<void> {
const { user } = req; const { user } = req;
try { const sessions = await this.sessionService.getSessionsForUser(user.id);
const sessions = await this.sessionService.getSessionsForUser( res.json(sessions);
user.id,
);
res.json(sessions);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
} }

View File

@ -2,7 +2,6 @@ import { Request, Response } from 'express';
import Controller from '../controller'; import Controller from '../controller';
import UserService from '../../services/user-service'; import UserService from '../../services/user-service';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { handleErrors } from '../admin-api/util';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';
@ -40,23 +39,15 @@ class ResetPasswordController extends Controller {
async sendResetPasswordEmail(req: Request, res: Response): Promise<void> { async sendResetPasswordEmail(req: Request, res: Response): Promise<void> {
const { email } = req.body; const { email } = req.body;
try { await this.userService.createResetPasswordEmail(email);
await this.userService.createResetPasswordEmail(email); res.status(200).end();
res.status(200).end();
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async validatePassword(req: Request, res: Response): Promise<void> { async validatePassword(req: Request, res: Response): Promise<void> {
const { password } = req.body; const { password } = req.body;
try { this.userService.validatePassword(password);
this.userService.validatePassword(password); res.status(200).end();
res.status(200).end();
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async validateToken( async validateToken(
@ -64,13 +55,9 @@ class ResetPasswordController extends Controller {
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { token } = req.query; const { token } = req.query;
try { const user = await this.userService.getUserForToken(token);
const user = await this.userService.getUserForToken(token); await this.logout(req);
await this.logout(req); res.status(200).json(user);
res.status(200).json(user);
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
async changePassword( async changePassword(
@ -79,12 +66,8 @@ class ResetPasswordController extends Controller {
): Promise<void> { ): Promise<void> {
await this.logout(req); await this.logout(req);
const { token, password } = req.body; const { token, password } = req.body;
try { await this.userService.resetPassword(token, password);
await this.userService.resetPassword(token, password); res.status(200).end();
res.status(200).end();
} catch (e) {
handleErrors(res, this.logger, e);
}
} }
private async logout(req: SessionRequest<any, any, any, any>) { private async logout(req: SessionRequest<any, any, any, any>) {

View File

@ -1,9 +1,9 @@
const Controller = require('../controller'); const Controller = require('../controller');
class PasswordProvider extends Controller { class PasswordProvider extends Controller {
constructor({ getLogger }, { userService }) { constructor(config, { userService }) {
super(); super(config);
this.logger = getLogger('/auth/password-provider.js'); this.logger = config.getLogger('/auth/password-provider.js');
this.userService = userService; this.userService = userService;
this.post('/login', this.login); this.post('/login', this.login);

View File

@ -1,6 +1,6 @@
import memoizee from 'memoizee'; import memoizee from 'memoizee';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { handleErrors } from '../admin-api/util'; import { handleErrors } from '../util';
import Controller from '../controller'; import Controller from '../controller';
import { IUnleashServices } from '../../types/services'; import { IUnleashServices } from '../../types/services';
import { IUnleashConfig } from '../../types/option'; import { IUnleashConfig } from '../../types/option';

View File

@ -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 { IUnleashConfig } from '../types/option';
import { handleErrors } from './util';
const { Router } = require('express'); const { Router } = require('express');
const NoAccessError = require('../error/no-access-error'); const NoAccessError = require('../error/no-access-error');
const requireContentType = require('../middleware/content_type_checker'); 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) => { const checkPermission = (permission) => async (req, res, next) => {
if (!permission) { if (!permission) {
return next(); return next();
@ -17,24 +31,48 @@ const checkPermission = (permission) => async (req, res, next) => {
/** /**
* Base class for Controllers to standardize binding to express Router. * 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 { export default class Controller {
private ownLogger: Logger;
app: IRouter; app: IRouter;
config: IUnleashConfig; config: IUnleashConfig;
constructor(config: IUnleashConfig) { constructor(config: IUnleashConfig) {
this.ownLogger = config.getLogger(
`controller/${this.constructor.name}`,
);
this.app = Router(); this.app = Router();
this.config = config; this.config = config;
} }
get(path: string, handler: Function, permission?: string): void { wrap(handler: IRequestHandler): IRequestHandler {
this.app.get(path, checkPermission(permission), handler.bind(this)); 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( post(
path: string, path: string,
handler: Function, handler: IRequestHandler,
permission?: string, permission?: string,
...acceptedContentTypes: string[] ...acceptedContentTypes: string[]
): void { ): void {
@ -42,13 +80,13 @@ export default class Controller {
path, path,
checkPermission(permission), checkPermission(permission),
requireContentType(...acceptedContentTypes), requireContentType(...acceptedContentTypes),
handler.bind(this), this.wrap(handler.bind(this)),
); );
} }
put( put(
path: string, path: string,
handler: Function, handler: IRequestHandler,
permission?: string, permission?: string,
...acceptedContentTypes: string[] ...acceptedContentTypes: string[]
): void { ): void {
@ -56,17 +94,21 @@ export default class Controller {
path, path,
checkPermission(permission), checkPermission(permission),
requireContentType(...acceptedContentTypes), requireContentType(...acceptedContentTypes),
handler.bind(this), this.wrap(handler.bind(this)),
); );
} }
delete(path: string, handler: Function, permission?: string): void { delete(path: string, handler: IRequestHandler, permission?: string): void {
this.app.delete(path, checkPermission(permission), handler.bind(this)); this.app.delete(
path,
checkPermission(permission),
this.wrap(handler.bind(this)),
);
} }
fileupload( fileupload(
path: string, path: string,
filehandler: Function, filehandler: IRequestHandler,
handler: Function, handler: Function,
permission?: string, permission?: string,
): void { ): void {
@ -74,11 +116,10 @@ export default class Controller {
path, path,
checkPermission(permission), checkPermission(permission),
filehandler.bind(this), 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 { use(path: string, router: IRouter): void {
this.app.use(path, router); this.app.use(path, router);
} }

View File

@ -15,7 +15,7 @@ class HealthCheckController extends Controller {
config: IUnleashConfig, config: IUnleashConfig,
{ healthService }: Pick<IUnleashServices, 'healthService'>, { healthService }: Pick<IUnleashServices, 'healthService'>,
) { ) {
super(); super(config);
this.logger = config.getLogger('health-check.js'); this.logger = config.getLogger('health-check.js');
this.healthService = healthService; this.healthService = healthService;
this.get('/', (req, res) => this.index(req, res)); this.get('/', (req, res) => this.index(req, res));

View File

@ -1,7 +1,13 @@
import { Request } from 'express'; import { Request } from 'express';
import * as core from 'express-serve-static-core';
import User from '../types/user'; 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; user: User;
logout: () => void; logout: () => void;
session: any; session: any;

View File

@ -1,6 +1,6 @@
import joi from 'joi'; import joi from 'joi';
import { Response } from 'express'; import { Response } from 'express';
import { Logger } from '../../logger'; import { Logger } from '../logger';
export const customJoi = joi.extend((j) => ({ export const customJoi = joi.extend((j) => ({
type: 'isUrlFriendly', type: 'isUrlFriendly',

View File

@ -1,5 +1,5 @@
import joi from 'joi'; import joi from 'joi';
import { nameType } from '../routes/admin-api/util'; import { nameType } from '../routes/util';
export const nameSchema = joi export const nameSchema = joi
.object() .object()

View File

@ -14,6 +14,7 @@ import User from './types/user';
import * as permissions from './types/permissions'; import * as permissions from './types/permissions';
import AuthenticationRequired from './types/authentication-required'; import AuthenticationRequired from './types/authentication-required';
import Controller from './routes/controller';
import * as eventType from './types/events'; import * as eventType from './types/events';
import { addEventHook } from './event-hook'; import { addEventHook } from './event-hook';
import registerGracefulShutdown from './util/graceful-shutdown'; import registerGracefulShutdown from './util/graceful-shutdown';
@ -145,6 +146,7 @@ const serverImpl = {
create, create,
User, User,
AuthenticationRequired, AuthenticationRequired,
Controller,
permissions, permissions,
eventType, eventType,
}; };

View File

@ -1,5 +1,5 @@
import joi from 'joi'; import joi from 'joi';
import { nameType } from '../routes/admin-api/util'; import { nameType } from '../routes/util';
export const addonSchema = joi export const addonSchema = joi
.object() .object()

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
const joi = require('joi'); const joi = require('joi');
const { nameType } = require('../routes/admin-api/util'); const { nameType } = require('../routes/util');
const nameSchema = joi.object().keys({ name: nameType }); const nameSchema = joi.object().keys({ name: nameType });

View File

@ -1,5 +1,5 @@
const joi = require('joi'); const joi = require('joi');
const { nameType } = require('../routes/admin-api/util'); const { nameType } = require('../routes/util');
const projectSchema = joi const projectSchema = joi
.object() .object()

View File

@ -2,7 +2,7 @@ import User from '../types/user';
import { AccessService } from './access-service'; import { AccessService } from './access-service';
import NameExistsError from '../error/name-exists-error'; import NameExistsError from '../error/name-exists-error';
import InvalidOperationError from '../error/invalid-operation-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 schema from './project-schema';
import NotFoundError from '../error/notfound-error'; import NotFoundError from '../error/notfound-error';
import { import {

View File

@ -4,7 +4,7 @@ import strategySchema from './strategy-schema';
import { tagSchema } from './tag-schema'; import { tagSchema } from './tag-schema';
import { tagTypeSchema } from './tag-type-schema'; import { tagTypeSchema } from './tag-type-schema';
import projectSchema from './project-schema'; import projectSchema from './project-schema';
import { nameType } from '../routes/admin-api/util'; import { nameType } from '../routes/util';
export const featureStrategySchema = joi export const featureStrategySchema = joi
.object() .object()

View File

@ -1,5 +1,5 @@
const joi = require('joi'); const joi = require('joi');
const { nameType } = require('../routes/admin-api/util'); const { nameType } = require('../routes/util');
const strategySchema = joi const strategySchema = joi
.object() .object()

View File

@ -1,6 +1,6 @@
import Joi from 'joi'; import Joi from 'joi';
import { customJoi } from '../routes/admin-api/util'; import { customJoi } from '../routes/util';
export const tagSchema = Joi.object() export const tagSchema = Joi.object()
.keys({ .keys({

View File

@ -1,5 +1,5 @@
import Joi from 'joi'; import Joi from 'joi';
import { customJoi } from '../routes/admin-api/util'; import { customJoi } from '../routes/util';
export const tagTypeSchema = Joi.object() export const tagTypeSchema = Joi.object()
.keys({ .keys({