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

validation: fix _some_ 201 created location header endpoints

This commit is contained in:
Thomas Heartman 2022-09-13 20:23:51 +02:00
parent d0500b6c1a
commit fe07191c63
12 changed files with 114 additions and 30 deletions

View File

@ -4,6 +4,7 @@ import {
IContextField,
IContextFieldDto,
IContextFieldStore,
ILegalValue,
} from '../types/stores/context-field-store';
const COLUMNS = [
@ -16,7 +17,16 @@ const COLUMNS = [
];
const TABLE = 'context_fields';
const mapRow: (object) => IContextField = (row) => ({
type ContextFieldDB = {
name: string;
description: string;
stickiness: boolean;
sort_order: number;
legal_values: ILegalValue[];
created_at: Date;
};
const mapRow = (row: ContextFieldDB): IContextField => ({
name: row.name,
description: row.description,
stickiness: row.stickiness,
@ -88,15 +98,17 @@ class ContextFieldStore implements IContextFieldStore {
return present;
}
// TODO: write tests for the changes you made here?
async create(contextField: IContextFieldDto): Promise<IContextField> {
const row = await this.db(TABLE)
const [row] = await this.db(TABLE)
.insert(this.fieldToRow(contextField))
.returning('*');
return mapRow(row);
}
async update(data: IContextFieldDto): Promise<IContextField> {
const row = await this.db(TABLE)
const [row] = await this.db(TABLE)
.where({ name: data.name })
.update(this.fieldToRow(data))
.returning('*');

View File

@ -14,3 +14,27 @@ export const createResponseSchema = (
},
};
};
export const resourceCreatedResponseSchema = (
schemaName: string,
): OpenAPIV3.ResponseObject => {
return {
headers: {
location: {
description: 'The location of the newly created resource.',
schema: {
type: 'string',
format: 'uri',
},
},
},
description: `The resource was successfully created.`,
content: {
'application/json': {
schema: {
$ref: `#/components/schemas/${schemaName}`,
},
},
},
};
};

View File

@ -14,7 +14,7 @@ const ajv = new Ajv({
),
});
addFormats(ajv, ['date-time']);
addFormats(ajv, ['date-time', 'uri', 'uri-reference']);
// example was superseded by examples in openapi 3.1, but we're still on 3.0, so
// let's add it back in!

View File

@ -19,7 +19,10 @@ import { createApiToken } from '../../schema/api-token-schema';
import { OpenApiService } from '../../services/openapi-service';
import { IUnleashServices } from '../../types';
import { createRequestSchema } from '../../openapi/util/create-request-schema';
import { createResponseSchema } from '../../openapi/util/create-response-schema';
import {
createResponseSchema,
resourceCreatedResponseSchema,
} from '../../openapi/util/create-response-schema';
import {
apiTokensSchema,
ApiTokensSchema,
@ -96,7 +99,7 @@ export class ApiTokenController extends Controller {
operationId: 'createApiToken',
requestBody: createRequestSchema('createApiTokenSchema'),
responses: {
201: createResponseSchema('apiTokenSchema'),
201: resourceCreatedResponseSchema('apiTokenSchema'),
},
}),
],

View File

@ -24,7 +24,10 @@ import {
import { ContextFieldsSchema } from '../../openapi/spec/context-fields-schema';
import { UpsertContextFieldSchema } from '../../openapi/spec/upsert-context-field-schema';
import { createRequestSchema } from '../../openapi/util/create-request-schema';
import { createResponseSchema } from '../../openapi/util/create-response-schema';
import {
createResponseSchema,
resourceCreatedResponseSchema,
} from '../../openapi/util/create-response-schema';
import { serializeDates } from '../../types/serialize-dates';
import NotFoundError from '../../error/notfound-error';
import { NameSchema } from '../../openapi/spec/name-schema';
@ -98,7 +101,9 @@ export class ContextController extends Controller {
'upsertContextFieldSchema',
),
responses: {
201: emptyResponse,
201: resourceCreatedResponseSchema(
'contextFieldSchema',
),
},
}),
],
@ -189,13 +194,19 @@ export class ContextController extends Controller {
async createContextField(
req: IAuthRequest<void, void, UpsertContextFieldSchema>,
res: Response,
res: Response<ContextFieldSchema>,
): Promise<void> {
const value = req.body;
const userName = extractUsername(req);
await this.contextService.createContextField(value, userName);
res.status(201).end();
const result = await this.contextService.createContextField(
value,
userName,
);
res.status(201)
.header('location', `context/${result.name}`) // todo: how to ensure that the location is (and stays) correct?
.json(serializeDates(result))
.end();
}
async updateContextField(

View File

@ -25,7 +25,10 @@ import { TagsSchema } from '../../openapi/spec/tags-schema';
import { serializeDates } from '../../types/serialize-dates';
import { OpenApiService } from '../../services/openapi-service';
import { createRequestSchema } from '../../openapi/util/create-request-schema';
import { createResponseSchema } from '../../openapi/util/create-response-schema';
import {
createResponseSchema,
resourceCreatedResponseSchema,
} from '../../openapi/util/create-response-schema';
import { emptyResponse } from '../../openapi/util/standard-responses';
const version = 1;
@ -123,7 +126,9 @@ class FeatureController extends Controller {
tags: ['Features'],
operationId: 'addTag',
requestBody: createRequestSchema('tagSchema'),
responses: { 201: createResponseSchema('tagSchema') },
responses: {
201: resourceCreatedResponseSchema('tagSchema'),
},
}),
],
});
@ -221,7 +226,7 @@ class FeatureController extends Controller {
req.body,
userName,
);
res.status(201).json(tag);
res.status(201).header('location', `${featureName}/tags`).json(tag);
}
// TODO

