2021-07-07 10:46:50 +02:00
|
|
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
2021-04-22 10:07:10 +02:00
|
|
|
import { Request, Response } from 'express';
|
2017-06-28 10:20:22 +02:00
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
import Controller from '../controller';
|
|
|
|
|
2021-09-14 19:58:48 +02:00
|
|
|
import { extractUsername } from '../../util/extract-user';
|
2021-04-22 10:07:10 +02:00
|
|
|
import {
|
2018-12-19 10:36:56 +01:00
|
|
|
UPDATE_FEATURE,
|
|
|
|
DELETE_FEATURE,
|
|
|
|
CREATE_FEATURE,
|
2021-05-02 20:58:02 +02:00
|
|
|
} from '../../types/permissions';
|
2021-04-22 10:07:10 +02:00
|
|
|
import { IUnleashConfig } from '../../types/option';
|
|
|
|
import { IUnleashServices } from '../../types/services';
|
2021-11-12 13:15:51 +01:00
|
|
|
import FeatureToggleService from '../../services/feature-toggle-service';
|
2021-07-07 10:46:50 +02:00
|
|
|
import { featureSchema, querySchema } from '../../schema/feature-schema';
|
|
|
|
import { IFeatureToggleQuery } from '../../types/model';
|
|
|
|
import FeatureTagService from '../../services/feature-tag-service';
|
2021-09-14 19:58:48 +02:00
|
|
|
import { IAuthRequest } from '../unleash-types';
|
2021-09-24 13:55:00 +02:00
|
|
|
import { DEFAULT_ENV } from '../../util/constants';
|
2020-04-14 22:29:11 +02:00
|
|
|
|
2017-06-28 10:20:22 +02:00
|
|
|
const version = 1;
|
|
|
|
|
2018-11-30 11:11:12 +01:00
|
|
|
class FeatureController extends Controller {
|
2021-10-28 15:48:07 +02:00
|
|
|
private tagService: FeatureTagService;
|
2021-04-22 10:07:10 +02:00
|
|
|
|
2021-11-12 13:15:51 +01:00
|
|
|
private service: FeatureToggleService;
|
2021-04-22 10:07:10 +02:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
config: IUnleashConfig,
|
|
|
|
{
|
2021-07-07 10:46:50 +02:00
|
|
|
featureTagService,
|
|
|
|
featureToggleServiceV2,
|
|
|
|
}: Pick<
|
2021-08-12 15:04:37 +02:00
|
|
|
IUnleashServices,
|
|
|
|
'featureTagService' | 'featureToggleServiceV2'
|
2021-07-07 10:46:50 +02:00
|
|
|
>,
|
2021-04-22 10:07:10 +02:00
|
|
|
) {
|
2018-12-19 14:50:01 +01:00
|
|
|
super(config);
|
2021-10-28 15:48:07 +02:00
|
|
|
this.tagService = featureTagService;
|
|
|
|
this.service = featureToggleServiceV2;
|
2018-11-30 11:11:12 +01:00
|
|
|
|
|
|
|
this.get('/', this.getAllToggles);
|
2018-12-19 10:36:56 +01:00
|
|
|
this.post('/', this.createToggle, CREATE_FEATURE);
|
2018-11-30 11:11:12 +01:00
|
|
|
this.get('/:featureName', this.getToggle);
|
2018-12-19 10:36:56 +01:00
|
|
|
this.put('/:featureName', this.updateToggle, UPDATE_FEATURE);
|
2021-05-06 14:11:56 +02:00
|
|
|
this.delete('/:featureName', this.archiveToggle, DELETE_FEATURE);
|
2018-11-30 11:11:12 +01:00
|
|
|
this.post('/validate', this.validate);
|
2018-12-19 10:36:56 +01:00
|
|
|
this.post('/:featureName/toggle', this.toggle, UPDATE_FEATURE);
|
2019-03-07 22:19:48 +01:00
|
|
|
this.post('/:featureName/toggle/on', this.toggleOn, UPDATE_FEATURE);
|
|
|
|
this.post('/:featureName/toggle/off', this.toggleOff, UPDATE_FEATURE);
|
2020-08-07 10:46:35 +02:00
|
|
|
this.post('/:featureName/stale/on', this.staleOn, UPDATE_FEATURE);
|
|
|
|
this.post('/:featureName/stale/off', this.staleOff, UPDATE_FEATURE);
|
2021-03-11 22:51:58 +01:00
|
|
|
this.get('/:featureName/tags', this.listTags);
|
2021-01-04 10:29:33 +01:00
|
|
|
this.post('/:featureName/tags', this.addTag, UPDATE_FEATURE);
|
|
|
|
this.delete(
|
2021-01-18 12:32:19 +01:00
|
|
|
'/:featureName/tags/:type/:value',
|
2021-01-04 10:29:33 +01:00
|
|
|
this.removeTag,
|
|
|
|
UPDATE_FEATURE,
|
|
|
|
);
|
2018-11-30 11:11:12 +01:00
|
|
|
}
|
2017-06-28 10:20:22 +02:00
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
|
|
paramToArray(param: any) {
|
|
|
|
if (!param) {
|
|
|
|
return param;
|
|
|
|
}
|
|
|
|
return Array.isArray(param) ? param : [param];
|
|
|
|
}
|
|
|
|
|
|
|
|
async prepQuery({
|
|
|
|
tag,
|
|
|
|
project,
|
|
|
|
namePrefix,
|
|
|
|
}: any): Promise<IFeatureToggleQuery> {
|
|
|
|
if (!tag && !project && !namePrefix) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const tagQuery = this.paramToArray(tag);
|
|
|
|
const projectQuery = this.paramToArray(project);
|
|
|
|
const query = await querySchema.validateAsync({
|
|
|
|
tag: tagQuery,
|
|
|
|
project: projectQuery,
|
|
|
|
namePrefix,
|
|
|
|
});
|
|
|
|
if (query.tag) {
|
2021-08-12 15:04:37 +02:00
|
|
|
query.tag = query.tag.map((q) => q.split(':'));
|
2021-07-07 10:46:50 +02:00
|
|
|
}
|
|
|
|
return query;
|
|
|
|
}
|
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
async getAllToggles(req: Request, res: Response): Promise<void> {
|
2021-07-07 10:46:50 +02:00
|
|
|
const query = await this.prepQuery(req.query);
|
2021-10-28 15:48:07 +02:00
|
|
|
const features = await this.service.getFeatureToggles(query);
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-08-13 10:36:19 +02:00
|
|
|
res.json({ version, features });
|
2018-11-30 11:11:12 +01:00
|
|
|
}
|
2017-06-28 10:20:22 +02:00
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
async getToggle(
|
|
|
|
req: Request<{ featureName: string }, any, any, any>,
|
|
|
|
res: Response,
|
|
|
|
): Promise<void> {
|
2021-08-13 10:36:19 +02:00
|
|
|
const name = req.params.featureName;
|
2021-10-28 15:48:07 +02:00
|
|
|
const feature = await this.service.getFeatureToggleLegacy(name);
|
2021-08-19 13:34:24 +02:00
|
|
|
res.json(feature).end();
|
|
|
|
}
|
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
async listTags(req: Request, res: Response): Promise<void> {
|
2021-10-28 15:48:07 +02:00
|
|
|
const tags = await this.tagService.listTags(req.params.featureName);
|
2021-08-13 10:36:19 +02:00
|
|
|
res.json({ version, tags });
|
2021-01-04 10:29:33 +01:00
|
|
|
}
|
|
|
|
|
2021-09-14 19:58:48 +02:00
|
|
|
async addTag(req: IAuthRequest, res: Response): Promise<void> {
|
2021-01-04 10:29:33 +01:00
|
|
|
const { featureName } = req.params;
|
2021-09-14 19:58:48 +02:00
|
|
|
const userName = extractUsername(req);
|
2021-10-28 15:48:07 +02:00
|
|
|
const tag = await this.tagService.addTag(
|
2021-08-13 10:36:19 +02:00
|
|
|
featureName,
|
|
|
|
req.body,
|
|
|
|
userName,
|
|
|
|
);
|
|
|
|
res.status(201).json(tag);
|
2021-01-04 10:29:33 +01:00
|
|
|
}
|
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
// TODO
|
2021-09-14 19:58:48 +02:00
|
|
|
async removeTag(req: IAuthRequest, res: Response): Promise<void> {
|
2021-01-18 12:32:19 +01:00
|
|
|
const { featureName, type, value } = req.params;
|
2021-09-14 19:58:48 +02:00
|
|
|
const userName = extractUsername(req);
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.tagService.removeTag(featureName, { type, value }, userName);
|
2021-08-13 10:36:19 +02:00
|
|
|
res.status(200).end();
|
2021-01-04 10:29:33 +01:00
|
|
|
}
|
|
|
|
|
2021-04-22 10:07:10 +02:00
|
|
|
async validate(req: Request, res: Response): Promise<void> {
|
2020-04-14 22:29:11 +02:00
|
|
|
const { name } = req.body;
|
2017-06-28 10:20:22 +02:00
|
|
|
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.validateName(name);
|
2021-08-13 10:36:19 +02:00
|
|
|
res.status(200).end();
|
2018-11-30 11:11:12 +01:00
|
|
|
}
|
|
|
|
|
2021-09-14 19:58:48 +02:00
|
|
|
async createToggle(req: IAuthRequest, res: Response): Promise<void> {
|
|
|
|
const userName = extractUsername(req);
|
2021-07-07 10:46:50 +02:00
|
|
|
const toggle = req.body;
|
2017-06-28 10:20:22 +02:00
|
|
|
|
2021-08-13 10:36:19 +02:00
|
|
|
const validatedToggle = await featureSchema.validateAsync(toggle);
|
|
|
|
const { enabled } = validatedToggle;
|
2021-10-28 15:48:07 +02:00
|
|
|
const createdFeature = await this.service.createFeatureToggle(
|
2021-08-13 10:36:19 +02:00
|
|
|
validatedToggle.project,
|
|
|
|
validatedToggle,
|
|
|
|
userName,
|
2021-11-25 14:53:58 +01:00
|
|
|
true,
|
2021-08-13 10:36:19 +02:00
|
|
|
);
|
|
|
|
const strategies = await Promise.all(
|
|
|
|
toggle.strategies.map(async (s) =>
|
2021-10-28 15:48:07 +02:00
|
|
|
this.service.createStrategy(
|
2021-08-13 10:36:19 +02:00
|
|
|
s,
|
2021-11-04 21:24:55 +01:00
|
|
|
{
|
|
|
|
projectId: createdFeature.project,
|
|
|
|
featureName: createdFeature.name,
|
|
|
|
environment: DEFAULT_ENV,
|
|
|
|
},
|
2021-09-20 12:13:38 +02:00
|
|
|
userName,
|
2021-07-07 10:46:50 +02:00
|
|
|
),
|
2021-08-13 10:36:19 +02:00
|
|
|
),
|
|
|
|
);
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.updateEnabled(
|
2021-09-13 10:23:57 +02:00
|
|
|
createdFeature.project,
|
|
|
|
createdFeature.name,
|
2021-09-24 13:55:00 +02:00
|
|
|
DEFAULT_ENV,
|
2021-08-13 10:36:19 +02:00
|
|
|
enabled,
|
|
|
|
userName,
|
|
|
|
);
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-08-13 10:36:19 +02:00
|
|
|
res.status(201).json({
|
|
|
|
...createdFeature,
|
|
|
|
enabled,
|
|
|
|
strategies,
|
|
|
|
});
|
2018-11-30 11:11:12 +01:00
|
|
|
}
|
2017-06-28 10:20:22 +02:00
|
|
|
|
2021-09-14 19:58:48 +02:00
|
|
|
async updateToggle(req: IAuthRequest, res: Response): Promise<void> {
|
2020-04-14 22:29:11 +02:00
|
|
|
const { featureName } = req.params;
|
2021-09-14 19:58:48 +02:00
|
|
|
const userName = extractUsername(req);
|
2017-06-28 10:20:22 +02:00
|
|
|
const updatedFeature = req.body;
|
|
|
|
|
|
|
|
updatedFeature.name = featureName;
|
|
|
|
|
2021-10-28 15:48:07 +02:00
|
|
|
const projectId = await this.service.getProjectId(updatedFeature.name);
|
2021-08-26 13:59:11 +02:00
|
|
|
const value = await featureSchema.validateAsync(updatedFeature);
|
|
|
|
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.updateFeatureToggle(projectId, value, userName);
|
2021-08-26 13:59:11 +02:00
|
|
|
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.removeAllStrategiesForEnv(featureName);
|
2021-07-07 10:46:50 +02:00
|
|
|
|
2021-08-26 13:59:11 +02:00
|
|
|
if (updatedFeature.strategies) {
|
|
|
|
await Promise.all(
|
|
|
|
updatedFeature.strategies.map(async (s) =>
|
2021-10-28 15:48:07 +02:00
|
|
|
this.service.createStrategy(
|
2021-08-26 13:59:11 +02:00
|
|
|
s,
|
2021-11-04 21:24:55 +01:00
|
|
|
{ projectId, featureName, environment: DEFAULT_ENV },
|
2021-09-20 12:13:38 +02:00
|
|
|
userName,
|
2021-08-13 10:36:19 +02:00
|
|
|
),
|
2021-08-26 13:59:11 +02:00
|
|
|
),
|
2021-08-13 10:36:19 +02:00
|
|
|
);
|
2018-11-30 11:11:12 +01:00
|
|
|
}
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.updateEnabled(
|
2021-09-13 10:23:57 +02:00
|
|
|
projectId,
|
2021-08-26 13:59:11 +02:00
|
|
|
updatedFeature.name,
|
2021-09-24 13:55:00 +02:00
|
|
|
DEFAULT_ENV,
|
2021-08-26 13:59:11 +02:00
|
|
|
updatedFeature.enabled,
|
|
|
|
userName,
|
|
|
|
);
|
|
|
|
|
2021-10-28 15:48:07 +02:00
|
|
|
const feature = await this.service.storeFeatureUpdatedEventLegacy(
|
|
|
|
featureName,
|
|
|
|
userName,
|
|
|
|
);
|
2021-10-21 20:53:39 +02:00
|
|
|
|
2021-08-26 13:59:11 +02:00
|
|
|
res.status(200).json(feature);
|
2018-11-30 11:11:12 +01:00
|
|
|
}
|
2017-06-28 10:20:22 +02:00
|
|
|
|
2021-07-07 10:46:50 +02:00
|
|
|
// TODO: remove?
|
2021-01-18 12:32:19 +01:00
|
|
|
// Kept to keep backward compatibility
|
2021-09-14 19:58:48 +02:00
|
|
|
async toggle(req: IAuthRequest, res: Response): Promise<void> {
|
|
|
|
const userName = extractUsername(req);
|
2021-09-13 10:23:57 +02:00
|
|
|
const { featureName } = req.params;
|
2021-10-28 15:48:07 +02:00
|
|
|
const projectId = await this.service.getProjectId(featureName);
|
|
|
|
const feature = await this.service.toggle(
|
2021-09-13 10:23:57 +02:00
|
|
|
projectId,
|
|
|
|
featureName,
|
2021-09-24 13:55:00 +02:00
|
|
|
DEFAULT_ENV,
|
2021-08-13 10:36:19 +02:00
|
|
|
userName,
|
|
|
|
);
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.storeFeatureUpdatedEventLegacy(
|
2021-10-21 20:53:39 +02:00
|
|
|
featureName,
|
|
|
|
userName,
|
|
|
|
);
|
2021-08-13 10:36:19 +02:00
|
|
|
res.status(200).json(feature);
|
2019-03-07 22:19:48 +01:00
|
|
|
}
|
|
|
|
|
2021-09-14 19:58:48 +02:00
|
|
|
async toggleOn(req: IAuthRequest, res: Response): Promise<void> {
|
2021-07-07 10:46:50 +02:00
|
|
|
const { featureName } = req.params;
|
2021-09-14 19:58:48 +02:00
|
|
|
const userName = extractUsername(req);
|
2021-10-28 15:48:07 +02:00
|
|
|
const projectId = await this.service.getProjectId(featureName);
|
|
|
|
const feature = await this.service.updateEnabled(
|
2021-09-13 10:23:57 +02:00
|
|
|
projectId,
|
2021-08-13 10:36:19 +02:00
|
|
|
featureName,
|
2021-09-24 13:55:00 +02:00
|
|
|
DEFAULT_ENV,
|
2021-08-13 10:36:19 +02:00
|
|
|
true,
|
|
|
|
userName,
|
|
|
|
);
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.storeFeatureUpdatedEventLegacy(
|
2021-10-21 20:53:39 +02:00
|
|
|
featureName,
|
|
|
|
userName,
|
|
|
|
);
|
2021-08-13 10:36:19 +02:00
|
|
|
res.json(feature);
|
2019-03-07 22:19:48 +01:00
|
|
|
}
|
|
|
|
|
2021-09-14 19:58:48 +02:00
|
|
|
async toggleOff(req: IAuthRequest, res: Response): Promise<void> {
|
2021-07-07 10:46:50 +02:00
|
|
|
const { featureName } = req.params;
|
2021-09-14 19:58:48 +02:00
|
|
|
const userName = extractUsername(req);
|
2021-10-28 15:48:07 +02:00
|
|
|
const projectId = await this.service.getProjectId(featureName);
|
|
|
|
const feature = await this.service.updateEnabled(
|
2021-09-13 10:23:57 +02:00
|
|
|
projectId,
|
2021-08-13 10:36:19 +02:00
|
|
|
featureName,
|
2021-09-24 13:55:00 +02:00
|
|
|
DEFAULT_ENV,
|
2021-08-13 10:36:19 +02:00
|
|
|
false,
|
|
|
|
userName,
|
|
|
|
);
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.storeFeatureUpdatedEventLegacy(
|
2021-10-21 20:53:39 +02:00
|
|
|
featureName,
|
|
|
|
userName,
|
|
|
|
);
|
2021-08-13 10:36:19 +02:00
|
|
|
res.json(feature);
|
2020-08-07 10:46:35 +02:00
|
|
|
}
|
|
|
|
|
2021-09-14 19:58:48 +02:00
|
|
|
async staleOn(req: IAuthRequest, res: Response): Promise<void> {
|
2021-08-13 10:36:19 +02:00
|
|
|
const { featureName } = req.params;
|
2021-09-14 19:58:48 +02:00
|
|
|
const userName = extractUsername(req);
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.updateStale(featureName, true, userName);
|
|
|
|
const feature = await this.service.getFeatureToggleLegacy(featureName);
|
|
|
|
res.json(feature);
|
2020-08-07 10:46:35 +02:00
|
|
|
}
|
|
|
|
|
2021-09-14 19:58:48 +02:00
|
|
|
async staleOff(req: IAuthRequest, res: Response): Promise<void> {
|
2021-08-13 10:36:19 +02:00
|
|
|
const { featureName } = req.params;
|
2021-09-14 19:58:48 +02:00
|
|
|
const userName = extractUsername(req);
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.updateStale(featureName, false, userName);
|
|
|
|
const feature = await this.service.getFeatureToggleLegacy(featureName);
|
|
|
|
res.json(feature);
|
2018-11-30 11:11:12 +01:00
|
|
|
}
|
|
|
|
|
2021-09-14 19:58:48 +02:00
|
|
|
async archiveToggle(req: IAuthRequest, res: Response): Promise<void> {
|
2020-04-14 22:29:11 +02:00
|
|
|
const { featureName } = req.params;
|
2021-09-14 19:58:48 +02:00
|
|
|
const userName = extractUsername(req);
|
2017-06-28 10:20:22 +02:00
|
|
|
|
2021-10-28 15:48:07 +02:00
|
|
|
await this.service.archiveToggle(featureName, userName);
|
2021-08-13 10:36:19 +02:00
|
|
|
res.status(200).end();
|
2018-11-30 11:11:12 +01:00
|
|
|
}
|
|
|
|
}
|
2021-04-22 10:07:10 +02:00
|
|
|
export default FeatureController;
|