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:
parent
d0500b6c1a
commit
fe07191c63
@ -4,6 +4,7 @@ import {
|
|||||||
IContextField,
|
IContextField,
|
||||||
IContextFieldDto,
|
IContextFieldDto,
|
||||||
IContextFieldStore,
|
IContextFieldStore,
|
||||||
|
ILegalValue,
|
||||||
} from '../types/stores/context-field-store';
|
} from '../types/stores/context-field-store';
|
||||||
|
|
||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
@ -16,7 +17,16 @@ const COLUMNS = [
|
|||||||
];
|
];
|
||||||
const TABLE = 'context_fields';
|
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,
|
name: row.name,
|
||||||
description: row.description,
|
description: row.description,
|
||||||
stickiness: row.stickiness,
|
stickiness: row.stickiness,
|
||||||
@ -88,15 +98,17 @@ class ContextFieldStore implements IContextFieldStore {
|
|||||||
return present;
|
return present;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: write tests for the changes you made here?
|
||||||
async create(contextField: IContextFieldDto): Promise<IContextField> {
|
async create(contextField: IContextFieldDto): Promise<IContextField> {
|
||||||
const row = await this.db(TABLE)
|
const [row] = await this.db(TABLE)
|
||||||
.insert(this.fieldToRow(contextField))
|
.insert(this.fieldToRow(contextField))
|
||||||
.returning('*');
|
.returning('*');
|
||||||
|
|
||||||
return mapRow(row);
|
return mapRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(data: IContextFieldDto): Promise<IContextField> {
|
async update(data: IContextFieldDto): Promise<IContextField> {
|
||||||
const row = await this.db(TABLE)
|
const [row] = await this.db(TABLE)
|
||||||
.where({ name: data.name })
|
.where({ name: data.name })
|
||||||
.update(this.fieldToRow(data))
|
.update(this.fieldToRow(data))
|
||||||
.returning('*');
|
.returning('*');
|
||||||
|
@ -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}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -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
|
// example was superseded by examples in openapi 3.1, but we're still on 3.0, so
|
||||||
// let's add it back in!
|
// let's add it back in!
|
||||||
|
@ -19,7 +19,10 @@ import { createApiToken } from '../../schema/api-token-schema';
|
|||||||
import { OpenApiService } from '../../services/openapi-service';
|
import { OpenApiService } from '../../services/openapi-service';
|
||||||
import { IUnleashServices } from '../../types';
|
import { IUnleashServices } from '../../types';
|
||||||
import { createRequestSchema } from '../../openapi/util/create-request-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 {
|
import {
|
||||||
apiTokensSchema,
|
apiTokensSchema,
|
||||||
ApiTokensSchema,
|
ApiTokensSchema,
|
||||||
@ -96,7 +99,7 @@ export class ApiTokenController extends Controller {
|
|||||||
operationId: 'createApiToken',
|
operationId: 'createApiToken',
|
||||||
requestBody: createRequestSchema('createApiTokenSchema'),
|
requestBody: createRequestSchema('createApiTokenSchema'),
|
||||||
responses: {
|
responses: {
|
||||||
201: createResponseSchema('apiTokenSchema'),
|
201: resourceCreatedResponseSchema('apiTokenSchema'),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -24,7 +24,10 @@ import {
|
|||||||
import { ContextFieldsSchema } from '../../openapi/spec/context-fields-schema';
|
import { ContextFieldsSchema } from '../../openapi/spec/context-fields-schema';
|
||||||
import { UpsertContextFieldSchema } from '../../openapi/spec/upsert-context-field-schema';
|
import { UpsertContextFieldSchema } from '../../openapi/spec/upsert-context-field-schema';
|
||||||
import { createRequestSchema } from '../../openapi/util/create-request-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 { serializeDates } from '../../types/serialize-dates';
|
||||||
import NotFoundError from '../../error/notfound-error';
|
import NotFoundError from '../../error/notfound-error';
|
||||||
import { NameSchema } from '../../openapi/spec/name-schema';
|
import { NameSchema } from '../../openapi/spec/name-schema';
|
||||||
@ -98,7 +101,9 @@ export class ContextController extends Controller {
|
|||||||
'upsertContextFieldSchema',
|
'upsertContextFieldSchema',
|
||||||
),
|
),
|
||||||
responses: {
|
responses: {
|
||||||
201: emptyResponse,
|
201: resourceCreatedResponseSchema(
|
||||||
|
'contextFieldSchema',
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@ -189,13 +194,19 @@ export class ContextController extends Controller {
|
|||||||
|
|
||||||
async createContextField(
|
async createContextField(
|
||||||
req: IAuthRequest<void, void, UpsertContextFieldSchema>,
|
req: IAuthRequest<void, void, UpsertContextFieldSchema>,
|
||||||
res: Response,
|
res: Response<ContextFieldSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const value = req.body;
|
const value = req.body;
|
||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
|
|
||||||
await this.contextService.createContextField(value, userName);
|
const result = await this.contextService.createContextField(
|
||||||
res.status(201).end();
|
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(
|
async updateContextField(
|
||||||
|
@ -25,7 +25,10 @@ import { TagsSchema } from '../../openapi/spec/tags-schema';
|
|||||||
import { serializeDates } from '../../types/serialize-dates';
|
import { serializeDates } from '../../types/serialize-dates';
|
||||||
import { OpenApiService } from '../../services/openapi-service';
|
import { OpenApiService } from '../../services/openapi-service';
|
||||||
import { createRequestSchema } from '../../openapi/util/create-request-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 { emptyResponse } from '../../openapi/util/standard-responses';
|
import { emptyResponse } from '../../openapi/util/standard-responses';
|
||||||
|
|
||||||
const version = 1;
|
const version = 1;
|
||||||
@ -123,7 +126,9 @@ class FeatureController extends Controller {
|
|||||||
tags: ['Features'],
|
tags: ['Features'],
|
||||||
operationId: 'addTag',
|
operationId: 'addTag',
|
||||||
requestBody: createRequestSchema('tagSchema'),
|
requestBody: createRequestSchema('tagSchema'),
|
||||||
responses: { 201: createResponseSchema('tagSchema') },
|
responses: {
|
||||||
|
201: resourceCreatedResponseSchema('tagSchema'),
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -221,7 +226,7 @@ class FeatureController extends Controller {
|
|||||||
req.body,
|
req.body,
|
||||||
userName,
|
userName,
|
||||||
);
|
);
|
||||||
res.status(201).json(tag);
|
res.status(201).header('location', `${featureName}/tags`).json(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -15,7 +15,10 @@ import { IAuthRequest } from '../unleash-types';
|
|||||||
import { OpenApiService } from '../../services/openapi-service';
|
import { OpenApiService } from '../../services/openapi-service';
|
||||||
import { emptyResponse } from '../../openapi/util/standard-responses';
|
import { emptyResponse } from '../../openapi/util/standard-responses';
|
||||||
import { createRequestSchema } from '../../openapi/util/create-request-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 {
|
import {
|
||||||
strategySchema,
|
strategySchema,
|
||||||
StrategySchema,
|
StrategySchema,
|
||||||
@ -102,7 +105,9 @@ class StrategyController extends Controller {
|
|||||||
tags: ['Strategies'],
|
tags: ['Strategies'],
|
||||||
operationId: 'createStrategy',
|
operationId: 'createStrategy',
|
||||||
requestBody: createRequestSchema('upsertStrategySchema'),
|
requestBody: createRequestSchema('upsertStrategySchema'),
|
||||||
responses: { 201: emptyResponse },
|
responses: {
|
||||||
|
201: resourceCreatedResponseSchema('strategySchema'),
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -193,12 +198,18 @@ class StrategyController extends Controller {
|
|||||||
|
|
||||||
async createStrategy(
|
async createStrategy(
|
||||||
req: IAuthRequest<unknown, UpsertStrategySchema>,
|
req: IAuthRequest<unknown, UpsertStrategySchema>,
|
||||||
res: Response<void>,
|
res: Response<StrategySchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
|
|
||||||
await this.strategyService.createStrategy(req.body, userName);
|
const strategy = await this.strategyService.createStrategy(
|
||||||
res.status(201).end();
|
req.body,
|
||||||
|
userName,
|
||||||
|
);
|
||||||
|
res.header('location', `strategies/${strategy.name}`)
|
||||||
|
.status(201)
|
||||||
|
.json(strategy)
|
||||||
|
.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateStrategy(
|
async updateStrategy(
|
||||||
|
@ -13,7 +13,10 @@ import TagTypeService from '../../services/tag-type-service';
|
|||||||
import { Logger } from '../../logger';
|
import { Logger } from '../../logger';
|
||||||
import { IAuthRequest } from '../unleash-types';
|
import { IAuthRequest } from '../unleash-types';
|
||||||
import { createRequestSchema } from '../../openapi/util/create-request-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 { TagTypesSchema } from '../../openapi/spec/tag-types-schema';
|
import { TagTypesSchema } from '../../openapi/spec/tag-types-schema';
|
||||||
import { ValidateTagTypeSchema } from '../../openapi/spec/validate-tag-type-schema';
|
import { ValidateTagTypeSchema } from '../../openapi/spec/validate-tag-type-schema';
|
||||||
import {
|
import {
|
||||||
@ -66,7 +69,9 @@ class TagTypeController extends Controller {
|
|||||||
openApiService.validPath({
|
openApiService.validPath({
|
||||||
tags: ['Tags'],
|
tags: ['Tags'],
|
||||||
operationId: 'createTagType',
|
operationId: 'createTagType',
|
||||||
responses: { 201: createResponseSchema('tagTypeSchema') },
|
responses: {
|
||||||
|
201: resourceCreatedResponseSchema('tagTypeSchema'),
|
||||||
|
},
|
||||||
requestBody: createRequestSchema('tagTypeSchema'),
|
requestBody: createRequestSchema('tagTypeSchema'),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -10,7 +10,10 @@ 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 } from '../../openapi/util/create-request-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 { tagsSchema, TagsSchema } from '../../openapi/spec/tags-schema';
|
import { tagsSchema, TagsSchema } from '../../openapi/spec/tags-schema';
|
||||||
import { TagSchema } from '../../openapi/spec/tag-schema';
|
import { TagSchema } from '../../openapi/spec/tag-schema';
|
||||||
import { OpenApiService } from '../../services/openapi-service';
|
import { OpenApiService } from '../../services/openapi-service';
|
||||||
@ -64,7 +67,9 @@ class TagController extends Controller {
|
|||||||
tags: ['Tags'],
|
tags: ['Tags'],
|
||||||
operationId: 'createTag',
|
operationId: 'createTag',
|
||||||
responses: {
|
responses: {
|
||||||
201: emptyResponse,
|
201: resourceCreatedResponseSchema(
|
||||||
|
'tagWithVersionSchema',
|
||||||
|
),
|
||||||
},
|
},
|
||||||
requestBody: createRequestSchema('tagSchema'),
|
requestBody: createRequestSchema('tagSchema'),
|
||||||
}),
|
}),
|
||||||
@ -157,11 +162,14 @@ class TagController extends Controller {
|
|||||||
|
|
||||||
async createTag(
|
async createTag(
|
||||||
req: IAuthRequest<unknown, unknown, TagSchema>,
|
req: IAuthRequest<unknown, unknown, TagSchema>,
|
||||||
res: Response,
|
res: Response<TagWithVersionSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
await this.tagService.createTag(req.body, userName);
|
const tag = await this.tagService.createTag(req.body, userName);
|
||||||
res.status(201).end();
|
res.status(201)
|
||||||
|
.header('location', `tags/${tag.type}/${tag.value}`)
|
||||||
|
.json({ version, tag })
|
||||||
|
.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteTag(
|
async deleteTag(
|
||||||
|
@ -55,18 +55,20 @@ class ContextService {
|
|||||||
async createContextField(
|
async createContextField(
|
||||||
value: IContextFieldDto,
|
value: IContextFieldDto,
|
||||||
userName: string,
|
userName: string,
|
||||||
): Promise<void> {
|
): Promise<IContextField> {
|
||||||
// validations
|
// validations
|
||||||
await this.validateUniqueName(value);
|
await this.validateUniqueName(value);
|
||||||
const contextField = await contextSchema.validateAsync(value);
|
const contextField = await contextSchema.validateAsync(value);
|
||||||
|
|
||||||
// creations
|
// creations
|
||||||
await this.contextFieldStore.create(value);
|
const createdField = await this.contextFieldStore.create(value);
|
||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
type: CONTEXT_FIELD_CREATED,
|
type: CONTEXT_FIELD_CREATED,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data: contextField,
|
data: contextField,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return createdField;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateContextField(
|
async updateContextField(
|
||||||
|
@ -101,7 +101,7 @@ class StrategyService {
|
|||||||
async createStrategy(
|
async createStrategy(
|
||||||
value: IMinimalStrategy,
|
value: IMinimalStrategy,
|
||||||
userName: string,
|
userName: string,
|
||||||
): Promise<void> {
|
): Promise<IStrategy> {
|
||||||
const strategy = await strategySchema.validateAsync(value);
|
const strategy = await strategySchema.validateAsync(value);
|
||||||
strategy.deprecated = false;
|
strategy.deprecated = false;
|
||||||
await this._validateStrategyName(strategy);
|
await this._validateStrategyName(strategy);
|
||||||
@ -111,6 +111,7 @@ class StrategyService {
|
|||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data: strategy,
|
data: strategy,
|
||||||
});
|
});
|
||||||
|
return this.strategyStore.get(strategy.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateStrategy(
|
async updateStrategy(
|
||||||
|
@ -52,7 +52,7 @@ export default class TagService {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createTag(tag: ITag, userName: string): Promise<void> {
|
async createTag(tag: ITag, userName: string): Promise<ITag> {
|
||||||
const data = await this.validate(tag);
|
const data = await this.validate(tag);
|
||||||
await this.tagStore.createTag(data);
|
await this.tagStore.createTag(data);
|
||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
@ -60,6 +60,8 @@ export default class TagService {
|
|||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteTag(tag: ITag, userName: string): Promise<void> {
|
async deleteTag(tag: ITag, userName: string): Promise<void> {
|
||||||
|
Loading…
Reference in New Issue
Block a user