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:
parent
09a6b578bf
commit
04c107a26e
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
|
25
src/lib/openapi/spec/environment-schema.ts
Normal file
25
src/lib/openapi/spec/environment-schema.ts
Normal 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>;
|
@ -25,6 +25,13 @@ test('featureSchema', () => {
|
||||
payload: { type: 'a', value: 'b' },
|
||||
},
|
||||
],
|
||||
environments: [
|
||||
{
|
||||
name: 'a',
|
||||
type: 'b',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
62
src/lib/openapi/spec/health-overview-schema.ts
Normal file
62
src/lib/openapi/spec/health-overview-schema.ts
Normal 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>;
|
27
src/lib/openapi/spec/health-report-schema.ts
Normal file
27
src/lib/openapi/spec/health-report-schema.ts
Normal 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>;
|
18
src/lib/openapi/spec/project-environment-schema.ts
Normal file
18
src/lib/openapi/spec/project-environment-schema.ts
Normal 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
|
||||
>;
|
40
src/lib/openapi/spec/project-schema.ts
Normal file
40
src/lib/openapi/spec/project-schema.ts
Normal 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>;
|
27
src/lib/openapi/spec/projects-schema.ts
Normal file
27
src/lib/openapi/spec/projects-schema.ts
Normal 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>;
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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) },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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 });
|
@ -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);
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user