feat: wip server folder structure (#4573)
* feat: wip server folder structure * fix: merge * fix: wrong merge * fix: remove unused file * fix: comment * fix: lint * fix: merge * fix: remove console.log * fix: metadata graphql arguments broken
This commit is contained in:
@ -0,0 +1,30 @@
|
||||
import { Controller, Get, Req, Res } from '@nestjs/common';
|
||||
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
import { OpenApiService } from 'src/engine/core-modules/open-api/open-api.service';
|
||||
|
||||
@Controller('open-api')
|
||||
export class OpenApiController {
|
||||
constructor(private readonly openApiService: OpenApiService) {}
|
||||
|
||||
@Get('core')
|
||||
async generateOpenApiSchemaCore(
|
||||
@Req() request: Request,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
const data = await this.openApiService.generateCoreSchema(request);
|
||||
|
||||
res.send(data);
|
||||
}
|
||||
|
||||
@Get('metadata')
|
||||
async generateOpenApiSchemaMetaData(
|
||||
@Req() request: Request,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
const data = await this.openApiService.generateMetaDataSchema(request);
|
||||
|
||||
res.send(data);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { OpenApiController } from 'src/engine/core-modules/open-api/open-api.controller';
|
||||
import { OpenApiService } from 'src/engine/core-modules/open-api/open-api.service';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
|
||||
@Module({
|
||||
imports: [ObjectMetadataModule, AuthModule],
|
||||
controllers: [OpenApiController],
|
||||
providers: [OpenApiService],
|
||||
})
|
||||
export class OpenApiModule {}
|
||||
@ -0,0 +1,35 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { OpenApiService } from 'src/engine/core-modules/open-api/open-api.service';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
|
||||
describe('OpenApiService', () => {
|
||||
let service: OpenApiService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
OpenApiService,
|
||||
{
|
||||
provide: TokenService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: ObjectMetadataService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: EnvironmentService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<OpenApiService>(OpenApiService);
|
||||
});
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,199 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { baseSchema } from 'src/engine/core-modules/open-api/utils/base-schema.utils';
|
||||
import {
|
||||
computeManyResultPath,
|
||||
computeSingleResultPath,
|
||||
} from 'src/engine/core-modules/open-api/utils/path.utils';
|
||||
import { getErrorResponses } from 'src/engine/core-modules/open-api/utils/get-error-responses.utils';
|
||||
import {
|
||||
computeMetadataSchemaComponents,
|
||||
computeParameterComponents,
|
||||
computeSchemaComponents,
|
||||
} from 'src/engine/core-modules/open-api/utils/components.utils';
|
||||
import { computeSchemaTags } from 'src/engine/core-modules/open-api/utils/compute-schema-tags.utils';
|
||||
import { computeWebhooks } from 'src/engine/core-modules/open-api/utils/computeWebhooks.utils';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
import {
|
||||
getDeleteResponse200,
|
||||
getManyResultResponse200,
|
||||
getSingleResultSuccessResponse,
|
||||
} from 'src/engine/core-modules/open-api/utils/responses.utils';
|
||||
import { getRequestBody } from 'src/engine/core-modules/open-api/utils/request-body.utils';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { getServerUrl } from 'src/utils/get-server-url';
|
||||
|
||||
@Injectable()
|
||||
export class OpenApiService {
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
) {}
|
||||
|
||||
async generateCoreSchema(request: Request): Promise<OpenAPIV3_1.Document> {
|
||||
const baseUrl = getServerUrl(
|
||||
request,
|
||||
this.environmentService.get('SERVER_URL'),
|
||||
);
|
||||
|
||||
const schema = baseSchema('core', baseUrl);
|
||||
|
||||
let objectMetadataItems;
|
||||
|
||||
try {
|
||||
const { workspace } = await this.tokenService.validateToken(request);
|
||||
|
||||
objectMetadataItems =
|
||||
await this.objectMetadataService.findManyWithinWorkspace(workspace.id);
|
||||
} catch (err) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
if (!objectMetadataItems.length) {
|
||||
return schema;
|
||||
}
|
||||
schema.paths = objectMetadataItems.reduce((paths, item) => {
|
||||
paths[`/${item.namePlural}`] = computeManyResultPath(item);
|
||||
paths[`/${item.namePlural}/{id}`] = computeSingleResultPath(item);
|
||||
|
||||
return paths;
|
||||
}, schema.paths as OpenAPIV3_1.PathsObject);
|
||||
|
||||
schema.webhooks = objectMetadataItems.reduce(
|
||||
(paths, item) => {
|
||||
paths[`Create ${item.nameSingular}`] = computeWebhooks('create', item);
|
||||
paths[`Update ${item.nameSingular}`] = computeWebhooks('update', item);
|
||||
paths[`Delete ${item.nameSingular}`] = computeWebhooks('delete', item);
|
||||
|
||||
return paths;
|
||||
},
|
||||
{} as Record<
|
||||
string,
|
||||
OpenAPIV3_1.PathItemObject | OpenAPIV3_1.ReferenceObject
|
||||
>,
|
||||
);
|
||||
|
||||
schema.tags = computeSchemaTags(objectMetadataItems);
|
||||
|
||||
schema.components = {
|
||||
...schema.components, // components.securitySchemes is defined in base Schema
|
||||
schemas: computeSchemaComponents(objectMetadataItems),
|
||||
parameters: computeParameterComponents(),
|
||||
responses: {
|
||||
'400': getErrorResponses('Invalid request'),
|
||||
'401': getErrorResponses('Unauthorized'),
|
||||
},
|
||||
};
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
async generateMetaDataSchema(
|
||||
request: Request,
|
||||
): Promise<OpenAPIV3_1.Document> {
|
||||
const baseUrl = getServerUrl(
|
||||
request,
|
||||
this.environmentService.get('SERVER_URL'),
|
||||
);
|
||||
|
||||
const schema = baseSchema('metadata', baseUrl);
|
||||
|
||||
schema.tags = [{ name: 'placeholder' }];
|
||||
|
||||
const metadata = [
|
||||
{
|
||||
nameSingular: 'object',
|
||||
namePlural: 'objects',
|
||||
},
|
||||
{
|
||||
nameSingular: 'field',
|
||||
namePlural: 'fields',
|
||||
},
|
||||
{
|
||||
nameSingular: 'relation',
|
||||
namePlural: 'relations',
|
||||
},
|
||||
];
|
||||
|
||||
schema.paths = metadata.reduce((path, item) => {
|
||||
path[`/${item.namePlural}`] = {
|
||||
get: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Find Many ${item.namePlural}`,
|
||||
parameters: [{ $ref: '#/components/parameters/filter' }],
|
||||
responses: {
|
||||
'200': getManyResultResponse200(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
post: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Create One ${item.nameSingular}`,
|
||||
operationId: `createOne${capitalize(item.nameSingular)}`,
|
||||
requestBody: getRequestBody(item),
|
||||
responses: {
|
||||
'200': getSingleResultSuccessResponse(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
path[`/${item.namePlural}/{id}`] = {
|
||||
get: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Find One ${item.nameSingular}`,
|
||||
parameters: [{ $ref: '#/components/parameters/idPath' }],
|
||||
responses: {
|
||||
'200': getSingleResultSuccessResponse(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Delete One ${item.nameSingular}`,
|
||||
operationId: `deleteOne${capitalize(item.nameSingular)}`,
|
||||
parameters: [{ $ref: '#/components/parameters/idPath' }],
|
||||
responses: {
|
||||
'200': getDeleteResponse200(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
put: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Update One ${item.namePlural}`,
|
||||
operationId: `updateOne${capitalize(item.nameSingular)}`,
|
||||
parameters: [{ $ref: '#/components/parameters/idPath' }],
|
||||
requestBody: getRequestBody(item),
|
||||
responses: {
|
||||
'200': getSingleResultSuccessResponse(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
|
||||
return path;
|
||||
}, schema.paths as OpenAPIV3_1.PathsObject);
|
||||
|
||||
schema.components = {
|
||||
...schema.components, // components.securitySchemes is defined in base Schema
|
||||
schemas: computeMetadataSchemaComponents(metadata),
|
||||
parameters: computeParameterComponents(),
|
||||
responses: {
|
||||
'400': getErrorResponses('Invalid request'),
|
||||
'401': getErrorResponses('Unauthorized'),
|
||||
},
|
||||
};
|
||||
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import { computeSchemaComponents } from 'src/engine/core-modules/open-api/utils/components.utils';
|
||||
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
describe('computeSchemaComponents', () => {
|
||||
it('should compute schema components', () => {
|
||||
expect(
|
||||
computeSchemaComponents([
|
||||
objectMetadataItemMock,
|
||||
] as ObjectMetadataEntity[]),
|
||||
).toEqual({
|
||||
ObjectName: {
|
||||
type: 'object',
|
||||
required: ['fieldNumber'],
|
||||
example: { fieldNumber: '' },
|
||||
properties: {
|
||||
fieldCurrency: {
|
||||
properties: {
|
||||
amountMicros: { type: 'string' },
|
||||
currencyCode: { type: 'string' },
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
fieldLink: {
|
||||
properties: {
|
||||
label: { type: 'string' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
fieldNumber: {
|
||||
type: 'number',
|
||||
},
|
||||
fieldString: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,136 @@
|
||||
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
|
||||
import {
|
||||
computeDepthParameters,
|
||||
computeFilterParameters,
|
||||
computeIdPathParameter,
|
||||
computeLastCursorParameters,
|
||||
computeLimitParameters,
|
||||
computeOrderByParameters,
|
||||
} from 'src/engine/core-modules/open-api/utils/parameters.utils';
|
||||
import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory';
|
||||
import { FilterComparators } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils';
|
||||
import { Conjunctions } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils';
|
||||
import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils';
|
||||
|
||||
describe('computeParameters', () => {
|
||||
describe('computeLimit', () => {
|
||||
it('should compute limit', () => {
|
||||
expect(computeLimitParameters()).toEqual({
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
description: 'Limits the number of objects returned.',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
minimum: 0,
|
||||
maximum: 60,
|
||||
default: 60,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('computeOrderBy', () => {
|
||||
it('should compute order by', () => {
|
||||
expect(computeOrderByParameters()).toEqual({
|
||||
name: 'order_by',
|
||||
in: 'query',
|
||||
description: `Sorts objects returned.
|
||||
Should have the following shape: **field_name_1,field_name_2[DIRECTION_2],...**
|
||||
Available directions are **${Object.values(OrderByDirection).join(
|
||||
'**, **',
|
||||
)}**.
|
||||
Default direction is **${DEFAULT_ORDER_DIRECTION}**`,
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
examples: {
|
||||
simple: {
|
||||
value: 'createdAt',
|
||||
summary: 'A simple order_by param',
|
||||
},
|
||||
complex: {
|
||||
value: `id[${OrderByDirection.AscNullsFirst}],createdAt[${OrderByDirection.DescNullsLast}]`,
|
||||
summary: 'A more complex order_by param',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('computeDepth', () => {
|
||||
it('should compute depth', () => {
|
||||
expect(computeDepthParameters()).toEqual({
|
||||
name: 'depth',
|
||||
in: 'query',
|
||||
description: 'Limits the depth objects returned.',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
enum: [1, 2],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('computeFilter', () => {
|
||||
it('should compute filters', () => {
|
||||
expect(computeFilterParameters()).toEqual({
|
||||
name: 'filter',
|
||||
in: 'query',
|
||||
description: `Filters objects returned.
|
||||
Should have the following shape: **field_1[COMPARATOR]:value_1,field_2[COMPARATOR]:value_2,...**
|
||||
Available comparators are **${Object.values(FilterComparators).join(
|
||||
'**, **',
|
||||
)}**.
|
||||
You can create more complex filters using conjunctions **${Object.values(
|
||||
Conjunctions,
|
||||
).join('**, **')}**.
|
||||
Default root conjunction is **${DEFAULT_CONJUNCTION}**.
|
||||
To filter **null** values use **field[is]:NULL** or **field[is]:NOT_NULL**
|
||||
To filter using **boolean** values use **field[eq]:true** or **field[eq]:false**`,
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
examples: {
|
||||
simple: {
|
||||
value: 'createdAt[gte]:"2023-01-01"',
|
||||
description: 'A simple filter param',
|
||||
},
|
||||
complex: {
|
||||
value:
|
||||
'or(createdAt[gte]:"2024-01-01",createdAt[lte]:"2023-01-01",not(id[is]:NULL))',
|
||||
description: 'A more complex filter param',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('computeLastCursor', () => {
|
||||
it('should compute last cursor', () => {
|
||||
expect(computeLastCursorParameters()).toEqual({
|
||||
name: 'last_cursor',
|
||||
in: 'query',
|
||||
description: 'Returns objects starting from a specific cursor.',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('computeIdPathParameter', () => {
|
||||
it('should compute id path param', () => {
|
||||
expect(computeIdPathParameter()).toEqual({
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
description: 'Object id.',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,53 @@
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
import { computeOpenApiPath } from 'src/engine/core-modules/open-api/utils/path.utils';
|
||||
|
||||
export const baseSchema = (
|
||||
schemaName: 'core' | 'metadata',
|
||||
serverUrl: string,
|
||||
): OpenAPIV3_1.Document => {
|
||||
return {
|
||||
openapi: '3.0.3',
|
||||
info: {
|
||||
title: 'Twenty Api',
|
||||
description: `This is a **Twenty REST/API** playground based on the **OpenAPI 3.0 specification**.`,
|
||||
termsOfService: 'https://github.com/twentyhq/twenty?tab=coc-ov-file',
|
||||
contact: {
|
||||
email: 'felix@twenty.com',
|
||||
},
|
||||
license: {
|
||||
name: 'AGPL-3.0',
|
||||
url: 'https://github.com/twentyhq/twenty?tab=AGPL-3.0-1-ov-file#readme',
|
||||
},
|
||||
version: '0.2.0',
|
||||
},
|
||||
// Testing purposes
|
||||
servers: [
|
||||
{
|
||||
url: `${serverUrl}/rest/${schemaName !== 'core' ? schemaName : ''}`,
|
||||
description: 'Production Development',
|
||||
},
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
description:
|
||||
'Enter the token with the `Bearer: ` prefix, e.g. "Bearer abcde12345".',
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
},
|
||||
],
|
||||
externalDocs: {
|
||||
description: 'Find out more about **Twenty**',
|
||||
url: 'https://twenty.com',
|
||||
},
|
||||
paths: { [`/open-api/${schemaName}`]: computeOpenApiPath(serverUrl) },
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,299 @@
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
import {
|
||||
computeDepthParameters,
|
||||
computeFilterParameters,
|
||||
computeIdPathParameter,
|
||||
computeLastCursorParameters,
|
||||
computeLimitParameters,
|
||||
computeOrderByParameters,
|
||||
} from 'src/engine/core-modules/open-api/utils/parameters.utils';
|
||||
|
||||
type Property = OpenAPIV3_1.SchemaObject;
|
||||
|
||||
type Properties = {
|
||||
[name: string]: Property;
|
||||
};
|
||||
|
||||
const getSchemaComponentsProperties = (
|
||||
item: ObjectMetadataEntity,
|
||||
): Properties => {
|
||||
return item.fields.reduce((node, field) => {
|
||||
let itemProperty = {} as Property;
|
||||
|
||||
switch (field.type) {
|
||||
case FieldMetadataType.UUID:
|
||||
case FieldMetadataType.TEXT:
|
||||
case FieldMetadataType.PHONE:
|
||||
case FieldMetadataType.EMAIL:
|
||||
case FieldMetadataType.DATE_TIME:
|
||||
itemProperty.type = 'string';
|
||||
break;
|
||||
case FieldMetadataType.NUMBER:
|
||||
case FieldMetadataType.NUMERIC:
|
||||
case FieldMetadataType.PROBABILITY:
|
||||
case FieldMetadataType.RATING:
|
||||
case FieldMetadataType.POSITION:
|
||||
itemProperty.type = 'number';
|
||||
break;
|
||||
case FieldMetadataType.BOOLEAN:
|
||||
itemProperty.type = 'boolean';
|
||||
break;
|
||||
case FieldMetadataType.RELATION:
|
||||
if (field.fromRelationMetadata?.toObjectMetadata.nameSingular) {
|
||||
itemProperty = {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: `#/components/schemas/${capitalize(
|
||||
field.fromRelationMetadata?.toObjectMetadata.nameSingular || '',
|
||||
)}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
break;
|
||||
case FieldMetadataType.LINK:
|
||||
case FieldMetadataType.CURRENCY:
|
||||
case FieldMetadataType.FULL_NAME:
|
||||
itemProperty = {
|
||||
type: 'object',
|
||||
properties: Object.keys(field.targetColumnMap).reduce(
|
||||
(properties, key) => {
|
||||
properties[key] = { type: 'string' };
|
||||
|
||||
return properties;
|
||||
},
|
||||
{} as Properties,
|
||||
),
|
||||
};
|
||||
break;
|
||||
case FieldMetadataType.JSON:
|
||||
type: 'object';
|
||||
break;
|
||||
default:
|
||||
itemProperty.type = 'string';
|
||||
break;
|
||||
}
|
||||
|
||||
if (field.description) {
|
||||
itemProperty.description = field.description;
|
||||
}
|
||||
|
||||
if (Object.keys(itemProperty).length) {
|
||||
node[field.name] = itemProperty;
|
||||
}
|
||||
|
||||
return node;
|
||||
}, {} as Properties);
|
||||
};
|
||||
|
||||
const getRequiredFields = (item: ObjectMetadataEntity): string[] => {
|
||||
return item.fields.reduce((required, field) => {
|
||||
if (!field.isNullable && field.defaultValue === null) {
|
||||
required.push(field.name);
|
||||
|
||||
return required;
|
||||
}
|
||||
|
||||
return required;
|
||||
}, [] as string[]);
|
||||
};
|
||||
|
||||
const computeSchemaComponent = (
|
||||
item: ObjectMetadataEntity,
|
||||
): OpenAPIV3_1.SchemaObject => {
|
||||
const result = {
|
||||
type: 'object',
|
||||
description: item.description,
|
||||
properties: getSchemaComponentsProperties(item),
|
||||
example: {},
|
||||
} as OpenAPIV3_1.SchemaObject;
|
||||
|
||||
const requiredFields = getRequiredFields(item);
|
||||
|
||||
if (requiredFields?.length) {
|
||||
result.required = requiredFields;
|
||||
result.example = requiredFields.reduce(
|
||||
(example, requiredField) => {
|
||||
example[requiredField] = '';
|
||||
|
||||
return example;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const computeSchemaComponents = (
|
||||
objectMetadataItems: ObjectMetadataEntity[],
|
||||
): Record<string, OpenAPIV3_1.SchemaObject> => {
|
||||
return objectMetadataItems.reduce(
|
||||
(schemas, item) => {
|
||||
schemas[capitalize(item.nameSingular)] = computeSchemaComponent(item);
|
||||
|
||||
return schemas;
|
||||
},
|
||||
{} as Record<string, OpenAPIV3_1.SchemaObject>,
|
||||
);
|
||||
};
|
||||
|
||||
export const computeParameterComponents = (): Record<
|
||||
string,
|
||||
OpenAPIV3_1.ParameterObject
|
||||
> => {
|
||||
return {
|
||||
idPath: computeIdPathParameter(),
|
||||
lastCursor: computeLastCursorParameters(),
|
||||
filter: computeFilterParameters(),
|
||||
depth: computeDepthParameters(),
|
||||
orderBy: computeOrderByParameters(),
|
||||
limit: computeLimitParameters(),
|
||||
};
|
||||
};
|
||||
|
||||
export const computeMetadataSchemaComponents = (
|
||||
metadataSchema: { nameSingular: string; namePlural: string }[],
|
||||
): Record<string, OpenAPIV3_1.SchemaObject> => {
|
||||
return metadataSchema.reduce(
|
||||
(schemas, item) => {
|
||||
switch (item.nameSingular) {
|
||||
case 'object': {
|
||||
schemas[`${capitalize(item.nameSingular)}`] = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
dataSourceId: { type: 'string' },
|
||||
nameSingular: { type: 'string' },
|
||||
namePlural: { type: 'string' },
|
||||
labelSingular: { type: 'string' },
|
||||
labelPlural: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
icon: { type: 'string' },
|
||||
isCustom: { type: 'boolean' },
|
||||
isActive: { type: 'boolean' },
|
||||
isSystem: { type: 'boolean' },
|
||||
createdAt: { type: 'string' },
|
||||
updatedAt: { type: 'string' },
|
||||
labelIdentifierFieldMetadataId: { type: 'string' },
|
||||
imageIdentifierFieldMetadataId: { type: 'string' },
|
||||
fields: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
edges: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
node: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/Field',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return schemas;
|
||||
}
|
||||
case 'field': {
|
||||
schemas[`${capitalize(item.nameSingular)}`] = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
label: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
icon: { type: 'string' },
|
||||
isCustom: { type: 'boolean' },
|
||||
isActive: { type: 'boolean' },
|
||||
isSystem: { type: 'boolean' },
|
||||
isNullable: { type: 'boolean' },
|
||||
createdAt: { type: 'string' },
|
||||
updatedAt: { type: 'string' },
|
||||
fromRelationMetadata: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
relationType: { type: 'string' },
|
||||
toObjectMetadata: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
dataSourceId: { type: 'string' },
|
||||
nameSingular: { type: 'string' },
|
||||
namePlural: { type: 'string' },
|
||||
isSystem: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
toFieldMetadataId: { type: 'string' },
|
||||
},
|
||||
},
|
||||
toRelationMetadata: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
relationType: { type: 'string' },
|
||||
fromObjectMetadata: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
dataSourceId: { type: 'string' },
|
||||
nameSingular: { type: 'string' },
|
||||
namePlural: { type: 'string' },
|
||||
isSystem: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
fromFieldMetadataId: { type: 'string' },
|
||||
},
|
||||
},
|
||||
defaultValue: { type: 'object' },
|
||||
options: { type: 'object' },
|
||||
},
|
||||
};
|
||||
|
||||
return schemas;
|
||||
}
|
||||
case 'relation': {
|
||||
schemas[`${capitalize(item.nameSingular)}`] = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
relationType: { type: 'string' },
|
||||
fromObjectMetadata: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
dataSourceId: { type: 'string' },
|
||||
nameSingular: { type: 'string' },
|
||||
namePlural: { type: 'string' },
|
||||
isSystem: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
fromObjectMetadataId: { type: 'string' },
|
||||
toObjectMetadata: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
dataSourceId: { type: 'string' },
|
||||
nameSingular: { type: 'string' },
|
||||
namePlural: { type: 'string' },
|
||||
isSystem: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
toObjectMetadataId: { type: 'string' },
|
||||
fromFieldMetadataId: { type: 'string' },
|
||||
toFieldMetadataId: { type: 'string' },
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return schemas;
|
||||
},
|
||||
{} as Record<string, OpenAPIV3_1.SchemaObject>,
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
export const computeSchemaTags = (
|
||||
items: ObjectMetadataEntity[],
|
||||
): OpenAPIV3_1.TagObject[] => {
|
||||
const results = [{ name: 'General', description: 'General requests' }];
|
||||
|
||||
items.forEach((item) => {
|
||||
results.push({
|
||||
name: item.namePlural,
|
||||
description: `Object \`${capitalize(item.namePlural)}\``,
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
@ -0,0 +1,74 @@
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
export const computeWebhooks = (
|
||||
type: 'create' | 'update' | 'delete',
|
||||
item: ObjectMetadataEntity,
|
||||
): OpenAPIV3_1.PathItemObject => {
|
||||
return {
|
||||
post: {
|
||||
tags: [item.nameSingular],
|
||||
security: [],
|
||||
requestBody: {
|
||||
description: `*${type}*.**${item.nameSingular}**, ***.**${item.nameSingular}**, ***.*****`,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
targetUrl: {
|
||||
type: 'string',
|
||||
example: 'https://example.com/incomingWebhook',
|
||||
},
|
||||
eventType: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
'*.*',
|
||||
'*.' + item.nameSingular,
|
||||
type + '.' + item.nameSingular,
|
||||
],
|
||||
},
|
||||
objectMetadata: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
example: '370985db-22d8-4463-8e5f-2271d30913bd',
|
||||
},
|
||||
nameSingular: {
|
||||
type: 'string',
|
||||
enum: [item.nameSingular],
|
||||
},
|
||||
},
|
||||
},
|
||||
workspaceId: {
|
||||
type: 'string',
|
||||
example: '872cfcf1-c79f-42bc-877d-5829f06eb3f9',
|
||||
},
|
||||
webhookId: {
|
||||
type: 'string',
|
||||
example: '90056586-1228-4e03-a507-70140aa85c05',
|
||||
},
|
||||
eventDate: {
|
||||
type: 'string',
|
||||
example: '2024-02-14T11:27:01.779Z',
|
||||
},
|
||||
record: {
|
||||
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description:
|
||||
'Return a 200 status to indicate that the data was received successfully',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
export const getErrorResponses = (
|
||||
description: string,
|
||||
): OpenAPIV3_1.ResponseObject => {
|
||||
return {
|
||||
description,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
error: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,121 @@
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
|
||||
import { FilterComparators } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils';
|
||||
import { Conjunctions } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils';
|
||||
import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory';
|
||||
import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils';
|
||||
|
||||
export const computeLimitParameters = (): OpenAPIV3_1.ParameterObject => {
|
||||
return {
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
description: 'Limits the number of objects returned.',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
minimum: 0,
|
||||
maximum: 60,
|
||||
default: 60,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const computeOrderByParameters = (): OpenAPIV3_1.ParameterObject => {
|
||||
return {
|
||||
name: 'order_by',
|
||||
in: 'query',
|
||||
description: `Sorts objects returned.
|
||||
Should have the following shape: **field_name_1,field_name_2[DIRECTION_2],...**
|
||||
Available directions are **${Object.values(OrderByDirection).join(
|
||||
'**, **',
|
||||
)}**.
|
||||
Default direction is **${DEFAULT_ORDER_DIRECTION}**`,
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
examples: {
|
||||
simple: {
|
||||
value: `createdAt`,
|
||||
summary: 'A simple order_by param',
|
||||
},
|
||||
complex: {
|
||||
value: `id[${OrderByDirection.AscNullsFirst}],createdAt[${OrderByDirection.DescNullsLast}]`,
|
||||
summary: 'A more complex order_by param',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const computeDepthParameters = (): OpenAPIV3_1.ParameterObject => {
|
||||
return {
|
||||
name: 'depth',
|
||||
in: 'query',
|
||||
description: 'Limits the depth objects returned.',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
enum: [1, 2],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const computeFilterParameters = (): OpenAPIV3_1.ParameterObject => {
|
||||
return {
|
||||
name: 'filter',
|
||||
in: 'query',
|
||||
description: `Filters objects returned.
|
||||
Should have the following shape: **field_1[COMPARATOR]:value_1,field_2[COMPARATOR]:value_2,...**
|
||||
Available comparators are **${Object.values(FilterComparators).join(
|
||||
'**, **',
|
||||
)}**.
|
||||
You can create more complex filters using conjunctions **${Object.values(
|
||||
Conjunctions,
|
||||
).join('**, **')}**.
|
||||
Default root conjunction is **${DEFAULT_CONJUNCTION}**.
|
||||
To filter **null** values use **field[is]:NULL** or **field[is]:NOT_NULL**
|
||||
To filter using **boolean** values use **field[eq]:true** or **field[eq]:false**`,
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
examples: {
|
||||
simple: {
|
||||
value: 'createdAt[gte]:"2023-01-01"',
|
||||
description: 'A simple filter param',
|
||||
},
|
||||
complex: {
|
||||
value:
|
||||
'or(createdAt[gte]:"2024-01-01",createdAt[lte]:"2023-01-01",not(id[is]:NULL))',
|
||||
description: 'A more complex filter param',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const computeLastCursorParameters = (): OpenAPIV3_1.ParameterObject => {
|
||||
return {
|
||||
name: 'last_cursor',
|
||||
in: 'query',
|
||||
description: 'Returns objects starting from a specific cursor.',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const computeIdPathParameter = (): OpenAPIV3_1.ParameterObject => {
|
||||
return {
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
description: 'Object id.',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,116 @@
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
getDeleteResponse200,
|
||||
getJsonResponse,
|
||||
getManyResultResponse200,
|
||||
getSingleResultSuccessResponse,
|
||||
} from 'src/engine/core-modules/open-api/utils/responses.utils';
|
||||
import { getRequestBody } from 'src/engine/core-modules/open-api/utils/request-body.utils';
|
||||
|
||||
export const computeManyResultPath = (
|
||||
item: ObjectMetadataEntity,
|
||||
): OpenAPIV3_1.PathItemObject => {
|
||||
return {
|
||||
get: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Find Many ${item.namePlural}`,
|
||||
description: `**order_by**, **filter**, **limit**, **depth** or **last_cursor** can be provided to request your **${item.namePlural}**`,
|
||||
operationId: `findMany${capitalize(item.namePlural)}`,
|
||||
parameters: [
|
||||
{ $ref: '#/components/parameters/orderBy' },
|
||||
{ $ref: '#/components/parameters/filter' },
|
||||
{ $ref: '#/components/parameters/limit' },
|
||||
{ $ref: '#/components/parameters/depth' },
|
||||
{ $ref: '#/components/parameters/lastCursor' },
|
||||
],
|
||||
responses: {
|
||||
'200': getManyResultResponse200(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
post: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Create One ${item.nameSingular}`,
|
||||
operationId: `createOne${capitalize(item.nameSingular)}`,
|
||||
parameters: [{ $ref: '#/components/parameters/depth' }],
|
||||
requestBody: getRequestBody(item),
|
||||
responses: {
|
||||
'201': getSingleResultSuccessResponse(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
};
|
||||
|
||||
export const computeSingleResultPath = (
|
||||
item: ObjectMetadataEntity,
|
||||
): OpenAPIV3_1.PathItemObject => {
|
||||
return {
|
||||
get: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Find One ${item.nameSingular}`,
|
||||
description: `**depth** can be provided to request your **${item.nameSingular}**`,
|
||||
operationId: `findOne${capitalize(item.nameSingular)}`,
|
||||
parameters: [
|
||||
{ $ref: '#/components/parameters/idPath' },
|
||||
{ $ref: '#/components/parameters/depth' },
|
||||
],
|
||||
responses: {
|
||||
'200': getSingleResultSuccessResponse(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Delete One ${item.nameSingular}`,
|
||||
operationId: `deleteOne${capitalize(item.nameSingular)}`,
|
||||
parameters: [{ $ref: '#/components/parameters/idPath' }],
|
||||
responses: {
|
||||
'200': getDeleteResponse200(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
put: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Update One ${item.namePlural}`,
|
||||
operationId: `UpdateOne${capitalize(item.nameSingular)}`,
|
||||
parameters: [
|
||||
{ $ref: '#/components/parameters/idPath' },
|
||||
{ $ref: '#/components/parameters/depth' },
|
||||
],
|
||||
requestBody: getRequestBody(item),
|
||||
responses: {
|
||||
'200': getSingleResultSuccessResponse(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
};
|
||||
|
||||
export const computeOpenApiPath = (
|
||||
serverUrl: string,
|
||||
): OpenAPIV3_1.PathItemObject => {
|
||||
return {
|
||||
get: {
|
||||
tags: ['General'],
|
||||
summary: 'Get Open Api Schema',
|
||||
operationId: 'GetOpenApiSchema',
|
||||
servers: [
|
||||
{
|
||||
url: serverUrl,
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': getJsonResponse(),
|
||||
},
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
export const getRequestBody = (
|
||||
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
|
||||
) => {
|
||||
return {
|
||||
description: 'body',
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,152 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
export const getManyResultResponse200 = (
|
||||
item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
|
||||
) => {
|
||||
return {
|
||||
description: 'Successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[item.namePlural]: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: `#/components/schemas/${capitalize(
|
||||
item.nameSingular,
|
||||
)}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
example: {
|
||||
data: {
|
||||
[item.namePlural]: [
|
||||
`${capitalize(item.nameSingular)}Object`,
|
||||
'...',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getSingleResultSuccessResponse = (
|
||||
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
|
||||
) => {
|
||||
return {
|
||||
description: 'Successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[item.nameSingular]: {
|
||||
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getDeleteResponse200 = (
|
||||
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
|
||||
) => {
|
||||
return {
|
||||
description: 'Successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[item.nameSingular]: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getJsonResponse = () => {
|
||||
return {
|
||||
description: 'Successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
openapi: { type: 'string' },
|
||||
info: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
termsOfService: { type: 'string' },
|
||||
contact: {
|
||||
type: 'object',
|
||||
properties: { email: { type: 'string' } },
|
||||
},
|
||||
license: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: {
|
||||
type: 'array',
|
||||
items: {
|
||||
url: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
},
|
||||
},
|
||||
components: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
schemas: { type: 'object' },
|
||||
parameters: { type: 'object' },
|
||||
responses: { type: 'object' },
|
||||
},
|
||||
},
|
||||
paths: {
|
||||
type: 'object',
|
||||
},
|
||||
tags: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user