1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00
unleash.unleash/src/lib/features/dependent-features/dependent-features-controller.ts

245 lines
7.9 KiB
TypeScript
Raw Normal View History

import { Response } from 'express';
import Controller from '../../routes/controller';
import { OpenApiService } from '../../services';
import {
IFlagResolver,
IUnleashConfig,
IUnleashServices,
NONE,
2023-09-25 15:50:05 +02:00
UPDATE_FEATURE,
} from '../../types';
import { Logger } from '../../logger';
import {
CreateDependentFeatureSchema,
createRequestSchema,
createResponseSchema,
emptyResponse,
getStandardResponses,
ParentFeatureOptionsSchema,
} from '../../openapi';
import { IAuthRequest } from '../../routes/unleash-types';
import { InvalidOperationError } from '../../error';
import { DependentFeaturesService } from './dependent-features-service';
import { TransactionCreator, UnleashTransaction } from '../../db/transaction';
interface FeatureParams {
2023-09-25 15:50:05 +02:00
child: string;
}
interface DeleteDependencyParams {
child: string;
parent: string;
}
const PATH = '/:projectId/features';
2023-09-25 15:50:05 +02:00
const PATH_FEATURE = `${PATH}/:child`;
const PATH_DEPENDENCIES = `${PATH_FEATURE}/dependencies`;
const PATH_PARENTS = `${PATH_FEATURE}/parents`;
2023-09-25 15:50:05 +02:00
const PATH_DEPENDENCY = `${PATH_FEATURE}/dependencies/:parent`;
type DependentFeaturesServices = Pick<
IUnleashServices,
2023-09-25 15:50:05 +02:00
| 'transactionalDependentFeaturesService'
| 'dependentFeaturesService'
| 'openApiService'
>;
export default class DependentFeaturesController extends Controller {
private transactionalDependentFeaturesService: (
db: UnleashTransaction,
) => DependentFeaturesService;
2023-09-25 15:50:05 +02:00
private dependentFeaturesService: DependentFeaturesService;
private readonly startTransaction: TransactionCreator<UnleashTransaction>;
private openApiService: OpenApiService;
private flagResolver: IFlagResolver;
private readonly logger: Logger;
constructor(
config: IUnleashConfig,
{
transactionalDependentFeaturesService,
2023-09-25 15:50:05 +02:00
dependentFeaturesService,
openApiService,
}: DependentFeaturesServices,
startTransaction: TransactionCreator<UnleashTransaction>,
) {
super(config);
this.transactionalDependentFeaturesService =
transactionalDependentFeaturesService;
2023-09-25 15:50:05 +02:00
this.dependentFeaturesService = dependentFeaturesService;
this.openApiService = openApiService;
this.flagResolver = config.flagResolver;
this.startTransaction = startTransaction;
this.logger = config.getLogger(
'/dependent-features/dependent-feature-service.ts',
);
this.route({
method: 'post',
path: PATH_DEPENDENCIES,
handler: this.addFeatureDependency,
2023-09-25 15:50:05 +02:00
permission: UPDATE_FEATURE,
middleware: [
openApiService.validPath({
tags: ['Features'],
summary: 'Add a feature dependency.',
description:
'Add a dependency to a parent feature. Each environment will resolve corresponding dependency independently.',
operationId: 'addFeatureDependency',
requestBody: createRequestSchema(
'createDependentFeatureSchema',
),
responses: {
200: emptyResponse,
...getStandardResponses(401, 403, 404),
},
}),
],
});
2023-09-25 15:50:05 +02:00
this.route({
method: 'delete',
path: PATH_DEPENDENCY,
handler: this.deleteFeatureDependency,
permission: UPDATE_FEATURE,
acceptAnyContentType: true,
middleware: [
openApiService.validPath({
tags: ['Features'],
summary: 'Deletes a feature dependency.',
description: 'Remove a dependency to a parent feature.',
operationId: 'deleteFeatureDependency',
responses: {
200: emptyResponse,
...getStandardResponses(401, 403, 404),
},
}),
],
});
this.route({
method: 'delete',
path: PATH_DEPENDENCIES,
handler: this.deleteFeatureDependencies,
permission: UPDATE_FEATURE,
acceptAnyContentType: true,
middleware: [
openApiService.validPath({
tags: ['Features'],
summary: 'Deletes feature dependencies.',
description: 'Remove dependencies to all parent features.',
operationId: 'deleteFeatureDependencies',
responses: {
200: emptyResponse,
...getStandardResponses(401, 403, 404),
},
}),
],
});
this.route({
method: 'get',
path: PATH_PARENTS,
handler: this.getParentOptions,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['Features'],
summary: 'List parent options.',
description:
'List available parents who have no transitive dependencies.',
operationId: 'listParentOptions',
responses: {
200: createResponseSchema('parentFeatureOptionsSchema'),
...getStandardResponses(401, 403, 404),
},
}),
],
});
}
async addFeatureDependency(
req: IAuthRequest<FeatureParams, any, CreateDependentFeatureSchema>,
res: Response,
): Promise<void> {
2023-09-25 15:50:05 +02:00
const { child } = req.params;
const { variants, enabled, feature } = req.body;
if (this.config.flagResolver.isEnabled('dependentFeatures')) {
await this.startTransaction(async (tx) =>
this.transactionalDependentFeaturesService(
tx,
2023-09-25 15:50:05 +02:00
).upsertFeatureDependency(child, {
variants,
enabled,
feature,
}),
);
res.status(200).end();
} else {
throw new InvalidOperationError(
'Dependent features are not enabled',
);
}
}
2023-09-25 15:50:05 +02:00
async deleteFeatureDependency(
req: IAuthRequest<DeleteDependencyParams, any, any>,
res: Response,
): Promise<void> {
const { child, parent } = req.params;
if (this.config.flagResolver.isEnabled('dependentFeatures')) {
await this.dependentFeaturesService.deleteFeatureDependency({
parent,
child,
});
res.status(200).end();
} else {
throw new InvalidOperationError(
'Dependent features are not enabled',
);
}
}
async deleteFeatureDependencies(
req: IAuthRequest<FeatureParams, any, any>,
res: Response,
): Promise<void> {
const { child } = req.params;
if (this.config.flagResolver.isEnabled('dependentFeatures')) {
await this.dependentFeaturesService.deleteFeatureDependencies(
child,
);
res.status(200).end();
} else {
throw new InvalidOperationError(
'Dependent features are not enabled',
);
}
}
async getParentOptions(
req: IAuthRequest<FeatureParams, any, any>,
res: Response<ParentFeatureOptionsSchema>,
): Promise<void> {
const { child } = req.params;
if (this.config.flagResolver.isEnabled('dependentFeatures')) {
const parentOptions =
await this.dependentFeaturesService.getParentOptions(child);
res.send(parentOptions);
} else {
throw new InvalidOperationError(
'Dependent features are not enabled',
);
}
}
}