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,
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,
};
}

View File

@ -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<OpenAPIV3.OperationObject, 'tags'> {
operationId: string;
tags: ['admin'];
}
export interface ClientApiOperation
extends Omit<OpenAPIV3.OperationObject, 'tags'> {
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,

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' },
},
],
environments: [
{
name: 'a',
type: 'b',
enabled: true,
},
],
};
expect(

View File

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

View File

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

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 { 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<any, { featureName: string }, any, any>,
res: Response,
req: IAuthRequest<{ featureName: string }>,
res: Response<EmptySchema>,
): Promise<void> {
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<void> {
async reviveFeature(
req: IAuthRequest<{ featureName: string }>,
res: Response<EmptySchema>,
): Promise<void> {
const userName = extractUsername(req);
const { featureName } = req.params;
await this.featureService.reviveToggle(featureName, userName);

View File

@ -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<IUnleashServices, 'environmentService'>,
{
environmentService,
openApiService,
}: Pick<IUnleashServices, 'environmentService' | 'openApiService'>,
) {
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<IProjectEnvironmentParams, 'environment'>,
any,
EnvironmentBody,
any
void,
ProjectEnvironmentSchema
>,
res: Response,
): Promise<void> {
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<IProjectEnvironmentParams, any, any, any>,
res: Response,
req: Request<IProjectEnvironmentParams>,
res: Response<void>,
): Promise<void> {
const { projectId, environment } = req.params;
await this.environmentService.removeEnvironmentFromProject(
environment,
projectId,
);
res.status(200).end();
}
}

View File

@ -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<IUnleashServices, 'projectHealthService'>,
openApiService,
}: Pick<IUnleashServices, 'projectHealthService' | 'openApiService'>,
) {
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<IProjectParam, any, any, IArchivedQuery>,
res: Response,
async getProjectHealthOverview(
req: Request<IProjectParam, unknown, unknown, IArchivedQuery>,
res: Response<HealthOverviewSchema>,
): Promise<void> {
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<IProjectParam, any, any, any>,
res: Response,
req: Request<IProjectParam>,
res: Response<HealthReportSchema>,
): Promise<void> {
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),
);
}
}

View File

@ -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<void> {
async getProjects(
req: Request,
res: Response<ProjectsSchema>,
): Promise<void> {
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) },
);
}
}

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

View File

@ -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",