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 637447c3f..8a6810b84 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 @@ -36,14 +36,15 @@ import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field- import { isSelectFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-select-field-metadata-type.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; import { RelationMetadataEntity, RelationMetadataType, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; import { exceedsDatabaseIdentifierMaximumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils'; import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils'; import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; +import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; import { @@ -59,7 +60,6 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; import { ViewService } from 'src/modules/view/services/view.service'; import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; -import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; import { FieldMetadataValidationService } from './field-metadata-validation.service'; import { FieldMetadataEntity } from './field-metadata.entity'; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util.ts index af8c3cd87..e1874ebb4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util.ts @@ -9,8 +9,13 @@ import { FieldMetadataException, FieldMetadataExceptionCode, } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; +import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => { + if (error instanceof InvalidMetadataException) { + throw new UserInputError(error.message); + } + if (error instanceof FieldMetadataException) { switch (error.code) { case FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND: diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index 309345de9..78fc4ea36 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -27,12 +27,12 @@ import { ObjectMetadataRelationService } from 'src/engine/metadata-modules/objec import { buildDefaultFieldsForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util'; import { validateLowerCasedAndTrimmedStringsAreDifferentOrThrow, - validateNameAndLabelAreSyncOrThrow, validateObjectMetadataInputLabelsOrThrow, validateObjectMetadataInputNamesOrThrow, } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service'; import { SearchService } from 'src/engine/metadata-modules/search/search.service'; +import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts index ef00ed60f..c4eca8e8c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts @@ -9,8 +9,13 @@ import { ObjectMetadataException, ObjectMetadataExceptionCode, } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; +import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; export const objectMetadataGraphqlApiExceptionHandler = (error: Error) => { + if (error instanceof InvalidMetadataException) { + throw new UserInputError(error.message); + } + if (error instanceof ObjectMetadataException) { switch (error.code) { case ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND: 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 2ed051c78..c9d2ad4f4 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 @@ -1,4 +1,3 @@ -import { slugify } from 'transliteration'; import { isDefined } from 'twenty-shared'; import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; @@ -11,7 +10,6 @@ import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/ import { validateMetadataNameIsNotTooLongOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-long.utils'; import { validateMetadataNameIsNotTooShortOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-short.utils'; import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; -import { camelCase } from 'src/utils/camel-case'; export const validateObjectMetadataInputNamesOrThrow = < T extends UpdateObjectPayload | CreateObjectInput, @@ -71,50 +69,6 @@ const validateObjectMetadataInputLabelOrThrow = (name: string): void => { } }; -export const computeMetadataNameFromLabel = (label: string): string => { - if (!isDefined(label)) { - throw new ObjectMetadataException( - 'Label is required', - ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, - ); - } - - const prefixedLabel = /^\d/.test(label) ? `n${label}` : label; - - if (prefixedLabel === '') { - return ''; - } - - const formattedString = slugify(prefixedLabel, { - trim: true, - separator: '_', - allowedChars: 'a-zA-Z0-9', - }); - - if (formattedString === '') { - throw new ObjectMetadataException( - `Invalid label: "${label}"`, - ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, - ); - } - - return camelCase(formattedString); -}; - -export const validateNameAndLabelAreSyncOrThrow = ( - label: string, - name: string, -) => { - const computedName = computeMetadataNameFromLabel(label); - - if (name !== computedName) { - throw new ObjectMetadataException( - `Name is not synced with label. Expected name: "${computedName}", got ${name}`, - ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, - ); - } -}; - type ValidateLowerCasedAndTrimmedStringAreDifferentOrThrowArgs = { inputs: [string, string]; message: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/utils/relation-metadata-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/utils/relation-metadata-graphql-api-exception-handler.util.ts index 4367e4035..ad17d2748 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/utils/relation-metadata-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/utils/relation-metadata-graphql-api-exception-handler.util.ts @@ -8,8 +8,13 @@ import { RelationMetadataException, RelationMetadataExceptionCode, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception'; +import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; export const relationMetadataGraphqlApiExceptionHandler = (error: Error) => { + if (error instanceof InvalidMetadataException) { + throw new UserInputError(error.message); + } + if (error instanceof RelationMetadataException) { switch (error.code) { case RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND: diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts new file mode 100644 index 000000000..11caa9df2 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts @@ -0,0 +1,29 @@ +import camelCase from 'lodash.camelcase'; +import { slugify } from 'transliteration'; +import { isDefined } from 'twenty-shared'; + +import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; + +export const computeMetadataNameFromLabel = (label: string): string => { + if (!isDefined(label)) { + throw new InvalidMetadataException('Label is required'); + } + + const prefixedLabel = /^\d/.test(label) ? `n${label}` : label; + + if (prefixedLabel === '') { + return ''; + } + + const formattedString = slugify(prefixedLabel, { + trim: true, + separator: '_', + allowedChars: 'a-zA-Z0-9', + }); + + if (formattedString === '') { + throw new InvalidMetadataException(`Invalid label: "${label}"`); + } + + return camelCase(formattedString); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception.ts new file mode 100644 index 000000000..8f3e59644 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception.ts @@ -0,0 +1,5 @@ +export class InvalidMetadataException extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util.ts new file mode 100644 index 000000000..d1c8e76ac --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util.ts @@ -0,0 +1,42 @@ +import camelCase from 'lodash.camelcase'; +import { slugify } from 'transliteration'; +import { isDefined } from 'twenty-shared'; + +import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; + +export const validateNameAndLabelAreSyncOrThrow = ( + label: string, + name: string, +) => { + const computedName = computeMetadataNameFromLabel(label); + + if (name !== computedName) { + throw new InvalidMetadataException( + `Name is not synced with label. Expected name: "${computedName}", got ${name}`, + ); + } +}; + +export const computeMetadataNameFromLabel = (label: string): string => { + if (!isDefined(label)) { + throw new InvalidMetadataException('Label is required'); + } + + const prefixedLabel = /^\d/.test(label) ? `n${label}` : label; + + if (prefixedLabel === '') { + return ''; + } + + const formattedString = slugify(prefixedLabel, { + trim: true, + separator: '_', + allowedChars: 'a-zA-Z0-9', + }); + + if (formattedString === '') { + throw new InvalidMetadataException(`Invalid label: "${label}"`); + } + + return camelCase(formattedString); +};