diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts index c14dee14f..c414c6fe3 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts @@ -23,7 +23,10 @@ describe('computeSchemaComponents', () => { fieldPhones: { properties: { additionalPhones: { - type: 'object', + type: 'array', + items: { + type: 'string', + }, }, primaryPhoneCountryCode: { type: 'string', @@ -41,7 +44,11 @@ describe('computeSchemaComponents', () => { type: 'string', }, additionalEmails: { - type: 'object', + type: 'array', + items: { + type: 'string', + format: 'email', + }, }, }, }, @@ -85,6 +92,7 @@ describe('computeSchemaComponents', () => { properties: { url: { type: 'string', + format: 'uri', }, label: { type: 'string', @@ -200,7 +208,10 @@ describe('computeSchemaComponents', () => { fieldPhones: { properties: { additionalPhones: { - type: 'object', + type: 'array', + items: { + type: 'string', + }, }, primaryPhoneCountryCode: { type: 'string', @@ -218,7 +229,11 @@ describe('computeSchemaComponents', () => { type: 'string', }, additionalEmails: { - type: 'object', + type: 'array', + items: { + type: 'string', + format: 'email', + }, }, }, }, @@ -262,6 +277,7 @@ describe('computeSchemaComponents', () => { properties: { url: { type: 'string', + format: 'uri', }, label: { type: 'string', @@ -376,7 +392,10 @@ describe('computeSchemaComponents', () => { fieldPhones: { properties: { additionalPhones: { - type: 'object', + type: 'array', + items: { + type: 'string', + }, }, primaryPhoneCountryCode: { type: 'string', @@ -394,7 +413,11 @@ describe('computeSchemaComponents', () => { type: 'string', }, additionalEmails: { - type: 'object', + type: 'array', + items: { + type: 'string', + format: 'email', + }, }, }, }, @@ -438,6 +461,7 @@ describe('computeSchemaComponents', () => { properties: { url: { type: 'string', + format: 'uri', }, label: { type: 'string', diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index 3abd55ca4..8b5d33589 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -1,7 +1,5 @@ import { OpenAPIV3_1 } from 'openapi-types'; -import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface'; - import { computeDepthParameters, computeEndingBeforeParameters, @@ -11,7 +9,6 @@ import { computeOrderByParameters, computeStartingAfterParameters, } from 'src/engine/core-modules/open-api/utils/parameters.utils'; -import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { FieldMetadataEntity, FieldMetadataType, @@ -41,18 +38,8 @@ const isFieldAvailable = (field: FieldMetadataEntity, forResponse: boolean) => { } }; -const getFieldProperties = ( - type: FieldMetadataType, - propertyName?: string, - options?: FieldMetadataOptions, -): Property => { +const getFieldProperties = (type: FieldMetadataType): Property => { switch (type) { - case FieldMetadataType.SELECT: - case FieldMetadataType.MULTI_SELECT: - return { - type: 'string', - enum: options?.map((option: { value: string }) => option.value), - }; case FieldMetadataType.UUID: return { type: 'string', format: 'uuid' }; case FieldMetadataType.TEXT: @@ -64,31 +51,12 @@ const getFieldProperties = ( return { type: 'string', format: 'date' }; case FieldMetadataType.NUMBER: return { type: 'integer' }; - case FieldMetadataType.RATING: - return { - type: 'string', - enum: options?.map((option: { value: string }) => option.value), - }; case FieldMetadataType.NUMERIC: case FieldMetadataType.POSITION: return { type: 'number' }; case FieldMetadataType.BOOLEAN: return { type: 'boolean' }; 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' }; default: @@ -147,32 +115,155 @@ const getSchemaComponentsProperties = ({ }; break; case FieldMetadataType.LINKS: - case FieldMetadataType.CURRENCY: - case FieldMetadataType.FULL_NAME: - case FieldMetadataType.ADDRESS: - case FieldMetadataType.ACTOR: - case FieldMetadataType.EMAILS: - case FieldMetadataType.PHONES: itemProperty = { type: 'object', - properties: compositeTypeDefinitions - .get(field.type) - ?.properties?.reduce((properties, property) => { - 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; - }, {} as Properties), + properties: { + primaryLinkLabel: { + type: 'string', + }, + primaryLinkUrl: { + type: 'string', + }, + secondaryLinks: { + type: 'array', + items: { + type: 'object', + description: 'A secondary link', + properties: { + url: { + type: 'string', + format: 'uri', + }, + label: { + type: 'string', + }, + }, + }, + }, + }, + }; + break; + case FieldMetadataType.CURRENCY: + itemProperty = { + type: 'object', + properties: { + amountMicros: { + type: 'number', + }, + currencyCode: { + type: 'string', + }, + }, + }; + break; + case FieldMetadataType.FULL_NAME: + itemProperty = { + type: 'object', + properties: { + firstName: { + type: 'string', + }, + lastName: { + type: 'string', + }, + }, + }; + break; + case FieldMetadataType.ADDRESS: + itemProperty = { + 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', + }, + }, + }; + break; + case FieldMetadataType.ACTOR: + itemProperty = { + type: 'object', + properties: { + source: { + type: 'string', + enum: [ + 'EMAIL', + 'CALENDAR', + 'WORKFLOW', + 'API', + 'IMPORT', + 'MANUAL', + 'SYSTEM', + ], + }, + ...(forResponse + ? { + workspaceMemberId: { + type: 'string', + format: 'uuid', + }, + name: { + type: 'string', + }, + } + : {}), + }, + }; + break; + case FieldMetadataType.EMAILS: + itemProperty = { + type: 'object', + properties: { + primaryEmail: { + type: 'string', + }, + additionalEmails: { + type: 'array', + items: { + type: 'string', + format: 'email', + }, + }, + }, + }; + break; + case FieldMetadataType.PHONES: + itemProperty = { + properties: { + additionalPhones: { + type: 'array', + items: { + type: 'string', + }, + }, + primaryPhoneCountryCode: { + type: 'string', + }, + primaryPhoneNumber: { + type: 'string', + }, + }, + type: 'object', }; break; default: @@ -401,22 +492,59 @@ export const computeMetadataSchemaComponents = ( return schemas; } case 'field': { - schemas[`${capitalize(item.nameSingular)}`] = { + const baseFieldProperties = ({ + withImmutableFields, + withRequiredFields, + }: { + withImmutableFields: boolean; + withRequiredFields: boolean; + }): OpenAPIV3_1.SchemaObject => ({ type: 'object', description: `A field`, properties: { - type: { - type: 'string', - enum: Object.keys(FieldMetadataType), - }, + ...(withImmutableFields + ? { + type: { + type: 'string', + enum: Object.keys(FieldMetadataType), + }, + objectMetadataId: { type: 'string', format: 'uuid' }, + } + : {}), name: { type: 'string' }, label: { type: 'string' }, description: { type: 'string' }, icon: { type: 'string' }, + defaultValue: {}, isNullable: { type: 'boolean' }, - objectMetadataId: { type: 'string', format: 'uuid' }, + settings: { type: 'object' }, + options: { + type: 'array', + description: 'For enum field types like SELECT or MULTI_SELECT', + items: { + type: 'object', + properties: { + color: { type: 'string' }, + label: { type: 'string' }, + value: { + type: 'string', + pattern: '^[A-Z0-9]+_[A-Z0-9]+$', + example: 'OPTION_1', + }, + position: { type: 'number' }, + }, + }, + }, }, - }; + ...(withRequiredFields + ? { required: ['type', 'name', 'label', 'objectMetadataId'] } + : {}), + }); + + schemas[`${capitalize(item.nameSingular)}`] = baseFieldProperties({ + withImmutableFields: true, + withRequiredFields: true, + }); schemas[`${capitalize(item.namePlural)}`] = { type: 'array', description: `A list of ${item.namePlural}`, @@ -424,38 +552,22 @@ export const computeMetadataSchemaComponents = ( $ref: `#/components/schemas/${capitalize(item.nameSingular)}`, }, }; - 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 Update`] = + baseFieldProperties({ + withImmutableFields: false, + withRequiredFields: false, + }); schemas[`${capitalize(item.nameSingular)} for Response`] = { - ...schemas[`${capitalize(item.nameSingular)}`], + ...baseFieldProperties({ + withImmutableFields: true, + withRequiredFields: false, + }), properties: { - type: { - type: 'string', - enum: Object.keys(FieldMetadataType), - }, - name: { type: 'string' }, - label: { type: 'string' }, - description: { type: 'string' }, - icon: { type: 'string' }, - isNullable: { type: 'boolean' }, + ...schemas[`${capitalize(item.nameSingular)}`].properties, 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: { diff --git a/packages/twenty-website/src/content/developers/backend-development/queue.mdx b/packages/twenty-website/src/content/developers/backend-development/queue.mdx index 5089ba18f..52d8c5d55 100644 --- a/packages/twenty-website/src/content/developers/backend-development/queue.mdx +++ b/packages/twenty-website/src/content/developers/backend-development/queue.mdx @@ -8,8 +8,8 @@ Queues facilitate async operations to be performed. They can be used for perform Each use case will have its own queue class extended from `MessageQueueServiceBase`. Currently, queue supports two drivers which can be configured by env variable `MESSAGE_QUEUE_TYPE`. -1. `pg-boss`: this is the default driver, which uses [pg-boss](https://github.com/timgit/pg-boss) under the hood. -2. `bull-mq`: this uses [bull-mq](https://bullmq.io/) under the hood. +1. `bull-mq`: this is the default driver, which uses [bull-mq](https://bullmq.io/) under the hood. +2. `pg-boss`: this uses [pg-boss](https://github.com/timgit/pg-boss) under the hood. ## Steps to create and use a new queue @@ -43,4 +43,4 @@ class CustomWorker { } ``` - \ No newline at end of file +