From 04c107a26e1115b920d4a0cf6414e93b9bb1f15d Mon Sep 17 00:00:00 2001 From: olav Date: Wed, 8 Jun 2022 15:31:34 +0200 Subject: [PATCH] refactor: add OpenAPI schemas to more controllers (#1680) --- src/lib/db/project-store.ts | 4 +- src/lib/openapi/index.ts | 14 + src/lib/openapi/spec/environment-schema.ts | 25 ++ src/lib/openapi/spec/feature-schema.test.ts | 7 + src/lib/openapi/spec/feature-schema.ts | 4 +- src/lib/openapi/spec/features-schema.ts | 2 + .../openapi/spec/health-overview-schema.ts | 62 +++ src/lib/openapi/spec/health-report-schema.ts | 27 ++ .../spec/project-environment-schema.ts | 18 + src/lib/openapi/spec/project-schema.ts | 40 ++ src/lib/openapi/spec/projects-schema.ts | 27 ++ src/lib/routes/admin-api/archive.ts | 47 +- .../routes/admin-api/project/environments.ts | 65 ++- .../routes/admin-api/project/health-report.ts | 79 +++- src/lib/routes/admin-api/project/index.ts | 42 +- src/lib/schema/project-schema.ts | 6 - src/lib/services/openapi-service.ts | 7 +- .../__snapshots__/openapi.e2e.test.ts.snap | 410 +++++++++++++++++- 18 files changed, 832 insertions(+), 54 deletions(-) create mode 100644 src/lib/openapi/spec/environment-schema.ts create mode 100644 src/lib/openapi/spec/health-overview-schema.ts create mode 100644 src/lib/openapi/spec/health-report-schema.ts create mode 100644 src/lib/openapi/spec/project-environment-schema.ts create mode 100644 src/lib/openapi/spec/project-schema.ts create mode 100644 src/lib/openapi/spec/projects-schema.ts delete mode 100644 src/lib/schema/project-schema.ts diff --git a/src/lib/db/project-store.ts b/src/lib/db/project-store.ts index c37121221d..3d89da6e48 100644 --- a/src/lib/db/project-store.ts +++ b/src/lib/db/project-store.ts @@ -108,8 +108,8 @@ class ProjectStore implements IProjectStore { id: row.id, description: row.description, health: row.health, - featureCount: row.number_of_features, - memberCount: row.number_of_users || 0, + featureCount: Number(row.number_of_features) || 0, + memberCount: Number(row.number_of_users) || 0, updatedAt: row.updated_at, }; } diff --git a/src/lib/openapi/index.ts b/src/lib/openapi/index.ts index 6a241eab2d..f6bd2bcbed 100644 --- a/src/lib/openapi/index.ts +++ b/src/lib/openapi/index.ts @@ -4,17 +4,23 @@ import { constraintSchema } from './spec/constraint-schema'; import { createFeatureSchema } from './spec/create-feature-schema'; import { createStrategySchema } from './spec/create-strategy-schema'; import { emptySchema } from './spec/empty-schema'; +import { environmentSchema } from './spec/environment-schema'; import { featureEnvironmentSchema } from './spec/feature-environment-schema'; import { featureSchema } from './spec/feature-schema'; import { featureStrategySchema } from './spec/feature-strategy-schema'; import { featureVariantsSchema } from './spec/feature-variants-schema'; import { featuresSchema } from './spec/features-schema'; +import { healthOverviewSchema } from './spec/health-overview-schema'; +import { healthReportSchema } from './spec/health-report-schema'; import { mapValues } from '../util/map-values'; import { omitKeys } from '../util/omit-keys'; import { overrideSchema } from './spec/override-schema'; import { parametersSchema } from './spec/parameters-schema'; import { patchSchema } from './spec/patch-schema'; import { patchesSchema } from './spec/patches-schema'; +import { projectEnvironmentSchema } from './spec/project-environment-schema'; +import { projectSchema } from './spec/project-schema'; +import { projectsSchema } from './spec/projects-schema'; import { strategySchema } from './spec/strategy-schema'; import { tagSchema } from './spec/tag-schema'; import { tagsSchema } from './spec/tags-schema'; @@ -33,11 +39,13 @@ export type SchemaRef = typeof schemas[keyof typeof schemas]['components']; export interface AdminApiOperation extends Omit { + operationId: string; tags: ['admin']; } export interface ClientApiOperation extends Omit { + operationId: string; tags: ['client']; } @@ -47,15 +55,21 @@ export const schemas = { createFeatureSchema, createStrategySchema, emptySchema, + environmentSchema, featureEnvironmentSchema, featureSchema, featureStrategySchema, featureVariantsSchema, featuresSchema, + healthOverviewSchema, + healthReportSchema, overrideSchema, parametersSchema, patchSchema, patchesSchema, + projectEnvironmentSchema, + projectSchema, + projectsSchema, strategySchema, tagSchema, tagsSchema, diff --git a/src/lib/openapi/spec/environment-schema.ts b/src/lib/openapi/spec/environment-schema.ts new file mode 100644 index 0000000000..befed054dc --- /dev/null +++ b/src/lib/openapi/spec/environment-schema.ts @@ -0,0 +1,25 @@ +import { FromSchema } from 'json-schema-to-ts'; + +export const environmentSchema = { + $id: '#/components/schemas/environmentSchema', + type: 'object', + additionalProperties: false, + required: ['name', 'type', 'enabled'], + properties: { + name: { + type: 'string', + }, + type: { + type: 'string', + }, + enabled: { + type: 'boolean', + }, + sortOrder: { + type: 'number', + }, + }, + components: {}, +} as const; + +export type EnvironmentSchema = FromSchema; diff --git a/src/lib/openapi/spec/feature-schema.test.ts b/src/lib/openapi/spec/feature-schema.test.ts index fbfc3e2feb..f135755bfd 100644 --- a/src/lib/openapi/spec/feature-schema.test.ts +++ b/src/lib/openapi/spec/feature-schema.test.ts @@ -25,6 +25,13 @@ test('featureSchema', () => { payload: { type: 'a', value: 'b' }, }, ], + environments: [ + { + name: 'a', + type: 'b', + enabled: true, + }, + ], }; expect( diff --git a/src/lib/openapi/spec/feature-schema.ts b/src/lib/openapi/spec/feature-schema.ts index 17cfdeb017..d0baceaafc 100644 --- a/src/lib/openapi/spec/feature-schema.ts +++ b/src/lib/openapi/spec/feature-schema.ts @@ -4,6 +4,7 @@ import { strategySchema } from './strategy-schema'; import { constraintSchema } from './constraint-schema'; import { overrideSchema } from './override-schema'; import { parametersSchema } from './parameters-schema'; +import { environmentSchema } from './environment-schema'; export const featureSchema = { $id: '#/components/schemas/featureSchema', @@ -48,7 +49,7 @@ export const featureSchema = { environments: { type: 'array', items: { - type: 'object', + $ref: '#/components/schemas/environmentSchema', }, }, strategies: { @@ -67,6 +68,7 @@ export const featureSchema = { components: { schemas: { constraintSchema, + environmentSchema, overrideSchema, parametersSchema, strategySchema, diff --git a/src/lib/openapi/spec/features-schema.ts b/src/lib/openapi/spec/features-schema.ts index 373b5c7873..df89c0bee0 100644 --- a/src/lib/openapi/spec/features-schema.ts +++ b/src/lib/openapi/spec/features-schema.ts @@ -5,6 +5,7 @@ import { variantSchema } from './variant-schema'; import { overrideSchema } from './override-schema'; import { constraintSchema } from './constraint-schema'; import { strategySchema } from './strategy-schema'; +import { environmentSchema } from './environment-schema'; export const featuresSchema = { $id: '#/components/schemas/featuresSchema', @@ -25,6 +26,7 @@ export const featuresSchema = { components: { schemas: { constraintSchema, + environmentSchema, featureSchema, overrideSchema, parametersSchema, diff --git a/src/lib/openapi/spec/health-overview-schema.ts b/src/lib/openapi/spec/health-overview-schema.ts new file mode 100644 index 0000000000..1b73fae3e9 --- /dev/null +++ b/src/lib/openapi/spec/health-overview-schema.ts @@ -0,0 +1,62 @@ +import { FromSchema } from 'json-schema-to-ts'; +import { parametersSchema } from './parameters-schema'; +import { variantSchema } from './variant-schema'; +import { overrideSchema } from './override-schema'; +import { strategySchema } from './strategy-schema'; +import { featureSchema } from './feature-schema'; +import { constraintSchema } from './constraint-schema'; +import { environmentSchema } from './environment-schema'; + +export const healthOverviewSchema = { + $id: '#/components/schemas/healthOverviewSchema', + type: 'object', + additionalProperties: false, + required: ['version', 'name'], + properties: { + version: { + type: 'number', + }, + name: { + type: 'string', + }, + description: { + type: 'string', + }, + members: { + type: 'number', + }, + health: { + type: 'number', + }, + environments: { + type: 'array', + items: { + type: 'string', + }, + }, + features: { + type: 'array', + items: { + $ref: '#/components/schemas/featureSchema', + }, + }, + updatedAt: { + type: 'string', + format: 'date-time', + nullable: true, + }, + }, + components: { + schemas: { + constraintSchema, + environmentSchema, + featureSchema, + overrideSchema, + parametersSchema, + strategySchema, + variantSchema, + }, + }, +} as const; + +export type HealthOverviewSchema = FromSchema; diff --git a/src/lib/openapi/spec/health-report-schema.ts b/src/lib/openapi/spec/health-report-schema.ts new file mode 100644 index 0000000000..ac445b78bc --- /dev/null +++ b/src/lib/openapi/spec/health-report-schema.ts @@ -0,0 +1,27 @@ +import { FromSchema } from 'json-schema-to-ts'; +import { healthOverviewSchema } from './health-overview-schema'; + +export const healthReportSchema = { + ...healthOverviewSchema, + $id: '#/components/schemas/healthReportSchema', + required: [ + ...healthOverviewSchema.required, + 'potentiallyStaleCount', + 'activeCount', + 'staleCount', + ], + properties: { + ...healthOverviewSchema.properties, + potentiallyStaleCount: { + type: 'number', + }, + activeCount: { + type: 'number', + }, + staleCount: { + type: 'number', + }, + }, +} as const; + +export type HealthReportSchema = FromSchema; diff --git a/src/lib/openapi/spec/project-environment-schema.ts b/src/lib/openapi/spec/project-environment-schema.ts new file mode 100644 index 0000000000..0cc6f61ffe --- /dev/null +++ b/src/lib/openapi/spec/project-environment-schema.ts @@ -0,0 +1,18 @@ +import { FromSchema } from 'json-schema-to-ts'; + +export const projectEnvironmentSchema = { + $id: '#/components/schemas/projectEnvironmentSchema', + type: 'object', + additionalProperties: false, + required: ['environment'], + properties: { + environment: { + type: 'string', + }, + }, + components: {}, +} as const; + +export type ProjectEnvironmentSchema = FromSchema< + typeof projectEnvironmentSchema +>; diff --git a/src/lib/openapi/spec/project-schema.ts b/src/lib/openapi/spec/project-schema.ts new file mode 100644 index 0000000000..23740bec00 --- /dev/null +++ b/src/lib/openapi/spec/project-schema.ts @@ -0,0 +1,40 @@ +import { FromSchema } from 'json-schema-to-ts'; + +export const projectSchema = { + $id: '#/components/schemas/projectSchema', + type: 'object', + additionalProperties: false, + required: ['id', 'name'], + properties: { + id: { + type: 'string', + }, + name: { + type: 'string', + }, + description: { + type: 'string', + }, + health: { + type: 'number', + }, + featureCount: { + type: 'number', + }, + memberCount: { + type: 'number', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + updatedAt: { + type: 'string', + format: 'date-time', + nullable: true, + }, + }, + components: {}, +} as const; + +export type ProjectSchema = FromSchema; diff --git a/src/lib/openapi/spec/projects-schema.ts b/src/lib/openapi/spec/projects-schema.ts new file mode 100644 index 0000000000..0634b71df4 --- /dev/null +++ b/src/lib/openapi/spec/projects-schema.ts @@ -0,0 +1,27 @@ +import { FromSchema } from 'json-schema-to-ts'; +import { projectSchema } from './project-schema'; + +export const projectsSchema = { + $id: '#/components/schemas/projectsSchema', + type: 'object', + additionalProperties: false, + required: ['version', 'projects'], + properties: { + version: { + type: 'integer', + }, + projects: { + type: 'array', + items: { + $ref: '#/components/schemas/projectSchema', + }, + }, + }, + components: { + schemas: { + projectSchema, + }, + }, +} as const; + +export type ProjectsSchema = FromSchema; diff --git a/src/lib/routes/admin-api/archive.ts b/src/lib/routes/admin-api/archive.ts index 899f9d1ce9..53682ad196 100644 --- a/src/lib/routes/admin-api/archive.ts +++ b/src/lib/routes/admin-api/archive.ts @@ -14,6 +14,7 @@ import { import { serializeDates } from '../../types/serialize-dates'; import { OpenApiService } from '../../services/openapi-service'; import { createResponseSchema } from '../../openapi'; +import { EmptySchema } from '../../openapi/spec/empty-schema'; export default class ArchiveController extends Controller { private readonly logger: Logger; @@ -42,6 +43,7 @@ export default class ArchiveController extends Controller { middleware: [ openApiService.validPath({ tags: ['admin'], + operationId: 'getArchivedFeatures', responses: { 200: createResponseSchema('featuresSchema') }, deprecated: true, }), @@ -56,18 +58,42 @@ export default class ArchiveController extends Controller { middleware: [ openApiService.validPath({ tags: ['admin'], + operationId: 'getArchivedFeaturesByProjectId', responses: { 200: createResponseSchema('featuresSchema') }, deprecated: true, }), ], }); - this.delete('/:featureName', this.deleteFeature, DELETE_FEATURE); - this.post( - '/revive/:featureName', - this.reviveFeatureToggle, - UPDATE_FEATURE, - ); + this.route({ + method: 'delete', + path: '/:featureName', + acceptAnyContentType: true, + handler: this.deleteFeature, + permission: DELETE_FEATURE, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'deleteFeature', + responses: { 200: createResponseSchema('emptySchema') }, + }), + ], + }); + + this.route({ + method: 'post', + path: '/revive/:featureName', + acceptAnyContentType: true, + handler: this.reviveFeature, + permission: UPDATE_FEATURE, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'reviveFeature', + responses: { 200: createResponseSchema('emptySchema') }, + }), + ], + }); } async getArchivedFeatures( @@ -104,8 +130,8 @@ export default class ArchiveController extends Controller { } async deleteFeature( - req: IAuthRequest, - res: Response, + req: IAuthRequest<{ featureName: string }>, + res: Response, ): Promise { const { featureName } = req.params; const user = extractUsername(req); @@ -113,7 +139,10 @@ export default class ArchiveController extends Controller { res.status(200).end(); } - async reviveFeatureToggle(req: IAuthRequest, res: Response): Promise { + async reviveFeature( + req: IAuthRequest<{ featureName: string }>, + res: Response, + ): Promise { const userName = extractUsername(req); const { featureName } = req.params; await this.featureService.reviveToggle(featureName, userName); diff --git a/src/lib/routes/admin-api/project/environments.ts b/src/lib/routes/admin-api/project/environments.ts index a1bf75cb84..c7336b21b3 100644 --- a/src/lib/routes/admin-api/project/environments.ts +++ b/src/lib/routes/admin-api/project/environments.ts @@ -5,7 +5,8 @@ import { IUnleashServices } from '../../../types/services'; import { Logger } from '../../../logger'; import EnvironmentService from '../../../services/environment-service'; import { UPDATE_PROJECT } from '../../../types/permissions'; -import { addEnvironment } from '../../../schema/project-schema'; +import { createRequestSchema, createResponseSchema } from '../../../openapi'; +import { ProjectEnvironmentSchema } from '../../../openapi/spec/project-environment-schema'; const PREFIX = '/:projectId/environments'; @@ -14,10 +15,6 @@ interface IProjectEnvironmentParams { environment: string; } -interface EnvironmentBody { - environment: string; -} - export default class EnvironmentsController extends Controller { private logger: Logger; @@ -25,49 +22,79 @@ export default class EnvironmentsController extends Controller { constructor( config: IUnleashConfig, - { environmentService }: Pick, + { + environmentService, + openApiService, + }: Pick, ) { super(config); this.logger = config.getLogger('admin-api/project/environments.ts'); this.environmentService = environmentService; - this.post(PREFIX, this.addEnvironmentToProject, UPDATE_PROJECT); - this.delete( - `${PREFIX}/:environment`, - this.removeEnvironmentFromProject, - UPDATE_PROJECT, - ); + + this.route({ + method: 'post', + path: PREFIX, + handler: this.addEnvironmentToProject, + permission: UPDATE_PROJECT, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'addEnvironmentToProject', + requestBody: createRequestSchema( + 'projectEnvironmentSchema', + ), + responses: { 200: createResponseSchema('emptySchema') }, + }), + ], + }); + + this.route({ + method: 'delete', + path: `${PREFIX}/:environment`, + acceptAnyContentType: true, + handler: this.removeEnvironmentFromProject, + permission: UPDATE_PROJECT, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'removeEnvironmentFromProject', + responses: { 200: createResponseSchema('emptySchema') }, + }), + ], + }); } async addEnvironmentToProject( req: Request< Omit, - any, - EnvironmentBody, - any + void, + ProjectEnvironmentSchema >, res: Response, ): Promise { const { projectId } = req.params; - - const { environment } = await addEnvironment.validateAsync(req.body); + const { environment } = req.body; await this.environmentService.addEnvironmentToProject( environment, projectId, ); + res.status(200).end(); } async removeEnvironmentFromProject( - req: Request, - res: Response, + req: Request, + res: Response, ): Promise { const { projectId, environment } = req.params; + await this.environmentService.removeEnvironmentFromProject( environment, projectId, ); + res.status(200).end(); } } diff --git a/src/lib/routes/admin-api/project/health-report.ts b/src/lib/routes/admin-api/project/health-report.ts index 83f22f25d9..a64e7a8c65 100644 --- a/src/lib/routes/admin-api/project/health-report.ts +++ b/src/lib/routes/admin-api/project/health-report.ts @@ -5,28 +5,74 @@ import { IUnleashConfig } from '../../../types/option'; import ProjectHealthService from '../../../services/project-health-service'; import { Logger } from '../../../logger'; import { IArchivedQuery, IProjectParam } from '../../../types/model'; +import { NONE } from '../../../types/permissions'; +import { OpenApiService } from '../../../services/openapi-service'; +import { createResponseSchema } from '../../../openapi'; +import { + healthOverviewSchema, + HealthOverviewSchema, +} from '../../../openapi/spec/health-overview-schema'; +import { serializeDates } from '../../../types/serialize-dates'; +import { + healthReportSchema, + HealthReportSchema, +} from '../../../openapi/spec/health-report-schema'; export default class ProjectHealthReport extends Controller { private projectHealthService: ProjectHealthService; + private openApiService: OpenApiService; + private logger: Logger; constructor( config: IUnleashConfig, { projectHealthService, - }: Pick, + openApiService, + }: Pick, ) { super(config); this.logger = config.getLogger('/admin-api/project/health-report'); this.projectHealthService = projectHealthService; - this.get('/:projectId', this.getProjectOverview); - this.get('/:projectId/health-report', this.getProjectHealthReport); + this.openApiService = openApiService; + + this.route({ + method: 'get', + path: '/:projectId', + handler: this.getProjectHealthOverview, + permission: NONE, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'getProjectHealthOverview', + responses: { + 200: createResponseSchema('healthOverviewSchema'), + }, + }), + ], + }); + + this.route({ + method: 'get', + path: '/:projectId/health-report', + handler: this.getProjectHealthReport, + permission: NONE, + middleware: [ + openApiService.validPath({ + tags: ['admin'], + operationId: 'getProjectHealthReport', + responses: { + 200: createResponseSchema('healthReportSchema'), + }, + }), + ], + }); } - async getProjectOverview( - req: Request, - res: Response, + async getProjectHealthOverview( + req: Request, + res: Response, ): Promise { const { projectId } = req.params; const { archived } = req.query; @@ -34,20 +80,27 @@ export default class ProjectHealthReport extends Controller { projectId, archived, ); - res.json(overview); + this.openApiService.respondWithValidation( + 200, + res, + healthOverviewSchema.$id, + serializeDates(overview), + ); } async getProjectHealthReport( - req: Request, - res: Response, + req: Request, + res: Response, ): Promise { const { projectId } = req.params; const overview = await this.projectHealthService.getProjectHealthReport( projectId, ); - res.json({ - version: 2, - ...overview, - }); + this.openApiService.respondWithValidation( + 200, + res, + healthReportSchema.$id, + serializeDates(overview), + ); } } diff --git a/src/lib/routes/admin-api/project/index.ts b/src/lib/routes/admin-api/project/index.ts index 10f54fe765..abb6845bf7 100644 --- a/src/lib/routes/admin-api/project/index.ts +++ b/src/lib/routes/admin-api/project/index.ts @@ -7,24 +7,62 @@ import EnvironmentsController from './environments'; import ProjectHealthReport from './health-report'; import ProjectService from '../../../services/project-service'; import VariantsController from './variants'; +import { NONE } from '../../../types/permissions'; +import { + projectsSchema, + ProjectsSchema, +} from '../../../openapi/spec/projects-schema'; +import { OpenApiService } from '../../../services/openapi-service'; +import { serializeDates } from '../../../types/serialize-dates'; +import { createResponseSchema } from '../../../openapi'; export default class ProjectApi extends Controller { private projectService: ProjectService; + private openApiService: OpenApiService; + constructor(config: IUnleashConfig, services: IUnleashServices) { super(config); this.projectService = services.projectService; + this.openApiService = services.openApiService; + this.get('/', this.getProjects); + + this.route({ + path: '', + method: 'get', + handler: this.getProjects, + permission: NONE, + middleware: [ + services.openApiService.validPath({ + tags: ['admin'], + operationId: 'getProjects', + responses: { + 200: createResponseSchema('projectsSchema'), + }, + }), + ], + }); + this.use('/', new ProjectFeaturesController(config, services).router); this.use('/', new EnvironmentsController(config, services).router); this.use('/', new ProjectHealthReport(config, services).router); this.use('/', new VariantsController(config, services).router); } - async getProjects(req: Request, res: Response): Promise { + async getProjects( + req: Request, + res: Response, + ): Promise { const projects = await this.projectService.getProjects({ id: 'default', }); - res.json({ version: 1, projects }).end(); + + this.openApiService.respondWithValidation( + 200, + res, + projectsSchema.$id, + { version: 1, projects: serializeDates(projects) }, + ); } } diff --git a/src/lib/schema/project-schema.ts b/src/lib/schema/project-schema.ts deleted file mode 100644 index 4e09d50046..0000000000 --- a/src/lib/schema/project-schema.ts +++ /dev/null @@ -1,6 +0,0 @@ -import joi from 'joi'; - -export const addEnvironment = joi - .object() - .keys({ environment: joi.string().required() }) - .options({ stripUnknown: true, allowUnknown: false, abortEarly: false }); diff --git a/src/lib/services/openapi-service.ts b/src/lib/services/openapi-service.ts index 0dba791453..d477d88950 100644 --- a/src/lib/services/openapi-service.ts +++ b/src/lib/services/openapi-service.ts @@ -73,7 +73,12 @@ export class OpenApiService { const errors = validateSchema(schema, data); if (errors) { - this.logger.warn('Invalid response:', errors); + this.logger.warn( + 'Invalid response:', + process.env.NODE_ENV === 'development' + ? JSON.stringify(errors, null, 2) + : errors, + ); } res.status(status).json(data); diff --git a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap index b67866a363..aeb39d2d7c 100644 --- a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap +++ b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap @@ -160,6 +160,29 @@ Object { "emptySchema": Object { "description": "emptySchema", }, + "environmentSchema": Object { + "additionalProperties": false, + "properties": Object { + "enabled": Object { + "type": "boolean", + }, + "name": Object { + "type": "string", + }, + "sortOrder": Object { + "type": "number", + }, + "type": Object { + "type": "string", + }, + }, + "required": Array [ + "name", + "type", + "enabled", + ], + "type": "object", + }, "featureEnvironmentSchema": Object { "additionalProperties": false, "properties": Object { @@ -207,7 +230,7 @@ Object { }, "environments": Object { "items": Object { - "type": "object", + "$ref": "#/components/schemas/environmentSchema", }, "type": "array", }, @@ -336,6 +359,102 @@ Object { ], "type": "object", }, + "healthOverviewSchema": Object { + "additionalProperties": false, + "properties": Object { + "description": Object { + "type": "string", + }, + "environments": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "features": Object { + "items": Object { + "$ref": "#/components/schemas/featureSchema", + }, + "type": "array", + }, + "health": Object { + "type": "number", + }, + "members": Object { + "type": "number", + }, + "name": Object { + "type": "string", + }, + "updatedAt": Object { + "format": "date-time", + "nullable": true, + "type": "string", + }, + "version": Object { + "type": "number", + }, + }, + "required": Array [ + "version", + "name", + ], + "type": "object", + }, + "healthReportSchema": Object { + "additionalProperties": false, + "properties": Object { + "activeCount": Object { + "type": "number", + }, + "description": Object { + "type": "string", + }, + "environments": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "features": Object { + "items": Object { + "$ref": "#/components/schemas/featureSchema", + }, + "type": "array", + }, + "health": Object { + "type": "number", + }, + "members": Object { + "type": "number", + }, + "name": Object { + "type": "string", + }, + "potentiallyStaleCount": Object { + "type": "number", + }, + "staleCount": Object { + "type": "number", + }, + "updatedAt": Object { + "format": "date-time", + "nullable": true, + "type": "string", + }, + "version": Object { + "type": "number", + }, + }, + "required": Array [ + "version", + "name", + "potentiallyStaleCount", + "activeCount", + "staleCount", + ], + "type": "object", + }, "overrideSchema": Object { "additionalProperties": false, "properties": Object { @@ -393,6 +512,74 @@ Object { }, "type": "array", }, + "projectEnvironmentSchema": Object { + "additionalProperties": false, + "properties": Object { + "environment": Object { + "type": "string", + }, + }, + "required": Array [ + "environment", + ], + "type": "object", + }, + "projectSchema": Object { + "additionalProperties": false, + "properties": Object { + "createdAt": Object { + "format": "date-time", + "type": "string", + }, + "description": Object { + "type": "string", + }, + "featureCount": Object { + "type": "number", + }, + "health": Object { + "type": "number", + }, + "id": Object { + "type": "string", + }, + "memberCount": Object { + "type": "number", + }, + "name": Object { + "type": "string", + }, + "updatedAt": Object { + "format": "date-time", + "nullable": true, + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + ], + "type": "object", + }, + "projectsSchema": Object { + "additionalProperties": false, + "properties": Object { + "projects": Object { + "items": Object { + "$ref": "#/components/schemas/projectSchema", + }, + "type": "array", + }, + "version": Object { + "type": "integer", + }, + }, + "required": Array [ + "version", + "projects", + ], + "type": "object", + }, "strategySchema": Object { "additionalProperties": false, "properties": Object { @@ -691,6 +878,7 @@ Object { "/api/admin/archive/features": Object { "get": Object { "deprecated": true, + "operationId": "getArchivedFeatures", "responses": Object { "200": Object { "content": Object { @@ -711,6 +899,7 @@ Object { "/api/admin/archive/features/{projectId}": Object { "get": Object { "deprecated": true, + "operationId": "getArchivedFeaturesByProjectId", "parameters": Array [ Object { "in": "path", @@ -738,6 +927,66 @@ Object { ], }, }, + "/api/admin/archive/revive/{featureName}": Object { + "post": Object { + "operationId": "reviveFeature", + "parameters": Array [ + Object { + "in": "path", + "name": "featureName", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/emptySchema", + }, + }, + }, + "description": "emptySchema", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, + "/api/admin/archive/{featureName}": Object { + "delete": Object { + "operationId": "deleteFeature", + "parameters": Array [ + Object { + "in": "path", + "name": "featureName", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/emptySchema", + }, + }, + }, + "description": "emptySchema", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, "/api/admin/features": Object { "get": Object { "deprecated": true, @@ -894,6 +1143,135 @@ Object { ], }, }, + "/api/admin/projects": Object { + "get": Object { + "operationId": "getProjects", + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/projectsSchema", + }, + }, + }, + "description": "projectsSchema", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, + "/api/admin/projects/{projectId}": Object { + "get": Object { + "operationId": "getProjectHealthOverview", + "parameters": Array [ + Object { + "in": "path", + "name": "projectId", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/healthOverviewSchema", + }, + }, + }, + "description": "healthOverviewSchema", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, + "/api/admin/projects/{projectId}/environments": Object { + "post": Object { + "operationId": "addEnvironmentToProject", + "parameters": Array [ + Object { + "in": "path", + "name": "projectId", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "requestBody": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/projectEnvironmentSchema", + }, + }, + }, + "description": "projectEnvironmentSchema", + "required": true, + }, + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/emptySchema", + }, + }, + }, + "description": "emptySchema", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, + "/api/admin/projects/{projectId}/environments/{environment}": Object { + "delete": Object { + "operationId": "removeEnvironmentFromProject", + "parameters": Array [ + Object { + "in": "path", + "name": "projectId", + "required": true, + "schema": Object { + "type": "string", + }, + }, + Object { + "in": "path", + "name": "environment", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/emptySchema", + }, + }, + }, + "description": "emptySchema", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, "/api/admin/projects/{projectId}/features": Object { "get": Object { "operationId": "getFeatures", @@ -1783,6 +2161,36 @@ Object { ], }, }, + "/api/admin/projects/{projectId}/health-report": Object { + "get": Object { + "operationId": "getProjectHealthReport", + "parameters": Array [ + Object { + "in": "path", + "name": "projectId", + "required": true, + "schema": Object { + "type": "string", + }, + }, + ], + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "$ref": "#/components/schemas/healthReportSchema", + }, + }, + }, + "description": "healthReportSchema", + }, + }, + "tags": Array [ + "admin", + ], + }, + }, "/api/admin/ui-config": Object { "get": Object { "operationId": "getUIConfig",