mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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