1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-27 00:19:39 +01:00

refactor: add schemas to client application registration ()

This commit is contained in:
olav 2022-06-24 15:29:27 +02:00 committed by GitHub
parent 286b016b04
commit 5fff523670
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 253 additions and 10 deletions

View File

@ -79,6 +79,7 @@ import { emailSchema } from './spec/email-schema';
import { strategySchema } from './spec/strategy-schema';
import { strategiesSchema } from './spec/strategies-schema';
import { upsertStrategySchema } from './spec/upsert-strategy-schema';
import { clientApplicationSchema } from './spec/client-application-schema';
// All schemas in `openapi/spec` should be listed here.
export const schemas = {
@ -90,6 +91,7 @@ export const schemas = {
apiTokensSchema,
applicationSchema,
applicationsSchema,
clientApplicationSchema,
cloneFeatureSchema,
changePasswordSchema,
constraintSchema,

View File

@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`clientApplicationSchema no fields 1`] = `
Object {
"errors": Array [
Object {
"instancePath": "",
"keyword": "required",
"message": "must have required property 'appName'",
"params": Object {
"missingProperty": "appName",
},
"schemaPath": "#/required",
},
],
"schema": "#/components/schemas/clientApplicationSchema",
}
`;

View File

@ -0,0 +1,92 @@
import { validateSchema } from '../validate';
import { ClientApplicationSchema } from './client-application-schema';
test('clientApplicationSchema no fields', () => {
expect(
validateSchema('#/components/schemas/clientApplicationSchema', {}),
).toMatchSnapshot();
});
test('clientApplicationSchema required fields', () => {
const data: ClientApplicationSchema = {
appName: '',
interval: 0,
started: 0,
strategies: [''],
};
expect(
validateSchema('#/components/schemas/clientApplicationSchema', data),
).toBeUndefined();
});
test('clientApplicationSchema all fields', () => {
const data: ClientApplicationSchema = {
appName: '',
instanceId: '',
sdkVersion: '',
environment: '',
interval: 0,
started: 0,
strategies: [''],
};
expect(
validateSchema('#/components/schemas/clientApplicationSchema', data),
).toBeUndefined();
});
test('clientApplicationSchema go-sdk request', () => {
const json = `{
"appName": "x",
"instanceId": "y",
"sdkVersion": "unleash-client-go:3.3.1",
"strategies": [
"default",
"applicationHostname",
"gradualRolloutRandom",
"gradualRolloutSessionId",
"gradualRolloutUserId",
"remoteAddress",
"userWithId",
"flexibleRollout"
],
"started": "2022-06-24T09:59:12.822607943+02:00",
"interval": 1
}`;
expect(
validateSchema(
'#/components/schemas/clientApplicationSchema',
JSON.parse(json),
),
).toBeUndefined();
});
test('clientApplicationSchema node-sdk request', () => {
const json = `{
"appName": "unleash-test-node-appName2",
"instanceId": "unleash-test-node-instanceId",
"sdkVersion": "unleash-client-node:3.11.0",
"strategies": [
"p",
"default",
"applicationHostname",
"gradualRolloutRandom",
"gradualRolloutUserId",
"gradualRolloutSessionId",
"userWithId",
"remoteAddress",
"flexibleRollout"
],
"started": "2022-06-24T09:54:03.649Z",
"interval": 1000
}`;
expect(
validateSchema(
'#/components/schemas/clientApplicationSchema',
JSON.parse(json),
),
).toBeUndefined();
});

View File

@ -0,0 +1,41 @@
import { FromSchema } from 'json-schema-to-ts';
export const clientApplicationSchema = {
$id: '#/components/schemas/clientApplicationSchema',
type: 'object',
required: ['appName', 'interval', 'started', 'strategies'],
properties: {
appName: {
type: 'string',
},
instanceId: {
type: 'string',
},
sdkVersion: {
type: 'string',
},
environment: {
type: 'string',
},
interval: {
type: 'number',
},
started: {
oneOf: [
{ type: 'string', format: 'date-time' },
{ type: 'number' },
],
},
strategies: {
type: 'array',
items: {
type: 'string',
},
},
},
components: {},
} as const;
export type ClientApplicationSchema = FromSchema<
typeof clientApplicationSchema
>;

View File

@ -9,27 +9,47 @@ import { IClientApp } from '../../types/model';
import ApiUser from '../../types/api-user';
import { ALL } from '../../types/models/api-token';
import { NONE } from '../../types/permissions';
import { OpenApiService } from '../../services/openapi-service';
import { emptyResponse } from '../../openapi/spec/empty-response';
import { createRequestSchema } from '../../openapi';
import { ClientApplicationSchema } from '../../openapi/spec/client-application-schema';
export default class RegisterController extends Controller {
logger: Logger;
metrics: ClientInstanceService;
clientInstanceService: ClientInstanceService;
openApiService: OpenApiService;
constructor(
{
clientInstanceService,
}: Pick<IUnleashServices, 'clientInstanceService'>,
openApiService,
}: Pick<IUnleashServices, 'clientInstanceService' | 'openApiService'>,
config: IUnleashConfig,
) {
super(config);
this.logger = config.getLogger('/api/client/register');
this.metrics = clientInstanceService;
this.clientInstanceService = clientInstanceService;
this.openApiService = openApiService;
// NONE permission is not optimal here in terms of readability.
this.post('/', this.handleRegister, NONE);
this.route({
method: 'post',
path: '',
handler: this.registerClientApplication,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['client'],
operationId: 'registerClientApplication',
requestBody: createRequestSchema('clientApplicationSchema'),
responses: { 202: emptyResponse },
}),
],
});
}
private resolveEnvironment(user: User, data: IClientApp) {
private static resolveEnvironment(user: User, data: Partial<IClientApp>) {
if (user instanceof ApiUser) {
if (user.environment !== ALL) {
return user.environment;
@ -40,10 +60,13 @@ export default class RegisterController extends Controller {
return 'default';
}
async handleRegister(req: IAuthRequest, res: Response): Promise<void> {
async registerClientApplication(
req: IAuthRequest<unknown, void, ClientApplicationSchema>,
res: Response<void>,
): Promise<void> {
const { body: data, ip: clientIp, user } = req;
data.environment = this.resolveEnvironment(user, data);
await this.metrics.registerClient(data, clientIp);
data.environment = RegisterController.resolveEnvironment(user, data);
await this.clientInstanceService.registerClient(data, clientIp);
return res.status(202).end();
}
}

View File

@ -280,7 +280,7 @@ export interface IClientApp {
strategies?: string[] | Record<string, string>[];
bucket?: any;
count?: number;
started?: number | Date;
started?: string | number | Date;
interval?: number;
icon?: string;
description?: string;

View File

@ -309,6 +309,49 @@ Object {
],
"type": "object",
},
"clientApplicationSchema": Object {
"properties": Object {
"appName": Object {
"type": "string",
},
"environment": Object {
"type": "string",
},
"instanceId": Object {
"type": "string",
},
"interval": Object {
"type": "number",
},
"sdkVersion": Object {
"type": "string",
},
"started": Object {
"oneOf": Array [
Object {
"format": "date-time",
"type": "string",
},
Object {
"type": "number",
},
],
},
"strategies": Object {
"items": Object {
"type": "string",
},
"type": "array",
},
},
"required": Array [
"appName",
"interval",
"started",
"strategies",
],
"type": "object",
},
"cloneFeatureSchema": Object {
"properties": Object {
"name": Object {
@ -4908,6 +4951,30 @@ Object {
],
},
},
"/api/client/register": Object {
"post": Object {
"operationId": "registerClientApplication",
"requestBody": Object {
"content": Object {
"application/json": Object {
"schema": Object {
"$ref": "#/components/schemas/clientApplicationSchema",
},
},
},
"description": "clientApplicationSchema",
"required": true,
},
"responses": Object {
"202": Object {
"description": "emptyResponse",
},
},
"tags": Array [
"client",
],
},
},
"/auth/reset/password": Object {
"post": Object {
"operationId": "changePassword",