1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

refactor: add OpenAPI schemas to more controllers (#1680)

This commit is contained in:
olav 2022-06-08 15:31:34 +02:00 committed by GitHub
parent 09a6b578bf
commit 04c107a26e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 832 additions and 54 deletions

View File

@ -108,8 +108,8 @@ class ProjectStore implements IProjectStore {
id: row.id, id: row.id,
description: row.description, description: row.description,
health: row.health, health: row.health,
featureCount: row.number_of_features, featureCount: Number(row.number_of_features) || 0,
memberCount: row.number_of_users || 0, memberCount: Number(row.number_of_users) || 0,
updatedAt: row.updated_at, updatedAt: row.updated_at,
}; };
} }

View File

@ -4,17 +4,23 @@ import { constraintSchema } from './spec/constraint-schema';
import { createFeatureSchema } from './spec/create-feature-schema'; import { createFeatureSchema } from './spec/create-feature-schema';
import { createStrategySchema } from './spec/create-strategy-schema'; import { createStrategySchema } from './spec/create-strategy-schema';
import { emptySchema } from './spec/empty-schema'; import { emptySchema } from './spec/empty-schema';
import { environmentSchema } from './spec/environment-schema';
import { featureEnvironmentSchema } from './spec/feature-environment-schema'; import { featureEnvironmentSchema } from './spec/feature-environment-schema';
import { featureSchema } from './spec/feature-schema'; import { featureSchema } from './spec/feature-schema';
import { featureStrategySchema } from './spec/feature-strategy-schema'; import { featureStrategySchema } from './spec/feature-strategy-schema';
import { featureVariantsSchema } from './spec/feature-variants-schema'; import { featureVariantsSchema } from './spec/feature-variants-schema';
import { featuresSchema } from './spec/features-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 { mapValues } from '../util/map-values';
import { omitKeys } from '../util/omit-keys'; import { omitKeys } from '../util/omit-keys';
import { overrideSchema } from './spec/override-schema'; import { overrideSchema } from './spec/override-schema';
import { parametersSchema } from './spec/parameters-schema'; import { parametersSchema } from './spec/parameters-schema';
import { patchSchema } from './spec/patch-schema'; import { patchSchema } from './spec/patch-schema';
import { patchesSchema } from './spec/patches-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 { strategySchema } from './spec/strategy-schema';
import { tagSchema } from './spec/tag-schema'; import { tagSchema } from './spec/tag-schema';
import { tagsSchema } from './spec/tags-schema'; import { tagsSchema } from './spec/tags-schema';
@ -33,11 +39,13 @@ export type SchemaRef = typeof schemas[keyof typeof schemas]['components'];
export interface AdminApiOperation export interface AdminApiOperation
extends Omit<OpenAPIV3.OperationObject, 'tags'> { extends Omit<OpenAPIV3.OperationObject, 'tags'> {
operationId: string;
tags: ['admin']; tags: ['admin'];
} }
export interface ClientApiOperation export interface ClientApiOperation
extends Omit<OpenAPIV3.OperationObject, 'tags'> { extends Omit<OpenAPIV3.OperationObject, 'tags'> {
operationId: string;
tags: ['client']; tags: ['client'];
} }
@ -47,15 +55,21 @@ export const schemas = {
createFeatureSchema, createFeatureSchema,
createStrategySchema, createStrategySchema,
emptySchema, emptySchema,
environmentSchema,
featureEnvironmentSchema, featureEnvironmentSchema,
featureSchema, featureSchema,
featureStrategySchema, featureStrategySchema,
featureVariantsSchema, featureVariantsSchema,
featuresSchema, featuresSchema,
healthOverviewSchema,
healthReportSchema,
overrideSchema, overrideSchema,
parametersSchema, parametersSchema,
patchSchema, patchSchema,
patchesSchema, patchesSchema,
projectEnvironmentSchema,
projectSchema,
projectsSchema,
strategySchema, strategySchema,
tagSchema, tagSchema,
tagsSchema, tagsSchema,

View File

@ -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<typeof environmentSchema>;

View File

@ -25,6 +25,13 @@ test('featureSchema', () => {
payload: { type: 'a', value: 'b' }, payload: { type: 'a', value: 'b' },
}, },
], ],
environments: [
{
name: 'a',
type: 'b',
enabled: true,
},
],
}; };
expect( expect(

View File

@ -4,6 +4,7 @@ import { strategySchema } from './strategy-schema';
import { constraintSchema } from './constraint-schema'; import { constraintSchema } from './constraint-schema';
import { overrideSchema } from './override-schema'; import { overrideSchema } from './override-schema';
import { parametersSchema } from './parameters-schema'; import { parametersSchema } from './parameters-schema';
import { environmentSchema } from './environment-schema';
export const featureSchema = { export const featureSchema = {
$id: '#/components/schemas/featureSchema', $id: '#/components/schemas/featureSchema',
@ -48,7 +49,7 @@ export const featureSchema = {
environments: { environments: {
type: 'array', type: 'array',
items: { items: {
type: 'object', $ref: '#/components/schemas/environmentSchema',
}, },
}, },
strategies: { strategies: {
@ -67,6 +68,7 @@ export const featureSchema = {
components: { components: {
schemas: { schemas: {
constraintSchema, constraintSchema,
environmentSchema,
overrideSchema, overrideSchema,
parametersSchema, parametersSchema,
strategySchema, strategySchema,

View File

@ -5,6 +5,7 @@ import { variantSchema } from './variant-schema';
import { overrideSchema } from './override-schema'; import { overrideSchema } from './override-schema';
import { constraintSchema } from './constraint-schema'; import { constraintSchema } from './constraint-schema';
import { strategySchema } from './strategy-schema'; import { strategySchema } from './strategy-schema';
import { environmentSchema } from './environment-schema';
export const featuresSchema = { export const featuresSchema = {
$id: '#/components/schemas/featuresSchema', $id: '#/components/schemas/featuresSchema',
@ -25,6 +26,7 @@ export const featuresSchema = {
components: { components: {
schemas: { schemas: {
constraintSchema, constraintSchema,
environmentSchema,
featureSchema, featureSchema,
overrideSchema, overrideSchema,
parametersSchema, parametersSchema,

View File

@ -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<typeof healthOverviewSchema>;

View File

@ -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<typeof healthReportSchema>;

View File

@ -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
>;

View File

@ -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<typeof projectSchema>;

View File

@ -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<typeof projectsSchema>;

View File

@ -14,6 +14,7 @@ import {
import { serializeDates } from '../../types/serialize-dates'; import { serializeDates } from '../../types/serialize-dates';
import { OpenApiService } from '../../services/openapi-service'; import { OpenApiService } from '../../services/openapi-service';
import { createResponseSchema } from '../../openapi'; import { createResponseSchema } from '../../openapi';
import { EmptySchema } from '../../openapi/spec/empty-schema';
export default class ArchiveController extends Controller { export default class ArchiveController extends Controller {
private readonly logger: Logger; private readonly logger: Logger;
@ -42,6 +43,7 @@ export default class ArchiveController extends Controller {
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['admin'], tags: ['admin'],
operationId: 'getArchivedFeatures',
responses: { 200: createResponseSchema('featuresSchema') }, responses: { 200: createResponseSchema('featuresSchema') },
deprecated: true, deprecated: true,
}), }),
@ -56,18 +58,42 @@ export default class ArchiveController extends Controller {
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['admin'], tags: ['admin'],
operationId: 'getArchivedFeaturesByProjectId',
responses: { 200: createResponseSchema('featuresSchema') }, responses: { 200: createResponseSchema('featuresSchema') },
deprecated: true, deprecated: true,
}), }),
], ],
}); });
this.delete('/:featureName', this.deleteFeature, DELETE_FEATURE); this.route({
this.post( method: 'delete',
'/revive/:featureName', path: '/:featureName',
this.reviveFeatureToggle, acceptAnyContentType: true,
UPDATE_FEATURE, 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( async getArchivedFeatures(
@ -104,8 +130,8 @@ export default class ArchiveController extends Controller {
} }
async deleteFeature( async deleteFeature(
req: IAuthRequest<any, { featureName: string }, any, any>, req: IAuthRequest<{ featureName: string }>,
res: Response, res: Response<EmptySchema>,
): Promise<void> { ): Promise<void> {
const { featureName } = req.params; const { featureName } = req.params;
const user = extractUsername(req); const user = extractUsername(req);
@ -113,7 +139,10 @@ export default class ArchiveController extends Controller {
res.status(200).end(); res.status(200).end();
} }
async reviveFeatureToggle(req: IAuthRequest, res: Response): Promise<void> { async reviveFeature(
req: IAuthRequest<{ featureName: string }>,
res: Response<EmptySchema>,
): Promise<void> {
const userName = extractUsername(req); const userName = extractUsername(req);
const { featureName } = req.params; const { featureName } = req.params;
await this.featureService.reviveToggle(featureName, userName); await this.featureService.reviveToggle(featureName, userName);

View File

@ -5,7 +5,8 @@ import { IUnleashServices } from '../../../types/services';
import { Logger } from '../../../logger'; import { Logger } from '../../../logger';
import EnvironmentService from '../../../services/environment-service'; import EnvironmentService from '../../../services/environment-service';
import { UPDATE_PROJECT } from '../../../types/permissions'; 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'; const PREFIX = '/:projectId/environments';
@ -14,10 +15,6 @@ interface IProjectEnvironmentParams {
environment: string; environment: string;
} }
interface EnvironmentBody {
environment: string;
}
export default class EnvironmentsController extends Controller { export default class EnvironmentsController extends Controller {
private logger: Logger; private logger: Logger;
@ -25,49 +22,79 @@ export default class EnvironmentsController extends Controller {
constructor( constructor(
config: IUnleashConfig, config: IUnleashConfig,
{ environmentService }: Pick<IUnleashServices, 'environmentService'>, {
environmentService,
openApiService,
}: Pick<IUnleashServices, 'environmentService' | 'openApiService'>,
) { ) {
super(config); super(config);
this.logger = config.getLogger('admin-api/project/environments.ts'); this.logger = config.getLogger('admin-api/project/environments.ts');
this.environmentService = environmentService; this.environmentService = environmentService;
this.post(PREFIX, this.addEnvironmentToProject, UPDATE_PROJECT);
this.delete( this.route({
`${PREFIX}/:environment`, method: 'post',
this.removeEnvironmentFromProject, path: PREFIX,
UPDATE_PROJECT, 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( async addEnvironmentToProject(
req: Request< req: Request<
Omit<IProjectEnvironmentParams, 'environment'>, Omit<IProjectEnvironmentParams, 'environment'>,
any, void,
EnvironmentBody, ProjectEnvironmentSchema
any
>, >,
res: Response, res: Response,
): Promise<void> { ): Promise<void> {
const { projectId } = req.params; const { projectId } = req.params;
const { environment } = req.body;
const { environment } = await addEnvironment.validateAsync(req.body);
await this.environmentService.addEnvironmentToProject( await this.environmentService.addEnvironmentToProject(
environment, environment,
projectId, projectId,
); );
res.status(200).end(); res.status(200).end();
} }
async removeEnvironmentFromProject( async removeEnvironmentFromProject(
req: Request<IProjectEnvironmentParams, any, any, any>, req: Request<IProjectEnvironmentParams>,
res: Response, res: Response<void>,
): Promise<void> { ): Promise<void> {
const { projectId, environment } = req.params; const { projectId, environment } = req.params;
await this.environmentService.removeEnvironmentFromProject( await this.environmentService.removeEnvironmentFromProject(
environment, environment,
projectId, projectId,
); );
res.status(200).end(); res.status(200).end();
} }
} }

View File

@ -5,28 +5,74 @@ import { IUnleashConfig } from '../../../types/option';
import ProjectHealthService from '../../../services/project-health-service'; import ProjectHealthService from '../../../services/project-health-service';
import { Logger } from '../../../logger'; import { Logger } from '../../../logger';
import { IArchivedQuery, IProjectParam } from '../../../types/model'; 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 { export default class ProjectHealthReport extends Controller {
private projectHealthService: ProjectHealthService; private projectHealthService: ProjectHealthService;
private openApiService: OpenApiService;
private logger: Logger; private logger: Logger;
constructor( constructor(
config: IUnleashConfig, config: IUnleashConfig,
{ {
projectHealthService, projectHealthService,
}: Pick<IUnleashServices, 'projectHealthService'>, openApiService,
}: Pick<IUnleashServices, 'projectHealthService' | 'openApiService'>,
) { ) {
super(config); super(config);
this.logger = config.getLogger('/admin-api/project/health-report'); this.logger = config.getLogger('/admin-api/project/health-report');
this.projectHealthService = projectHealthService; this.projectHealthService = projectHealthService;
this.get('/:projectId', this.getProjectOverview); this.openApiService = openApiService;
this.get('/:projectId/health-report', this.getProjectHealthReport);
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( async getProjectHealthOverview(
req: Request<IProjectParam, any, any, IArchivedQuery>, req: Request<IProjectParam, unknown, unknown, IArchivedQuery>,
res: Response, res: Response<HealthOverviewSchema>,
): Promise<void> { ): Promise<void> {
const { projectId } = req.params; const { projectId } = req.params;
const { archived } = req.query; const { archived } = req.query;
@ -34,20 +80,27 @@ export default class ProjectHealthReport extends Controller {
projectId, projectId,
archived, archived,
); );
res.json(overview); this.openApiService.respondWithValidation(
200,
res,
healthOverviewSchema.$id,
serializeDates(overview),
);
} }
async getProjectHealthReport( async getProjectHealthReport(
req: Request<IProjectParam, any, any, any>, req: Request<IProjectParam>,
res: Response, res: Response<HealthReportSchema>,
): Promise<void> { ): Promise<void> {
const { projectId } = req.params; const { projectId } = req.params;
const overview = await this.projectHealthService.getProjectHealthReport( const overview = await this.projectHealthService.getProjectHealthReport(
projectId, projectId,
); );
res.json({ this.openApiService.respondWithValidation(
version: 2, 200,
...overview, res,
}); healthReportSchema.$id,
serializeDates(overview),
);
} }
} }

View File

@ -7,24 +7,62 @@ import EnvironmentsController from './environments';
import ProjectHealthReport from './health-report'; import ProjectHealthReport from './health-report';
import ProjectService from '../../../services/project-service'; import ProjectService from '../../../services/project-service';
import VariantsController from './variants'; 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 { export default class ProjectApi extends Controller {
private projectService: ProjectService; private projectService: ProjectService;
private openApiService: OpenApiService;
constructor(config: IUnleashConfig, services: IUnleashServices) { constructor(config: IUnleashConfig, services: IUnleashServices) {
super(config); super(config);
this.projectService = services.projectService; this.projectService = services.projectService;
this.openApiService = services.openApiService;
this.get('/', this.getProjects); 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 ProjectFeaturesController(config, services).router);
this.use('/', new EnvironmentsController(config, services).router); this.use('/', new EnvironmentsController(config, services).router);
this.use('/', new ProjectHealthReport(config, services).router); this.use('/', new ProjectHealthReport(config, services).router);
this.use('/', new VariantsController(config, services).router); this.use('/', new VariantsController(config, services).router);
} }
async getProjects(req: Request, res: Response): Promise<void> { async getProjects(
req: Request,
res: Response<ProjectsSchema>,
): Promise<void> {
const projects = await this.projectService.getProjects({ const projects = await this.projectService.getProjects({
id: 'default', id: 'default',
}); });
res.json({ version: 1, projects }).end();
this.openApiService.respondWithValidation(
200,
res,
projectsSchema.$id,
{ version: 1, projects: serializeDates(projects) },
);
} }
} }

View File

@ -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 });

View File

@ -73,7 +73,12 @@ export class OpenApiService {
const errors = validateSchema(schema, data); const errors = validateSchema(schema, data);
if (errors) { 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); res.status(status).json(data);

View File

@ -160,6 +160,29 @@ Object {
"emptySchema": Object { "emptySchema": Object {
"description": "emptySchema", "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 { "featureEnvironmentSchema": Object {
"additionalProperties": false, "additionalProperties": false,
"properties": Object { "properties": Object {
@ -207,7 +230,7 @@ Object {
}, },
"environments": Object { "environments": Object {
"items": Object { "items": Object {
"type": "object", "$ref": "#/components/schemas/environmentSchema",
}, },
"type": "array", "type": "array",
}, },
@ -336,6 +359,102 @@ Object {
], ],
"type": "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 { "overrideSchema": Object {
"additionalProperties": false, "additionalProperties": false,
"properties": Object { "properties": Object {
@ -393,6 +512,74 @@ Object {
}, },
"type": "array", "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 { "strategySchema": Object {
"additionalProperties": false, "additionalProperties": false,
"properties": Object { "properties": Object {
@ -691,6 +878,7 @@ Object {
"/api/admin/archive/features": Object { "/api/admin/archive/features": Object {
"get": Object { "get": Object {
"deprecated": true, "deprecated": true,
"operationId": "getArchivedFeatures",
"responses": Object { "responses": Object {
"200": Object { "200": Object {
"content": Object { "content": Object {
@ -711,6 +899,7 @@ Object {
"/api/admin/archive/features/{projectId}": Object { "/api/admin/archive/features/{projectId}": Object {
"get": Object { "get": Object {
"deprecated": true, "deprecated": true,
"operationId": "getArchivedFeaturesByProjectId",
"parameters": Array [ "parameters": Array [
Object { Object {
"in": "path", "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 { "/api/admin/features": Object {
"get": Object { "get": Object {
"deprecated": true, "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 { "/api/admin/projects/{projectId}/features": Object {
"get": Object { "get": Object {
"operationId": "getFeatures", "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 { "/api/admin/ui-config": Object {
"get": Object { "get": Object {
"operationId": "getUIConfig", "operationId": "getUIConfig",