1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-24 01:18:01 +02:00

task: add openapi for tags (#1724)

* task: add openapi for tags
This commit is contained in:
Christopher Kolstad 2022-06-21 08:23:30 +02:00 committed by GitHub
parent 00bef41836
commit 1821af8fe7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 310 additions and 22 deletions

View File

@ -49,6 +49,7 @@ import { validateTagTypeSchema } from './spec/validate-tag-type-schema';
import { variantSchema } from './spec/variant-schema'; import { variantSchema } from './spec/variant-schema';
import { variantsSchema } from './spec/variants-schema'; import { variantsSchema } from './spec/variants-schema';
import { versionSchema } from './spec/version-schema'; import { versionSchema } from './spec/version-schema';
import { tagWithVersionSchema } from './spec/tag-with-version-schema';
// All schemas in `openapi/spec` should be listed here. // All schemas in `openapi/spec` should be listed here.
export const schemas = { export const schemas = {
@ -87,6 +88,7 @@ export const schemas = {
splashSchema, splashSchema,
strategySchema, strategySchema,
tagSchema, tagSchema,
tagWithVersionSchema,
tagsSchema, tagsSchema,
tagTypeSchema, tagTypeSchema,
tagTypesSchema, tagTypesSchema,

View File

@ -0,0 +1,22 @@
import { tagSchema } from './tag-schema';
import { FromSchema } from 'json-schema-to-ts';
export const tagWithVersionSchema = {
$id: '#/components/schemas/tagWithVersionSchema',
type: 'object',
additionalProperties: false,
required: ['version', 'tag'],
properties: {
version: {
type: 'integer',
},
tag: {
$ref: '#/components/schemas/tagSchema',
},
},
components: {
schemas: { tagSchema },
},
} as const;
export type TagWithVersionSchema = FromSchema<typeof tagWithVersionSchema>;

View File

@ -72,9 +72,9 @@ test('should get all tags added', () => {
}); });
}); });
test('should be able to get single tag by type and value', () => { test('should be able to get single tag by type and value', async () => {
expect.assertions(1); expect.assertions(1);
tagStore.createTag({ value: 'TeamRed', type: 'simple' }); await tagStore.createTag({ value: 'TeamRed', type: 'simple' });
return request return request
.get(`${base}/api/admin/tags/simple/TeamRed`) .get(`${base}/api/admin/tags/simple/TeamRed`)
.expect('Content-Type', /json/) .expect('Content-Type', /json/)

View File

@ -6,9 +6,18 @@ import { Logger } from '../../logger';
import Controller from '../controller'; import Controller from '../controller';
import { UPDATE_FEATURE } from '../../types/permissions'; import { NONE, UPDATE_FEATURE } from '../../types/permissions';
import { extractUsername } from '../../util/extract-user'; import { extractUsername } from '../../util/extract-user';
import { IAuthRequest } from '../unleash-types'; import { IAuthRequest } from '../unleash-types';
import { createRequestSchema, createResponseSchema } from '../../openapi';
import { emptyResponse } from '../../openapi/spec/empty-response';
import { tagsSchema, TagsSchema } from '../../openapi/spec/tags-schema';
import { TagSchema } from '../../openapi/spec/tag-schema';
import { OpenApiService } from '../../services/openapi-service';
import {
tagWithVersionSchema,
TagWithVersionSchema,
} from '../../openapi/spec/tag-with-version-schema';
const version = 1; const version = 1;
@ -17,44 +26,147 @@ class TagController extends Controller {
private tagService: TagService; private tagService: TagService;
private openApiService: OpenApiService;
constructor( constructor(
config: IUnleashConfig, config: IUnleashConfig,
{ tagService }: Pick<IUnleashServices, 'tagService'>, {
tagService,
openApiService,
}: Pick<IUnleashServices, 'tagService' | 'openApiService'>,
) { ) {
super(config); super(config);
this.tagService = tagService; this.tagService = tagService;
this.openApiService = openApiService;
this.logger = config.getLogger('/admin-api/tag.js'); this.logger = config.getLogger('/admin-api/tag.js');
this.get('/', this.getTags); this.route({
this.post('/', this.createTag, UPDATE_FEATURE); method: 'get',
this.get('/:type', this.getTagsByType); path: '',
this.get('/:type/:value', this.getTag); handler: this.getTags,
this.delete('/:type/:value', this.deleteTag, UPDATE_FEATURE); permission: NONE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'getTags',
responses: { 200: createResponseSchema('tagsSchema') },
}),
],
});
this.route({
method: 'post',
path: '',
handler: this.createTag,
permission: UPDATE_FEATURE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'createTag',
responses: {
201: emptyResponse,
},
requestBody: createRequestSchema('tagSchema'),
}),
],
});
this.route({
method: 'get',
path: '/:type',
handler: this.getTagsByType,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'getTagsByType',
responses: {
200: createResponseSchema('tagsSchema'),
},
}),
],
});
this.route({
method: 'get',
path: '/:type/:value',
handler: this.getTag,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'getTag',
responses: {
200: createResponseSchema('tagWithVersionSchema'),
},
}),
],
});
this.route({
method: 'delete',
path: '/:type/:value',
handler: this.deleteTag,
acceptAnyContentType: true,
permission: UPDATE_FEATURE,
middleware: [
openApiService.validPath({
tags: ['admin'],
operationId: 'deleteTag',
responses: {
200: emptyResponse,
},
}),
],
});
} }
async getTags(req: Request, res: Response): Promise<void> { async getTags(req: Request, res: Response<TagsSchema>): Promise<void> {
const tags = await this.tagService.getTags(); const tags = await this.tagService.getTags();
res.json({ version, tags }); this.openApiService.respondWithValidation<TagsSchema>(
200,
res,
tagsSchema.$id,
{ version, tags },
);
} }
async getTagsByType(req: Request, res: Response): Promise<void> { async getTagsByType(
req: Request,
res: Response<TagsSchema>,
): Promise<void> {
const tags = await this.tagService.getTagsByType(req.params.type); const tags = await this.tagService.getTagsByType(req.params.type);
res.json({ version, tags }); this.openApiService.respondWithValidation<TagsSchema>(
200,
res,
tagsSchema.$id,
{ version, tags },
);
} }
async getTag(req: Request, res: Response): Promise<void> { async getTag(
req: Request<TagSchema>,
res: Response<TagWithVersionSchema>,
): Promise<void> {
const { type, value } = req.params; const { type, value } = req.params;
const tag = await this.tagService.getTag({ type, value }); const tag = await this.tagService.getTag({ type, value });
res.json({ version, tag }); this.openApiService.respondWithValidation<TagWithVersionSchema>(
200,
res,
tagWithVersionSchema.$id,
{ version, tag },
);
} }
async createTag(req: IAuthRequest, res: Response): Promise<void> { async createTag(
req: IAuthRequest<unknown, unknown, TagSchema>,
res: Response,
): Promise<void> {
const userName = extractUsername(req); const userName = extractUsername(req);
await this.tagService.createTag(req.body, userName); await this.tagService.createTag(req.body, userName);
res.status(201).end(); res.status(201).end();
} }
async deleteTag(req: IAuthRequest, res: Response): Promise<void> { async deleteTag(
req: IAuthRequest<TagSchema>,
res: Response,
): Promise<void> {
const { type, value } = req.params; const { type, value } = req.params;
const userName = extractUsername(req); const userName = extractUsername(req);
await this.tagService.deleteTag({ type, value }, userName); await this.tagService.deleteTag({ type, value }, userName);

View File

@ -62,13 +62,13 @@ test('Can create a tag', async () =>
app.request app.request
.post('/api/admin/tags') .post('/api/admin/tags')
.send({ .send({
id: 1,
value: 'TeamRed', value: 'TeamRed',
type: 'simple', type: 'simple',
}) })
.expect((res) => { .expect((res) => {
expect(res.status).toBe(201); expect(res.status).toBe(201);
})); }));
test('Can validate a tag', async () => test('Can validate a tag', async () =>
app.request app.request
.post('/api/admin/tags') .post('/api/admin/tags')
@ -79,11 +79,8 @@ test('Can validate a tag', async () =>
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(400) .expect(400)
.expect((res) => { .expect((res) => {
expect(res.body.details.length).toBe(2); expect(res.body.details.length).toBe(1);
expect(res.body.details[0].message).toBe( expect(res.body.details[0].message).toBe(
'"value" must be a string',
);
expect(res.body.details[1].message).toBe(
'"type" must be URL friendly', '"type" must be URL friendly',
); );
})); }));

View File

@ -950,6 +950,22 @@ Object {
], ],
"type": "object", "type": "object",
}, },
"tagWithVersionSchema": Object {
"additionalProperties": false,
"properties": Object {
"tag": Object {
"$ref": "#/components/schemas/tagSchema",
},
"version": Object {
"type": "integer",
},
},
"required": Array [
"version",
"tag",
],
"type": "object",
},
"tagsSchema": Object { "tagsSchema": Object {
"additionalProperties": false, "additionalProperties": false,
"properties": Object { "properties": Object {
@ -3208,6 +3224,145 @@ Object {
], ],
}, },
}, },
"/api/admin/tags": Object {
"get": Object {
"operationId": "getTags",
"responses": Object {
"200": Object {
"content": Object {
"application/json": Object {
"schema": Object {
"$ref": "#/components/schemas/tagsSchema",
},
},
},
"description": "tagsSchema",
},
},
"tags": Array [
"admin",
],
},
"post": Object {
"operationId": "createTag",
"requestBody": Object {
"content": Object {
"application/json": Object {
"schema": Object {
"$ref": "#/components/schemas/tagSchema",
},
},
},
"description": "tagSchema",
"required": true,
},
"responses": Object {
"201": Object {
"description": "emptyResponse",
},
},
"tags": Array [
"admin",
],
},
},
"/api/admin/tags/{type}": Object {
"get": Object {
"operationId": "getTagsByType",
"parameters": Array [
Object {
"in": "path",
"name": "type",
"required": true,
"schema": Object {
"type": "string",
},
},
],
"responses": Object {
"200": Object {
"content": Object {
"application/json": Object {
"schema": Object {
"$ref": "#/components/schemas/tagsSchema",
},
},
},
"description": "tagsSchema",
},
},
"tags": Array [
"admin",
],
},
},
"/api/admin/tags/{type}/{value}": Object {
"delete": Object {
"operationId": "deleteTag",
"parameters": Array [
Object {
"in": "path",
"name": "type",
"required": true,
"schema": Object {
"type": "string",
},
},
Object {
"in": "path",
"name": "value",
"required": true,
"schema": Object {
"type": "string",
},
},
],
"responses": Object {
"200": Object {
"description": "emptyResponse",
},
},
"tags": Array [
"admin",
],
},
"get": Object {
"operationId": "getTag",
"parameters": Array [
Object {
"in": "path",
"name": "type",
"required": true,
"schema": Object {
"type": "string",
},
},
Object {
"in": "path",
"name": "value",
"required": true,
"schema": Object {
"type": "string",
},
},
],
"responses": Object {
"200": Object {
"content": Object {
"application/json": Object {
"schema": Object {
"$ref": "#/components/schemas/tagWithVersionSchema",
},
},
},
"description": "tagWithVersionSchema",
},
},
"tags": Array [
"admin",
],
},
},
"/api/admin/ui-config": Object { "/api/admin/ui-config": Object {
"get": Object { "get": Object {
"operationId": "getUIConfig", "operationId": "getUIConfig",