From 753a07729d38ac5d578a8ba2aa2ad829f3e02f88 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 19 Apr 2024 14:03:51 +0200 Subject: [PATCH 01/12] Introduce transliteration lib and util --- packages/twenty-server/package.json | 1 + .../errors/CharactersNotSupportedException.ts | 9 +++++ .../utils/__tests__/format-string.spec.ts | 36 +++++++++++++++++++ .../utils/format-string.util.ts | 36 +++++++++++++++++++ yarn.lock | 15 +++++++- 5 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 packages/twenty-server/src/engine/metadata-modules/errors/CharactersNotSupportedException.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index 8474b3d7ab27..2d947360cd1b 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -30,6 +30,7 @@ "lodash.uniqby": "^4.7.0", "passport": "^0.7.0", "psl": "^1.9.0", + "transliteration": "^2.3.5", "tsconfig-paths": "^4.2.0" }, "devDependencies": { diff --git a/packages/twenty-server/src/engine/metadata-modules/errors/CharactersNotSupportedException.ts b/packages/twenty-server/src/engine/metadata-modules/errors/CharactersNotSupportedException.ts new file mode 100644 index 000000000000..e75ae877059c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/errors/CharactersNotSupportedException.ts @@ -0,0 +1,9 @@ +import { BadRequestException } from '@nestjs/common'; + +export class ChararactersNotSupportedException extends BadRequestException { + constructor(string: string) { + const message = `String "${string}" contains unsupported characters`; + + super(message); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts new file mode 100644 index 000000000000..b9ab28992822 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts @@ -0,0 +1,36 @@ +import { + formatString, + validPattern, +} from 'src/engine/metadata-modules/utils/format-string.util'; + +describe('formatString', () => { + it('format strings starting with digits', () => { + const input = '123string'; + const expected = 'string123'; + + expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input)).toEqual(expected); + + const inputCapitalized = '123String'; + const expectedCamelCased = 'string123'; + + expect(formatString(inputCapitalized).match(validPattern)?.length).toBe(1); + expect(formatString(inputCapitalized)).toEqual(expectedCamelCased); + }); + + it('format strings with non latin characters', () => { + const input = 'בְרִבְרִ'; + const expected = 'aabRibRi2Transliterated'; + + expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input)).toEqual(expected); + }); + + it('format strings with mixed characters', () => { + const input = '2aaבְרִבְרִ'; + const expected = 'aabRibRi2Transliterated'; + + expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input)).toEqual(expected); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts new file mode 100644 index 000000000000..6a7a9d851b24 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts @@ -0,0 +1,36 @@ +import { transliterate, slugify } from 'transliteration'; +import toCamelCase from 'lodash.camelcase'; + +import { ChararactersNotSupportedException } from 'src/engine/metadata-modules/errors/CharactersNotSupportedException'; + +export const validPattern = /^[a-zA-Z][a-zA-Z0-9 ]*$/; +const startsWithDigitPattern = /^[^\d]*(\d+)/; + +export const formatString = (string: string): string => { + let formattedString = string; + + if (formattedString.match(validPattern)) { + return formattedString; + } + + if (formattedString.match(startsWithDigitPattern)) { + const digitsAtStart = formattedString.match(startsWithDigitPattern)?.[0]; + + formattedString = + formattedString.slice(digitsAtStart?.length || 0) + digitsAtStart; + } + + if (formattedString.match(validPattern)) { + return toCamelCase(formattedString); + } + + formattedString = toCamelCase( + slugify(transliterate(formattedString, { trim: true }) + '-transliterated'), + ); + + if (!formattedString.match(validPattern)) { + throw new ChararactersNotSupportedException(string); + } + + return formattedString; +}; diff --git a/yarn.lock b/yarn.lock index cf9ab5cb5a08..d25a236a53bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45804,6 +45804,18 @@ __metadata: languageName: node linkType: hard +"transliteration@npm:^2.3.5": + version: 2.3.5 + resolution: "transliteration@npm:2.3.5" + dependencies: + yargs: "npm:^17.5.1" + bin: + slugify: dist/bin/slugify + transliterate: dist/bin/transliterate + checksum: 68397225c2ca59b8e33206c65f905724e86b64460cbf90576d352dc2366e763ded97e2c7b8b1f140fb36a565d61a97c51080df9fa638e6b1769f6cb24f383756 + languageName: node + linkType: hard + "traverse@npm:0.6.7": version: 0.6.7 resolution: "traverse@npm:0.6.7" @@ -46300,6 +46312,7 @@ __metadata: passport: "npm:^0.7.0" psl: "npm:^1.9.0" rimraf: "npm:^5.0.5" + transliteration: "npm:^2.3.5" tsconfig-paths: "npm:^4.2.0" typescript: "npm:5.3.3" languageName: unknown @@ -49452,7 +49465,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.0.0, yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2": +"yargs@npm:^17.0.0, yargs@npm:^17.3.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: From 333baf66a6df1ce11bde31bbac983f50ef6f5644 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 19 Apr 2024 15:01:59 +0200 Subject: [PATCH 02/12] Format object, fields, relation names before create and save --- .../field-metadata/field-metadata.service.ts | 34 +++++++++++++++++++ .../hooks/before-update-one-object.hook.ts | 3 ++ .../object-metadata.service.ts | 3 ++ .../format-object-metadata-input.util.ts | 31 +++++++++++++++++ .../relation-metadata.service.ts | 19 +++++++++++ 5 files changed, 90 insertions(+) create mode 100644 packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/format-object-metadata-input.util.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 38b2d4b5718f..f93c503f144b 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 @@ -39,6 +39,8 @@ import { import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input'; import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; +import { formatString } from 'src/engine/metadata-modules/utils/format-string.util'; +import { ChararactersNotSupportedException } from 'src/engine/metadata-modules/errors/CharactersNotSupportedException'; import { FieldMetadataEntity, @@ -114,6 +116,9 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput); + const fieldAlreadyExists = await fieldMetadataRepository.findOne({ where: { name: fieldMetadataInput.name, @@ -293,6 +298,9 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput); + const updatableFieldInput = existingFieldMetadata.isCustom === false ? this.buildUpdatableStandardFieldInput( @@ -533,4 +541,30 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput: T): T { + if (fieldMetadataInput.name) { + try { + fieldMetadataInput = { + ...fieldMetadataInput, + name: formatString(fieldMetadataInput.name), + }; + + return fieldMetadataInput; + } catch (error) { + if (error instanceof ChararactersNotSupportedException) { + console.error(error.message); + throw new BadRequestException( + `Characters used in name "${fieldMetadataInput.name}" are not supported`, + ); + } else { + throw error; + } + } + } + + return fieldMetadataInput; + } } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts index 53018786215e..696ecf4d47ce 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts @@ -15,6 +15,7 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/ import { UpdateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { formatObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/format-object-metadata-input.util'; @Injectable() export class BeforeUpdateOneObject @@ -100,6 +101,8 @@ export class BeforeUpdateOneObject } } + formatObjectMetadataInput(instance.update); + this.checkIfFieldIsEditable(instance.update, objectMetadata); return instance; 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 1f9f239ecfec..26adf886a015 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 @@ -59,6 +59,7 @@ import { FeatureFlagKeys, } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; +import { formatObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/format-object-metadata-input.util'; import { ObjectMetadataEntity } from './object-metadata.entity'; @@ -237,6 +238,8 @@ export class ObjectMetadataService extends TypeOrmQueryService { + try { + objectMetadataInput = { + ...objectMetadataInput, + nameSingular: objectMetadataInput.nameSingular + ? formatString(objectMetadataInput.nameSingular) + : objectMetadataInput.nameSingular, + namePlural: objectMetadataInput.namePlural + ? formatString(objectMetadataInput.namePlural) + : objectMetadataInput.namePlural, + }; + } catch (error) { + if (error instanceof ChararactersNotSupportedException) { + console.error(error.message); + throw new BadRequestException( + `Characters used in name "${objectMetadataInput.nameSingular}" or "${objectMetadataInput.namePlural}" are not supported`, + ); + } else { + throw error; + } + } +}; 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 1180e0dc9957..76df526149a4 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 @@ -24,6 +24,8 @@ import { import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; +import { formatString } from 'src/engine/metadata-modules/utils/format-string.util'; +import { ChararactersNotSupportedException } from 'src/engine/metadata-modules/errors/CharactersNotSupportedException'; import { RelationMetadataEntity, @@ -51,6 +53,23 @@ export class RelationMetadataService extends TypeOrmQueryService Date: Fri, 19 Apr 2024 15:03:20 +0200 Subject: [PATCH 03/12] Remove char value restriction on field and relation --- .../utils/__tests__/validateMetadataLabel.test.ts | 8 -------- .../object-metadata/utils/validateMetadataLabel.ts | 4 ---- .../components/SettingsObjectFieldFormSection.tsx | 5 +---- .../components/SettingsObjectFieldRelationForm.tsx | 9 +++------ 4 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-metadata/utils/__tests__/validateMetadataLabel.test.ts delete mode 100644 packages/twenty-front/src/modules/object-metadata/utils/validateMetadataLabel.ts diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/validateMetadataLabel.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/validateMetadataLabel.test.ts deleted file mode 100644 index 7f29a921f193..000000000000 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/validateMetadataLabel.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel'; - -describe('validateMetadataLabel', () => { - it('should work as expected', () => { - const res = validateMetadataLabel('Pipeline Step'); - expect(res).toBe(true); - }); -}); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/validateMetadataLabel.ts b/packages/twenty-front/src/modules/object-metadata/utils/validateMetadataLabel.ts deleted file mode 100644 index 3aef4b866ed2..000000000000 --- a/packages/twenty-front/src/modules/object-metadata/utils/validateMetadataLabel.ts +++ /dev/null @@ -1,4 +0,0 @@ -const metadataLabelValidationPattern = /^[a-zA-Z][a-zA-Z0-9 ]*$/; - -export const validateMetadataLabel = (value: string) => - !!value.match(metadataLabelValidationPattern); diff --git a/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldFormSection.tsx b/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldFormSection.tsx index f452d06b36f6..de310a7c0c27 100644 --- a/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldFormSection.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldFormSection.tsx @@ -1,6 +1,5 @@ import styled from '@emotion/styled'; -import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel'; import { H2Title } from '@/ui/display/typography/components/H2Title'; import { IconPicker } from '@/ui/input/components/IconPicker'; import { TextArea } from '@/ui/input/components/TextArea'; @@ -51,9 +50,7 @@ export const SettingsObjectFieldFormSection = ({ placeholder="Employees" value={name} onChange={(value) => { - if (!value || validateMetadataLabel(value)) { - onChange?.({ label: value }); - } + onChange?.({ label: value }); }} disabled={disabled} fullWidth diff --git a/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx b/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx index 4df29780d014..403e9967551a 100644 --- a/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx @@ -3,7 +3,6 @@ import { useIcons } from 'twenty-ui'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation'; -import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel'; import { IconPicker } from '@/ui/input/components/IconPicker'; import { Select } from '@/ui/input/components/Select'; import { TextInput } from '@/ui/input/components/TextInput'; @@ -121,11 +120,9 @@ export const SettingsObjectFieldRelationForm = ({ placeholder="Field name" value={values.field.label} onChange={(value) => { - if (!value || validateMetadataLabel(value)) { - onChange({ - field: { ...values.field, label: value }, - }); - } + onChange({ + field: { ...values.field, label: value }, + }); }} fullWidth /> From cd075cb80181878f6bb9e0a72b012ed54a18580c Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 19 Apr 2024 15:39:48 +0200 Subject: [PATCH 04/12] Fix issues with object and fields updates --- .../field-metadata/field-metadata.service.ts | 6 ++---- .../hooks/before-update-one-object.hook.ts | 2 +- .../object-metadata/object-metadata.service.ts | 2 +- .../utils/format-object-metadata-input.util.ts | 12 +++++++----- 4 files changed, 11 insertions(+), 11 deletions(-) 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 f93c503f144b..09c67ba6f385 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 @@ -547,12 +547,10 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput: T): T { if (fieldMetadataInput.name) { try { - fieldMetadataInput = { + return (fieldMetadataInput = { ...fieldMetadataInput, name: formatString(fieldMetadataInput.name), - }; - - return fieldMetadataInput; + }); } catch (error) { if (error instanceof ChararactersNotSupportedException) { console.error(error.message); diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts index 696ecf4d47ce..0ba476af8ae2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts @@ -101,7 +101,7 @@ export class BeforeUpdateOneObject } } - formatObjectMetadataInput(instance.update); + instance.update = formatObjectMetadataInput(instance.update); this.checkIfFieldIsEditable(instance.update, objectMetadata); 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 26adf886a015..9179abe2853b 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 @@ -238,7 +238,7 @@ export class ObjectMetadataService extends TypeOrmQueryService { +export const formatObjectMetadataInput = < + T extends UpdateObjectInput | CreateObjectInput, +>( + objectMetadataInput: T, +): T => { try { - objectMetadataInput = { + return (objectMetadataInput = { ...objectMetadataInput, nameSingular: objectMetadataInput.nameSingular ? formatString(objectMetadataInput.nameSingular) @@ -17,7 +19,7 @@ export const formatObjectMetadataInput = ( namePlural: objectMetadataInput.namePlural ? formatString(objectMetadataInput.namePlural) : objectMetadataInput.namePlural, - }; + }); } catch (error) { if (error instanceof ChararactersNotSupportedException) { console.error(error.message); From 7772bbb6a80d9c14e9c228623dee9a130bac1b69 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 19 Apr 2024 15:42:28 +0200 Subject: [PATCH 05/12] Fix format-string test --- .../metadata-modules/utils/__tests__/format-string.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts index b9ab28992822..e518ec6a2cdf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts @@ -20,7 +20,7 @@ describe('formatString', () => { it('format strings with non latin characters', () => { const input = 'בְרִבְרִ'; - const expected = 'aabRibRi2Transliterated'; + const expected = 'bRibRiTransliterated'; expect(formatString(input).match(validPattern)?.length).toBe(1); expect(formatString(input)).toEqual(expected); From f2d5b8f77ad9d3afd5e1a96ba4612aa4a944da9f Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Mon, 22 Apr 2024 14:24:48 +0200 Subject: [PATCH 06/12] Stop handling digit prefixes --- .../utils/__tests__/format-string.spec.ts | 36 +++++++++++++------ .../utils/format-string.util.ts | 10 +----- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts index e518ec6a2cdf..25c200b5217f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts @@ -1,36 +1,50 @@ +import { ChararactersNotSupportedException } from 'src/engine/metadata-modules/errors/CharactersNotSupportedException'; import { formatString, validPattern, } from 'src/engine/metadata-modules/utils/format-string.util'; describe('formatString', () => { - it('format strings starting with digits', () => { - const input = '123string'; - const expected = 'string123'; + it('leaves strings unchanged if only latin characters', () => { + const input = 'testName'; expect(formatString(input).match(validPattern)?.length).toBe(1); - expect(formatString(input)).toEqual(expected); + expect(formatString(input)).toEqual(input); + }); - const inputCapitalized = '123String'; - const expectedCamelCased = 'string123'; + it('leaves strings unchanged if only latin characters and digits', () => { + const input = 'testName123'; - expect(formatString(inputCapitalized).match(validPattern)?.length).toBe(1); - expect(formatString(inputCapitalized)).toEqual(expectedCamelCased); + expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input)).toEqual(input); }); it('format strings with non latin characters', () => { const input = 'בְרִבְרִ'; - const expected = 'bRibRiTransliterated'; + const expected = 'bRibRi'; expect(formatString(input).match(validPattern)?.length).toBe(1); expect(formatString(input)).toEqual(expected); }); it('format strings with mixed characters', () => { - const input = '2aaבְרִבְרִ'; - const expected = 'aabRibRi2Transliterated'; + const input = 'aa2בְרִבְרִ'; + const expected = 'aa2BRibRi'; expect(formatString(input).match(validPattern)?.length).toBe(1); expect(formatString(input)).toEqual(expected); }); + + it('throws error if starts with digits', () => { + const input = '123string'; + + try { + formatString(input); + } catch (error: any) { + expect(error.name).toBe(ChararactersNotSupportedException.name); + + return; + } + throw new Error('formatString should have thrown'); + }); }); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts index 6a7a9d851b24..59efde4c3103 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts @@ -4,7 +4,6 @@ import toCamelCase from 'lodash.camelcase'; import { ChararactersNotSupportedException } from 'src/engine/metadata-modules/errors/CharactersNotSupportedException'; export const validPattern = /^[a-zA-Z][a-zA-Z0-9 ]*$/; -const startsWithDigitPattern = /^[^\d]*(\d+)/; export const formatString = (string: string): string => { let formattedString = string; @@ -13,19 +12,12 @@ export const formatString = (string: string): string => { return formattedString; } - if (formattedString.match(startsWithDigitPattern)) { - const digitsAtStart = formattedString.match(startsWithDigitPattern)?.[0]; - - formattedString = - formattedString.slice(digitsAtStart?.length || 0) + digitsAtStart; - } - if (formattedString.match(validPattern)) { return toCamelCase(formattedString); } formattedString = toCamelCase( - slugify(transliterate(formattedString, { trim: true }) + '-transliterated'), + slugify(transliterate(formattedString, { trim: true })), ); if (!formattedString.match(validPattern)) { From bce9e22c036caf68939da2a1432e39fd1430c7ae Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Mon, 22 Apr 2024 17:34:00 +0200 Subject: [PATCH 07/12] Perform transliteration in the front-end --- packages/twenty-front/package.json | 3 ++ .../__tests__/validateMetadataLabel.test.ts | 8 +++ .../utils/formatFieldMetadataItemInput.ts | 4 +- .../utils/validateMetadataLabel.ts | 4 ++ .../SettingsObjectFieldFormSection.tsx | 5 +- .../SettingsObjectFieldRelationForm.tsx | 9 ++-- .../SettingsDataModelObjectAboutForm.tsx | 7 ++- .../settingsCreateObjectInputSchema.ts | 7 ++- .../settingsUpdateObjectInputSchema.ts | 7 ++- .../utils/__tests__/format-string.spec.ts | 50 +++++++++++++++++++ .../data-model/utils/format-string.util.ts | 20 ++++++++ yarn.lock | 3 +- 12 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/__tests__/validateMetadataLabel.test.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/validateMetadataLabel.ts create mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts create mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/format-string.util.ts diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index a329e339ea21..9e5d4ec5421f 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -28,5 +28,8 @@ }, "msw": { "workerDirectory": "public" + }, + "dependencies": { + "transliteration": "^2.3.5" } } diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/validateMetadataLabel.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/validateMetadataLabel.test.ts new file mode 100644 index 000000000000..7f29a921f193 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/validateMetadataLabel.test.ts @@ -0,0 +1,8 @@ +import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel'; + +describe('validateMetadataLabel', () => { + it('should work as expected', () => { + const res = validateMetadataLabel('Pipeline Step'); + expect(res).toBe(true); + }); +}); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts index cf762af5cb7a..5377c190b251 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts @@ -1,7 +1,7 @@ -import toCamelCase from 'lodash.camelcase'; import toSnakeCase from 'lodash.snakecase'; import { Field, FieldMetadataType } from '~/generated-metadata/graphql'; +import { formatString } from '~/pages/settings/data-model/utils/format-string.util'; import { isDefined } from '~/utils/isDefined'; import { FieldMetadataOption } from '../types/FieldMetadataOption'; @@ -64,7 +64,7 @@ export const formatFieldMetadataItemInput = ( description: input.description?.trim() ?? null, icon: input.icon, label: input.label.trim(), - name: toCamelCase(input.label.trim()), + name: formatString(input.label.trim()), options: options?.map((option, index) => ({ color: option.color, id: option.id, diff --git a/packages/twenty-front/src/modules/object-metadata/utils/validateMetadataLabel.ts b/packages/twenty-front/src/modules/object-metadata/utils/validateMetadataLabel.ts new file mode 100644 index 000000000000..74654c9eb6a1 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/validateMetadataLabel.ts @@ -0,0 +1,4 @@ +const metadataLabelValidationPattern = /^[^0-9].*$/; + +export const validateMetadataLabel = (value: string) => + !!value.match(metadataLabelValidationPattern); diff --git a/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldFormSection.tsx b/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldFormSection.tsx index de310a7c0c27..f452d06b36f6 100644 --- a/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldFormSection.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldFormSection.tsx @@ -1,5 +1,6 @@ import styled from '@emotion/styled'; +import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel'; import { H2Title } from '@/ui/display/typography/components/H2Title'; import { IconPicker } from '@/ui/input/components/IconPicker'; import { TextArea } from '@/ui/input/components/TextArea'; @@ -50,7 +51,9 @@ export const SettingsObjectFieldFormSection = ({ placeholder="Employees" value={name} onChange={(value) => { - onChange?.({ label: value }); + if (!value || validateMetadataLabel(value)) { + onChange?.({ label: value }); + } }} disabled={disabled} fullWidth diff --git a/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx b/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx index 403e9967551a..4df29780d014 100644 --- a/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx @@ -3,6 +3,7 @@ import { useIcons } from 'twenty-ui'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation'; +import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel'; import { IconPicker } from '@/ui/input/components/IconPicker'; import { Select } from '@/ui/input/components/Select'; import { TextInput } from '@/ui/input/components/TextInput'; @@ -120,9 +121,11 @@ export const SettingsObjectFieldRelationForm = ({ placeholder="Field name" value={values.field.label} onChange={(value) => { - onChange({ - field: { ...values.field, label: value }, - }); + if (!value || validateMetadataLabel(value)) { + onChange({ + field: { ...values.field, label: value }, + }); + } }} fullWidth /> diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx index b7dfc9229ff7..64db0318dc32 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm.tsx @@ -3,6 +3,7 @@ import styled from '@emotion/styled'; import { z } from 'zod'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel'; import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema'; import { IconPicker } from '@/ui/input/components/IconPicker'; import { TextArea } from '@/ui/input/components/TextArea'; @@ -92,7 +93,11 @@ export const SettingsDataModelObjectAboutForm = ({ label={label} placeholder={placeholder} value={value} - onChange={onChange} + onChange={(value) => { + if (!value || validateMetadataLabel(value)) { + onChange?.(value); + } + }} disabled={disabled} fullWidth /> diff --git a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts index 0188eda77e2c..86bb3a466460 100644 --- a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts +++ b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts @@ -1,7 +1,6 @@ -import camelCase from 'lodash.camelcase'; - import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema'; import { CreateObjectInput } from '~/generated-metadata/graphql'; +import { formatString } from '~/pages/settings/data-model/utils/format-string.util'; export const settingsCreateObjectInputSchema = objectMetadataItemSchema .pick({ @@ -12,6 +11,6 @@ export const settingsCreateObjectInputSchema = objectMetadataItemSchema }) .transform((value) => ({ ...value, - nameSingular: camelCase(value.labelSingular), - namePlural: camelCase(value.labelPlural), + nameSingular: formatString(value.labelSingular), + namePlural: formatString(value.labelPlural), })); diff --git a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts index 36e8ee008c93..73a80afa2c5f 100644 --- a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts +++ b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts @@ -1,7 +1,6 @@ -import camelCase from 'lodash.camelcase'; - import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema'; import { UpdateObjectInput } from '~/generated-metadata/graphql'; +import { formatString } from '~/pages/settings/data-model/utils/format-string.util'; export const settingsUpdateObjectInputSchema = objectMetadataItemSchema .pick({ @@ -17,7 +16,7 @@ export const settingsUpdateObjectInputSchema = objectMetadataItemSchema .transform((value) => ({ ...value, nameSingular: value.labelSingular - ? camelCase(value.labelSingular) + ? formatString(value.labelSingular) : undefined, - namePlural: value.labelPlural ? camelCase(value.labelPlural) : undefined, + namePlural: value.labelPlural ? formatString(value.labelPlural) : undefined, })); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts new file mode 100644 index 000000000000..25c200b5217f --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts @@ -0,0 +1,50 @@ +import { ChararactersNotSupportedException } from 'src/engine/metadata-modules/errors/CharactersNotSupportedException'; +import { + formatString, + validPattern, +} from 'src/engine/metadata-modules/utils/format-string.util'; + +describe('formatString', () => { + it('leaves strings unchanged if only latin characters', () => { + const input = 'testName'; + + expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input)).toEqual(input); + }); + + it('leaves strings unchanged if only latin characters and digits', () => { + const input = 'testName123'; + + expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input)).toEqual(input); + }); + + it('format strings with non latin characters', () => { + const input = 'בְרִבְרִ'; + const expected = 'bRibRi'; + + expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input)).toEqual(expected); + }); + + it('format strings with mixed characters', () => { + const input = 'aa2בְרִבְרִ'; + const expected = 'aa2BRibRi'; + + expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input)).toEqual(expected); + }); + + it('throws error if starts with digits', () => { + const input = '123string'; + + try { + formatString(input); + } catch (error: any) { + expect(error.name).toBe(ChararactersNotSupportedException.name); + + return; + } + throw new Error('formatString should have thrown'); + }); +}); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/format-string.util.ts b/packages/twenty-front/src/pages/settings/data-model/utils/format-string.util.ts new file mode 100644 index 000000000000..95be4bdf9fc4 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/utils/format-string.util.ts @@ -0,0 +1,20 @@ +import toCamelCase from 'lodash.camelcase'; +import { slugify, transliterate } from 'transliteration'; + +import { isDefined } from '~/utils/isDefined'; + +const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/; + +export const formatString = (string: string): string => { + let formattedString = string; + + if (isDefined(formattedString.match(VALID_STRING_PATTERN))) { + return toCamelCase(formattedString); + } + + formattedString = toCamelCase( + slugify(transliterate(formattedString, { trim: true })), + ); + + return formattedString; +}; diff --git a/yarn.lock b/yarn.lock index d25a236a53bf..9408bdd80f45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -46276,6 +46276,8 @@ __metadata: "twenty-front@workspace:packages/twenty-front": version: 0.0.0-use.local resolution: "twenty-front@workspace:packages/twenty-front" + dependencies: + transliteration: "npm:^2.3.5" languageName: unknown linkType: soft @@ -46312,7 +46314,6 @@ __metadata: passport: "npm:^0.7.0" psl: "npm:^1.9.0" rimraf: "npm:^5.0.5" - transliteration: "npm:^2.3.5" tsconfig-paths: "npm:^4.2.0" typescript: "npm:5.3.3" languageName: unknown From 6e51aa82c5030a35b7f738bcade719cd9b512581 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Mon, 22 Apr 2024 17:35:46 +0200 Subject: [PATCH 08/12] validate object name in the back-end --- packages/twenty-server/package.json | 1 - .../errors/CharactersNotSupportedException.ts | 9 ---- .../errors/InvalidStringException.ts | 9 ++++ .../field-metadata/field-metadata.service.ts | 19 +++---- .../hooks/before-update-one-object.hook.ts | 3 -- .../object-metadata.service.ts | 4 +- .../format-object-metadata-input.util.ts | 33 ------------ .../validate-object-metadata-input.util.ts | 31 ++++++++++++ .../relation-metadata.service.ts | 13 ++--- .../utils/__tests__/format-string.spec.ts | 50 ------------------- .../utils/__tests__/validate-string.spec.ts | 36 +++++++++++++ .../utils/format-string.util.ts | 28 ----------- .../utils/validate-string.utils.ts | 9 ++++ 13 files changed, 99 insertions(+), 146 deletions(-) delete mode 100644 packages/twenty-server/src/engine/metadata-modules/errors/CharactersNotSupportedException.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/errors/InvalidStringException.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/format-object-metadata-input.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/__tests__/format-string.spec.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-string.spec.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/validate-string.utils.ts diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index 2d947360cd1b..8474b3d7ab27 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -30,7 +30,6 @@ "lodash.uniqby": "^4.7.0", "passport": "^0.7.0", "psl": "^1.9.0", - "transliteration": "^2.3.5", "tsconfig-paths": "^4.2.0" }, "devDependencies": { diff --git a/packages/twenty-server/src/engine/metadata-modules/errors/CharactersNotSupportedException.ts b/packages/twenty-server/src/engine/metadata-modules/errors/CharactersNotSupportedException.ts deleted file mode 100644 index e75ae877059c..000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/errors/CharactersNotSupportedException.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; - -export class ChararactersNotSupportedException extends BadRequestException { - constructor(string: string) { - const message = `String "${string}" contains unsupported characters`; - - super(message); - } -} diff --git a/packages/twenty-server/src/engine/metadata-modules/errors/InvalidStringException.ts b/packages/twenty-server/src/engine/metadata-modules/errors/InvalidStringException.ts new file mode 100644 index 000000000000..eabfb0140b5c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/errors/InvalidStringException.ts @@ -0,0 +1,9 @@ +import { BadRequestException } from '@nestjs/common'; + +export class InvalidStringException extends BadRequestException { + constructor(string: string) { + const message = `String "${string}" is not valid`; + + super(message); + } +} 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 09c67ba6f385..2434d49f7208 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 @@ -39,8 +39,8 @@ import { import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input'; import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { formatString } from 'src/engine/metadata-modules/utils/format-string.util'; -import { ChararactersNotSupportedException } from 'src/engine/metadata-modules/errors/CharactersNotSupportedException'; +import { validateString } from 'src/engine/metadata-modules/remote-server/utils/validate-remote-server-input'; +import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; import { FieldMetadataEntity, @@ -116,8 +116,7 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput); + this.validateFieldMetadataInput(fieldMetadataInput); const fieldAlreadyExists = await fieldMetadataRepository.findOne({ where: { @@ -298,8 +297,7 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput); + this.validateFieldMetadataInput(fieldMetadataInput); const updatableFieldInput = existingFieldMetadata.isCustom === false @@ -542,17 +540,14 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput: T): T { if (fieldMetadataInput.name) { try { - return (fieldMetadataInput = { - ...fieldMetadataInput, - name: formatString(fieldMetadataInput.name), - }); + validateString(fieldMetadataInput.name); } catch (error) { - if (error instanceof ChararactersNotSupportedException) { + if (error instanceof InvalidStringException) { console.error(error.message); throw new BadRequestException( `Characters used in name "${fieldMetadataInput.name}" are not supported`, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts index 0ba476af8ae2..53018786215e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook.ts @@ -15,7 +15,6 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/ import { UpdateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { formatObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/format-object-metadata-input.util'; @Injectable() export class BeforeUpdateOneObject @@ -101,8 +100,6 @@ export class BeforeUpdateOneObject } } - instance.update = formatObjectMetadataInput(instance.update); - this.checkIfFieldIsEditable(instance.update, objectMetadata); return instance; 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 b899d8a8059a..919dced0a973 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 @@ -59,7 +59,7 @@ import { FeatureFlagKeys, } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; -import { formatObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/format-object-metadata-input.util'; +import { validateObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; import { ObjectMetadataEntity } from './object-metadata.entity'; @@ -238,7 +238,7 @@ export class ObjectMetadataService extends TypeOrmQueryService( - objectMetadataInput: T, -): T => { - try { - return (objectMetadataInput = { - ...objectMetadataInput, - nameSingular: objectMetadataInput.nameSingular - ? formatString(objectMetadataInput.nameSingular) - : objectMetadataInput.nameSingular, - namePlural: objectMetadataInput.namePlural - ? formatString(objectMetadataInput.namePlural) - : objectMetadataInput.namePlural, - }); - } catch (error) { - if (error instanceof ChararactersNotSupportedException) { - console.error(error.message); - throw new BadRequestException( - `Characters used in name "${objectMetadataInput.nameSingular}" or "${objectMetadataInput.namePlural}" are not supported`, - ); - } else { - throw error; - } - } -}; 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 new file mode 100644 index 000000000000..73b87d697050 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts @@ -0,0 +1,31 @@ +import { BadRequestException } from '@nestjs/common'; + +import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; +import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; +import { UpdateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; +import { validateString } from 'src/engine/metadata-modules/utils/validate-string.utils'; + +export const validateObjectMetadataInput = < + T extends UpdateObjectInput | CreateObjectInput, +>( + objectMetadataInput: T, +): void => { + try { + if (objectMetadataInput.nameSingular) { + validateString(objectMetadataInput.nameSingular); + } + + if (objectMetadataInput.namePlural) { + validateString(objectMetadataInput.namePlural); + } + } catch (error) { + if (error instanceof InvalidStringException) { + console.error(error.message); + throw new BadRequestException( + `Characters used in name "${objectMetadataInput.nameSingular}" or "${objectMetadataInput.namePlural}" are not supported`, + ); + } else { + throw error; + } + } +}; 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 76df526149a4..5cf1451cd29e 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 @@ -24,8 +24,8 @@ import { import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; -import { formatString } from 'src/engine/metadata-modules/utils/format-string.util'; -import { ChararactersNotSupportedException } from 'src/engine/metadata-modules/errors/CharactersNotSupportedException'; +import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; +import { validateString } from 'src/engine/metadata-modules/utils/validate-string.utils'; import { RelationMetadataEntity, @@ -54,13 +54,10 @@ export class RelationMetadataService extends TypeOrmQueryService { - it('leaves strings unchanged if only latin characters', () => { - const input = 'testName'; - - expect(formatString(input).match(validPattern)?.length).toBe(1); - expect(formatString(input)).toEqual(input); - }); - - it('leaves strings unchanged if only latin characters and digits', () => { - const input = 'testName123'; - - expect(formatString(input).match(validPattern)?.length).toBe(1); - expect(formatString(input)).toEqual(input); - }); - - it('format strings with non latin characters', () => { - const input = 'בְרִבְרִ'; - const expected = 'bRibRi'; - - expect(formatString(input).match(validPattern)?.length).toBe(1); - expect(formatString(input)).toEqual(expected); - }); - - it('format strings with mixed characters', () => { - const input = 'aa2בְרִבְרִ'; - const expected = 'aa2BRibRi'; - - expect(formatString(input).match(validPattern)?.length).toBe(1); - expect(formatString(input)).toEqual(expected); - }); - - it('throws error if starts with digits', () => { - const input = '123string'; - - try { - formatString(input); - } catch (error: any) { - expect(error.name).toBe(ChararactersNotSupportedException.name); - - return; - } - throw new Error('formatString should have thrown'); - }); -}); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-string.spec.ts b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-string.spec.ts new file mode 100644 index 000000000000..b3307c3dfc0f --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-string.spec.ts @@ -0,0 +1,36 @@ +import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; +import { validateString } from 'src/engine/metadata-modules/utils/validate-string.utils'; + +describe('validateString', () => { + it('does not throw if string is valid', () => { + const input = 'testName'; + + expect(validateString(input)).not.toThrow; + }); + + it('throws error if string has non latin characters', () => { + const input = 'בְרִבְרִ'; + + try { + validateString(input); + } catch (error: any) { + expect(error.name).toBe(InvalidStringException.name); + + return; + } + throw new Error('validateString should have thrown'); + }); + + it('throws error if starts with digits', () => { + const input = '123string'; + + try { + validateString(input); + } catch (error: any) { + expect(error.name).toBe(InvalidStringException.name); + + return; + } + throw new Error('validateString should have thrown'); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts deleted file mode 100644 index 59efde4c3103..000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/utils/format-string.util.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { transliterate, slugify } from 'transliteration'; -import toCamelCase from 'lodash.camelcase'; - -import { ChararactersNotSupportedException } from 'src/engine/metadata-modules/errors/CharactersNotSupportedException'; - -export const validPattern = /^[a-zA-Z][a-zA-Z0-9 ]*$/; - -export const formatString = (string: string): string => { - let formattedString = string; - - if (formattedString.match(validPattern)) { - return formattedString; - } - - if (formattedString.match(validPattern)) { - return toCamelCase(formattedString); - } - - formattedString = toCamelCase( - slugify(transliterate(formattedString, { trim: true })), - ); - - if (!formattedString.match(validPattern)) { - throw new ChararactersNotSupportedException(string); - } - - return formattedString; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-string.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-string.utils.ts new file mode 100644 index 000000000000..cfa76d5fdfec --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-string.utils.ts @@ -0,0 +1,9 @@ +import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; + +const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/; + +export const validateString = (string: string) => { + if (!string.match(VALID_STRING_PATTERN)) { + throw new InvalidStringException(string); + } +}; From b7ebc2be2cdbc1e08b9b6f984e3aa541bdd2519c Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Mon, 22 Apr 2024 17:53:03 +0200 Subject: [PATCH 09/12] Fix test file for format-string --- .../utils/__tests__/format-string.spec.ts | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts index 25c200b5217f..4c3d947a9fec 100644 --- a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts +++ b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts @@ -1,21 +1,19 @@ -import { ChararactersNotSupportedException } from 'src/engine/metadata-modules/errors/CharactersNotSupportedException'; -import { - formatString, - validPattern, -} from 'src/engine/metadata-modules/utils/format-string.util'; +import { formatString } from '~/pages/settings/data-model/utils/format-string.util'; + +const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/; describe('formatString', () => { it('leaves strings unchanged if only latin characters', () => { const input = 'testName'; - expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input).match(VALID_STRING_PATTERN)?.length).toBe(1); expect(formatString(input)).toEqual(input); }); it('leaves strings unchanged if only latin characters and digits', () => { const input = 'testName123'; - expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input).match(VALID_STRING_PATTERN)?.length).toBe(1); expect(formatString(input)).toEqual(input); }); @@ -23,7 +21,7 @@ describe('formatString', () => { const input = 'בְרִבְרִ'; const expected = 'bRibRi'; - expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input).match(VALID_STRING_PATTERN)?.length).toBe(1); expect(formatString(input)).toEqual(expected); }); @@ -31,20 +29,7 @@ describe('formatString', () => { const input = 'aa2בְרִבְרִ'; const expected = 'aa2BRibRi'; - expect(formatString(input).match(validPattern)?.length).toBe(1); + expect(formatString(input).match(VALID_STRING_PATTERN)?.length).toBe(1); expect(formatString(input)).toEqual(expected); }); - - it('throws error if starts with digits', () => { - const input = '123string'; - - try { - formatString(input); - } catch (error: any) { - expect(error.name).toBe(ChararactersNotSupportedException.name); - - return; - } - throw new Error('formatString should have thrown'); - }); }); From 70a3431d07d057995d0219330b8af5e9ac4f0726 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Mon, 22 Apr 2024 18:54:11 +0200 Subject: [PATCH 10/12] Improve naming and test writing --- .../utils/formatFieldMetadataItemInput.ts | 4 +- .../settingsCreateObjectInputSchema.ts | 6 +-- .../settingsUpdateObjectInputSchema.ts | 8 ++-- .../__tests__/format-metadata-label.spec.ts | 43 +++++++++++++++++++ .../utils/__tests__/format-string.spec.ts | 35 --------------- ....util.ts => format-metadata-label.util.ts} | 2 +- .../field-metadata/field-metadata.service.ts | 1 - .../validate-object-metadata-input.util.ts | 6 +-- .../relation-metadata.service.ts | 6 +-- .../__tests__/validate-metadata-name.spec.ts | 22 ++++++++++ .../utils/__tests__/validate-string.spec.ts | 36 ---------------- ...ils.ts => validate-metadata-name.utils.ts} | 2 +- 12 files changed, 83 insertions(+), 88 deletions(-) create mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label.spec.ts delete mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts rename packages/twenty-front/src/pages/settings/data-model/utils/{format-string.util.ts => format-metadata-label.util.ts} (88%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name.spec.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-string.spec.ts rename packages/twenty-server/src/engine/metadata-modules/utils/{validate-string.utils.ts => validate-metadata-name.utils.ts} (81%) diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts index 5377c190b251..2b0f558482c0 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts @@ -1,7 +1,7 @@ import toSnakeCase from 'lodash.snakecase'; import { Field, FieldMetadataType } from '~/generated-metadata/graphql'; -import { formatString } from '~/pages/settings/data-model/utils/format-string.util'; +import { formatMetadataLabel } from '~/pages/settings/data-model/utils/format-metadata-label.util'; import { isDefined } from '~/utils/isDefined'; import { FieldMetadataOption } from '../types/FieldMetadataOption'; @@ -64,7 +64,7 @@ export const formatFieldMetadataItemInput = ( description: input.description?.trim() ?? null, icon: input.icon, label: input.label.trim(), - name: formatString(input.label.trim()), + name: formatMetadataLabel(input.label.trim()), options: options?.map((option, index) => ({ color: option.color, id: option.id, diff --git a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts index 86bb3a466460..02e7039825ba 100644 --- a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts +++ b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts @@ -1,6 +1,6 @@ import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema'; import { CreateObjectInput } from '~/generated-metadata/graphql'; -import { formatString } from '~/pages/settings/data-model/utils/format-string.util'; +import { formatMetadataLabel } from '~/pages/settings/data-model/utils/format-metadata-label.util'; export const settingsCreateObjectInputSchema = objectMetadataItemSchema .pick({ @@ -11,6 +11,6 @@ export const settingsCreateObjectInputSchema = objectMetadataItemSchema }) .transform((value) => ({ ...value, - nameSingular: formatString(value.labelSingular), - namePlural: formatString(value.labelPlural), + nameSingular: formatMetadataLabel(value.labelSingular), + namePlural: formatMetadataLabel(value.labelPlural), })); diff --git a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts index 73a80afa2c5f..3756a0f75104 100644 --- a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts +++ b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts @@ -1,6 +1,6 @@ import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema'; import { UpdateObjectInput } from '~/generated-metadata/graphql'; -import { formatString } from '~/pages/settings/data-model/utils/format-string.util'; +import { formatMetadataLabel } from '~/pages/settings/data-model/utils/format-metadata-label.util'; export const settingsUpdateObjectInputSchema = objectMetadataItemSchema .pick({ @@ -16,7 +16,9 @@ export const settingsUpdateObjectInputSchema = objectMetadataItemSchema .transform((value) => ({ ...value, nameSingular: value.labelSingular - ? formatString(value.labelSingular) + ? formatMetadataLabel(value.labelSingular) + : undefined, + namePlural: value.labelPlural + ? formatMetadataLabel(value.labelPlural) : undefined, - namePlural: value.labelPlural ? formatString(value.labelPlural) : undefined, })); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label.spec.ts b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label.spec.ts new file mode 100644 index 000000000000..1c20e6bb3c38 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label.spec.ts @@ -0,0 +1,43 @@ +import { formatMetadataLabel } from '~/pages/settings/data-model/utils/format-metadata-label.util'; + +const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/; + +describe('formatMetadataLabel', () => { + it('leaves strings unchanged if only latin characters', () => { + const input = 'testName'; + + expect(formatMetadataLabel(input).match(VALID_STRING_PATTERN)?.length).toBe( + 1, + ); + expect(formatMetadataLabel(input)).toEqual(input); + }); + + it('leaves strings unchanged if only latin characters and digits', () => { + const input = 'testName123'; + + expect(formatMetadataLabel(input).match(VALID_STRING_PATTERN)?.length).toBe( + 1, + ); + expect(formatMetadataLabel(input)).toEqual(input); + }); + + it('format strings with non latin characters', () => { + const input = 'בְרִבְרִ'; + const expected = 'bRibRi'; + + expect(formatMetadataLabel(input).match(VALID_STRING_PATTERN)?.length).toBe( + 1, + ); + expect(formatMetadataLabel(input)).toEqual(expected); + }); + + it('format strings with mixed characters', () => { + const input = 'aa2בְרִבְרִ'; + const expected = 'aa2BRibRi'; + + expect(formatMetadataLabel(input).match(VALID_STRING_PATTERN)?.length).toBe( + 1, + ); + expect(formatMetadataLabel(input)).toEqual(expected); + }); +}); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts deleted file mode 100644 index 4c3d947a9fec..000000000000 --- a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-string.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { formatString } from '~/pages/settings/data-model/utils/format-string.util'; - -const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/; - -describe('formatString', () => { - it('leaves strings unchanged if only latin characters', () => { - const input = 'testName'; - - expect(formatString(input).match(VALID_STRING_PATTERN)?.length).toBe(1); - expect(formatString(input)).toEqual(input); - }); - - it('leaves strings unchanged if only latin characters and digits', () => { - const input = 'testName123'; - - expect(formatString(input).match(VALID_STRING_PATTERN)?.length).toBe(1); - expect(formatString(input)).toEqual(input); - }); - - it('format strings with non latin characters', () => { - const input = 'בְרִבְרִ'; - const expected = 'bRibRi'; - - expect(formatString(input).match(VALID_STRING_PATTERN)?.length).toBe(1); - expect(formatString(input)).toEqual(expected); - }); - - it('format strings with mixed characters', () => { - const input = 'aa2בְרִבְרִ'; - const expected = 'aa2BRibRi'; - - expect(formatString(input).match(VALID_STRING_PATTERN)?.length).toBe(1); - expect(formatString(input)).toEqual(expected); - }); -}); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/format-string.util.ts b/packages/twenty-front/src/pages/settings/data-model/utils/format-metadata-label.util.ts similarity index 88% rename from packages/twenty-front/src/pages/settings/data-model/utils/format-string.util.ts rename to packages/twenty-front/src/pages/settings/data-model/utils/format-metadata-label.util.ts index 95be4bdf9fc4..f92253e0d772 100644 --- a/packages/twenty-front/src/pages/settings/data-model/utils/format-string.util.ts +++ b/packages/twenty-front/src/pages/settings/data-model/utils/format-metadata-label.util.ts @@ -5,7 +5,7 @@ import { isDefined } from '~/utils/isDefined'; const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/; -export const formatString = (string: string): string => { +export const formatMetadataLabel = (string: string): string => { let formattedString = string; if (isDefined(formattedString.match(VALID_STRING_PATTERN))) { 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 2434d49f7208..4e09de51b022 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 @@ -548,7 +548,6 @@ export class FieldMetadataService extends TypeOrmQueryService { try { if (objectMetadataInput.nameSingular) { - validateString(objectMetadataInput.nameSingular); + validateMetadataName(objectMetadataInput.nameSingular); } if (objectMetadataInput.namePlural) { - validateString(objectMetadataInput.namePlural); + validateMetadataName(objectMetadataInput.namePlural); } } 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 5cf1451cd29e..b65cbce87dfb 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 @@ -25,7 +25,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; -import { validateString } from 'src/engine/metadata-modules/utils/validate-string.utils'; +import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; import { RelationMetadataEntity, @@ -54,8 +54,8 @@ export class RelationMetadataService extends TypeOrmQueryService { + it('does not throw if string is valid', () => { + const input = 'testName'; + + expect(validateMetadataName(input)).not.toThrow; + }); + + it('throws error if string has non latin characters', () => { + const input = 'בְרִבְרִ'; + + expect(() => validateMetadataName(input)).toThrow(InvalidStringException); + }); + + it('throws error if starts with digits', () => { + const input = '123string'; + + expect(() => validateMetadataName(input)).toThrow(InvalidStringException); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-string.spec.ts b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-string.spec.ts deleted file mode 100644 index b3307c3dfc0f..000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-string.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; -import { validateString } from 'src/engine/metadata-modules/utils/validate-string.utils'; - -describe('validateString', () => { - it('does not throw if string is valid', () => { - const input = 'testName'; - - expect(validateString(input)).not.toThrow; - }); - - it('throws error if string has non latin characters', () => { - const input = 'בְרִבְרִ'; - - try { - validateString(input); - } catch (error: any) { - expect(error.name).toBe(InvalidStringException.name); - - return; - } - throw new Error('validateString should have thrown'); - }); - - it('throws error if starts with digits', () => { - const input = '123string'; - - try { - validateString(input); - } catch (error: any) { - expect(error.name).toBe(InvalidStringException.name); - - return; - } - throw new Error('validateString should have thrown'); - }); -}); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-string.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts similarity index 81% rename from packages/twenty-server/src/engine/metadata-modules/utils/validate-string.utils.ts rename to packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts index cfa76d5fdfec..5f56677d7028 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-string.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts @@ -2,7 +2,7 @@ import { InvalidStringException } from 'src/engine/metadata-modules/errors/Inval const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/; -export const validateString = (string: string) => { +export const validateMetadataName = (string: string) => { if (!string.match(VALID_STRING_PATTERN)) { throw new InvalidStringException(string); } From f22f6377765152a6e6bb6f8772a191f4077a1a26 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Tue, 23 Apr 2024 12:04:29 +0200 Subject: [PATCH 11/12] Throw error in formatting does not work --- .../utils/formatFieldMetadataItemInput.ts | 4 +- .../settingsCreateObjectInputSchema.ts | 8 ++- .../settingsUpdateObjectInputSchema.ts | 6 +- .../format-metadata-label-to-name.spec.ts | 57 +++++++++++++++++++ .../__tests__/format-metadata-label.spec.ts | 43 -------------- ... => format-metadata-label-to-name.util.ts} | 8 ++- 6 files changed, 74 insertions(+), 52 deletions(-) create mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label-to-name.spec.ts delete mode 100644 packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label.spec.ts rename packages/twenty-front/src/pages/settings/data-model/utils/{format-metadata-label.util.ts => format-metadata-label-to-name.util.ts} (70%) diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts index 2b0f558482c0..c87a9a5e5bc0 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemInput.ts @@ -1,7 +1,7 @@ import toSnakeCase from 'lodash.snakecase'; import { Field, FieldMetadataType } from '~/generated-metadata/graphql'; -import { formatMetadataLabel } from '~/pages/settings/data-model/utils/format-metadata-label.util'; +import { formatMetadataLabelToMetadataNameOrThrows } from '~/pages/settings/data-model/utils/format-metadata-label-to-name.util'; import { isDefined } from '~/utils/isDefined'; import { FieldMetadataOption } from '../types/FieldMetadataOption'; @@ -64,7 +64,7 @@ export const formatFieldMetadataItemInput = ( description: input.description?.trim() ?? null, icon: input.icon, label: input.label.trim(), - name: formatMetadataLabel(input.label.trim()), + name: formatMetadataLabelToMetadataNameOrThrows(input.label.trim()), options: options?.map((option, index) => ({ color: option.color, id: option.id, diff --git a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts index 02e7039825ba..f35093ab52f0 100644 --- a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts +++ b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsCreateObjectInputSchema.ts @@ -1,6 +1,6 @@ import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema'; import { CreateObjectInput } from '~/generated-metadata/graphql'; -import { formatMetadataLabel } from '~/pages/settings/data-model/utils/format-metadata-label.util'; +import { formatMetadataLabelToMetadataNameOrThrows } from '~/pages/settings/data-model/utils/format-metadata-label-to-name.util'; export const settingsCreateObjectInputSchema = objectMetadataItemSchema .pick({ @@ -11,6 +11,8 @@ export const settingsCreateObjectInputSchema = objectMetadataItemSchema }) .transform((value) => ({ ...value, - nameSingular: formatMetadataLabel(value.labelSingular), - namePlural: formatMetadataLabel(value.labelPlural), + nameSingular: formatMetadataLabelToMetadataNameOrThrows( + value.labelSingular, + ), + namePlural: formatMetadataLabelToMetadataNameOrThrows(value.labelPlural), })); diff --git a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts index 3756a0f75104..553fb2edcc5e 100644 --- a/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts +++ b/packages/twenty-front/src/modules/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema.ts @@ -1,6 +1,6 @@ import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema'; import { UpdateObjectInput } from '~/generated-metadata/graphql'; -import { formatMetadataLabel } from '~/pages/settings/data-model/utils/format-metadata-label.util'; +import { formatMetadataLabelToMetadataNameOrThrows } from '~/pages/settings/data-model/utils/format-metadata-label-to-name.util'; export const settingsUpdateObjectInputSchema = objectMetadataItemSchema .pick({ @@ -16,9 +16,9 @@ export const settingsUpdateObjectInputSchema = objectMetadataItemSchema .transform((value) => ({ ...value, nameSingular: value.labelSingular - ? formatMetadataLabel(value.labelSingular) + ? formatMetadataLabelToMetadataNameOrThrows(value.labelSingular) : undefined, namePlural: value.labelPlural - ? formatMetadataLabel(value.labelPlural) + ? formatMetadataLabelToMetadataNameOrThrows(value.labelPlural) : undefined, })); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label-to-name.spec.ts b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label-to-name.spec.ts new file mode 100644 index 000000000000..f2638968b77c --- /dev/null +++ b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label-to-name.spec.ts @@ -0,0 +1,57 @@ +import { formatMetadataLabelToMetadataNameOrThrows } from '~/pages/settings/data-model/utils/format-metadata-label.util'; + +const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/; + +describe('formatMetadataLabelToMetadataNameOrThrows', () => { + it('leaves strings unchanged if only latin characters', () => { + const input = 'testName'; + + expect( + formatMetadataLabelToMetadataNameOrThrows(input).match( + VALID_STRING_PATTERN, + )?.length, + ).toBe(1); + expect(formatMetadataLabelToMetadataNameOrThrows(input)).toEqual(input); + }); + + it('leaves strings unchanged if only latin characters and digits', () => { + const input = 'testName123'; + + expect( + formatMetadataLabelToMetadataNameOrThrows(input).match( + VALID_STRING_PATTERN, + )?.length, + ).toBe(1); + expect(formatMetadataLabelToMetadataNameOrThrows(input)).toEqual(input); + }); + + it('format strings with non latin characters', () => { + const input = 'בְרִבְרִ'; + const expected = 'bRibRi'; + + expect( + formatMetadataLabelToMetadataNameOrThrows(input).match( + VALID_STRING_PATTERN, + )?.length, + ).toBe(1); + expect(formatMetadataLabelToMetadataNameOrThrows(input)).toEqual(expected); + }); + + it('format strings with mixed characters', () => { + const input = 'aa2בְרִבְרִ'; + const expected = 'aa2BRibRi'; + + expect( + formatMetadataLabelToMetadataNameOrThrows(input).match( + VALID_STRING_PATTERN, + )?.length, + ).toBe(1); + expect(formatMetadataLabelToMetadataNameOrThrows(input)).toEqual(expected); + }); + + it('throws error if could not format', () => { + const input = '$$$***'; + + expect(() => formatMetadataLabelToMetadataNameOrThrows(input)).toThrow(); + }); +}); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label.spec.ts b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label.spec.ts deleted file mode 100644 index 1c20e6bb3c38..000000000000 --- a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { formatMetadataLabel } from '~/pages/settings/data-model/utils/format-metadata-label.util'; - -const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/; - -describe('formatMetadataLabel', () => { - it('leaves strings unchanged if only latin characters', () => { - const input = 'testName'; - - expect(formatMetadataLabel(input).match(VALID_STRING_PATTERN)?.length).toBe( - 1, - ); - expect(formatMetadataLabel(input)).toEqual(input); - }); - - it('leaves strings unchanged if only latin characters and digits', () => { - const input = 'testName123'; - - expect(formatMetadataLabel(input).match(VALID_STRING_PATTERN)?.length).toBe( - 1, - ); - expect(formatMetadataLabel(input)).toEqual(input); - }); - - it('format strings with non latin characters', () => { - const input = 'בְרִבְרִ'; - const expected = 'bRibRi'; - - expect(formatMetadataLabel(input).match(VALID_STRING_PATTERN)?.length).toBe( - 1, - ); - expect(formatMetadataLabel(input)).toEqual(expected); - }); - - it('format strings with mixed characters', () => { - const input = 'aa2בְרִבְרִ'; - const expected = 'aa2BRibRi'; - - expect(formatMetadataLabel(input).match(VALID_STRING_PATTERN)?.length).toBe( - 1, - ); - expect(formatMetadataLabel(input)).toEqual(expected); - }); -}); diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/format-metadata-label.util.ts b/packages/twenty-front/src/pages/settings/data-model/utils/format-metadata-label-to-name.util.ts similarity index 70% rename from packages/twenty-front/src/pages/settings/data-model/utils/format-metadata-label.util.ts rename to packages/twenty-front/src/pages/settings/data-model/utils/format-metadata-label-to-name.util.ts index f92253e0d772..84e5bbc0432f 100644 --- a/packages/twenty-front/src/pages/settings/data-model/utils/format-metadata-label.util.ts +++ b/packages/twenty-front/src/pages/settings/data-model/utils/format-metadata-label-to-name.util.ts @@ -5,7 +5,9 @@ import { isDefined } from '~/utils/isDefined'; const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/; -export const formatMetadataLabel = (string: string): string => { +export const formatMetadataLabelToMetadataNameOrThrows = ( + string: string, +): string => { let formattedString = string; if (isDefined(formattedString.match(VALID_STRING_PATTERN))) { @@ -16,5 +18,9 @@ export const formatMetadataLabel = (string: string): string => { slugify(transliterate(formattedString, { trim: true })), ); + if (!formattedString.match(VALID_STRING_PATTERN)) { + throw new Error(`"${string}" is not a valid name`); + } + return formattedString; }; From 961b92498a39a95505fc2e2f160102cb555d874b Mon Sep 17 00:00:00 2001 From: Weiko Date: Tue, 23 Apr 2024 12:38:28 +0200 Subject: [PATCH 12/12] fix renamed util --- .../utils/__tests__/format-metadata-label-to-name.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label-to-name.spec.ts b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label-to-name.spec.ts index f2638968b77c..c2bdda8a5826 100644 --- a/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label-to-name.spec.ts +++ b/packages/twenty-front/src/pages/settings/data-model/utils/__tests__/format-metadata-label-to-name.spec.ts @@ -1,4 +1,4 @@ -import { formatMetadataLabelToMetadataNameOrThrows } from '~/pages/settings/data-model/utils/format-metadata-label.util'; +import { formatMetadataLabelToMetadataNameOrThrows } from '~/pages/settings/data-model/utils/format-metadata-label-to-name.util'; const VALID_STRING_PATTERN = /^[a-zA-Z][a-zA-Z0-9 ]*$/;