1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-27 00:19:39 +01:00
unleash.unleash/src/lib/features/context/context.ts
2025-01-03 14:18:36 +01:00

348 lines
12 KiB
TypeScript

import type { Request, Response } from 'express';
import Controller from '../../routes/controller';
import {
CREATE_CONTEXT_FIELD,
UPDATE_CONTEXT_FIELD,
DELETE_CONTEXT_FIELD,
NONE,
} from '../../types/permissions';
import type { IUnleashConfig } from '../../types/option';
import type { IUnleashServices } from '../../types/services';
import type ContextService from './context-service';
import type { Logger } from '../../logger';
import type { IAuthRequest } from '../../routes/unleash-types';
import type { OpenApiService } from '../../services/openapi-service';
import {
contextFieldSchema,
type ContextFieldSchema,
} from '../../openapi/spec/context-field-schema';
import type { ContextFieldsSchema } from '../../openapi/spec/context-fields-schema';
import { createRequestSchema } from '../../openapi/util/create-request-schema';
import {
createResponseSchema,
resourceCreatedResponseSchema,
} from '../../openapi/util/create-response-schema';
import { serializeDates } from '../../types/serialize-dates';
import NotFoundError from '../../error/notfound-error';
import type { NameSchema } from '../../openapi/spec/name-schema';
import {
emptyResponse,
getStandardResponses,
} from '../../openapi/util/standard-responses';
import {
type ContextFieldStrategiesSchema,
contextFieldStrategiesSchema,
} from '../../openapi/spec/context-field-strategies-schema';
import type { UpdateContextFieldSchema } from '../../openapi/spec/update-context-field-schema';
import type { CreateContextFieldSchema } from '../../openapi/spec/create-context-field-schema';
import { extractUserIdFromUser } from '../../util';
import type { LegalValueSchema } from '../../openapi';
interface ContextParam {
contextField: string;
}
export class ContextController extends Controller {
private contextService: ContextService;
private openApiService: OpenApiService;
private logger: Logger;
constructor(
config: IUnleashConfig,
{
contextService,
openApiService,
}: Pick<IUnleashServices, 'contextService' | 'openApiService'>,
) {
super(config);
this.openApiService = openApiService;
this.logger = config.getLogger('/admin-api/context.ts');
this.contextService = contextService;
this.route({
method: 'get',
path: '',
handler: this.getContextFields,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['Context'],
summary: 'Gets configured context fields',
description:
'Returns all configured [Context fields](https://docs.getunleash.io/reference/unleash-context) that have been created.',
operationId: 'getContextFields',
responses: {
200: createResponseSchema('contextFieldsSchema'),
},
}),
],
});
this.route({
method: 'get',
path: '/:contextField',
handler: this.getContextField,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['Context'],
summary: 'Gets context field',
description:
'Returns specific [context field](https://docs.getunleash.io/reference/unleash-context) identified by the name in the path',
operationId: 'getContextField',
responses: {
200: createResponseSchema('contextFieldSchema'),
},
}),
],
});
this.route({
method: 'get',
path: '/:contextField/strategies',
handler: this.getStrategiesByContextField,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['Strategies'],
operationId: 'getStrategiesByContextField',
summary: 'Get strategies that use a context field',
description:
"Retrieves a list of all strategies that use the specified context field. If the context field doesn't exist, returns an empty list of strategies",
responses: {
200: createResponseSchema(
'contextFieldStrategiesSchema',
),
...getStandardResponses(401),
},
}),
],
});
this.route({
method: 'post',
path: '',
handler: this.createContextField,
permission: CREATE_CONTEXT_FIELD,
middleware: [
openApiService.validPath({
tags: ['Context'],
operationId: 'createContextField',
summary: 'Create a context field',
description:
'Endpoint that allows creation of [custom context fields](https://docs.getunleash.io/reference/unleash-context#custom-context-fields)',
requestBody: createRequestSchema(
'createContextFieldSchema',
),
responses: {
201: resourceCreatedResponseSchema(
'contextFieldSchema',
),
},
}),
],
});
this.route({
method: 'put',
path: '/:contextField',
handler: this.updateContextField,
permission: UPDATE_CONTEXT_FIELD,
middleware: [
openApiService.validPath({
tags: ['Context'],
summary: 'Update an existing context field',
description: `Endpoint that allows updating a custom context field. Used to toggle stickiness and add/remove legal values for this context field`,
operationId: 'updateContextField',
requestBody: createRequestSchema(
'updateContextFieldSchema',
),
responses: {
200: emptyResponse,
},
}),
],
});
this.route({
method: 'post',
path: '/:contextField/legal-values',
handler: this.updateContextFieldLegalValue,
permission: UPDATE_CONTEXT_FIELD,
middleware: [
openApiService.validPath({
tags: ['Context'],
summary: 'Add or update legal value for the context field',
description: `Endpoint that allows adding or updating a single custom context field legal value. If the legal value already exists, it will be updated with the new description`,
operationId: 'updateContextFieldLegalValue',
requestBody: createRequestSchema('legalValueSchema'),
responses: {
200: emptyResponse,
},
}),
],
});
this.route({
method: 'delete',
path: '/:contextField',
handler: this.deleteContextField,
acceptAnyContentType: true,
permission: DELETE_CONTEXT_FIELD,
middleware: [
openApiService.validPath({
tags: ['Context'],
summary: 'Delete an existing context field',
description:
'Endpoint that allows deletion of a custom context field. Does not validate that context field is not in use, but since context field configuration is stored in a json blob for the strategy, existing strategies are safe.',
operationId: 'deleteContextField',
responses: {
200: emptyResponse,
},
}),
],
});
this.route({
method: 'post',
path: '/validate',
handler: this.validate,
permission: UPDATE_CONTEXT_FIELD,
middleware: [
openApiService.validPath({
tags: ['Context'],
summary: 'Validate a context field',
description:
'Check whether the provided data can be used to create a context field. If the data is not valid, ...?',
operationId: 'validate',
requestBody: createRequestSchema('nameSchema'),
responses: {
200: emptyResponse,
},
}),
],
});
}
async getContextFields(
req: Request,
res: Response<ContextFieldsSchema>,
): Promise<void> {
res.status(200)
.json(serializeDates(await this.contextService.getAll()))
.end();
}
async getContextField(
req: Request<ContextParam>,
res: Response<ContextFieldSchema>,
): Promise<void> {
try {
const name = req.params.contextField;
const contextField =
await this.contextService.getContextField(name);
this.openApiService.respondWithValidation(
200,
res,
contextFieldSchema.$id,
serializeDates(contextField),
);
} catch (err) {
throw new NotFoundError('Could not find context field');
}
}
async createContextField(
req: IAuthRequest<void, void, CreateContextFieldSchema>,
res: Response<ContextFieldSchema>,
): Promise<void> {
const value = req.body;
const result = await this.contextService.createContextField(
value,
req.audit,
);
this.openApiService.respondWithValidation(
201,
res,
contextFieldSchema.$id,
serializeDates(result),
{ location: `context/${result.name}` },
);
}
async updateContextField(
req: IAuthRequest<ContextParam, void, UpdateContextFieldSchema>,
res: Response,
): Promise<void> {
const name = req.params.contextField;
const contextField = req.body;
await this.contextService.updateContextField(
{ ...contextField, name },
req.audit,
);
res.status(200).end();
}
async updateContextFieldLegalValue(
req: IAuthRequest<ContextParam, void, LegalValueSchema>,
res: Response,
): Promise<void> {
const name = req.params.contextField;
const legalValue = req.body;
await this.contextService.updateContextFieldLegalValue(
{ name, legalValue },
req.audit,
);
res.status(200).end();
}
async deleteContextField(
req: IAuthRequest<ContextParam>,
res: Response,
): Promise<void> {
const name = req.params.contextField;
await this.contextService.deleteContextField(name, req.audit);
res.status(200).end();
}
async validate(
req: Request<void, void, NameSchema>,
res: Response,
): Promise<void> {
const { name } = req.body;
await this.contextService.validateName(name);
res.status(200).end();
}
async getStrategiesByContextField(
req: IAuthRequest<{ contextField: string }>,
res: Response<ContextFieldStrategiesSchema>,
): Promise<void> {
const { contextField } = req.params;
const { user } = req;
const contextFields =
await this.contextService.getStrategiesByContextField(
contextField,
extractUserIdFromUser(user),
);
this.openApiService.respondWithValidation(
200,
res,
contextFieldStrategiesSchema.$id,
serializeDates(contextFields),
);
}
}