View File

@ -15,7 +15,10 @@ import { IAuthRequest } from '../unleash-types';
import { OpenApiService } from '../../services/openapi-service';
import { emptyResponse } from '../../openapi/util/standard-responses';
import { createRequestSchema } from '../../openapi/util/create-request-schema';
import { createResponseSchema } from '../../openapi/util/create-response-schema';
import {
createResponseSchema,
resourceCreatedResponseSchema,
} from '../../openapi/util/create-response-schema';
import {
strategySchema,
StrategySchema,
@ -102,7 +105,9 @@ class StrategyController extends Controller {
tags: ['Strategies'],
operationId: 'createStrategy',
requestBody: createRequestSchema('upsertStrategySchema'),
responses: { 201: emptyResponse },
responses: {
201: resourceCreatedResponseSchema('strategySchema'),
},
}),
],
});
@ -193,12 +198,18 @@ class StrategyController extends Controller {
async createStrategy(
req: IAuthRequest<unknown, UpsertStrategySchema>,
res: Response<void>,
res: Response<StrategySchema>,
): Promise<void> {
const userName = extractUsername(req);
await this.strategyService.createStrategy(req.body, userName);
res.status(201).end();
const strategy = await this.strategyService.createStrategy(
req.body,
userName,
);
res.header('location', `strategies/${strategy.name}`)
.status(201)
.json(strategy)
.end();
}
async updateStrategy(

View File

@ -13,7 +13,10 @@ import TagTypeService from '../../services/tag-type-service';
import { Logger } from '../../logger';
import { IAuthRequest } from '../unleash-types';
import { createRequestSchema } from '../../openapi/util/create-request-schema';
import { createResponseSchema } from '../../openapi/util/create-response-schema';
import {
createResponseSchema,
resourceCreatedResponseSchema,
} from '../../openapi/util/create-response-schema';
import { TagTypesSchema } from '../../openapi/spec/tag-types-schema';
import { ValidateTagTypeSchema } from '../../openapi/spec/validate-tag-type-schema';
import {
@ -66,7 +69,9 @@ class TagTypeController extends Controller {
openApiService.validPath({
tags: ['Tags'],
operationId: 'createTagType',
responses: { 201: createResponseSchema('tagTypeSchema') },
responses: {
201: resourceCreatedResponseSchema('tagTypeSchema'),
},
requestBody: createRequestSchema('tagTypeSchema'),
}),
],

View File

@ -10,7 +10,10 @@ import { NONE, UPDATE_FEATURE } from '../../types/permissions';
import { extractUsername } from '../../util/extract-user';
import { IAuthRequest } from '../unleash-types';
import { createRequestSchema } from '../../openapi/util/create-request-schema';
import { createResponseSchema } from '../../openapi/util/create-response-schema';
import {
createResponseSchema,
resourceCreatedResponseSchema,
} from '../../openapi/util/create-response-schema';
import { tagsSchema, TagsSchema } from '../../openapi/spec/tags-schema';
import { TagSchema } from '../../openapi/spec/tag-schema';
import { OpenApiService } from '../../services/openapi-service';
@ -64,7 +67,9 @@ class TagController extends Controller {
tags: ['Tags'],
operationId: 'createTag',
responses: {
201: emptyResponse,
201: resourceCreatedResponseSchema(
'tagWithVersionSchema',
),
},
requestBody: createRequestSchema('tagSchema'),
}),
@ -157,11 +162,14 @@ class TagController extends Controller {
async createTag(
req: IAuthRequest<unknown, unknown, TagSchema>,
res: Response,
res: Response<TagWithVersionSchema>,
): Promise<void> {
const userName = extractUsername(req);
await this.tagService.createTag(req.body, userName);
res.status(201).end();
const tag = await this.tagService.createTag(req.body, userName);
res.status(201)
.header('location', `tags/${tag.type}/${tag.value}`)
.json({ version, tag })
.end();
}
async deleteTag(

View File

@ -55,18 +55,20 @@ class ContextService {
async createContextField(
value: IContextFieldDto,
userName: string,
): Promise<void> {
): Promise<IContextField> {
// validations
await this.validateUniqueName(value);
const contextField = await contextSchema.validateAsync(value);
// creations
await this.contextFieldStore.create(value);
const createdField = await this.contextFieldStore.create(value);
await this.eventStore.store({
type: CONTEXT_FIELD_CREATED,
createdBy: userName,
data: contextField,
});
return createdField;
}
async updateContextField(

View File

@ -101,7 +101,7 @@ class StrategyService {
async createStrategy(
value: IMinimalStrategy,
userName: string,
): Promise<void> {
): Promise<IStrategy> {
const strategy = await strategySchema.validateAsync(value);
strategy.deprecated = false;
await this._validateStrategyName(strategy);
@ -111,6 +111,7 @@ class StrategyService {
createdBy: userName,
data: strategy,
});
return this.strategyStore.get(strategy.name);
}
async updateStrategy(

View File

@ -52,7 +52,7 @@ export default class TagService {
return data;
}
async createTag(tag: ITag, userName: string): Promise<void> {
async createTag(tag: ITag, userName: string): Promise<ITag> {
const data = await this.validate(tag);
await this.tagStore.createTag(data);
await this.eventStore.store({
@ -60,6 +60,8 @@ export default class TagService {
createdBy: userName,
data,
});
return data;
}
async deleteTag(tag: ITag, userName: string): Promise<void> {