From 4c65460990782f8366e5f6b52f64e8c487504cda Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 2 Aug 2024 18:02:31 +0200 Subject: [PATCH 01/11] Fallback to default value when migrating value from enum --- .../workspace-migration-enum.service.ts | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts index afd381cb16c4..157139a99560 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts @@ -5,7 +5,6 @@ import { QueryRunner, TableColumn } from 'typeorm'; import { v4 } from 'uuid'; import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value'; -import { unserializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/unserialize-default-value'; import { WorkspaceMigrationColumnAlter, WorkspaceMigrationRenamedEnum, @@ -80,15 +79,30 @@ export class WorkspaceMigrationEnumService { }), ); - await this.migrateEnumValues( - queryRunner, - schemaName, - migrationColumn, - tableName, - oldColumnName, - enumValues, - renamedEnumValues, - ); + if (columnDefinition.isNullable) { + await this.migrateEnumValues( + queryRunner, + schemaName, + migrationColumn, + tableName, + oldColumnName, + enumValues, + renamedEnumValues, + ); + } else { + const defaultValue = columnDefinition.defaultValue?.replaceAll("'", ''); + + await this.migrateEnumValues( + queryRunner, + schemaName, + migrationColumn, + tableName, + oldColumnName, + enumValues, + renamedEnumValues, + defaultValue, + ); + } // Drop old column await queryRunner.query(` @@ -146,6 +160,7 @@ export class WorkspaceMigrationEnumService { oldColumnName: string, enumValues: string[], renamedEnumValues?: WorkspaceMigrationRenamedEnum[], + defaultValueFallback?: string, ) { const columnDefinition = migrationColumn.alteredColumnDefinition; @@ -176,9 +191,7 @@ export class WorkspaceMigrationEnumService { value: val, renamedEnumValues: renamedEnumValues, allEnumValues: enumValues, - defaultValueFallback: columnDefinition.isNullable - ? null - : unserializeDefaultValue(columnDefinition.defaultValue), + defaultValueFallback: defaultValueFallback, }); val = isDefined(migratedValue) ? `'${migratedValue}'` : null; From 1cf33baa29b68545101ee61aef16291f40a7a8e9 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 21 Aug 2024 16:36:58 +0200 Subject: [PATCH 02/11] Forbid naming custom fields like composite fields subfields --- .../field-metadata/field-metadata.service.ts | 25 ++++- .../validate-name-availability.spec.ts | 80 ++++++++++++++++ .../utils/validate-name-availability.utils.ts | 92 +++++++++++++++++++ 3 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-name-availability.spec.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index 6cb1cdebefad..f6a215bf4d53 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -40,6 +40,10 @@ import { NameTooLongException, validateMetadataNameOrThrow, } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; +import { + NameNotAvailableException, + validateNameAvailabilityOrThrow, +} from 'src/engine/metadata-modules/utils/validate-name-availability.utils'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.service'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; import { @@ -140,7 +144,10 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput); + this.validateFieldMetadataInput( + fieldMetadataInput, + objectMetadata.standardId, + ); const fieldAlreadyExists = await fieldMetadataRepository.findOne({ where: { @@ -355,7 +362,10 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput); + this.validateFieldMetadataInput( + fieldMetadataInput, + objectMetadata.standardId, + ); const updatableFieldInput = existingFieldMetadata.isCustom === false @@ -673,10 +683,14 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput: T): T { + >(fieldMetadataInput: T, objectMetadataStandardId: string | null): T { if (fieldMetadataInput.name) { try { validateMetadataNameOrThrow(fieldMetadataInput.name); + validateNameAvailabilityOrThrow( + fieldMetadataInput.name, + objectMetadataStandardId, + ); } catch (error) { if (error instanceof InvalidStringException) { throw new FieldMetadataException( @@ -688,6 +702,11 @@ export class FieldMetadataService extends TypeOrmQueryService { + describe('on person', () => { + const objectMetadataStandardId = STANDARD_OBJECT_IDS.person; + + it('does not throw if name is not reserved', () => { + const name = 'testName'; + + expect(() => + validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + ).not.toThrow(); + }); + it('throws error if name is reserved', () => { + const name = 'nameFirstName'; + + expect(() => + validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + ).toThrow(NameNotAvailableException); + }); + }); + describe('on company', () => { + const objectMetadataStandardId = STANDARD_OBJECT_IDS.company; + + it('does not throw if name is not reserved', () => { + const name = 'nameFirstName'; + + expect(() => + validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + ).not.toThrow(); + }); + it('throws error if name is reserved', () => { + const name = 'domainNamePrimaryLinkUrl'; + + expect(() => + validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + ).toThrow(NameNotAvailableException); + }); + }); + describe('on opportunity', () => { + const objectMetadataStandardId = STANDARD_OBJECT_IDS.opportunity; + + it('does not throw if name is not reserved', () => { + const name = 'nameFirstName'; + + expect(() => + validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + ).not.toThrow(); + }); + it('throws error if name is reserved', () => { + const name = 'annualRecurringRevenueAmountMicros'; + + expect(() => + validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + ).toThrow(NameNotAvailableException); + }); + }); + describe('on custom object', () => { + const objectMetadataStandardId = null; + + it('does not throw if name is not reserved', () => { + const name = 'nameFirstName'; + + expect(() => + validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + ).not.toThrow(); + }); + it('throws error if name is reserved', () => { + const name = 'createdByName'; + + expect(() => + validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + ).toThrow(NameNotAvailableException); + }); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts new file mode 100644 index 000000000000..6876d1ff023c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts @@ -0,0 +1,92 @@ +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; + +const createdByFieldsNames = [ + 'createdByName', + 'createdByWorkspaceMemberId', + 'createdBySource', +]; + +const linkedinLinkFieldsNames = [ + 'linkedinLinkPrimaryLinkLabel', + 'linkedinLinkPrimaryLinkUrl', + 'linkedinLinkPrimaryLinkLabel', +]; + +const xLinkFieldsNames = [ + 'xLinkPrimaryLinkLabel', + 'xLinkPrimaryLinkUrl', + 'xLinkSecondaryLinks', +]; + +const amountFieldsNames = [ + 'annualRecurringRevenueAmountMicros', + 'annualRecurringRevenueCurrencyCode', +]; + +const reservedCompositeFieldsNamesForPerson = [ + ...createdByFieldsNames, + ...linkedinLinkFieldsNames, + ...xLinkFieldsNames, + 'nameFirstName', + 'nameLastName', +]; +const reservedCompositeFieldsNamesForCompany = [ + ...createdByFieldsNames, + ...linkedinLinkFieldsNames, + ...xLinkFieldsNames, + ...amountFieldsNames, + 'domainNamePrimaryLinkLabel', + 'domainNamePrimaryLinkUrl', + 'domainNameSecondaryLinks', + 'addressAddressStreet1', + 'addressAddressStreet2', + 'addressAddressCity', + 'addressAddressState', + 'addressAddressPostcode', + 'addressAddressCountry', + 'addressAddressLat', + 'addressAddressLng', + 'introVideoPrimaryLinkLabel', + 'introVideoPrimaryLinkUrl', + 'introVideoSecondaryLinks', +]; +const reservedCompositeFieldsNamesForOpportunity = [ + ...createdByFieldsNames, + ...amountFieldsNames, +]; + +const getReservedCompositeFieldsNames = ( + objectMetadataStandardId: string | null, +) => { + switch (objectMetadataStandardId) { + case STANDARD_OBJECT_IDS.person: + return reservedCompositeFieldsNamesForPerson; + case STANDARD_OBJECT_IDS.company: + return reservedCompositeFieldsNamesForCompany; + case STANDARD_OBJECT_IDS.opportunity: + return reservedCompositeFieldsNamesForOpportunity; + default: + return createdByFieldsNames; + } +}; + +export const validateNameAvailabilityOrThrow = ( + name: string, + objectMetadataStandardId: string | null, +) => { + const reservedCompositeFieldsNames = getReservedCompositeFieldsNames( + objectMetadataStandardId, + ); + + if (reservedCompositeFieldsNames.includes(name)) { + throw new NameNotAvailableException(name); + } +}; + +export class NameNotAvailableException extends Error { + constructor(name: string) { + const message = `Name "${name}" is not available`; + + super(message); + } +} From fd268787ec2177b92229ff04fa7959ca071394f3 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 21 Aug 2024 16:45:40 +0200 Subject: [PATCH 03/11] Revert "Fallback to default value when migrating value from enum" This reverts commit 4c65460990782f8366e5f6b52f64e8c487504cda. --- .../workspace-migration-enum.service.ts | 39 +++++++------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts index 157139a99560..afd381cb16c4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service.ts @@ -5,6 +5,7 @@ import { QueryRunner, TableColumn } from 'typeorm'; import { v4 } from 'uuid'; import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value'; +import { unserializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/unserialize-default-value'; import { WorkspaceMigrationColumnAlter, WorkspaceMigrationRenamedEnum, @@ -79,30 +80,15 @@ export class WorkspaceMigrationEnumService { }), ); - if (columnDefinition.isNullable) { - await this.migrateEnumValues( - queryRunner, - schemaName, - migrationColumn, - tableName, - oldColumnName, - enumValues, - renamedEnumValues, - ); - } else { - const defaultValue = columnDefinition.defaultValue?.replaceAll("'", ''); - - await this.migrateEnumValues( - queryRunner, - schemaName, - migrationColumn, - tableName, - oldColumnName, - enumValues, - renamedEnumValues, - defaultValue, - ); - } + await this.migrateEnumValues( + queryRunner, + schemaName, + migrationColumn, + tableName, + oldColumnName, + enumValues, + renamedEnumValues, + ); // Drop old column await queryRunner.query(` @@ -160,7 +146,6 @@ export class WorkspaceMigrationEnumService { oldColumnName: string, enumValues: string[], renamedEnumValues?: WorkspaceMigrationRenamedEnum[], - defaultValueFallback?: string, ) { const columnDefinition = migrationColumn.alteredColumnDefinition; @@ -191,7 +176,9 @@ export class WorkspaceMigrationEnumService { value: val, renamedEnumValues: renamedEnumValues, allEnumValues: enumValues, - defaultValueFallback: defaultValueFallback, + defaultValueFallback: columnDefinition.isNullable + ? null + : unserializeDefaultValue(columnDefinition.defaultValue), }); val = isDefined(migratedValue) ? `'${migratedValue}'` : null; From ac0b4657d0e68479857d6e50e0a182b9e1b7cd3f Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 22 Aug 2024 12:03:08 +0200 Subject: [PATCH 04/11] Dynamically compute reserved composite fields subfields names --- .../__mocks__/object-metadata-item.mock.ts | 24 +++-- .../validate-name-availability.spec.ts | 82 +++++++--------- .../utils/validate-name-availability.utils.ts | 93 +++++-------------- 3 files changed, 66 insertions(+), 133 deletions(-) diff --git a/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts b/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts index 37ee2af4e7e4..382e2173f951 100644 --- a/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts +++ b/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts @@ -2,6 +2,12 @@ import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/com import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +export const FIELD_LINKS_MOCK_NAME = 'fieldLink'; +export const FIELD_CURRENCY_MOCK_NAME = 'fieldCurrency'; +export const FIELD_ADDRESS_MOCK_NAME = 'fieldAddress'; +export const FIELD_ACTOR_MOCK_NAME = 'fieldActor'; +export const FIELD_FULL_NAME_MOCK_NAME = 'fieldFullName'; + export const fieldNumberMock = { name: 'fieldNumber', type: FieldMetadataType.NUMBER, @@ -16,15 +22,8 @@ export const fieldTextMock = { defaultValue: null, }; -export const fieldLinkMock = { - name: 'fieldLink', - type: FieldMetadataType.LINK, - isNullable: false, - defaultValue: { label: '', url: '' }, -}; - export const fieldCurrencyMock = { - name: 'fieldCurrency', + name: FIELD_CURRENCY_MOCK_NAME, type: FieldMetadataType.CURRENCY, isNullable: true, defaultValue: { amountMicros: null, currencyCode: "''" }, @@ -89,7 +88,7 @@ export const fieldRelationMock = { }; const fieldLinksMock = { - name: 'fieldLinks', + name: FIELD_LINKS_MOCK_NAME, type: FieldMetadataType.LINKS, isNullable: false, defaultValue: [ @@ -147,7 +146,7 @@ const fieldNumericMock = { }; const fieldFullNameMock = { - name: 'fieldFullName', + name: FIELD_FULL_NAME_MOCK_NAME, type: FieldMetadataType.FULL_NAME, isNullable: true, defaultValue: { firstName: '', lastName: '' }, @@ -168,7 +167,7 @@ const fieldPositionMock = { }; const fieldAddressMock = { - name: 'fieldAddress', + name: FIELD_ADDRESS_MOCK_NAME, type: FieldMetadataType.ADDRESS, isNullable: true, defaultValue: { @@ -198,7 +197,7 @@ const fieldRichTextMock = { }; const fieldActorMock = { - name: 'fieldActor', + name: FIELD_ACTOR_MOCK_NAME, type: FieldMetadataType.ACTOR, isNullable: true, defaultValue: { @@ -217,7 +216,6 @@ export const fields = [ fieldBooleanMock, fieldNumberMock, fieldNumericMock, - fieldLinkMock, fieldLinksMock, fieldCurrencyMock, fieldFullNameMock, diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-name-availability.spec.ts b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-name-availability.spec.ts index e34aa459d8b5..ecbaec62eb26 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-name-availability.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-name-availability.spec.ts @@ -1,79 +1,61 @@ +import { + FIELD_ACTOR_MOCK_NAME, + FIELD_ADDRESS_MOCK_NAME, + FIELD_CURRENCY_MOCK_NAME, + FIELD_FULL_NAME_MOCK_NAME, + FIELD_LINKS_MOCK_NAME, + objectMetadataItemMock, +} from 'src/engine/api/__mocks__/object-metadata-item.mock'; import { NameNotAvailableException, validateNameAvailabilityOrThrow, } from 'src/engine/metadata-modules/utils/validate-name-availability.utils'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; describe('validateNameAvailabilityOrThrow', () => { - describe('on person', () => { - const objectMetadataStandardId = STANDARD_OBJECT_IDS.person; - - it('does not throw if name is not reserved', () => { - const name = 'testName'; + const objectMetadata = objectMetadataItemMock; - expect(() => - validateNameAvailabilityOrThrow(name, objectMetadataStandardId), - ).not.toThrow(); - }); - it('throws error if name is reserved', () => { - const name = 'nameFirstName'; + it('does not throw if name is not reserved', () => { + const name = 'testName'; - expect(() => - validateNameAvailabilityOrThrow(name, objectMetadataStandardId), - ).toThrow(NameNotAvailableException); - }); + expect(() => + validateNameAvailabilityOrThrow(name, objectMetadata), + ).not.toThrow(); }); - describe('on company', () => { - const objectMetadataStandardId = STANDARD_OBJECT_IDS.company; - - it('does not throw if name is not reserved', () => { - const name = 'nameFirstName'; - expect(() => - validateNameAvailabilityOrThrow(name, objectMetadataStandardId), - ).not.toThrow(); - }); - it('throws error if name is reserved', () => { - const name = 'domainNamePrimaryLinkUrl'; + describe('error cases', () => { + it('throws error with LINKS suffixes', () => { + const name = `${FIELD_LINKS_MOCK_NAME}PrimaryLinkLabel`; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + validateNameAvailabilityOrThrow(name, objectMetadata), ).toThrow(NameNotAvailableException); }); - }); - describe('on opportunity', () => { - const objectMetadataStandardId = STANDARD_OBJECT_IDS.opportunity; - - it('does not throw if name is not reserved', () => { - const name = 'nameFirstName'; + it('throws error with CURRENCY suffixes', () => { + const name = `${FIELD_CURRENCY_MOCK_NAME}AmountMicros`; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadataStandardId), - ).not.toThrow(); + validateNameAvailabilityOrThrow(name, objectMetadata), + ).toThrow(NameNotAvailableException); }); - it('throws error if name is reserved', () => { - const name = 'annualRecurringRevenueAmountMicros'; + it('throws error with FULL_NAME suffixes', () => { + const name = `${FIELD_FULL_NAME_MOCK_NAME}FirstName`; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + validateNameAvailabilityOrThrow(name, objectMetadata), ).toThrow(NameNotAvailableException); }); - }); - describe('on custom object', () => { - const objectMetadataStandardId = null; - - it('does not throw if name is not reserved', () => { - const name = 'nameFirstName'; + it('throws error with ACTOR suffixes', () => { + const name = `${FIELD_ACTOR_MOCK_NAME}Name`; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadataStandardId), - ).not.toThrow(); + validateNameAvailabilityOrThrow(name, objectMetadata), + ).toThrow(NameNotAvailableException); }); - it('throws error if name is reserved', () => { - const name = 'createdByName'; + it('throws error with ADDRESS suffixes', () => { + const name = `${FIELD_ADDRESS_MOCK_NAME}AddressStreet1`; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadataStandardId), + validateNameAvailabilityOrThrow(name, objectMetadata), ).toThrow(NameNotAvailableException); }); }); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts index 6876d1ff023c..4c1c8b315e7a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts @@ -1,82 +1,35 @@ -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; - -const createdByFieldsNames = [ - 'createdByName', - 'createdByWorkspaceMemberId', - 'createdBySource', -]; - -const linkedinLinkFieldsNames = [ - 'linkedinLinkPrimaryLinkLabel', - 'linkedinLinkPrimaryLinkUrl', - 'linkedinLinkPrimaryLinkLabel', -]; - -const xLinkFieldsNames = [ - 'xLinkPrimaryLinkLabel', - 'xLinkPrimaryLinkUrl', - 'xLinkSecondaryLinks', -]; - -const amountFieldsNames = [ - 'annualRecurringRevenueAmountMicros', - 'annualRecurringRevenueCurrencyCode', -]; - -const reservedCompositeFieldsNamesForPerson = [ - ...createdByFieldsNames, - ...linkedinLinkFieldsNames, - ...xLinkFieldsNames, - 'nameFirstName', - 'nameLastName', -]; -const reservedCompositeFieldsNamesForCompany = [ - ...createdByFieldsNames, - ...linkedinLinkFieldsNames, - ...xLinkFieldsNames, - ...amountFieldsNames, - 'domainNamePrimaryLinkLabel', - 'domainNamePrimaryLinkUrl', - 'domainNameSecondaryLinks', - 'addressAddressStreet1', - 'addressAddressStreet2', - 'addressAddressCity', - 'addressAddressState', - 'addressAddressPostcode', - 'addressAddressCountry', - 'addressAddressLat', - 'addressAddressLng', - 'introVideoPrimaryLinkLabel', - 'introVideoPrimaryLinkUrl', - 'introVideoSecondaryLinks', -]; -const reservedCompositeFieldsNamesForOpportunity = [ - ...createdByFieldsNames, - ...amountFieldsNames, -]; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; const getReservedCompositeFieldsNames = ( - objectMetadataStandardId: string | null, + objectMetadata: ObjectMetadataEntity, ) => { - switch (objectMetadataStandardId) { - case STANDARD_OBJECT_IDS.person: - return reservedCompositeFieldsNamesForPerson; - case STANDARD_OBJECT_IDS.company: - return reservedCompositeFieldsNamesForCompany; - case STANDARD_OBJECT_IDS.opportunity: - return reservedCompositeFieldsNamesForOpportunity; - default: - return createdByFieldsNames; + const reservedCompositeFieldsNames: string[] = []; + + for (const field of objectMetadata.fields) { + if (isCompositeFieldMetadataType(field.type)) { + const base = field.name; + const compositeType = compositeTypeDefinitions.get(field.type); + + compositeType?.properties.map((property) => + reservedCompositeFieldsNames.push( + computeCompositeColumnName(base, property), + ), + ) || []; + } } + + return reservedCompositeFieldsNames; }; export const validateNameAvailabilityOrThrow = ( name: string, - objectMetadataStandardId: string | null, + objectMetadata: ObjectMetadataEntity, ) => { - const reservedCompositeFieldsNames = getReservedCompositeFieldsNames( - objectMetadataStandardId, - ); + const reservedCompositeFieldsNames = + getReservedCompositeFieldsNames(objectMetadata); if (reservedCompositeFieldsNames.includes(name)) { throw new NameNotAvailableException(name); From 1cd1db6075289d3f9e21906e33a85631aedb8fbc Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 22 Aug 2024 12:03:16 +0200 Subject: [PATCH 05/11] Fix typo --- .../factories/args-alias.factory.ts | 4 +-- .../factories/field-alias.factory.ts | 6 ++-- .../type-definitions.generator.ts | 28 +++++++++---------- .../query-builder/utils/check-fields.utils.ts | 4 +-- .../utils/check-order-by.utils.ts | 6 ++-- .../open-api/utils/components.utils.ts | 4 +-- .../field-metadata/composite-types/index.ts | 2 +- .../field-metadata/field-metadata.service.ts | 13 +++++---- .../composite-column-action.factory.ts | 16 +++++------ .../factories/entity-schema-column.factory.ts | 6 ++-- .../repository/workspace.repository.ts | 6 ++-- .../services/database-structure.service.ts | 22 +++++++-------- .../services/field-metadata-health.service.ts | 24 ++++++++-------- 13 files changed, 71 insertions(+), 70 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/args-alias.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/args-alias.factory.ts index 5703a9747035..f5ccbf97515d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/args-alias.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/args-alias.factory.ts @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; @@ -52,7 +52,7 @@ export class ArgsAliasFactory { isCompositeFieldMetadataType(fieldMetadata.type) ) { // Get composite type definition - const compositeType = compositeTypeDefintions.get(fieldMetadata.type); + const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); if (!compositeType) { this.logger.error( diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/field-alias.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/field-alias.factory.ts index 67a22534f875..e50028109e6f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/field-alias.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/field-alias.factory.ts @@ -2,13 +2,13 @@ import { Injectable, Logger } from '@nestjs/common'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; -import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { createCompositeFieldKey } from 'src/engine/api/graphql/workspace-query-builder/utils/composite-field-metadata.util'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { computeColumnName, computeCompositeColumnName, } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; @Injectable() export class FieldAliasFactory { @@ -23,7 +23,7 @@ export class FieldAliasFactory { } // If it's a composite field, we need to get the definition - const compositeType = compositeTypeDefintions.get(fieldMetadata.type); + const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); if (!compositeType) { this.logger.error( diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/type-definitions.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/type-definitions.generator.ts index 142db1f7a686..57e959dfc34c 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/type-definitions.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/type-definitions.generator.ts @@ -1,27 +1,27 @@ import { Injectable, Logger } from '@nestjs/common'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; -import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; -import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory'; -import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory'; import { CompositeEnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-enum-type-definition.factory'; +import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory'; +import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory'; +import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; -import { TypeDefinitionsStorage } from './storages/type-definitions.storage'; -import { - ObjectTypeDefinitionFactory, - ObjectTypeDefinitionKind, -} from './factories/object-type-definition.factory'; +import { ConnectionTypeDefinitionFactory } from './factories/connection-type-definition.factory'; +import { EdgeTypeDefinitionFactory } from './factories/edge-type-definition.factory'; +import { ExtendObjectTypeDefinitionFactory } from './factories/extend-object-type-definition.factory'; import { InputTypeDefinitionFactory, InputTypeDefinitionKind, } from './factories/input-type-definition.factory'; +import { + ObjectTypeDefinitionFactory, + ObjectTypeDefinitionKind, +} from './factories/object-type-definition.factory'; import { WorkspaceBuildSchemaOptions } from './interfaces/workspace-build-schema-optionts.interface'; -import { ConnectionTypeDefinitionFactory } from './factories/connection-type-definition.factory'; -import { EdgeTypeDefinitionFactory } from './factories/edge-type-definition.factory'; -import { ExtendObjectTypeDefinitionFactory } from './factories/extend-object-type-definition.factory'; +import { TypeDefinitionsStorage } from './storages/type-definitions.storage'; import { objectContainsRelationField } from './utils/object-contains-relation-field'; @Injectable() @@ -55,7 +55,7 @@ export class TypeDefinitionsGenerator { * GENERATE COMPOSITE TYPE OBJECTS */ private generateCompositeTypeDefs(options: WorkspaceBuildSchemaOptions) { - const compositeTypeCollection = [...compositeTypeDefintions.values()]; + const compositeTypeCollection = [...compositeTypeDefinitions.values()]; this.logger.log( `Generating composite type objects: [${compositeTypeCollection diff --git a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-fields.utils.ts b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-fields.utils.ts index 00f5ec990c6c..df9141b99a4a 100644 --- a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-fields.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-fields.utils.ts @@ -2,7 +2,7 @@ import { BadRequestException } from '@nestjs/common'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; @@ -13,7 +13,7 @@ export const checkFields = ( const fieldMetadataNames = objectMetadata.fields .map((field) => { if (isCompositeFieldMetadataType(field.type)) { - const compositeType = compositeTypeDefintions.get(field.type); + const compositeType = compositeTypeDefinitions.get(field.type); if (!compositeType) { throw new BadRequestException( diff --git a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts index ce14a02bd276..851282b6727b 100644 --- a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/check-order-by.utils.ts @@ -1,9 +1,9 @@ import { BadRequestException } from '@nestjs/common'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; @@ -14,7 +14,7 @@ export const checkArrayFields = ( const fieldMetadataNames = objectMetadata.fields .map((field) => { if (isCompositeFieldMetadataType(field.type)) { - const compositeType = compositeTypeDefintions.get(field.type); + const compositeType = compositeTypeDefinitions.get(field.type); if (!compositeType) { throw new BadRequestException( 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 ea26a3118349..bd7248cc0939 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 @@ -9,7 +9,7 @@ import { computeOrderByParameters, computeStartingAfterParameters, } from 'src/engine/core-modules/open-api/utils/parameters.utils'; -import { compositeTypeDefintions } 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { capitalize } from 'src/utils/capitalize'; @@ -74,7 +74,7 @@ const getSchemaComponentsProperties = ( case FieldMetadataType.ACTOR: itemProperty = { type: 'object', - properties: compositeTypeDefintions + properties: compositeTypeDefinitions .get(field.type) ?.properties?.reduce((properties, property) => { properties[property.name] = getFieldProperties(property.type); diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/index.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/index.ts index 442fe63566ee..7f1a2f129b86 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/index.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/index.ts @@ -13,7 +13,7 @@ export type CompositeFieldsDefinitionFunction = ( fieldMetadata?: FieldMetadataInterface, ) => FieldMetadataInterface[]; -export const compositeTypeDefintions = new Map< +export const compositeTypeDefinitions = new Map< FieldMetadataType, CompositeType >([ diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index f6a215bf4d53..dce9dac52220 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -8,7 +8,7 @@ import { v4 as uuidV4 } from 'uuid'; import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input'; import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; @@ -28,6 +28,7 @@ import { } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { generateNullable } from 'src/engine/metadata-modules/field-metadata/utils/generate-nullable'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { @@ -146,7 +147,7 @@ export class FieldMetadataService extends TypeOrmQueryService( fieldMetadataInput, - objectMetadata.standardId, + objectMetadata, ); const fieldAlreadyExists = await fieldMetadataRepository.findOne({ @@ -364,7 +365,7 @@ export class FieldMetadataService extends TypeOrmQueryService( fieldMetadataInput, - objectMetadata.standardId, + objectMetadata, ); const updatableFieldInput = @@ -495,7 +496,7 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput: T, objectMetadataStandardId: string | null): T { + >(fieldMetadataInput: T, objectMetadata: ObjectMetadataEntity): T { if (fieldMetadataInput.name) { try { validateMetadataNameOrThrow(fieldMetadataInput.name); validateNameAvailabilityOrThrow( fieldMetadataInput.name, - objectMetadataStandardId, + objectMetadata, ); } catch (error) { if (error instanceof InvalidStringException) { diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts index 827092daaff7..2a6ed45b0fcd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts @@ -2,17 +2,17 @@ import { Injectable, Logger } from '@nestjs/common'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value'; +import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory'; +import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util'; import { WorkspaceMigrationColumnActionType, WorkspaceMigrationColumnAlter, WorkspaceMigrationColumnCreate, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; -import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value'; -import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util'; -import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory'; -import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { WorkspaceMigrationException, WorkspaceMigrationExceptionCode, @@ -32,7 +32,7 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory, ): WorkspaceMigrationColumnCreate[] { - const compositeType = compositeTypeDefintions.get(fieldMetadata.type); + const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); if (!compositeType) { this.logger.error( @@ -80,10 +80,10 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory, alteredFieldMetadata: FieldMetadataInterface, ): WorkspaceMigrationColumnAlter[] { - const currentCompositeType = compositeTypeDefintions.get( + const currentCompositeType = compositeTypeDefinitions.get( currentFieldMetadata.type, ); - const alteredCompositeType = compositeTypeDefintions.get( + const alteredCompositeType = compositeTypeDefinitions.get( alteredFieldMetadata.type, ); diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts index 251e3058d778..6b4564efaaea 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { ColumnType, EntitySchemaColumnOptions } from 'typeorm'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { FieldMetadataEntity, FieldMetadataType, @@ -105,11 +105,11 @@ export class EntitySchemaColumnFactory { fieldMetadata: FieldMetadataEntity, ): EntitySchemaColumnMap { const entitySchemaColumnMap: EntitySchemaColumnMap = {}; - const compositeType = compositeTypeDefintions.get(fieldMetadata.type); + const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); if (!compositeType) { throw new Error( - `Composite type ${fieldMetadata.type} is not defined in compositeTypeDefintions`, + `Composite type ${fieldMetadata.type} is not defined in compositeTypeDefinitions`, ); } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts index 3adb6e21dc97..6a436ced71aa 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts @@ -24,7 +24,7 @@ import { UpsertOptions } from 'typeorm/repository/UpsertOptions'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -697,7 +697,7 @@ export class WorkspaceRepository< continue; } - const compositeType = compositeTypeDefintions.get(fieldMetadata.type); + const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); if (!compositeType) { continue; @@ -751,7 +751,7 @@ export class WorkspaceRepository< const compositeFieldMetadataMap = new Map( compositeFieldMetadataCollection.flatMap((fieldMetadata) => { - const compositeType = compositeTypeDefintions.get(fieldMetadata.type); + const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); if (!compositeType) return []; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/database-structure.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/database-structure.service.ts index c4fab96efe34..82dd9fdbd258 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/database-structure.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/database-structure.service.ts @@ -3,28 +3,28 @@ import { Injectable } from '@nestjs/common'; import { ColumnType } from 'typeorm'; import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'; -import { - WorkspaceTableStructure, - WorkspaceTableStructureResult, -} from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-table-definition.interface'; import { FieldMetadataDefaultValue, FieldMetadataFunctionDefaultValue, } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; +import { + WorkspaceTableStructure, + WorkspaceTableStructureResult, +} from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-table-definition.interface'; import { TypeORMService } from 'src/database/typeorm/typeorm.service'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { FieldMetadataDefaultValueFunctionNames } from 'src/engine/metadata-modules/field-metadata/dtos/default-value.input'; import { FieldMetadataEntity, FieldMetadataType, } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util'; -import { serializeFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-function-default-value.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; import { isFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/is-function-default-value.util'; -import { FieldMetadataDefaultValueFunctionNames } from 'src/engine/metadata-modules/field-metadata/dtos/default-value.input'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { serializeFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-function-default-value.util'; +import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; +import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; @Injectable() export class DatabaseStructureService { @@ -192,7 +192,7 @@ export class DatabaseStructureService { }; if (isCompositeFieldMetadataType(fieldMetadata.type)) { - const compositeType = compositeTypeDefintions.get(fieldMetadata.type); + const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); if (!compositeType) { throw new Error( @@ -310,7 +310,7 @@ export class DatabaseStructureService { }; if (isCompositeFieldMetadataType(fieldMetadataType)) { - const compositeType = compositeTypeDefintions.get(fieldMetadataType); + const compositeType = compositeTypeDefinitions.get(fieldMetadataType); if (!compositeType) { throw new Error( diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/field-metadata-health.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/field-metadata-health.service.ts index 28652c50b6ed..994b91ec70ba 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/field-metadata-health.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-health/services/field-metadata-health.service.ts @@ -1,34 +1,34 @@ import { Injectable } from '@nestjs/common'; +import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; import { WorkspaceHealthIssue, WorkspaceHealthIssueType, } from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-health-issue.interface'; -import { WorkspaceTableStructure } from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-table-definition.interface'; import { WorkspaceHealthOptions } from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-health-options.interface'; -import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; +import { WorkspaceTableStructure } from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-table-definition.interface'; +import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { FieldMetadataEntity, FieldMetadataType, } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + computeColumnName, + computeCompositeColumnName, +} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; -import { DatabaseStructureService } from 'src/engine/workspace-manager/workspace-health/services/database-structure.service'; -import { validName } from 'src/engine/workspace-manager/workspace-health/utils/valid-name.util'; -import { validateDefaultValueForType } from 'src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util'; import { EnumFieldMetadataUnionType, isEnumFieldMetadataType, } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; -import { validateOptionsForType } from 'src/engine/metadata-modules/field-metadata/utils/validate-options-for-type.util'; import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value'; +import { validateDefaultValueForType } from 'src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util'; +import { validateOptionsForType } from 'src/engine/metadata-modules/field-metadata/utils/validate-options-for-type.util'; import { customNamePrefix } from 'src/engine/utils/compute-table-name.util'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; -import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; -import { - computeColumnName, - computeCompositeColumnName, -} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { DatabaseStructureService } from 'src/engine/workspace-manager/workspace-health/services/database-structure.service'; +import { validName } from 'src/engine/workspace-manager/workspace-health/utils/valid-name.util'; @Injectable() export class FieldMetadataHealthService { @@ -101,7 +101,7 @@ export class FieldMetadataHealthService { let columnNames: string[] = []; if (isCompositeFieldMetadataType(fieldMetadata.type)) { - const compositeType = compositeTypeDefintions.get(fieldMetadata.type); + const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); if (!compositeType) { throw new Error(`Composite type ${fieldMetadata.type} is not defined`); From 4c4e9f92a0ac127e33dad0bfa50275bac2fa619f Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 22 Aug 2024 12:06:16 +0200 Subject: [PATCH 06/11] =?UTF-8?q?Remove=20unnecessary=20||=C2=A0[]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metadata-modules/utils/validate-name-availability.utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts index 4c1c8b315e7a..8a3af3020c45 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts @@ -17,7 +17,7 @@ const getReservedCompositeFieldsNames = ( reservedCompositeFieldsNames.push( computeCompositeColumnName(base, property), ), - ) || []; + ); } } From caefbee3944f62adf3200a40638f5c36662b753d Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 22 Aug 2024 18:53:23 +0200 Subject: [PATCH 07/11] rename functions --- .../field-metadata/field-metadata.service.ts | 18 +++++------- .../validate-object-metadata-input.util.ts | 8 ++--- .../relation-metadata.service.ts | 10 +++---- ... validate-field-name-availability.spec.ts} | 20 ++++++------- ...> validate-metadata-name-validity.spec.ts} | 29 +++++++++---------- .../exceptions/invalid-string.exception.ts | 7 +++++ .../name-not-available.exception.ts | 7 +++++ .../exceptions/name-too-long.exception.ts | 7 +++++ ...validate-field-name-availability.utils.ts} | 15 +++------- .../validate-metadata-name-validity.utils.ts | 14 +++++++++ .../utils/validate-metadata-name.utils.ts | 28 ------------------ 11 files changed, 76 insertions(+), 87 deletions(-) rename packages/twenty-server/src/engine/metadata-modules/utils/__tests__/{validate-name-availability.spec.ts => validate-field-name-availability.spec.ts} (65%) rename packages/twenty-server/src/engine/metadata-modules/utils/__tests__/{validate-metadata-name.spec.ts => validate-metadata-name-validity.spec.ts} (51%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-string.exception.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/exceptions/name-not-available.exception.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/exceptions/name-too-long.exception.ts rename packages/twenty-server/src/engine/metadata-modules/utils/{validate-name-availability.utils.ts => validate-field-name-availability.utils.ts} (79%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-validity.utils.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index dce9dac52220..277db13e4f43 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -35,16 +35,12 @@ import { RelationMetadataEntity, RelationMetadataType, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { InvalidStringException } from 'src/engine/metadata-modules/utils/exceptions/invalid-string.exception'; +import { NameNotAvailableException } from 'src/engine/metadata-modules/utils/exceptions/name-not-available.exception'; +import { NameTooLongException } from 'src/engine/metadata-modules/utils/exceptions/name-too-long.exception'; import { exceedsDatabaseIdentifierMaximumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils'; -import { - InvalidStringException, - NameTooLongException, - validateMetadataNameOrThrow, -} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; -import { - NameNotAvailableException, - validateNameAvailabilityOrThrow, -} from 'src/engine/metadata-modules/utils/validate-name-availability.utils'; +import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils'; +import { validateMetadataNameValidityOrThrow as validateFieldNameValidityOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-validity.utils'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.service'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; import { @@ -687,8 +683,8 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput: T, objectMetadata: ObjectMetadataEntity): T { if (fieldMetadataInput.name) { try { - validateMetadataNameOrThrow(fieldMetadataInput.name); - validateNameAvailabilityOrThrow( + validateFieldNameValidityOrThrow(fieldMetadataInput.name); + validateFieldNameAvailabilityOrThrow( fieldMetadataInput.name, objectMetadata, ); diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts index a8a07ccc3353..a2de79cd07d3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts @@ -4,11 +4,9 @@ import { ObjectMetadataException, ObjectMetadataExceptionCode, } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; +import { InvalidStringException } from 'src/engine/metadata-modules/utils/exceptions/invalid-string.exception'; import { exceedsDatabaseIdentifierMaximumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils'; -import { - validateMetadataNameOrThrow, - InvalidStringException, -} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; +import { validateMetadataNameValidityOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-validity.utils'; import { camelCase } from 'src/utils/camel-case'; const coreObjectNames = [ @@ -96,7 +94,7 @@ const validateNameIsNotTooLongThrow = (name?: string) => { const validateNameCharactersOrThrow = (name?: string) => { try { if (name) { - validateMetadataNameOrThrow(name); + validateMetadataNameValidityOrThrow(name); } } catch (error) { if (error instanceof InvalidStringException) { diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts index 4e01a8a0ebaf..7112a60ee714 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts @@ -18,10 +18,8 @@ import { RelationMetadataException, RelationMetadataExceptionCode, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception'; -import { - InvalidStringException, - validateMetadataNameOrThrow, -} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; +import { InvalidStringException } from 'src/engine/metadata-modules/utils/exceptions/invalid-string.exception'; +import { validateMetadataNameValidityOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-validity.utils'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.service'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; import { @@ -63,8 +61,8 @@ export class RelationMetadataService extends TypeOrmQueryService { +describe('validateFieldNameAvailabilityOrThrow', () => { const objectMetadata = objectMetadataItemMock; it('does not throw if name is not reserved', () => { const name = 'testName'; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadata), + validateFieldNameAvailabilityOrThrow(name, objectMetadata), ).not.toThrow(); }); @@ -27,35 +25,35 @@ describe('validateNameAvailabilityOrThrow', () => { const name = `${FIELD_LINKS_MOCK_NAME}PrimaryLinkLabel`; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadata), + validateFieldNameAvailabilityOrThrow(name, objectMetadata), ).toThrow(NameNotAvailableException); }); it('throws error with CURRENCY suffixes', () => { const name = `${FIELD_CURRENCY_MOCK_NAME}AmountMicros`; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadata), + validateFieldNameAvailabilityOrThrow(name, objectMetadata), ).toThrow(NameNotAvailableException); }); it('throws error with FULL_NAME suffixes', () => { const name = `${FIELD_FULL_NAME_MOCK_NAME}FirstName`; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadata), + validateFieldNameAvailabilityOrThrow(name, objectMetadata), ).toThrow(NameNotAvailableException); }); it('throws error with ACTOR suffixes', () => { const name = `${FIELD_ACTOR_MOCK_NAME}Name`; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadata), + validateFieldNameAvailabilityOrThrow(name, objectMetadata), ).toThrow(NameNotAvailableException); }); it('throws error with ADDRESS suffixes', () => { const name = `${FIELD_ADDRESS_MOCK_NAME}AddressStreet1`; expect(() => - validateNameAvailabilityOrThrow(name, objectMetadata), + validateFieldNameAvailabilityOrThrow(name, objectMetadata), ).toThrow(NameNotAvailableException); }); }); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name.spec.ts b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name-validity.spec.ts similarity index 51% rename from packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name.spec.ts rename to packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name-validity.spec.ts index 445620dd4236..6cd9bd2d6478 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name-validity.spec.ts @@ -1,26 +1,24 @@ -import { - validateMetadataNameOrThrow, - InvalidStringException, - NameTooLongException, -} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; +import { InvalidStringException } from 'src/engine/metadata-modules/utils/exceptions/invalid-string.exception'; +import { NameTooLongException } from 'src/engine/metadata-modules/utils/exceptions/name-too-long.exception'; +import { validateMetadataNameValidityOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-validity.utils'; -describe('validateMetadataNameOrThrow', () => { +describe('validateMetadataNameValidityOrThrow', () => { it('does not throw if string is valid', () => { const input = 'testName'; - expect(validateMetadataNameOrThrow(input)).not.toThrow; + expect(validateMetadataNameValidityOrThrow(input)).not.toThrow; }); it('throws error if string has spaces', () => { const input = 'name with spaces'; - expect(() => validateMetadataNameOrThrow(input)).toThrow( + expect(() => validateMetadataNameValidityOrThrow(input)).toThrow( InvalidStringException, ); }); it('throws error if string starts with capital letter', () => { const input = 'StringStartingWithCapitalLetter'; - expect(() => validateMetadataNameOrThrow(input)).toThrow( + expect(() => validateMetadataNameValidityOrThrow(input)).toThrow( InvalidStringException, ); }); @@ -28,7 +26,7 @@ describe('validateMetadataNameOrThrow', () => { it('throws error if string has non latin characters', () => { const input = 'בְרִבְרִ'; - expect(() => validateMetadataNameOrThrow(input)).toThrow( + expect(() => validateMetadataNameValidityOrThrow(input)).toThrow( InvalidStringException, ); }); @@ -36,7 +34,7 @@ describe('validateMetadataNameOrThrow', () => { it('throws error if starts with digits', () => { const input = '123string'; - expect(() => validateMetadataNameOrThrow(input)).toThrow( + expect(() => validateMetadataNameValidityOrThrow(input)).toThrow( InvalidStringException, ); }); @@ -44,14 +42,15 @@ describe('validateMetadataNameOrThrow', () => { const inputWith63Characters = 'thisIsAstringWithSixtyThreeCharacters11111111111111111111111111'; - expect(validateMetadataNameOrThrow(inputWith63Characters)).not.toThrow; + expect(validateMetadataNameValidityOrThrow(inputWith63Characters)).not + .toThrow; }); it('throws error if string is above 63 characters', () => { const inputWith64Characters = 'thisIsAstringWithSixtyFourCharacters1111111111111111111111111111'; - expect(() => validateMetadataNameOrThrow(inputWith64Characters)).toThrow( - NameTooLongException, - ); + expect(() => + validateMetadataNameValidityOrThrow(inputWith64Characters), + ).toThrow(NameTooLongException); }); }); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-string.exception.ts b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-string.exception.ts new file mode 100644 index 000000000000..0404de70b53e --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-string.exception.ts @@ -0,0 +1,7 @@ +export class InvalidStringException extends Error { + constructor(string: string) { + const message = `String "${string}" is not valid`; + + super(message); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/name-not-available.exception.ts b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/name-not-available.exception.ts new file mode 100644 index 000000000000..308fc6ddd84f --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/name-not-available.exception.ts @@ -0,0 +1,7 @@ +export class NameNotAvailableException extends Error { + constructor(name: string) { + const message = `Name "${name}" is not available`; + + super(message); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/name-too-long.exception.ts b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/name-too-long.exception.ts new file mode 100644 index 000000000000..0c9cf45f6342 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/name-too-long.exception.ts @@ -0,0 +1,7 @@ +export class NameTooLongException extends Error { + constructor(string: string) { + const message = `String "${string}" exceeds 63 characters limit`; + + super(message); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts similarity index 79% rename from packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts rename to packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts index 8a3af3020c45..07db05aacfbd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-availability.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts @@ -2,8 +2,9 @@ import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-meta import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { NameNotAvailableException } from 'src/engine/metadata-modules/utils/exceptions/name-not-available.exception'; -const getReservedCompositeFieldsNames = ( +const getReservedCompositeFieldNames = ( objectMetadata: ObjectMetadataEntity, ) => { const reservedCompositeFieldsNames: string[] = []; @@ -24,22 +25,14 @@ const getReservedCompositeFieldsNames = ( return reservedCompositeFieldsNames; }; -export const validateNameAvailabilityOrThrow = ( +export const validateFieldNameAvailabilityOrThrow = ( name: string, objectMetadata: ObjectMetadataEntity, ) => { const reservedCompositeFieldsNames = - getReservedCompositeFieldsNames(objectMetadata); + getReservedCompositeFieldNames(objectMetadata); if (reservedCompositeFieldsNames.includes(name)) { throw new NameNotAvailableException(name); } }; - -export class NameNotAvailableException extends Error { - constructor(name: string) { - const message = `Name "${name}" is not available`; - - super(message); - } -} diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-validity.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-validity.utils.ts new file mode 100644 index 000000000000..42cb2fc70945 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-validity.utils.ts @@ -0,0 +1,14 @@ +import { InvalidStringException } from 'src/engine/metadata-modules/utils/exceptions/invalid-string.exception'; +import { NameTooLongException } from 'src/engine/metadata-modules/utils/exceptions/name-too-long.exception'; +import { exceedsDatabaseIdentifierMaximumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils'; + +const VALID_STRING_PATTERN = /^[a-z][a-zA-Z0-9]*$/; + +export const validateMetadataNameValidityOrThrow = (name: string) => { + if (!name.match(VALID_STRING_PATTERN)) { + throw new InvalidStringException(name); + } + if (exceedsDatabaseIdentifierMaximumLength(name)) { + throw new NameTooLongException(name); + } +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts deleted file mode 100644 index 72835eead45e..000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { exceedsDatabaseIdentifierMaximumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils'; - -const VALID_STRING_PATTERN = /^[a-z][a-zA-Z0-9]*$/; - -export const validateMetadataNameOrThrow = (name: string) => { - if (!name.match(VALID_STRING_PATTERN)) { - throw new InvalidStringException(name); - } - if (exceedsDatabaseIdentifierMaximumLength(name)) { - throw new NameTooLongException(name); - } -}; - -export class InvalidStringException extends Error { - constructor(string: string) { - const message = `String "${string}" is not valid`; - - super(message); - } -} - -export class NameTooLongException extends Error { - constructor(string: string) { - const message = `String "${string}" exceeds 63 characters limit`; - - super(message); - } -} From 770defced115fec42ed83e8f68cf71fc4f993961 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 23 Aug 2024 11:29:58 +0200 Subject: [PATCH 08/11] Check name availability for relations --- .../relation-metadata/relation-metadata.service.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts index 7112a60ee714..a08006adbf87 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts @@ -19,6 +19,7 @@ import { RelationMetadataExceptionCode, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception'; import { InvalidStringException } from 'src/engine/metadata-modules/utils/exceptions/invalid-string.exception'; +import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils'; import { validateMetadataNameValidityOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-validity.utils'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.service'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; @@ -159,6 +160,15 @@ export class RelationMetadataService extends TypeOrmQueryService Date: Fri, 23 Aug 2024 12:02:06 +0200 Subject: [PATCH 09/11] fix tests --- .../engine/api/__mocks__/object-metadata-item.mock.ts | 2 +- .../map-field-metadata-to-graphql-query.utils.spec.ts | 10 ---------- .../open-api/utils/__tests__/components.utils.spec.ts | 7 ------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts b/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts index 382e2173f951..1a20e09c9c55 100644 --- a/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts +++ b/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts @@ -2,7 +2,7 @@ import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/com import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -export const FIELD_LINKS_MOCK_NAME = 'fieldLink'; +export const FIELD_LINKS_MOCK_NAME = 'fieldLinks'; export const FIELD_CURRENCY_MOCK_NAME = 'fieldCurrency'; export const FIELD_ADDRESS_MOCK_NAME = 'fieldAddress'; export const FIELD_ACTOR_MOCK_NAME = 'fieldActor'; diff --git a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts index 473c6f8408d8..4fec6c7ab443 100644 --- a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts @@ -1,6 +1,5 @@ import { fieldCurrencyMock, - fieldLinkMock, fieldNumberMock, fieldTextMock, objectMetadataItemMock, @@ -17,15 +16,6 @@ describe('mapFieldMetadataToGraphqlQuery', () => { expect( mapFieldMetadataToGraphqlQuery([objectMetadataItemMock], fieldTextMock), ).toEqual('fieldText'); - expect( - mapFieldMetadataToGraphqlQuery([objectMetadataItemMock], fieldLinkMock), - ).toEqual(` - fieldLink - { - label - url - } - `); expect( mapFieldMetadataToGraphqlQuery( [objectMetadataItemMock], 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 a99a07ef36ed..648e65026370 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 @@ -55,13 +55,6 @@ describe('computeSchemaComponents', () => { fieldNumeric: { type: 'number', }, - fieldLink: { - properties: { - label: { type: 'string' }, - url: { type: 'string' }, - }, - type: 'object', - }, fieldLinks: { properties: { primaryLinkLabel: { type: 'string' }, From 8f135dcbddd2a4d5ac95058f676dd8ff0e4cbff1 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 23 Aug 2024 12:27:18 +0200 Subject: [PATCH 10/11] fix test --- .../open-api/utils/__tests__/components.utils.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 648e65026370..3275b8392d01 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 @@ -7,9 +7,11 @@ import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/fi import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; describe('computeSchemaComponents', () => { - it('should test all field types', () => { + it('should test all non-deprecated field types', () => { expect(fields.map((field) => field.type)).toEqual( - Object.keys(FieldMetadataType), + Object.keys(FieldMetadataType).filter( + (key) => key !== FieldMetadataType.LINK, + ), ); }); it('should compute schema components', () => { From d113d56b7a2323c72802f0c720df1c6c3db8385b Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 23 Aug 2024 12:36:11 +0200 Subject: [PATCH 11/11] fix test --- .../__tests__/order-by-input.factory.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/twenty-server/src/engine/api/rest/input-factories/__tests__/order-by-input.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/input-factories/__tests__/order-by-input.factory.spec.ts index 63b3179ee68d..9f6923c23802 100644 --- a/packages/twenty-server/src/engine/api/rest/input-factories/__tests__/order-by-input.factory.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/input-factories/__tests__/order-by-input.factory.spec.ts @@ -54,7 +54,7 @@ describe('OrderByInputFactory', () => { ]); }); - it('should handler complex fields', () => { + it('should handle complex fields', () => { const request: any = { query: { order_by: 'fieldCurrency.amountMicros', @@ -66,7 +66,7 @@ describe('OrderByInputFactory', () => { ]); }); - it('should handler complex fields with direction', () => { + it('should handle complex fields with direction', () => { const request: any = { query: { order_by: 'fieldCurrency.amountMicros[DescNullsLast]', @@ -78,17 +78,17 @@ describe('OrderByInputFactory', () => { ]); }); - it('should handler multiple complex fields with direction', () => { + it('should handle multiple complex fields with direction', () => { const request: any = { query: { order_by: - 'fieldCurrency.amountMicros[DescNullsLast],fieldLink.label[AscNullsLast]', + 'fieldCurrency.amountMicros[DescNullsLast],fieldText.label[AscNullsLast]', }, }; expect(service.create(request, objectMetadata)).toEqual([ { fieldCurrency: { amountMicros: OrderByDirection.DescNullsLast } }, - { fieldLink: { label: OrderByDirection.AscNullsLast } }, + { fieldText: { label: OrderByDirection.AscNullsLast } }, ]); });