mirror of
https://github.com/Unleash/unleash.git
synced 2025-11-10 01:19:53 +01:00
https://linear.app/unleash/issue/2-2439/create-new-integration-events-endpoint https://linear.app/unleash/issue/2-2436/create-new-integration-event-openapi-schemas This adds a new `/events` endpoint to the Addons API, allowing us to fetch integration events for a specific integration configuration id.  Also includes: - `IntegrationEventsSchema`: New schema to represent the response object of the list of integration events; - `yarn schema:update`: New `package.json` script to update the OpenAPI spec file; - `BasePaginationParameters`: This is copied from Enterprise. After merging this we should be able to refactor Enterprise to use this one instead of the one it has, so we don't repeat ourselves; We're also now correctly representing the BIGSERIAL as BigInt (string + pattern) in our OpenAPI schema. Otherwise our validation would complain, since we're saying it's a number in the schema but in fact returning a string.
314 lines
10 KiB
TypeScript
314 lines
10 KiB
TypeScript
import type { Request, Response } from 'express';
|
|
import Controller from '../controller';
|
|
import type {
|
|
IFlagResolver,
|
|
IUnleashConfig,
|
|
IUnleashServices,
|
|
} from '../../types';
|
|
import type { Logger } from '../../logger';
|
|
import type AddonService from '../../services/addon-service';
|
|
|
|
import {
|
|
ADMIN,
|
|
CREATE_ADDON,
|
|
DELETE_ADDON,
|
|
NONE,
|
|
UPDATE_ADDON,
|
|
} from '../../types/permissions';
|
|
import type { IAuthRequest } from '../unleash-types';
|
|
import { createRequestSchema } from '../../openapi/util/create-request-schema';
|
|
import { createResponseSchema } from '../../openapi/util/create-response-schema';
|
|
import type { OpenApiService } from '../../services/openapi-service';
|
|
import { type AddonSchema, addonSchema } from '../../openapi/spec/addon-schema';
|
|
import { serializeDates } from '../../types/serialize-dates';
|
|
import {
|
|
type AddonsSchema,
|
|
addonsSchema,
|
|
} from '../../openapi/spec/addons-schema';
|
|
import {
|
|
emptyResponse,
|
|
getStandardResponses,
|
|
} from '../../openapi/util/standard-responses';
|
|
import type { AddonCreateUpdateSchema } from '../../openapi/spec/addon-create-update-schema';
|
|
import {
|
|
type BasePaginationParameters,
|
|
basePaginationParameters,
|
|
} from '../../openapi/spec/base-pagination-parameters';
|
|
import {
|
|
type IntegrationEventsSchema,
|
|
integrationEventsSchema,
|
|
} from '../../openapi/spec/integration-events-schema';
|
|
import { BadDataError, NotFoundError } from '../../error';
|
|
import type { IntegrationEventsService } from '../../services';
|
|
|
|
type AddonServices = Pick<
|
|
IUnleashServices,
|
|
'addonService' | 'openApiService' | 'integrationEventsService'
|
|
>;
|
|
|
|
const PATH = '/';
|
|
|
|
class AddonController extends Controller {
|
|
private logger: Logger;
|
|
|
|
private addonService: AddonService;
|
|
|
|
private openApiService: OpenApiService;
|
|
|
|
private integrationEventsService: IntegrationEventsService;
|
|
|
|
private flagResolver: IFlagResolver;
|
|
|
|
constructor(
|
|
config: IUnleashConfig,
|
|
{
|
|
addonService,
|
|
openApiService,
|
|
integrationEventsService,
|
|
}: AddonServices,
|
|
) {
|
|
super(config);
|
|
this.logger = config.getLogger('/admin-api/addon.ts');
|
|
this.addonService = addonService;
|
|
this.openApiService = openApiService;
|
|
this.integrationEventsService = integrationEventsService;
|
|
this.flagResolver = config.flagResolver;
|
|
|
|
this.route({
|
|
method: 'get',
|
|
path: '',
|
|
permission: NONE,
|
|
handler: this.getAddons,
|
|
middleware: [
|
|
openApiService.validPath({
|
|
summary: 'Get all addons and providers',
|
|
description:
|
|
'Retrieve all addons and providers that are defined on this Unleash instance.',
|
|
tags: ['Addons'],
|
|
operationId: 'getAddons',
|
|
responses: {
|
|
...getStandardResponses(401),
|
|
200: createResponseSchema('addonsSchema'),
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
this.route({
|
|
method: 'post',
|
|
path: '',
|
|
handler: this.createAddon,
|
|
permission: CREATE_ADDON,
|
|
middleware: [
|
|
openApiService.validPath({
|
|
summary: 'Create a new addon',
|
|
description:
|
|
'Create an addon instance. The addon must use one of the providers available on this Unleash instance.',
|
|
tags: ['Addons'],
|
|
operationId: 'createAddon',
|
|
requestBody: createRequestSchema('addonCreateUpdateSchema'),
|
|
responses: {
|
|
200: createResponseSchema('addonSchema'),
|
|
...getStandardResponses(400, 401, 403, 413, 415),
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
this.route({
|
|
method: 'get',
|
|
path: `${PATH}:id`,
|
|
handler: this.getAddon,
|
|
permission: NONE,
|
|
middleware: [
|
|
openApiService.validPath({
|
|
summary: 'Get a specific addon',
|
|
description:
|
|
'Retrieve information about the addon whose ID matches the ID in the request URL.',
|
|
tags: ['Addons'],
|
|
operationId: 'getAddon',
|
|
responses: {
|
|
200: createResponseSchema('addonSchema'),
|
|
...getStandardResponses(401),
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
this.route({
|
|
method: 'put',
|
|
path: `${PATH}:id`,
|
|
handler: this.updateAddon,
|
|
permission: UPDATE_ADDON,
|
|
middleware: [
|
|
openApiService.validPath({
|
|
summary: 'Update an addon',
|
|
description: `Update the addon with a specific ID. Any fields in the update object will be updated. Properties that are not included in the update object will not be affected. To empty a property, pass \`null\` as that property's value.
|
|
|
|
Note: passing \`null\` as a value for the description property will set it to an empty string.`,
|
|
tags: ['Addons'],
|
|
operationId: 'updateAddon',
|
|
requestBody: createRequestSchema('addonCreateUpdateSchema'),
|
|
responses: {
|
|
200: createResponseSchema('addonSchema'),
|
|
...getStandardResponses(400, 401, 403, 404, 413, 415),
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
this.route({
|
|
method: 'delete',
|
|
path: `${PATH}:id`,
|
|
handler: this.deleteAddon,
|
|
acceptAnyContentType: true,
|
|
permission: DELETE_ADDON,
|
|
middleware: [
|
|
openApiService.validPath({
|
|
summary: 'Delete an addon',
|
|
description:
|
|
'Delete the addon specified by the ID in the request path.',
|
|
tags: ['Addons'],
|
|
operationId: 'deleteAddon',
|
|
responses: {
|
|
200: emptyResponse,
|
|
...getStandardResponses(401, 403, 404),
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
this.route({
|
|
method: 'get',
|
|
path: `${PATH}:id/events`,
|
|
handler: this.getIntegrationEvents,
|
|
permission: ADMIN,
|
|
middleware: [
|
|
openApiService.validPath({
|
|
tags: ['Unstable'],
|
|
operationId: 'getIntegrationEvents',
|
|
summary:
|
|
'Get integration events for a specific integration configuration.',
|
|
description:
|
|
'Returns a list of integration events belonging to a specific integration configuration, identified by its id.',
|
|
parameters: [...basePaginationParameters],
|
|
responses: {
|
|
...getStandardResponses(401, 403, 404),
|
|
200: createResponseSchema(integrationEventsSchema.$id),
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
}
|
|
|
|
async getAddons(req: Request, res: Response<AddonsSchema>): Promise<void> {
|
|
const addons = await this.addonService.getAddons();
|
|
const providers = this.addonService.getProviderDefinitions();
|
|
|
|
this.openApiService.respondWithValidation(200, res, addonsSchema.$id, {
|
|
addons: serializeDates(addons),
|
|
providers: serializeDates(providers),
|
|
});
|
|
}
|
|
|
|
async getAddon(
|
|
req: Request<{ id: number }, any, any, any>,
|
|
res: Response<AddonSchema>,
|
|
): Promise<void> {
|
|
const { id } = req.params;
|
|
const addon = await this.addonService.getAddon(id);
|
|
this.openApiService.respondWithValidation(
|
|
200,
|
|
res,
|
|
addonSchema.$id,
|
|
serializeDates(addon),
|
|
);
|
|
}
|
|
|
|
async updateAddon(
|
|
req: IAuthRequest<{ id: number }, any, AddonCreateUpdateSchema, any>,
|
|
res: Response<AddonSchema>,
|
|
): Promise<void> {
|
|
const { id } = req.params;
|
|
const data = req.body;
|
|
|
|
const addon = await this.addonService.updateAddon(id, data, req.audit);
|
|
|
|
this.openApiService.respondWithValidation(
|
|
200,
|
|
res,
|
|
addonSchema.$id,
|
|
serializeDates(addon),
|
|
);
|
|
}
|
|
|
|
async createAddon(
|
|
req: IAuthRequest<AddonCreateUpdateSchema, any, any, any>,
|
|
res: Response<AddonSchema>,
|
|
): Promise<void> {
|
|
const data = req.body;
|
|
const addon = await this.addonService.createAddon(data, req.audit);
|
|
|
|
this.openApiService.respondWithValidation(
|
|
201,
|
|
res,
|
|
addonSchema.$id,
|
|
serializeDates(addon),
|
|
);
|
|
}
|
|
|
|
async deleteAddon(
|
|
req: IAuthRequest<{ id: number }, any, any, any>,
|
|
res: Response<void>,
|
|
): Promise<void> {
|
|
const { id } = req.params;
|
|
await this.addonService.removeAddon(id, req.audit);
|
|
|
|
res.status(200).end();
|
|
}
|
|
|
|
async getIntegrationEvents(
|
|
req: IAuthRequest<
|
|
{ id: number },
|
|
unknown,
|
|
unknown,
|
|
BasePaginationParameters
|
|
>,
|
|
res: Response<IntegrationEventsSchema>,
|
|
): Promise<void> {
|
|
if (!this.flagResolver.isEnabled('integrationEvents')) {
|
|
throw new NotFoundError('This feature is not enabled');
|
|
}
|
|
|
|
const { id } = req.params;
|
|
|
|
if (Number.isNaN(Number(id))) {
|
|
throw new BadDataError('Invalid integration configuration id');
|
|
}
|
|
|
|
const { limit = '50', offset = '0' } = req.query;
|
|
|
|
const normalizedLimit =
|
|
Number(limit) > 0 && Number(limit) <= 100 ? Number(limit) : 50;
|
|
const normalizedOffset = Number(offset) > 0 ? Number(offset) : 0;
|
|
|
|
const integrationEvents =
|
|
await this.integrationEventsService.getPaginatedEvents(
|
|
id,
|
|
normalizedLimit,
|
|
normalizedOffset,
|
|
);
|
|
|
|
this.openApiService.respondWithValidation(
|
|
200,
|
|
res,
|
|
integrationEventsSchema.$id,
|
|
{
|
|
integrationEvents: serializeDates(integrationEvents),
|
|
},
|
|
);
|
|
}
|
|
}
|
|
export default AddonController;
|
|
module.exports = AddonController;
|