Fix unauthorized error handling (#6835)

from @BOHEUS comments in #6640
- fix bad 500 error when authentication invalid 
- remove "id", "createdAt", "updatedAt", etc from creation and update
paths schema
- improve error message 
- remove "id" from test body
- improve secondaryLink schema description
- improve depth parameter description
- remove required from response body
- improve examples
- improve error message formatting
- fix filter by position
- answered to negative float position @BOHEUS comment

Also:
- fix secondary field openapi field description
- remove schema display in playground

Screenshots

![image](https://github.com/user-attachments/assets/a5d52afd-ab10-49f3-8806-ee41b04bc775)

![image](https://github.com/user-attachments/assets/33f985bb-ff75-42f6-a0bb-741bd32a1d08)
This commit is contained in:
martmull
2024-09-04 17:25:59 +02:00
committed by GitHub
parent c1eae56bb6
commit c55dfbde6e
16 changed files with 863 additions and 340 deletions

View File

@ -156,7 +156,23 @@ const fieldRatingMock = {
name: 'fieldRating', name: 'fieldRating',
type: FieldMetadataType.RATING, type: FieldMetadataType.RATING,
isNullable: true, isNullable: true,
defaultValue: null, defaultValue: 'RATING_1',
options: [
{
id: '9a519a86-422b-4598-88ae-78751353f683',
color: 'red',
label: 'Opt 1',
value: 'RATING_1',
position: 0,
},
{
id: '33f28d51-bc82-4e1d-ae4b-d9e4c0ed0ab4',
color: 'purple',
label: 'Opt 2',
value: 'RATING_2',
position: 1,
},
],
}; };
const fieldPositionMock = { const fieldPositionMock = {

View File

@ -1,11 +1,13 @@
import { Controller, Post, Req, Res } from '@nestjs/common'; import { Controller, Post, Req, Res, UseGuards } from '@nestjs/common';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service'; import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service';
import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils'; import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils';
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
@Controller('rest/batch/*') @Controller('rest/batch/*')
@UseGuards(JwtAuthGuard)
export class RestApiCoreBatchController { export class RestApiCoreBatchController {
constructor(private readonly restApiCoreService: RestApiCoreService) {} constructor(private readonly restApiCoreService: RestApiCoreService) {}

View File

@ -7,14 +7,17 @@ import {
Put, Put,
Req, Req,
Res, Res,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service'; import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service';
import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils'; import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils';
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
@Controller('rest/*') @Controller('rest/*')
@UseGuards(JwtAuthGuard)
export class RestApiCoreController { export class RestApiCoreController {
constructor(private readonly restApiCoreService: RestApiCoreService) {} constructor(private readonly restApiCoreService: RestApiCoreService) {}

View File

@ -23,12 +23,16 @@ export const formatFieldValue = (
if (comparator === 'is') { if (comparator === 'is') {
return value; return value;
} }
if (fieldType === FieldMetadataType.NUMBER) { switch (fieldType) {
return parseInt(value); case FieldMetadataType.NUMERIC:
} return parseInt(value);
if (fieldType === FieldMetadataType.BOOLEAN) { case FieldMetadataType.NUMBER:
return value.toLowerCase() === 'true'; case FieldMetadataType.POSITION:
return parseFloat(value);
case FieldMetadataType.BOOLEAN:
return value.toLowerCase() === 'true';
} }
if ( if (
(value[0] === '"' || value[0] === "'") && (value[0] === '"' || value[0] === "'") &&
(value.charAt(value.length - 1) === '"' || (value.charAt(value.length - 1) === '"' ||

View File

@ -2,22 +2,34 @@ import { BadRequestException } from '@nestjs/common';
import { BaseGraphQLError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { BaseGraphQLError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
const formatMessage = (message: BaseGraphQLError) => { const formatMessage = (error: BaseGraphQLError) => {
if (message.extensions) { let formattedMessage = error.extensions
return message.extensions.response.message || message.extensions.response; ? error.extensions.response?.error ||
error.extensions.response ||
error.message
: error.error;
formattedMessage = formattedMessage
.replace(/"/g, "'")
.replace("Variable '$data' got i", 'I')
.replace("Variable '$input' got i", 'I');
const regex = /Field '[^']+' is not defined by type .*/;
const match = formattedMessage.match(regex);
if (match) {
formattedMessage = match[0];
} }
return message.message; return formattedMessage;
}; };
export class RestApiException extends BadRequestException { export class RestApiException extends BadRequestException {
constructor(errors: BaseGraphQLError[]) { constructor(errors: BaseGraphQLError[]) {
super({ super({
statusCode: 400, statusCode: 400,
message: messages: errors.map((error) => formatMessage(error)),
errors.length === 1
? formatMessage(errors[0])
: JSON.stringify(errors.map((error) => formatMessage(error))),
error: 'Bad Request', error: 'Bad Request',
}); });
} }

View File

@ -1,4 +1,28 @@
export const fetchMetadataFields = (objectNamePlural: string) => { export const fetchMetadataFields = (objectNamePlural: string) => {
const fromRelations = `
toObjectMetadata {
id
dataSourceId
nameSingular
namePlural
isSystem
isRemote
}
toFieldMetadataId
`;
const toRelations = `
fromObjectMetadata {
id
dataSourceId
nameSingular
namePlural
isSystem
isRemote
}
fromFieldMetadataId
`;
const fields = ` const fields = `
type type
name name
@ -14,26 +38,12 @@ export const fetchMetadataFields = (objectNamePlural: string) => {
fromRelationMetadata { fromRelationMetadata {
id id
relationType relationType
toObjectMetadata { ${fromRelations}
id
dataSourceId
nameSingular
namePlural
isSystem
}
toFieldMetadataId
} }
toRelationMetadata { toRelationMetadata {
id id
relationType relationType
fromObjectMetadata { ${toRelations}
id
dataSourceId
nameSingular
namePlural
isSystem
}
fromFieldMetadataId
} }
defaultValue defaultValue
options options
@ -69,25 +79,10 @@ export const fetchMetadataFields = (objectNamePlural: string) => {
return fields; return fields;
case 'relations': case 'relations':
return ` return `
id
relationType relationType
fromObjectMetadata { ${fromRelations}
id ${toRelations}
dataSourceId
nameSingular
namePlural
isSystem
}
fromObjectMetadataId
toObjectMetadata {
id
dataSourceId
nameSingular
namePlural
isSystem
}
toObjectMetadataId
fromFieldMetadataId
toFieldMetadataId
`; `;
} }
}; };

View File

@ -22,7 +22,10 @@ import {
computeManyResultPath, computeManyResultPath,
computeSingleResultPath, computeSingleResultPath,
} from 'src/engine/core-modules/open-api/utils/path.utils'; } from 'src/engine/core-modules/open-api/utils/path.utils';
import { getRequestBody } from 'src/engine/core-modules/open-api/utils/request-body.utils'; import {
getRequestBody,
getUpdateRequestBody,
} from 'src/engine/core-modules/open-api/utils/request-body.utils';
import { import {
getCreateOneResponse201, getCreateOneResponse201,
getDeleteResponse200, getDeleteResponse200,
@ -165,7 +168,7 @@ export class OpenApiService {
summary: `Find One ${item.nameSingular}`, summary: `Find One ${item.nameSingular}`,
parameters: [{ $ref: '#/components/parameters/idPath' }], parameters: [{ $ref: '#/components/parameters/idPath' }],
responses: { responses: {
'200': getFindOneResponse200(item, true), '200': getFindOneResponse200(item),
'400': { $ref: '#/components/responses/400' }, '400': { $ref: '#/components/responses/400' },
'401': { $ref: '#/components/responses/401' }, '401': { $ref: '#/components/responses/401' },
}, },
@ -187,7 +190,7 @@ export class OpenApiService {
summary: `Update One ${item.nameSingular}`, summary: `Update One ${item.nameSingular}`,
operationId: `updateOne${capitalize(item.nameSingular)}`, operationId: `updateOne${capitalize(item.nameSingular)}`,
parameters: [{ $ref: '#/components/parameters/idPath' }], parameters: [{ $ref: '#/components/parameters/idPath' }],
requestBody: getRequestBody(capitalize(item.nameSingular)), requestBody: getUpdateRequestBody(capitalize(item.nameSingular)),
responses: { responses: {
'200': getUpdateOneResponse200(item, true), '200': getUpdateOneResponse200(item, true),
'400': { $ref: '#/components/responses/400' }, '400': { $ref: '#/components/responses/400' },

View File

@ -22,9 +22,6 @@ describe('computeSchemaComponents', () => {
).toEqual({ ).toEqual({
ObjectName: { ObjectName: {
type: 'object', type: 'object',
description: undefined,
required: ['fieldNumber'],
example: { fieldNumber: '' },
properties: { properties: {
fieldUuid: { fieldUuid: {
type: 'string', type: 'string',
@ -40,9 +37,20 @@ describe('computeSchemaComponents', () => {
type: 'string', type: 'string',
format: 'email', format: 'email',
}, },
fieldEmails: {
type: 'object',
properties: {
primaryEmail: {
type: 'string',
},
additionalEmails: {
type: 'object',
},
},
},
fieldDateTime: { fieldDateTime: {
type: 'string', type: 'string',
format: 'date', format: 'date-time',
}, },
fieldDate: { fieldDate: {
type: 'string', type: 'string',
@ -58,21 +66,44 @@ describe('computeSchemaComponents', () => {
type: 'number', type: 'number',
}, },
fieldLinks: { fieldLinks: {
properties: {
primaryLinkLabel: { type: 'string' },
primaryLinkUrl: { type: 'string' },
secondaryLinks: { type: 'object' },
},
type: 'object', type: 'object',
properties: {
primaryLinkLabel: {
type: 'string',
},
primaryLinkUrl: {
type: 'string',
},
secondaryLinks: {
type: 'array',
items: {
type: 'object',
description: 'A secondary link',
properties: {
url: {
type: 'string',
},
label: {
type: 'string',
},
},
},
},
},
}, },
fieldCurrency: { fieldCurrency: {
properties: {
amountMicros: { type: 'number' },
currencyCode: { type: 'string' },
},
type: 'object', type: 'object',
properties: {
amountMicros: {
type: 'number',
},
currencyCode: {
type: 'string',
},
},
}, },
fieldFullName: { fieldFullName: {
type: 'object',
properties: { properties: {
firstName: { firstName: {
type: 'string', type: 'string',
@ -81,10 +112,10 @@ describe('computeSchemaComponents', () => {
type: 'string', type: 'string',
}, },
}, },
type: 'object',
}, },
fieldRating: { fieldRating: {
type: 'number', type: 'string',
enum: ['RATING_1', 'RATING_2'],
}, },
fieldSelect: { fieldSelect: {
type: 'string', type: 'string',
@ -98,10 +129,23 @@ describe('computeSchemaComponents', () => {
type: 'number', type: 'number',
}, },
fieldAddress: { fieldAddress: {
type: 'object',
properties: { properties: {
addressStreet1: {
type: 'string',
},
addressStreet2: {
type: 'string',
},
addressCity: { addressCity: {
type: 'string', type: 'string',
}, },
addressPostcode: {
type: 'string',
},
addressState: {
type: 'string',
},
addressCountry: { addressCountry: {
type: 'string', type: 'string',
}, },
@ -111,20 +155,7 @@ describe('computeSchemaComponents', () => {
addressLng: { addressLng: {
type: 'number', type: 'number',
}, },
addressPostcode: {
type: 'string',
},
addressState: {
type: 'string',
},
addressStreet1: {
type: 'string',
},
addressStreet2: {
type: 'string',
},
}, },
type: 'object',
}, },
fieldRawJson: { fieldRawJson: {
type: 'object', type: 'object',
@ -133,9 +164,341 @@ describe('computeSchemaComponents', () => {
type: 'string', type: 'string',
}, },
fieldActor: { fieldActor: {
type: 'object',
properties: { properties: {
source: { source: {
type: 'string', type: 'string',
enum: [
'EMAIL',
'CALENDAR',
'WORKFLOW',
'API',
'IMPORT',
'MANUAL',
],
},
},
},
},
required: ['fieldNumber'],
},
'ObjectName for Update': {
type: 'object',
properties: {
fieldUuid: {
type: 'string',
format: 'uuid',
},
fieldText: {
type: 'string',
},
fieldPhone: {
type: 'string',
},
fieldEmail: {
type: 'string',
format: 'email',
},
fieldEmails: {
type: 'object',
properties: {
primaryEmail: {
type: 'string',
},
additionalEmails: {
type: 'object',
},
},
},
fieldDateTime: {
type: 'string',
format: 'date-time',
},
fieldDate: {
type: 'string',
format: 'date',
},
fieldBoolean: {
type: 'boolean',
},
fieldNumber: {
type: 'integer',
},
fieldNumeric: {
type: 'number',
},
fieldLinks: {
type: 'object',
properties: {
primaryLinkLabel: {
type: 'string',
},
primaryLinkUrl: {
type: 'string',
},
secondaryLinks: {
type: 'array',
items: {
type: 'object',
description: 'A secondary link',
properties: {
url: {
type: 'string',
},
label: {
type: 'string',
},
},
},
},
},
},
fieldCurrency: {
type: 'object',
properties: {
amountMicros: {
type: 'number',
},
currencyCode: {
type: 'string',
},
},
},
fieldFullName: {
type: 'object',
properties: {
firstName: {
type: 'string',
},
lastName: {
type: 'string',
},
},
},
fieldRating: {
type: 'string',
enum: ['RATING_1', 'RATING_2'],
},
fieldSelect: {
type: 'string',
enum: ['OPTION_1', 'OPTION_2'],
},
fieldMultiSelect: {
type: 'string',
enum: ['OPTION_1', 'OPTION_2'],
},
fieldPosition: {
type: 'number',
},
fieldAddress: {
type: 'object',
properties: {
addressStreet1: {
type: 'string',
},
addressStreet2: {
type: 'string',
},
addressCity: {
type: 'string',
},
addressPostcode: {
type: 'string',
},
addressState: {
type: 'string',
},
addressCountry: {
type: 'string',
},
addressLat: {
type: 'number',
},
addressLng: {
type: 'number',
},
},
},
fieldRawJson: {
type: 'object',
},
fieldRichText: {
type: 'string',
},
fieldActor: {
type: 'object',
properties: {
source: {
type: 'string',
enum: [
'EMAIL',
'CALENDAR',
'WORKFLOW',
'API',
'IMPORT',
'MANUAL',
],
},
},
},
},
},
'ObjectName for Response': {
type: 'object',
properties: {
fieldUuid: {
type: 'string',
format: 'uuid',
},
fieldText: {
type: 'string',
},
fieldPhone: {
type: 'string',
},
fieldEmail: {
type: 'string',
format: 'email',
},
fieldEmails: {
type: 'object',
properties: {
primaryEmail: {
type: 'string',
},
additionalEmails: {
type: 'object',
},
},
},
fieldDateTime: {
type: 'string',
format: 'date-time',
},
fieldDate: {
type: 'string',
format: 'date',
},
fieldBoolean: {
type: 'boolean',
},
fieldNumber: {
type: 'integer',
},
fieldNumeric: {
type: 'number',
},
fieldLinks: {
type: 'object',
properties: {
primaryLinkLabel: {
type: 'string',
},
primaryLinkUrl: {
type: 'string',
},
secondaryLinks: {
type: 'array',
items: {
type: 'object',
description: 'A secondary link',
properties: {
url: {
type: 'string',
},
label: {
type: 'string',
},
},
},
},
},
},
fieldCurrency: {
type: 'object',
properties: {
amountMicros: {
type: 'number',
},
currencyCode: {
type: 'string',
},
},
},
fieldFullName: {
type: 'object',
properties: {
firstName: {
type: 'string',
},
lastName: {
type: 'string',
},
},
},
fieldRating: {
type: 'string',
enum: ['RATING_1', 'RATING_2'],
},
fieldSelect: {
type: 'string',
enum: ['OPTION_1', 'OPTION_2'],
},
fieldMultiSelect: {
type: 'string',
enum: ['OPTION_1', 'OPTION_2'],
},
fieldPosition: {
type: 'number',
},
fieldAddress: {
type: 'object',
properties: {
addressStreet1: {
type: 'string',
},
addressStreet2: {
type: 'string',
},
addressCity: {
type: 'string',
},
addressPostcode: {
type: 'string',
},
addressState: {
type: 'string',
},
addressCountry: {
type: 'string',
},
addressLat: {
type: 'number',
},
addressLng: {
type: 'number',
},
},
},
fieldRawJson: {
type: 'object',
},
fieldRichText: {
type: 'string',
},
fieldActor: {
type: 'object',
properties: {
source: {
type: 'string',
enum: [
'EMAIL',
'CALENDAR',
'WORKFLOW',
'API',
'IMPORT',
'MANUAL',
],
}, },
workspaceMemberId: { workspaceMemberId: {
type: 'string', type: 'string',
@ -145,44 +508,15 @@ describe('computeSchemaComponents', () => {
type: 'string', type: 'string',
}, },
}, },
type: 'object',
}, },
fieldEmails: { fieldRelation: {
properties: { type: 'array',
primaryEmail: { items: {
type: 'string', $ref: '#/components/schemas/ToObjectMetadataName for Response',
},
additionalEmails: {
type: 'object',
},
}, },
type: 'object',
}, },
}, },
}, },
'ObjectName with Relations': {
allOf: [
{
$ref: '#/components/schemas/ObjectName',
},
{
properties: {
fieldRelation: {
type: 'array',
items: {
$ref: '#/components/schemas/ToObjectMetadataName',
},
},
},
type: 'object',
},
],
description: undefined,
example: {
fieldNumber: '',
},
required: ['fieldNumber'],
},
}); });
}); });
}); });

View File

@ -64,7 +64,10 @@ describe('computeParameters', () => {
expect(computeDepthParameters()).toEqual({ expect(computeDepthParameters()).toEqual({
name: 'depth', name: 'depth',
in: 'query', in: 'query',
description: 'Limits the depth objects returned.', description: `Determines the level of nested related objects to include in the response.
- 0: Returns only the primary object's information.
- 1: Returns the primary object along with its directly related objects (with no additional nesting for related objects).
- 2: Returns the primary object, its directly related objects, and the related objects of those related objects.`,
required: false, required: false,
schema: { schema: {
type: 'integer', type: 'integer',

View File

@ -1,5 +1,7 @@
import { OpenAPIV3_1 } from 'openapi-types'; import { OpenAPIV3_1 } from 'openapi-types';
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
import { import {
computeDepthParameters, computeDepthParameters,
computeEndingBeforeParameters, computeEndingBeforeParameters,
@ -10,9 +12,13 @@ import {
computeStartingAfterParameters, computeStartingAfterParameters,
} from 'src/engine/core-modules/open-api/utils/parameters.utils'; } from 'src/engine/core-modules/open-api/utils/parameters.utils';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import {
FieldMetadataEntity,
FieldMetadataType,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { capitalize } from 'src/utils/capitalize'; import { capitalize } from 'src/utils/capitalize';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
type Property = OpenAPIV3_1.SchemaObject; type Property = OpenAPIV3_1.SchemaObject;
@ -20,8 +26,33 @@ type Properties = {
[name: string]: Property; [name: string]: Property;
}; };
const getFieldProperties = (type: FieldMetadataType): Property => { const isFieldAvailable = (field: FieldMetadataEntity, forResponse: boolean) => {
if (forResponse) {
return true;
}
switch (field.name) {
case 'id':
case 'createdAt':
case 'updatedAt':
case 'deletedAt':
return false;
default:
return true;
}
};
const getFieldProperties = (
type: FieldMetadataType,
propertyName?: string,
options?: FieldMetadataOptions,
): Property => {
switch (type) { switch (type) {
case FieldMetadataType.SELECT:
case FieldMetadataType.MULTI_SELECT:
return {
type: 'string',
enum: options?.map((option: { value: string }) => option.value),
};
case FieldMetadataType.UUID: case FieldMetadataType.UUID:
return { type: 'string', format: 'uuid' }; return { type: 'string', format: 'uuid' };
case FieldMetadataType.TEXT: case FieldMetadataType.TEXT:
@ -31,28 +62,55 @@ const getFieldProperties = (type: FieldMetadataType): Property => {
case FieldMetadataType.EMAIL: case FieldMetadataType.EMAIL:
return { type: 'string', format: 'email' }; return { type: 'string', format: 'email' };
case FieldMetadataType.DATE_TIME: case FieldMetadataType.DATE_TIME:
return { type: 'string', format: 'date-time' };
case FieldMetadataType.DATE: case FieldMetadataType.DATE:
return { type: 'string', format: 'date' }; return { type: 'string', format: 'date' };
case FieldMetadataType.NUMBER: case FieldMetadataType.NUMBER:
return { type: 'integer' }; return { type: 'integer' };
case FieldMetadataType.NUMERIC:
case FieldMetadataType.RATING: case FieldMetadataType.RATING:
return {
type: 'string',
enum: options?.map((option: { value: string }) => option.value),
};
case FieldMetadataType.NUMERIC:
case FieldMetadataType.POSITION: case FieldMetadataType.POSITION:
return { type: 'number' }; return { type: 'number' };
case FieldMetadataType.BOOLEAN: case FieldMetadataType.BOOLEAN:
return { type: 'boolean' }; return { type: 'boolean' };
case FieldMetadataType.RAW_JSON: case FieldMetadataType.RAW_JSON:
if (propertyName === 'secondaryLinks') {
return {
type: 'array',
items: {
type: 'object',
description: `A secondary link`,
properties: {
url: { type: 'string' },
label: { type: 'string' },
},
},
};
}
return { type: 'object' }; return { type: 'object' };
default: default:
return { type: 'string' }; return { type: 'string' };
} }
}; };
const getSchemaComponentsProperties = ( const getSchemaComponentsProperties = ({
item: ObjectMetadataEntity, item,
): Properties => { forResponse,
}: {
item: ObjectMetadataEntity;
forResponse: boolean;
}): Properties => {
return item.fields.reduce((node, field) => { return item.fields.reduce((node, field) => {
if (field.type == FieldMetadataType.RELATION) { if (
!isFieldAvailable(field, forResponse) ||
field.type === FieldMetadataType.RELATION
) {
return node; return node;
} }
@ -66,6 +124,12 @@ const getSchemaComponentsProperties = (
enum: field.options.map((option: { value: string }) => option.value), enum: field.options.map((option: { value: string }) => option.value),
}; };
break; break;
case FieldMetadataType.RATING:
itemProperty = {
type: 'string',
enum: field.options.map((option: { value: string }) => option.value),
};
break;
case FieldMetadataType.LINK: case FieldMetadataType.LINK:
case FieldMetadataType.LINKS: case FieldMetadataType.LINKS:
case FieldMetadataType.CURRENCY: case FieldMetadataType.CURRENCY:
@ -78,7 +142,18 @@ const getSchemaComponentsProperties = (
properties: compositeTypeDefinitions properties: compositeTypeDefinitions
.get(field.type) .get(field.type)
?.properties?.reduce((properties, property) => { ?.properties?.reduce((properties, property) => {
properties[property.name] = getFieldProperties(property.type); if (
property.hidden === true ||
(property.hidden === 'input' && !forResponse) ||
(property.hidden === 'output' && forResponse)
) {
return properties;
}
properties[property.name] = getFieldProperties(
property.type,
property.name,
property.options,
);
return properties; return properties;
}, {} as Properties), }, {} as Properties),
@ -105,19 +180,21 @@ const getSchemaComponentsRelationProperties = (
item: ObjectMetadataEntity, item: ObjectMetadataEntity,
): Properties => { ): Properties => {
return item.fields.reduce((node, field) => { return item.fields.reduce((node, field) => {
if (field.type !== FieldMetadataType.RELATION) {
return node;
}
let itemProperty = {} as Property; let itemProperty = {} as Property;
if (field.type == FieldMetadataType.RELATION) { if (field.fromRelationMetadata?.toObjectMetadata.nameSingular) {
if (field.fromRelationMetadata?.toObjectMetadata.nameSingular) { itemProperty = {
itemProperty = { type: 'array',
type: 'array', items: {
items: { $ref: `#/components/schemas/${capitalize(
$ref: `#/components/schemas/${capitalize( field.fromRelationMetadata?.toObjectMetadata.nameSingular,
field.fromRelationMetadata?.toObjectMetadata.nameSingular || '', )} for Response`,
)}`, },
}, };
};
}
} }
if (field.description) { if (field.description) {
@ -144,62 +221,38 @@ const getRequiredFields = (item: ObjectMetadataEntity): string[] => {
}, [] as string[]); }, [] as string[]);
}; };
const computeSchemaComponent = ( const computeSchemaComponent = ({
item: ObjectMetadataEntity, item,
): OpenAPIV3_1.SchemaObject => { withRequiredFields,
forResponse,
withRelations,
}: {
item: ObjectMetadataEntity;
withRequiredFields: boolean;
forResponse: boolean;
withRelations: boolean;
}): OpenAPIV3_1.SchemaObject => {
const result = { const result = {
type: 'object', type: 'object',
description: item.description, description: item.description,
properties: getSchemaComponentsProperties(item), properties: getSchemaComponentsProperties({ item, forResponse }),
example: {},
} as OpenAPIV3_1.SchemaObject; } as OpenAPIV3_1.SchemaObject;
const requiredFields = getRequiredFields(item); if (withRelations) {
result.properties = {
if (requiredFields?.length) { ...result.properties,
result.required = requiredFields; ...getSchemaComponentsRelationProperties(item),
result.example = requiredFields.reduce( };
(example, requiredField) => {
example[requiredField] = '';
return example;
},
{} as Record<string, string>,
);
} }
return result; if (!withRequiredFields) {
}; return result;
}
const computeRelationSchemaComponent = (
item: ObjectMetadataEntity,
): OpenAPIV3_1.SchemaObject => {
const result = {
description: item.description,
allOf: [
{
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`,
},
{
type: 'object',
properties: getSchemaComponentsRelationProperties(item),
},
],
example: {},
} as OpenAPIV3_1.SchemaObject;
const requiredFields = getRequiredFields(item); const requiredFields = getRequiredFields(item);
if (requiredFields?.length) { if (requiredFields?.length) {
result.required = requiredFields; result.required = requiredFields;
result.example = requiredFields.reduce(
(example, requiredField) => {
example[requiredField] = '';
return example;
},
{} as Record<string, string>,
);
} }
return result; return result;
@ -210,9 +263,26 @@ export const computeSchemaComponents = (
): Record<string, OpenAPIV3_1.SchemaObject> => { ): Record<string, OpenAPIV3_1.SchemaObject> => {
return objectMetadataItems.reduce( return objectMetadataItems.reduce(
(schemas, item) => { (schemas, item) => {
schemas[capitalize(item.nameSingular)] = computeSchemaComponent(item); schemas[capitalize(item.nameSingular)] = computeSchemaComponent({
schemas[capitalize(item.nameSingular) + ' with Relations'] = item,
computeRelationSchemaComponent(item); withRequiredFields: true,
forResponse: false,
withRelations: false,
});
schemas[capitalize(item.nameSingular) + ' for Update'] =
computeSchemaComponent({
item,
withRequiredFields: false,
forResponse: false,
withRelations: false,
});
schemas[capitalize(item.nameSingular) + ' for Response'] =
computeSchemaComponent({
item,
withRequiredFields: false,
forResponse: true,
withRelations: true,
});
return schemas; return schemas;
}, },
@ -245,21 +315,47 @@ export const computeMetadataSchemaComponents = (
type: 'object', type: 'object',
description: `An object`, description: `An object`,
properties: { properties: {
dataSourceId: { type: 'string' },
nameSingular: { type: 'string' }, nameSingular: { type: 'string' },
namePlural: { type: 'string' }, namePlural: { type: 'string' },
labelSingular: { type: 'string' }, labelSingular: { type: 'string' },
labelPlural: { type: 'string' }, labelPlural: { type: 'string' },
description: { type: 'string' }, description: { type: 'string' },
icon: { type: 'string' }, icon: { type: 'string' },
labelIdentifierFieldMetadataId: {
type: 'string',
format: 'uuid',
},
imageIdentifierFieldMetadataId: {
type: 'string',
format: 'uuid',
},
},
};
schemas[`${capitalize(item.namePlural)}`] = {
type: 'array',
description: `A list of ${item.namePlural}`,
items: {
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`,
},
};
schemas[`${capitalize(item.nameSingular)} for Update`] = {
type: 'object',
description: `An object`,
properties: {
isActive: { type: 'boolean' },
},
};
schemas[`${capitalize(item.nameSingular)} for Response`] = {
...schemas[`${capitalize(item.nameSingular)}`],
properties: {
...schemas[`${capitalize(item.nameSingular)}`].properties,
id: { type: 'string', format: 'uuid' },
dataSourceId: { type: 'string', format: 'uuid' },
isCustom: { type: 'boolean' }, isCustom: { type: 'boolean' },
isRemote: { type: 'boolean' },
isActive: { type: 'boolean' }, isActive: { type: 'boolean' },
isSystem: { type: 'boolean' }, isSystem: { type: 'boolean' },
createdAt: { type: 'string' }, createdAt: { type: 'string', format: 'date-time' },
updatedAt: { type: 'string' }, updatedAt: { type: 'string', format: 'date-time' },
labelIdentifierFieldMetadataId: { type: 'string' },
imageIdentifierFieldMetadataId: { type: 'string' },
fields: { fields: {
type: 'object', type: 'object',
properties: { properties: {
@ -269,7 +365,7 @@ export const computeMetadataSchemaComponents = (
node: { node: {
type: 'array', type: 'array',
items: { items: {
$ref: '#/components/schemas/Field', $ref: '#/components/schemas/Field for Response',
}, },
}, },
}, },
@ -277,15 +373,13 @@ export const computeMetadataSchemaComponents = (
}, },
}, },
}, },
example: {},
}; };
schemas[`${capitalize(item.namePlural)}`] = { schemas[`${capitalize(item.namePlural)} for Response`] = {
type: 'array', type: 'array',
description: `A list of ${item.namePlural}`, description: `A list of ${item.namePlural}`,
items: { items: {
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`, $ref: `#/components/schemas/${capitalize(item.nameSingular)} for Response`,
}, },
example: [{}],
}; };
return schemas; return schemas;
@ -295,57 +389,17 @@ export const computeMetadataSchemaComponents = (
type: 'object', type: 'object',
description: `A field`, description: `A field`,
properties: { properties: {
type: { type: 'string' }, type: {
type: 'string',
enum: Object.keys(FieldMetadataType),
},
name: { type: 'string' }, name: { type: 'string' },
label: { type: 'string' }, label: { type: 'string' },
description: { type: 'string' }, description: { type: 'string' },
icon: { type: 'string' }, icon: { type: 'string' },
isCustom: { type: 'boolean' },
isActive: { type: 'boolean' },
isSystem: { type: 'boolean' },
isNullable: { type: 'boolean' }, isNullable: { type: 'boolean' },
createdAt: { type: 'string' }, objectMetadataId: { type: 'string', format: 'uuid' },
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' },
}, },
example: {},
}; };
schemas[`${capitalize(item.namePlural)}`] = { schemas[`${capitalize(item.namePlural)}`] = {
type: 'array', type: 'array',
@ -353,7 +407,93 @@ export const computeMetadataSchemaComponents = (
items: { items: {
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`, $ref: `#/components/schemas/${capitalize(item.nameSingular)}`,
}, },
example: [{}], };
schemas[`${capitalize(item.nameSingular)} for Update`] = {
type: 'object',
description: `An object`,
properties: {
description: { type: 'string' },
icon: { type: 'string' },
isActive: { type: 'boolean' },
isCustom: { type: 'boolean' },
isNullable: { type: 'boolean' },
isSystem: { type: 'boolean' },
label: { type: 'string' },
name: { type: 'string' },
},
};
schemas[`${capitalize(item.nameSingular)} for Response`] = {
...schemas[`${capitalize(item.nameSingular)}`],
properties: {
type: {
type: 'string',
enum: Object.keys(FieldMetadataType),
},
name: { type: 'string' },
label: { type: 'string' },
description: { type: 'string' },
icon: { type: 'string' },
isNullable: { type: 'boolean' },
id: { type: 'string', format: 'uuid' },
isCustom: { type: 'boolean' },
isActive: { type: 'boolean' },
isSystem: { type: 'boolean' },
defaultValue: { type: 'object' },
options: { type: 'object' },
createdAt: { type: 'string', format: 'date-time' },
updatedAt: { type: 'string', format: 'date-time' },
fromRelationMetadata: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
relationType: {
type: 'string',
enum: Object.keys(RelationMetadataType),
},
toObjectMetadata: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
dataSourceId: { type: 'string', format: 'uuid' },
nameSingular: { type: 'string' },
namePlural: { type: 'string' },
isSystem: { type: 'boolean' },
isRemote: { type: 'boolean' },
},
},
toFieldMetadataId: { type: 'string', format: 'uuid' },
},
},
toRelationMetadata: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
relationType: {
type: 'string',
enum: Object.keys(RelationMetadataType),
},
fromObjectMetadata: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
dataSourceId: { type: 'string', format: 'uuid' },
nameSingular: { type: 'string' },
namePlural: { type: 'string' },
isSystem: { type: 'boolean' },
isRemote: { type: 'boolean' },
},
},
fromFieldMetadataId: { type: 'string', format: 'uuid' },
},
},
},
};
schemas[`${capitalize(item.namePlural)} for Response`] = {
type: 'array',
description: `A list of ${item.namePlural}`,
items: {
$ref: `#/components/schemas/${capitalize(item.nameSingular)} for Response`,
},
}; };
return schemas; return schemas;
@ -363,33 +503,17 @@ export const computeMetadataSchemaComponents = (
type: 'object', type: 'object',
description: 'A relation', description: 'A relation',
properties: { properties: {
relationType: { type: 'string' }, relationType: {
fromObjectMetadata: { type: 'string',
type: 'object', enum: Object.keys(RelationMetadataType),
properties: {
id: { type: 'string' },
dataSourceId: { type: 'string' },
nameSingular: { type: 'string' },
namePlural: { type: 'string' },
isSystem: { type: 'boolean' },
},
}, },
fromObjectMetadataId: { type: 'string' }, fromObjectMetadataId: { type: 'string', format: 'uuid' },
toObjectMetadata: { toObjectMetadataId: { type: 'string', format: 'uuid' },
type: 'object', fromName: { type: 'string' },
properties: { fromLabel: { type: 'string' },
id: { type: 'string' }, toName: { type: 'string' },
dataSourceId: { type: 'string' }, toLabel: { type: 'string' },
nameSingular: { type: 'string' },
namePlural: { type: 'string' },
isSystem: { type: 'boolean' },
},
},
toObjectMetadataId: { type: 'string' },
fromFieldMetadataId: { type: 'string' },
toFieldMetadataId: { type: 'string' },
}, },
example: {},
}; };
schemas[`${capitalize(item.namePlural)}`] = { schemas[`${capitalize(item.namePlural)}`] = {
type: 'array', type: 'array',
@ -397,7 +521,47 @@ export const computeMetadataSchemaComponents = (
items: { items: {
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`, $ref: `#/components/schemas/${capitalize(item.nameSingular)}`,
}, },
example: [{}], };
schemas[`${capitalize(item.nameSingular)} for Response`] = {
...schemas[`${capitalize(item.nameSingular)}`],
properties: {
relationType: {
type: 'string',
enum: Object.keys(RelationMetadataType),
},
id: { type: 'string', format: 'uuid' },
fromFieldMetadataId: { type: 'string', format: 'uuid' },
toFieldMetadataId: { type: 'string', format: 'uuid' },
fromObjectMetadata: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
dataSourceId: { type: 'string', format: 'uuid' },
nameSingular: { type: 'string' },
namePlural: { type: 'string' },
isSystem: { type: 'boolean' },
isRemote: { type: 'boolean' },
},
},
toObjectMetadata: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
dataSourceId: { type: 'string', format: 'uuid' },
nameSingular: { type: 'string' },
namePlural: { type: 'string' },
isSystem: { type: 'boolean' },
isRemote: { type: 'boolean' },
},
},
},
};
schemas[`${capitalize(item.namePlural)} for Response`] = {
type: 'array',
description: `A list of ${item.namePlural}`,
items: {
$ref: `#/components/schemas/${capitalize(item.nameSingular)} for Response`,
},
}; };
} }
} }

View File

@ -9,7 +9,7 @@ export const get400ErrorResponses = (): OpenAPIV3_1.ResponseObject => {
type: 'object', type: 'object',
properties: { properties: {
statusCode: { type: 'number' }, statusCode: { type: 'number' },
message: { type: 'string' }, messages: { type: 'array', items: { type: 'string' } },
error: { type: 'string' }, error: { type: 'string' },
}, },
example: { example: {

View File

@ -55,7 +55,10 @@ export const computeDepthParameters = (): OpenAPIV3_1.ParameterObject => {
return { return {
name: 'depth', name: 'depth',
in: 'query', in: 'query',
description: 'Limits the depth objects returned.', description: `Determines the level of nested related objects to include in the response.
- 0: Returns only the primary object's information.
- 1: Returns the primary object along with its directly related objects (with no additional nesting for related objects).
- 2: Returns the primary object, its directly related objects, and the related objects of those related objects.`,
required: false, required: false,
schema: { schema: {
type: 'integer', type: 'integer',

View File

@ -4,6 +4,7 @@ import {
getArrayRequestBody, getArrayRequestBody,
getFindDuplicatesRequestBody, getFindDuplicatesRequestBody,
getRequestBody, getRequestBody,
getUpdateRequestBody,
} from 'src/engine/core-modules/open-api/utils/request-body.utils'; } from 'src/engine/core-modules/open-api/utils/request-body.utils';
import { import {
getCreateManyResponse201, getCreateManyResponse201,
@ -113,7 +114,7 @@ export const computeSingleResultPath = (
{ $ref: '#/components/parameters/idPath' }, { $ref: '#/components/parameters/idPath' },
{ $ref: '#/components/parameters/depth' }, { $ref: '#/components/parameters/depth' },
], ],
requestBody: getRequestBody(capitalize(item.nameSingular)), requestBody: getUpdateRequestBody(capitalize(item.nameSingular)),
responses: { responses: {
'200': getUpdateOneResponse200(item), '200': getUpdateOneResponse200(item),
'400': { $ref: '#/components/responses/400' }, '400': { $ref: '#/components/responses/400' },

View File

@ -12,6 +12,20 @@ export const getRequestBody = (name: string) => {
}; };
}; };
export const getUpdateRequestBody = (name: string) => {
return {
description: 'body',
required: true,
content: {
'application/json': {
schema: {
$ref: `#/components/schemas/${name} for Update`,
},
},
},
};
};
export const getArrayRequestBody = (name: string) => { export const getArrayRequestBody = (name: string) => {
return { return {
required: true, required: true,
@ -45,10 +59,6 @@ export const getFindDuplicatesRequestBody = (name: string) => {
}, },
ids: { ids: {
type: 'array', type: 'array',
items: {
type: 'string',
format: 'uuid',
},
}, },
}, },
}, },

View File

@ -5,6 +5,10 @@ export const getFindManyResponse200 = (
item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>, item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
fromMetadata = false, fromMetadata = false,
) => { ) => {
const schemaRef = `#/components/schemas/${capitalize(
item.nameSingular,
)} for Response`;
return { return {
description: 'Successful operation', description: 'Successful operation',
content: { content: {
@ -18,9 +22,7 @@ export const getFindManyResponse200 = (
[item.namePlural]: { [item.namePlural]: {
type: 'array', type: 'array',
items: { items: {
$ref: `#/components/schemas/${capitalize( $ref: schemaRef,
item.nameSingular,
)}${!fromMetadata ? ' with Relations' : ''}`,
}, },
}, },
}, },
@ -29,8 +31,14 @@ export const getFindManyResponse200 = (
type: 'object', type: 'object',
properties: { properties: {
hasNextPage: { type: 'boolean' }, hasNextPage: { type: 'boolean' },
startCursor: { type: 'string' }, startCursor: {
endCursor: { type: 'string' }, type: 'string',
format: 'uuid',
},
endCursor: {
type: 'string',
format: 'uuid',
},
}, },
}, },
...(!fromMetadata && { ...(!fromMetadata && {
@ -39,21 +47,6 @@ export const getFindManyResponse200 = (
}, },
}), }),
}, },
example: {
data: {
[item.namePlural]: [
`${capitalize(item.nameSingular)}Object1`,
`${capitalize(item.nameSingular)}Object2`,
'...',
],
},
pageInfo: {
hasNextPage: true,
startCursor: '56f411fb-0900-4ffb-b942-d7e8d6709eff',
endCursor: '93adf3c6-6cf7-4a86-adcd-75f77857ba67',
},
totalCount: 132,
},
}, },
}, },
}, },
@ -62,8 +55,9 @@ export const getFindManyResponse200 = (
export const getFindOneResponse200 = ( export const getFindOneResponse200 = (
item: Pick<ObjectMetadataEntity, 'nameSingular'>, item: Pick<ObjectMetadataEntity, 'nameSingular'>,
fromMetadata = false,
) => { ) => {
const schemaRef = `#/components/schemas/${capitalize(item.nameSingular)} for Response`;
return { return {
description: 'Successful operation', description: 'Successful operation',
content: { content: {
@ -75,18 +69,11 @@ export const getFindOneResponse200 = (
type: 'object', type: 'object',
properties: { properties: {
[item.nameSingular]: { [item.nameSingular]: {
$ref: `#/components/schemas/${capitalize(item.nameSingular)}${ $ref: schemaRef,
!fromMetadata ? ' with Relations' : ''
}`,
}, },
}, },
}, },
}, },
example: {
data: {
[item.nameSingular]: `${capitalize(item.nameSingular)}Object`,
},
},
}, },
}, },
}, },
@ -98,6 +85,7 @@ export const getCreateOneResponse201 = (
fromMetadata = false, fromMetadata = false,
) => { ) => {
const one = fromMetadata ? 'One' : ''; const one = fromMetadata ? 'One' : '';
const schemaRef = `#/components/schemas/${capitalize(item.nameSingular)} for Response`;
return { return {
description: 'Successful operation', description: 'Successful operation',
@ -110,18 +98,11 @@ export const getCreateOneResponse201 = (
type: 'object', type: 'object',
properties: { properties: {
[`create${one}${capitalize(item.nameSingular)}`]: { [`create${one}${capitalize(item.nameSingular)}`]: {
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`, $ref: schemaRef,
}, },
}, },
}, },
}, },
example: {
data: {
[`create${one}${capitalize(item.nameSingular)}`]: `${capitalize(
item.nameSingular,
)}Object`,
},
},
}, },
}, },
}, },
@ -131,6 +112,10 @@ export const getCreateOneResponse201 = (
export const getCreateManyResponse201 = ( export const getCreateManyResponse201 = (
item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>, item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
) => { ) => {
const schemaRef = `#/components/schemas/${capitalize(
item.nameSingular,
)} for Response`;
return { return {
description: 'Successful operation', description: 'Successful operation',
content: { content: {
@ -144,23 +129,12 @@ export const getCreateManyResponse201 = (
[`create${capitalize(item.namePlural)}`]: { [`create${capitalize(item.namePlural)}`]: {
type: 'array', type: 'array',
items: { items: {
$ref: `#/components/schemas/${capitalize( $ref: schemaRef,
item.nameSingular,
)}`,
}, },
}, },
}, },
}, },
}, },
example: {
data: {
[`create${capitalize(item.namePlural)}`]: [
`${capitalize(item.nameSingular)}Object1`,
`${capitalize(item.nameSingular)}Object2`,
'...',
],
},
},
}, },
}, },
}, },
@ -172,6 +146,7 @@ export const getUpdateOneResponse200 = (
fromMetadata = false, fromMetadata = false,
) => { ) => {
const one = fromMetadata ? 'One' : ''; const one = fromMetadata ? 'One' : '';
const schemaRef = `#/components/schemas/${capitalize(item.nameSingular)} for Response`;
return { return {
description: 'Successful operation', description: 'Successful operation',
@ -184,18 +159,11 @@ export const getUpdateOneResponse200 = (
type: 'object', type: 'object',
properties: { properties: {
[`update${one}${capitalize(item.nameSingular)}`]: { [`update${one}${capitalize(item.nameSingular)}`]: {
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`, $ref: schemaRef,
}, },
}, },
}, },
}, },
example: {
data: {
[`update${one}${capitalize(item.nameSingular)}`]: `${capitalize(
item.nameSingular,
)}Object`,
},
},
}, },
}, },
}, },
@ -230,13 +198,6 @@ export const getDeleteResponse200 = (
}, },
}, },
}, },
example: {
data: {
[`delete${one}${capitalize(item.nameSingular)}`]: {
id: 'ffe75ac3-9786-4846-b56f-640685c3631e',
},
},
},
}, },
}, },
}, },
@ -302,6 +263,10 @@ export const getJsonResponse = () => {
export const getFindDuplicatesResponse200 = ( export const getFindDuplicatesResponse200 = (
item: Pick<ObjectMetadataEntity, 'nameSingular'>, item: Pick<ObjectMetadataEntity, 'nameSingular'>,
) => { ) => {
const schemaRef = `#/components/schemas/${capitalize(
item.nameSingular,
)} for Response`;
return { return {
description: 'Successful operation', description: 'Successful operation',
content: { content: {
@ -319,16 +284,20 @@ export const getFindDuplicatesResponse200 = (
type: 'object', type: 'object',
properties: { properties: {
hasNextPage: { type: 'boolean' }, hasNextPage: { type: 'boolean' },
startCursor: { type: 'string' }, startCursor: {
endCursor: { type: 'string' }, type: 'string',
format: 'uuid',
},
endCursor: {
type: 'string',
format: 'uuid',
},
}, },
}, },
companyDuplicates: { companyDuplicates: {
type: 'array', type: 'array',
items: { items: {
$ref: `#/components/schemas/${capitalize( $ref: schemaRef,
item.nameSingular,
)}`,
}, },
}, },
}, },

View File

@ -23,7 +23,11 @@ export const RestApiWrapper = ({ openApiJson }: { openApiJson: any }) => {
overflow: 'auto', overflow: 'auto',
}} }}
> >
<API apiDescriptionDocument={JSON.stringify(openApiJson)} router="hash" /> <API
apiDescriptionDocument={JSON.stringify(openApiJson)}
hideSchemas={true}
router="hash"
/>
</div> </div>
); );
}; };