mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	refactor: add OpenAPI schema to environments controller (#1682)
* refactor: normalize controller file names * refactor: throw invalid responses in dev mode * refactor: add OpenAPI schema to environments controller * refactor: improve JSON schema prop removal code * refactor: fix empty response specs
This commit is contained in:
		
							parent
							
								
									18e63d5ea3
								
							
						
					
					
						commit
						adface17c7
					
				
							
								
								
									
										52
									
								
								src/lib/openapi/index.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/lib/openapi/index.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
			
		||||
import {
 | 
			
		||||
    createOpenApiSchema,
 | 
			
		||||
    createRequestSchema,
 | 
			
		||||
    createResponseSchema,
 | 
			
		||||
    removeJsonSchemaProps,
 | 
			
		||||
} from './index';
 | 
			
		||||
 | 
			
		||||
test('createRequestSchema', () => {
 | 
			
		||||
    expect(createRequestSchema('schemaName')).toMatchInlineSnapshot(`
 | 
			
		||||
        Object {
 | 
			
		||||
          "content": Object {
 | 
			
		||||
            "application/json": Object {
 | 
			
		||||
              "schema": Object {
 | 
			
		||||
                "$ref": "#/components/schemas/schemaName",
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
          "description": "schemaName",
 | 
			
		||||
          "required": true,
 | 
			
		||||
        }
 | 
			
		||||
    `);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('createResponseSchema', () => {
 | 
			
		||||
    expect(createResponseSchema('schemaName')).toMatchInlineSnapshot(`
 | 
			
		||||
        Object {
 | 
			
		||||
          "content": Object {
 | 
			
		||||
            "application/json": Object {
 | 
			
		||||
              "schema": Object {
 | 
			
		||||
                "$ref": "#/components/schemas/schemaName",
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
          "description": "schemaName",
 | 
			
		||||
        }
 | 
			
		||||
    `);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('removeJsonSchemaProps', () => {
 | 
			
		||||
    expect(removeJsonSchemaProps({ a: 'b', $id: 'c', components: {} }))
 | 
			
		||||
        .toMatchInlineSnapshot(`
 | 
			
		||||
        Object {
 | 
			
		||||
          "a": "b",
 | 
			
		||||
        }
 | 
			
		||||
    `);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('createOpenApiSchema url', () => {
 | 
			
		||||
    expect(createOpenApiSchema('https://example.com').servers[0].url).toEqual(
 | 
			
		||||
        'https://example.com',
 | 
			
		||||
    );
 | 
			
		||||
});
 | 
			
		||||
@ -3,7 +3,6 @@ import { cloneFeatureSchema } from './spec/clone-feature-schema';
 | 
			
		||||
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';
 | 
			
		||||
@ -32,6 +31,8 @@ import { updateStrategySchema } from './spec/update-strategy-schema';
 | 
			
		||||
import { variantSchema } from './spec/variant-schema';
 | 
			
		||||
import { variantsSchema } from './spec/variants-schema';
 | 
			
		||||
import { versionSchema } from './spec/version-schema';
 | 
			
		||||
import { environmentsSchema } from './spec/environments-schema';
 | 
			
		||||
import { sortOrderSchema } from './spec/sort-order-schema';
 | 
			
		||||
 | 
			
		||||
// Schemas must have $id property on the form "#/components/schemas/mySchema".
 | 
			
		||||
export type SchemaId = typeof schemas[keyof typeof schemas]['$id'];
 | 
			
		||||
@ -39,6 +40,12 @@ export type SchemaId = typeof schemas[keyof typeof schemas]['$id'];
 | 
			
		||||
// Schemas must list all $ref schemas in "components", including nested schemas.
 | 
			
		||||
export type SchemaRef = typeof schemas[keyof typeof schemas]['components'];
 | 
			
		||||
 | 
			
		||||
// JSON schema properties that should not be included in the OpenAPI spec.
 | 
			
		||||
export interface JsonSchemaProps {
 | 
			
		||||
    $id: string;
 | 
			
		||||
    components: object;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface AdminApiOperation
 | 
			
		||||
    extends Omit<OpenAPIV3.OperationObject, 'tags'> {
 | 
			
		||||
    operationId: string;
 | 
			
		||||
@ -56,8 +63,8 @@ export const schemas = {
 | 
			
		||||
    constraintSchema,
 | 
			
		||||
    createFeatureSchema,
 | 
			
		||||
    createStrategySchema,
 | 
			
		||||
    emptySchema,
 | 
			
		||||
    environmentSchema,
 | 
			
		||||
    environmentsSchema,
 | 
			
		||||
    featureEnvironmentSchema,
 | 
			
		||||
    featureSchema,
 | 
			
		||||
    featureStrategySchema,
 | 
			
		||||
@ -74,6 +81,7 @@ export const schemas = {
 | 
			
		||||
    projectEnvironmentSchema,
 | 
			
		||||
    projectSchema,
 | 
			
		||||
    projectsSchema,
 | 
			
		||||
    sortOrderSchema,
 | 
			
		||||
    strategySchema,
 | 
			
		||||
    tagSchema,
 | 
			
		||||
    tagsSchema,
 | 
			
		||||
@ -116,6 +124,13 @@ export const createResponseSchema = (
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Remove JSONSchema keys that would result in an invalid OpenAPI spec.
 | 
			
		||||
export const removeJsonSchemaProps = <T extends JsonSchemaProps>(
 | 
			
		||||
    schema: T,
 | 
			
		||||
): OpenAPIV3.SchemaObject => {
 | 
			
		||||
    return omitKeys(schema, '$id', 'components');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createOpenApiSchema = (
 | 
			
		||||
    serverUrl?: string,
 | 
			
		||||
): Omit<OpenAPIV3.Document, 'paths'> => {
 | 
			
		||||
@ -135,9 +150,7 @@ export const createOpenApiSchema = (
 | 
			
		||||
                    name: 'Authorization',
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            schemas: mapValues(schemas, (schema) =>
 | 
			
		||||
                omitKeys(schema, '$id', 'components'),
 | 
			
		||||
            ),
 | 
			
		||||
            schemas: mapValues(schemas, removeJsonSchemaProps),
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
			
		||||
 | 
			
		||||
exports[`sortOrderSchema invalid value type 1`] = `
 | 
			
		||||
Object {
 | 
			
		||||
  "data": Object {
 | 
			
		||||
    "a": "1",
 | 
			
		||||
  },
 | 
			
		||||
  "errors": Array [
 | 
			
		||||
    Object {
 | 
			
		||||
      "instancePath": "/a",
 | 
			
		||||
      "keyword": "type",
 | 
			
		||||
      "message": "must be number",
 | 
			
		||||
      "params": Object {
 | 
			
		||||
        "type": "number",
 | 
			
		||||
      },
 | 
			
		||||
      "schemaPath": "#/additionalProperties/type",
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  "schema": "#/components/schemas/sortOrderSchema",
 | 
			
		||||
}
 | 
			
		||||
`;
 | 
			
		||||
							
								
								
									
										3
									
								
								src/lib/openapi/spec/empty-response.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/lib/openapi/spec/empty-response.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
export const emptyResponse = {
 | 
			
		||||
    description: 'emptyResponse',
 | 
			
		||||
};
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
import { FromSchema } from 'json-schema-to-ts';
 | 
			
		||||
 | 
			
		||||
export const emptySchema = {
 | 
			
		||||
    $id: '#/components/schemas/emptySchema',
 | 
			
		||||
    description: 'emptySchema',
 | 
			
		||||
    components: {},
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
export type EmptySchema = FromSchema<typeof emptySchema>;
 | 
			
		||||
@ -15,6 +15,9 @@ export const environmentSchema = {
 | 
			
		||||
        enabled: {
 | 
			
		||||
            type: 'boolean',
 | 
			
		||||
        },
 | 
			
		||||
        protected: {
 | 
			
		||||
            type: 'boolean',
 | 
			
		||||
        },
 | 
			
		||||
        sortOrder: {
 | 
			
		||||
            type: 'number',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								src/lib/openapi/spec/environments-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/lib/openapi/spec/environments-schema.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
import { FromSchema } from 'json-schema-to-ts';
 | 
			
		||||
import { environmentSchema } from './environment-schema';
 | 
			
		||||
 | 
			
		||||
export const environmentsSchema = {
 | 
			
		||||
    $id: '#/components/schemas/environmentsSchema',
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    additionalProperties: false,
 | 
			
		||||
    required: ['version', 'environments'],
 | 
			
		||||
    properties: {
 | 
			
		||||
        version: {
 | 
			
		||||
            type: 'integer',
 | 
			
		||||
        },
 | 
			
		||||
        environments: {
 | 
			
		||||
            type: 'array',
 | 
			
		||||
            items: {
 | 
			
		||||
                $ref: '#/components/schemas/environmentSchema',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
        schemas: {
 | 
			
		||||
            environmentSchema,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
export type EnvironmentsSchema = FromSchema<typeof environmentsSchema>;
 | 
			
		||||
							
								
								
									
										19
									
								
								src/lib/openapi/spec/sort-order-schema.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/lib/openapi/spec/sort-order-schema.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
import { validateSchema } from '../validate';
 | 
			
		||||
import { SortOrderSchema } from './sort-order-schema';
 | 
			
		||||
 | 
			
		||||
test('sortOrderSchema', () => {
 | 
			
		||||
    const data: SortOrderSchema = {
 | 
			
		||||
        a: 1,
 | 
			
		||||
        b: 2,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    expect(
 | 
			
		||||
        validateSchema('#/components/schemas/sortOrderSchema', data),
 | 
			
		||||
    ).toBeUndefined();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('sortOrderSchema invalid value type', () => {
 | 
			
		||||
    expect(
 | 
			
		||||
        validateSchema('#/components/schemas/sortOrderSchema', { a: '1' }),
 | 
			
		||||
    ).toMatchSnapshot();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										12
									
								
								src/lib/openapi/spec/sort-order-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/lib/openapi/spec/sort-order-schema.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
import { FromSchema } from 'json-schema-to-ts';
 | 
			
		||||
 | 
			
		||||
export const sortOrderSchema = {
 | 
			
		||||
    $id: '#/components/schemas/sortOrderSchema',
 | 
			
		||||
    type: 'object',
 | 
			
		||||
    additionalProperties: {
 | 
			
		||||
        type: 'number',
 | 
			
		||||
    },
 | 
			
		||||
    components: {},
 | 
			
		||||
} as const;
 | 
			
		||||
 | 
			
		||||
export type SortOrderSchema = FromSchema<typeof sortOrderSchema>;
 | 
			
		||||
@ -14,7 +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';
 | 
			
		||||
import { emptyResponse } from '../../openapi/spec/empty-response';
 | 
			
		||||
 | 
			
		||||
export default class ArchiveController extends Controller {
 | 
			
		||||
    private readonly logger: Logger;
 | 
			
		||||
@ -75,7 +75,7 @@ export default class ArchiveController extends Controller {
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'deleteFeature',
 | 
			
		||||
                    responses: { 200: createResponseSchema('emptySchema') },
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
@ -90,7 +90,7 @@ export default class ArchiveController extends Controller {
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'reviveFeature',
 | 
			
		||||
                    responses: { 200: createResponseSchema('emptySchema') },
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
@ -131,7 +131,7 @@ export default class ArchiveController extends Controller {
 | 
			
		||||
 | 
			
		||||
    async deleteFeature(
 | 
			
		||||
        req: IAuthRequest<{ featureName: string }>,
 | 
			
		||||
        res: Response<EmptySchema>,
 | 
			
		||||
        res: Response<void>,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const { featureName } = req.params;
 | 
			
		||||
        const user = extractUsername(req);
 | 
			
		||||
@ -141,7 +141,7 @@ export default class ArchiveController extends Controller {
 | 
			
		||||
 | 
			
		||||
    async reviveFeature(
 | 
			
		||||
        req: IAuthRequest<{ featureName: string }>,
 | 
			
		||||
        res: Response<EmptySchema>,
 | 
			
		||||
        res: Response<void>,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const userName = extractUsername(req);
 | 
			
		||||
        const { featureName } = req.params;
 | 
			
		||||
 | 
			
		||||
@ -1,73 +0,0 @@
 | 
			
		||||
import { Request, Response } from 'express';
 | 
			
		||||
import Controller from '../controller';
 | 
			
		||||
import { IUnleashServices } from '../../types/services';
 | 
			
		||||
import { IUnleashConfig } from '../../types/option';
 | 
			
		||||
import { ISortOrder } from '../../types/model';
 | 
			
		||||
import EnvironmentService from '../../services/environment-service';
 | 
			
		||||
import { Logger } from '../../logger';
 | 
			
		||||
import { ADMIN } from '../../types/permissions';
 | 
			
		||||
 | 
			
		||||
interface EnvironmentParam {
 | 
			
		||||
    name: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class EnvironmentsController extends Controller {
 | 
			
		||||
    private logger: Logger;
 | 
			
		||||
 | 
			
		||||
    private service: EnvironmentService;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        config: IUnleashConfig,
 | 
			
		||||
        { environmentService }: Pick<IUnleashServices, 'environmentService'>,
 | 
			
		||||
    ) {
 | 
			
		||||
        super(config);
 | 
			
		||||
        this.logger = config.getLogger('admin-api/environments-controller.ts');
 | 
			
		||||
        this.service = environmentService;
 | 
			
		||||
        this.get('/', this.getAll);
 | 
			
		||||
        this.put('/sort-order', this.updateSortOrder, ADMIN);
 | 
			
		||||
        this.get('/:name', this.getEnv);
 | 
			
		||||
        this.post('/:name/on', this.toggleEnvironmentOn, ADMIN);
 | 
			
		||||
        this.post('/:name/off', this.toggleEnvironmentOff, ADMIN);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getAll(req: Request, res: Response): Promise<void> {
 | 
			
		||||
        const environments = await this.service.getAll();
 | 
			
		||||
        res.status(200).json({ version: 1, environments });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async updateSortOrder(
 | 
			
		||||
        req: Request<any, any, ISortOrder, any>,
 | 
			
		||||
        res: Response,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        await this.service.updateSortOrder(req.body);
 | 
			
		||||
        res.status(200).end();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async toggleEnvironmentOn(
 | 
			
		||||
        req: Request<EnvironmentParam, any, any, any>,
 | 
			
		||||
        res: Response,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const { name } = req.params;
 | 
			
		||||
        await this.service.toggleEnvironment(name, true);
 | 
			
		||||
        res.status(204).end();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async toggleEnvironmentOff(
 | 
			
		||||
        req: Request<EnvironmentParam, any, any, any>,
 | 
			
		||||
        res: Response,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const { name } = req.params;
 | 
			
		||||
        await this.service.toggleEnvironment(name, false);
 | 
			
		||||
        res.status(204).end();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getEnv(
 | 
			
		||||
        req: Request<EnvironmentParam, any, any, any>,
 | 
			
		||||
        res: Response,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const { name } = req.params;
 | 
			
		||||
 | 
			
		||||
        const env = await this.service.get(name);
 | 
			
		||||
        res.status(200).json(env);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										169
									
								
								src/lib/routes/admin-api/environments.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								src/lib/routes/admin-api/environments.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,169 @@
 | 
			
		||||
import { Request, Response } from 'express';
 | 
			
		||||
import Controller from '../controller';
 | 
			
		||||
import { IUnleashServices } from '../../types/services';
 | 
			
		||||
import { IUnleashConfig } from '../../types/option';
 | 
			
		||||
import EnvironmentService from '../../services/environment-service';
 | 
			
		||||
import { Logger } from '../../logger';
 | 
			
		||||
import { ADMIN, NONE } from '../../types/permissions';
 | 
			
		||||
import { OpenApiService } from '../../services/openapi-service';
 | 
			
		||||
import { createRequestSchema, createResponseSchema } from '../../openapi';
 | 
			
		||||
import {
 | 
			
		||||
    environmentsSchema,
 | 
			
		||||
    EnvironmentsSchema,
 | 
			
		||||
} from '../../openapi/spec/environments-schema';
 | 
			
		||||
import {
 | 
			
		||||
    environmentSchema,
 | 
			
		||||
    EnvironmentSchema,
 | 
			
		||||
} from '../../openapi/spec/environment-schema';
 | 
			
		||||
import { SortOrderSchema } from '../../openapi/spec/sort-order-schema';
 | 
			
		||||
import { emptyResponse } from '../../openapi/spec/empty-response';
 | 
			
		||||
 | 
			
		||||
interface EnvironmentParam {
 | 
			
		||||
    name: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class EnvironmentsController extends Controller {
 | 
			
		||||
    private logger: Logger;
 | 
			
		||||
 | 
			
		||||
    private openApiService: OpenApiService;
 | 
			
		||||
 | 
			
		||||
    private service: EnvironmentService;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        config: IUnleashConfig,
 | 
			
		||||
        {
 | 
			
		||||
            environmentService,
 | 
			
		||||
            openApiService,
 | 
			
		||||
        }: Pick<IUnleashServices, 'environmentService' | 'openApiService'>,
 | 
			
		||||
    ) {
 | 
			
		||||
        super(config);
 | 
			
		||||
        this.logger = config.getLogger('admin-api/environments-controller.ts');
 | 
			
		||||
        this.openApiService = openApiService;
 | 
			
		||||
        this.service = environmentService;
 | 
			
		||||
 | 
			
		||||
        this.route({
 | 
			
		||||
            method: 'get',
 | 
			
		||||
            path: '',
 | 
			
		||||
            handler: this.getAllEnvironments,
 | 
			
		||||
            permission: NONE,
 | 
			
		||||
            middleware: [
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'getAllEnvironments',
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.route({
 | 
			
		||||
            method: 'get',
 | 
			
		||||
            path: '/:name',
 | 
			
		||||
            handler: this.getEnvironment,
 | 
			
		||||
            permission: NONE,
 | 
			
		||||
            middleware: [
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'getEnvironment',
 | 
			
		||||
                    responses: {
 | 
			
		||||
                        200: createResponseSchema('environmentSchema'),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.route({
 | 
			
		||||
            method: 'put',
 | 
			
		||||
            path: '/sort-order',
 | 
			
		||||
            handler: this.updateSortOrder,
 | 
			
		||||
            permission: ADMIN,
 | 
			
		||||
            middleware: [
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'updateSortOrder',
 | 
			
		||||
                    requestBody: createRequestSchema('sortOrderSchema'),
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.route({
 | 
			
		||||
            method: 'post',
 | 
			
		||||
            path: '/:name/on',
 | 
			
		||||
            acceptAnyContentType: true,
 | 
			
		||||
            handler: this.toggleEnvironmentOn,
 | 
			
		||||
            permission: ADMIN,
 | 
			
		||||
            middleware: [
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'toggleEnvironmentOn',
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.route({
 | 
			
		||||
            method: 'post',
 | 
			
		||||
            path: '/:name/off',
 | 
			
		||||
            acceptAnyContentType: true,
 | 
			
		||||
            handler: this.toggleEnvironmentOff,
 | 
			
		||||
            permission: ADMIN,
 | 
			
		||||
            middleware: [
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'toggleEnvironmentOff',
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getAllEnvironments(
 | 
			
		||||
        req: Request,
 | 
			
		||||
        res: Response<EnvironmentsSchema>,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        this.openApiService.respondWithValidation(
 | 
			
		||||
            200,
 | 
			
		||||
            res,
 | 
			
		||||
            environmentsSchema.$id,
 | 
			
		||||
            { version: 1, environments: await this.service.getAll() },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async updateSortOrder(
 | 
			
		||||
        req: Request<unknown, unknown, SortOrderSchema>,
 | 
			
		||||
        res: Response,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        await this.service.updateSortOrder(req.body);
 | 
			
		||||
        res.status(200).end();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async toggleEnvironmentOn(
 | 
			
		||||
        req: Request<EnvironmentParam>,
 | 
			
		||||
        res: Response,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const { name } = req.params;
 | 
			
		||||
        await this.service.toggleEnvironment(name, true);
 | 
			
		||||
        res.status(204).end();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async toggleEnvironmentOff(
 | 
			
		||||
        req: Request<EnvironmentParam>,
 | 
			
		||||
        res: Response,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const { name } = req.params;
 | 
			
		||||
        await this.service.toggleEnvironment(name, false);
 | 
			
		||||
        res.status(204).end();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getEnvironment(
 | 
			
		||||
        req: Request<EnvironmentParam>,
 | 
			
		||||
        res: Response<EnvironmentSchema>,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        this.openApiService.respondWithValidation(
 | 
			
		||||
            200,
 | 
			
		||||
            res,
 | 
			
		||||
            environmentSchema.$id,
 | 
			
		||||
            await this.service.get(req.params.name),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -25,6 +25,7 @@ import { TagsSchema } from '../../openapi/spec/tags-schema';
 | 
			
		||||
import { serializeDates } from '../../types/serialize-dates';
 | 
			
		||||
import { OpenApiService } from '../../services/openapi-service';
 | 
			
		||||
import { createRequestSchema, createResponseSchema } from '../../openapi';
 | 
			
		||||
import { emptyResponse } from '../../openapi/spec/empty-response';
 | 
			
		||||
 | 
			
		||||
const version = 1;
 | 
			
		||||
 | 
			
		||||
@ -92,7 +93,7 @@ class FeatureController extends Controller {
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'validateFeature',
 | 
			
		||||
                    responses: { 200: createResponseSchema('emptySchema') },
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
@ -136,7 +137,7 @@ class FeatureController extends Controller {
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'removeTag',
 | 
			
		||||
                    responses: { 200: createResponseSchema('emptySchema') },
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@ -12,18 +12,18 @@ import UserController from './user';
 | 
			
		||||
import ConfigController from './config';
 | 
			
		||||
import ContextController from './context';
 | 
			
		||||
import ClientMetricsController from './client-metrics';
 | 
			
		||||
import BootstrapController from './bootstrap-controller';
 | 
			
		||||
import BootstrapController from './bootstrap';
 | 
			
		||||
import StateController from './state';
 | 
			
		||||
import TagController from './tag';
 | 
			
		||||
import TagTypeController from './tag-type';
 | 
			
		||||
import AddonController from './addon';
 | 
			
		||||
import ApiTokenController from './api-token-controller';
 | 
			
		||||
import ApiTokenController from './api-token';
 | 
			
		||||
import UserAdminController from './user-admin';
 | 
			
		||||
import EmailController from './email';
 | 
			
		||||
import UserFeedbackController from './user-feedback-controller';
 | 
			
		||||
import UserSplashController from './user-splash-controller';
 | 
			
		||||
import UserFeedbackController from './user-feedback';
 | 
			
		||||
import UserSplashController from './user-splash';
 | 
			
		||||
import ProjectApi from './project';
 | 
			
		||||
import { EnvironmentsController } from './environments-controller';
 | 
			
		||||
import { EnvironmentsController } from './environments';
 | 
			
		||||
import ConstraintsController from './constraints';
 | 
			
		||||
 | 
			
		||||
class AdminApi extends Controller {
 | 
			
		||||
 | 
			
		||||
@ -5,8 +5,9 @@ import { IUnleashServices } from '../../../types/services';
 | 
			
		||||
import { Logger } from '../../../logger';
 | 
			
		||||
import EnvironmentService from '../../../services/environment-service';
 | 
			
		||||
import { UPDATE_PROJECT } from '../../../types/permissions';
 | 
			
		||||
import { createRequestSchema, createResponseSchema } from '../../../openapi';
 | 
			
		||||
import { createRequestSchema } from '../../../openapi';
 | 
			
		||||
import { ProjectEnvironmentSchema } from '../../../openapi/spec/project-environment-schema';
 | 
			
		||||
import { emptyResponse } from '../../../openapi/spec/empty-response';
 | 
			
		||||
 | 
			
		||||
const PREFIX = '/:projectId/environments';
 | 
			
		||||
 | 
			
		||||
@ -44,7 +45,7 @@ export default class EnvironmentsController extends Controller {
 | 
			
		||||
                    requestBody: createRequestSchema(
 | 
			
		||||
                        'projectEnvironmentSchema',
 | 
			
		||||
                    ),
 | 
			
		||||
                    responses: { 200: createResponseSchema('emptySchema') },
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
@ -59,7 +60,7 @@ export default class EnvironmentsController extends Controller {
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'removeEnvironmentFromProject',
 | 
			
		||||
                    responses: { 200: createResponseSchema('emptySchema') },
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@ -35,6 +35,7 @@ import { serializeDates } from '../../../types/serialize-dates';
 | 
			
		||||
import { OpenApiService } from '../../../services/openapi-service';
 | 
			
		||||
import { createRequestSchema, createResponseSchema } from '../../../openapi';
 | 
			
		||||
import { FeatureEnvironmentSchema } from '../../../openapi/spec/feature-environment-schema';
 | 
			
		||||
import { emptyResponse } from '../../../openapi/spec/empty-response';
 | 
			
		||||
 | 
			
		||||
interface FeatureStrategyParams {
 | 
			
		||||
    projectId: string;
 | 
			
		||||
@ -216,7 +217,7 @@ export default class ProjectFeaturesController extends Controller {
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    operationId: 'deleteStrategy',
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    responses: { 200: createResponseSchema('emptySchema') },
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
@ -322,7 +323,7 @@ export default class ProjectFeaturesController extends Controller {
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    tags: ['admin'],
 | 
			
		||||
                    operationId: 'archiveFeature',
 | 
			
		||||
                    responses: { 200: createResponseSchema('emptySchema') },
 | 
			
		||||
                    responses: { 200: emptyResponse },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@ -5,11 +5,12 @@ import {
 | 
			
		||||
    AdminApiOperation,
 | 
			
		||||
    ClientApiOperation,
 | 
			
		||||
    createOpenApiSchema,
 | 
			
		||||
    JsonSchemaProps,
 | 
			
		||||
    removeJsonSchemaProps,
 | 
			
		||||
    SchemaId,
 | 
			
		||||
} from '../openapi';
 | 
			
		||||
import { Logger } from '../logger';
 | 
			
		||||
import { validateSchema } from '../openapi/validate';
 | 
			
		||||
import { omitKeys } from '../util/omit-keys';
 | 
			
		||||
 | 
			
		||||
export class OpenApiService {
 | 
			
		||||
    private readonly config: IUnleashConfig;
 | 
			
		||||
@ -43,11 +44,11 @@ export class OpenApiService {
 | 
			
		||||
        return `${baseUriPath}/docs/openapi`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    registerCustomSchemas<T extends object>(schemas: {
 | 
			
		||||
        [name: string]: { $id: string; components: T };
 | 
			
		||||
    }): void {
 | 
			
		||||
    registerCustomSchemas<T extends JsonSchemaProps>(
 | 
			
		||||
        schemas: Record<string, T>,
 | 
			
		||||
    ): void {
 | 
			
		||||
        Object.entries(schemas).forEach(([name, schema]) => {
 | 
			
		||||
            this.api.schema(name, omitKeys(schema, '$id', 'components'));
 | 
			
		||||
            this.api.schema(name, removeJsonSchemaProps(schema));
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -73,12 +74,11 @@ export class OpenApiService {
 | 
			
		||||
        const errors = validateSchema(schema, data);
 | 
			
		||||
 | 
			
		||||
        if (errors) {
 | 
			
		||||
            this.logger.warn(
 | 
			
		||||
                'Invalid response:',
 | 
			
		||||
                process.env.NODE_ENV === 'development'
 | 
			
		||||
                    ? JSON.stringify(errors, null, 2)
 | 
			
		||||
                    : errors,
 | 
			
		||||
            );
 | 
			
		||||
            if (process.env.NODE_ENV === 'development') {
 | 
			
		||||
                throw new Error(JSON.stringify(errors, null, 2));
 | 
			
		||||
            } else {
 | 
			
		||||
                this.logger.warn('Invalid response:', errors);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        res.status(status).json(data);
 | 
			
		||||
 | 
			
		||||
@ -157,9 +157,6 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
      "emptySchema": Object {
 | 
			
		||||
        "description": "emptySchema",
 | 
			
		||||
      },
 | 
			
		||||
      "environmentSchema": Object {
 | 
			
		||||
        "additionalProperties": false,
 | 
			
		||||
        "properties": Object {
 | 
			
		||||
@ -169,6 +166,9 @@ Object {
 | 
			
		||||
          "name": Object {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
          },
 | 
			
		||||
          "protected": Object {
 | 
			
		||||
            "type": "boolean",
 | 
			
		||||
          },
 | 
			
		||||
          "sortOrder": Object {
 | 
			
		||||
            "type": "number",
 | 
			
		||||
          },
 | 
			
		||||
@ -183,6 +183,25 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
      "environmentsSchema": Object {
 | 
			
		||||
        "additionalProperties": false,
 | 
			
		||||
        "properties": Object {
 | 
			
		||||
          "environments": Object {
 | 
			
		||||
            "items": Object {
 | 
			
		||||
              "$ref": "#/components/schemas/environmentSchema",
 | 
			
		||||
            },
 | 
			
		||||
            "type": "array",
 | 
			
		||||
          },
 | 
			
		||||
          "version": Object {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "required": Array [
 | 
			
		||||
          "version",
 | 
			
		||||
          "environments",
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
      "featureEnvironmentSchema": Object {
 | 
			
		||||
        "additionalProperties": false,
 | 
			
		||||
        "properties": Object {
 | 
			
		||||
@ -624,6 +643,12 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
      "sortOrderSchema": Object {
 | 
			
		||||
        "additionalProperties": Object {
 | 
			
		||||
          "type": "number",
 | 
			
		||||
        },
 | 
			
		||||
        "type": "object",
 | 
			
		||||
      },
 | 
			
		||||
      "strategySchema": Object {
 | 
			
		||||
        "additionalProperties": false,
 | 
			
		||||
        "properties": Object {
 | 
			
		||||
@ -986,14 +1011,7 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/emptySchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "emptySchema",
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
@ -1016,14 +1034,7 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/emptySchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "emptySchema",
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
@ -1058,6 +1069,119 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "/api/admin/environments": Object {
 | 
			
		||||
      "get": Object {
 | 
			
		||||
        "operationId": "getAllEnvironments",
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
          "admin",
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "/api/admin/environments/sort-order": Object {
 | 
			
		||||
      "put": Object {
 | 
			
		||||
        "operationId": "updateSortOrder",
 | 
			
		||||
        "requestBody": Object {
 | 
			
		||||
          "content": Object {
 | 
			
		||||
            "application/json": Object {
 | 
			
		||||
              "schema": Object {
 | 
			
		||||
                "$ref": "#/components/schemas/sortOrderSchema",
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
          "description": "sortOrderSchema",
 | 
			
		||||
          "required": true,
 | 
			
		||||
        },
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
          "admin",
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "/api/admin/environments/{name}": Object {
 | 
			
		||||
      "get": Object {
 | 
			
		||||
        "operationId": "getEnvironment",
 | 
			
		||||
        "parameters": Array [
 | 
			
		||||
          Object {
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "name": "name",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": Object {
 | 
			
		||||
              "type": "string",
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/environmentSchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "environmentSchema",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
          "admin",
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "/api/admin/environments/{name}/off": Object {
 | 
			
		||||
      "post": Object {
 | 
			
		||||
        "operationId": "toggleEnvironmentOff",
 | 
			
		||||
        "parameters": Array [
 | 
			
		||||
          Object {
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "name": "name",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": Object {
 | 
			
		||||
              "type": "string",
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
          "admin",
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "/api/admin/environments/{name}/on": Object {
 | 
			
		||||
      "post": Object {
 | 
			
		||||
        "operationId": "toggleEnvironmentOn",
 | 
			
		||||
        "parameters": Array [
 | 
			
		||||
          Object {
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "name": "name",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": Object {
 | 
			
		||||
              "type": "string",
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
          "admin",
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "/api/admin/feature-types": Object {
 | 
			
		||||
      "get": Object {
 | 
			
		||||
        "operationId": "getAllFeatureTypes",
 | 
			
		||||
@ -1104,14 +1228,7 @@ Object {
 | 
			
		||||
        "operationId": "validateFeature",
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/emptySchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "emptySchema",
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
@ -1219,14 +1336,7 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/emptySchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "emptySchema",
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
@ -1310,14 +1420,7 @@ Object {
 | 
			
		||||
        },
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/emptySchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "emptySchema",
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
@ -1348,14 +1451,7 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/emptySchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "emptySchema",
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
@ -1455,14 +1551,7 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/emptySchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "emptySchema",
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
@ -1927,14 +2016,7 @@ Object {
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": Object {
 | 
			
		||||
          "200": Object {
 | 
			
		||||
            "content": Object {
 | 
			
		||||
              "application/json": Object {
 | 
			
		||||
                "schema": Object {
 | 
			
		||||
                  "$ref": "#/components/schemas/emptySchema",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            "description": "emptySchema",
 | 
			
		||||
            "description": "emptyResponse",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        "tags": Array [
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user