From 86170c705ecc258f380f78eb65a350f9f2869f8c Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:53:46 +0200 Subject: [PATCH] Standard relation field for custom objects (#13968) ## Introduction Introducing default standard relation field for custom objects --- ...relation-properties-to-compare.constant.ts | 1 + ...and-flat-field-metadatas-to-create.util.ts | 97 +++++++ ...ject-input-to-flat-object-metadata.util.ts | 70 ----- .../object-metadata-v2.service.ts | 64 +++- .../object-metadata.service.ts | 2 +- .../object-metadata-field-relation.service.ts | 2 +- ...d-default-fields-for-custom-object.util.ts | 267 ----------------- ...-field-metadatas-for-custom-object.util.ts | 274 ++++++++++++++++++ ...-field-metadatas-for-custom-object.util.ts | 261 +++++++++++++++++ .../workspace-permissions-cache.service.ts | 13 +- .../constants/standard-field-ids.ts | 88 +++--- .../constants/standard-object-icons.ts | 2 +- .../constants/standard-object-ids.ts | 2 +- 13 files changed, 738 insertions(+), 405 deletions(-) create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-relation-properties-to-compare.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-relation-properties-to-compare.constant.ts index 6749fb43d3..2e35eabb22 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-relation-properties-to-compare.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-relation-properties-to-compare.constant.ts @@ -4,4 +4,5 @@ export const FLAT_FIELD_METADATA_RELATION_PROPERTIES_TO_COMPARE = [ 'label', 'description', 'isActive', + 'standardOverrides', ] as const satisfies FlatFieldMetadataPropertiesToCompare[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts new file mode 100644 index 0000000000..c68543d630 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts @@ -0,0 +1,97 @@ +import { + capitalize, + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties, +} from 'twenty-shared/utils'; +import { v4 } from 'uuid'; + +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { type CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; +import { buildDefaultFlatFieldMetadatasForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util'; +import { buildDefaultRelationFlatFieldMetadatasForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util'; + +type FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs = + { + createObjectInput: Omit; + workspaceId: string; + existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; + }; +export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate = + ({ + createObjectInput: rawCreateObjectInput, + workspaceId, + existingFlatObjectMetadataMaps, + }: FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs): { + flatObjectMetadataToCreate: FlatObjectMetadata; + relationTargetFlatFieldMetadatas: FlatFieldMetadata[]; + } => { + const createObjectInput = + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( + rawCreateObjectInput, + [ + 'description', + 'icon', + 'labelPlural', + 'labelSingular', + 'namePlural', + 'nameSingular', + 'shortcut', + ], + ); + + const objectMetadataId = v4(); + const baseCustomFlatFieldMetadatas = + buildDefaultFlatFieldMetadatasForCustomObject({ + objectMetadataId, + workspaceId, + }); + const createdAt = new Date(); + const flatObjectMetadataToCreate: FlatObjectMetadata = { + createdAt, + flatFieldMetadatas: [], + updatedAt: createdAt, + duplicateCriteria: null, + description: createObjectInput.description ?? null, + flatIndexMetadatas: [], + icon: createObjectInput.icon ?? null, + id: objectMetadataId, + imageIdentifierFieldMetadataId: null, + isActive: true, + isAuditLogged: true, + isCustom: true, + isLabelSyncedWithName: createObjectInput.isLabelSyncedWithName ?? false, + isRemote: createObjectInput.isRemote ?? false, + isSearchable: true, + isSystem: false, + labelIdentifierFieldMetadataId: baseCustomFlatFieldMetadatas.nameField.id, + labelPlural: capitalize(createObjectInput.labelPlural), + labelSingular: capitalize(createObjectInput.labelSingular), + namePlural: createObjectInput.namePlural, + nameSingular: createObjectInput.nameSingular, + shortcut: createObjectInput.shortcut ?? null, + standardId: null, + standardOverrides: null, + uniqueIdentifier: objectMetadataId, + targetTableName: 'DEPRECATED', + workspaceId, + }; + const { + standardSourceFlatFieldMetadatas, + standardTargetFlatFieldMetadatas, + } = buildDefaultRelationFlatFieldMetadatasForCustomObject({ + existingFlatObjectMetadataMaps, + sourceFlatObjectMetadata: flatObjectMetadataToCreate, + workspaceId, + }); + + flatObjectMetadataToCreate.flatFieldMetadatas = [ + ...Object.values(baseCustomFlatFieldMetadatas), + ...standardSourceFlatFieldMetadatas, + ]; + + return { + flatObjectMetadataToCreate, + relationTargetFlatFieldMetadatas: standardTargetFlatFieldMetadatas, + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util.ts deleted file mode 100644 index 9cb03b8870..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - capitalize, - trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties, -} from 'twenty-shared/utils'; -import { v4 } from 'uuid'; - -import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -import { type CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; -import { buildDefaultFlatFieldMetadataForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util'; - -export const fromCreateObjectInputToFlatObjectMetadata = ({ - createObjectInput: rawCreateObjectInput, - workspaceId, -}: { - createObjectInput: Omit; - workspaceId: string; -}): FlatObjectMetadata => { - const createObjectInput = - trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( - rawCreateObjectInput, - [ - 'description', - 'icon', - 'labelPlural', - 'labelSingular', - 'namePlural', - 'nameSingular', - 'shortcut', - ], - ); - - const objectMetadataId = v4(); - const baseCustomFlatFieldMetadatas = - buildDefaultFlatFieldMetadataForCustomObject({ - objectMetadataId, - workspaceId, - }); - - const createdAt = new Date(); - - return { - createdAt, - updatedAt: createdAt, - duplicateCriteria: null, - description: createObjectInput.description ?? null, - flatFieldMetadatas: Object.values(baseCustomFlatFieldMetadatas), - flatIndexMetadatas: [], - icon: createObjectInput.icon ?? null, - id: objectMetadataId, - imageIdentifierFieldMetadataId: null, - isActive: true, - isAuditLogged: true, - isCustom: true, - isLabelSyncedWithName: createObjectInput.isLabelSyncedWithName ?? false, - isRemote: createObjectInput.isRemote ?? false, - isSearchable: true, - isSystem: false, - labelIdentifierFieldMetadataId: baseCustomFlatFieldMetadatas.nameField.id, - labelPlural: capitalize(createObjectInput.labelPlural), - labelSingular: capitalize(createObjectInput.labelSingular), - namePlural: createObjectInput.namePlural, - nameSingular: createObjectInput.nameSingular, - shortcut: createObjectInput.shortcut ?? null, - standardId: null, - standardOverrides: null, - uniqueIdentifier: objectMetadataId, - targetTableName: 'DEPRECATED', - workspaceId, - }; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts index d05b7be742..5e777940a5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts @@ -2,15 +2,15 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; -import { EMPTY_FLAT_OBJECT_METADATA_MAPS } from 'src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant'; +import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util'; import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util'; import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util'; import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.util'; -import { getSubFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps.util'; +import { getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.util'; import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -import { fromCreateObjectInputToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util'; +import { fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util'; import { fromDeleteObjectInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util'; import { fromFlatObjectMetadataToObjectMetadataDto } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util'; import { fromUpdateObjectInputToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-update-object-input-to-flat-object-metadata.util'; @@ -184,26 +184,60 @@ export class ObjectMetadataServiceV2 { }, ); - const flatObjectMetadataToCreate = - fromCreateObjectInputToFlatObjectMetadata({ + const { flatObjectMetadataToCreate, relationTargetFlatFieldMetadatas } = + fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate({ createObjectInput, workspaceId, + existingFlatObjectMetadataMaps, }); - const fromFlatObjectMetadataMaps = - getSubFlatObjectMetadataMaps({ - flatObjectMetadataMaps: existingFlatObjectMetadataMaps, - objectMetadataIds: [flatObjectMetadataToCreate.id], - }) ?? EMPTY_FLAT_OBJECT_METADATA_MAPS; + const existingFlatObjectMetadataMapsWithTargetRelationFlatFieldMetadatas = + relationTargetFlatFieldMetadatas.reduce( + (flatObjectMetadataMaps, flatFieldMetadata) => + addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ + flatFieldMetadata, + flatObjectMetadataMaps, + }), + existingFlatObjectMetadataMaps, + ); + + const flatObjectMetadataMapsWithTargetRelationFlatFieldMetadatas = + getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow({ + flatFieldMetadatas: relationTargetFlatFieldMetadatas, + flatObjectMetadataMaps: + existingFlatObjectMetadataMapsWithTargetRelationFlatFieldMetadatas, + }); + + const toFlatObjectMetadataMaps = + addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({ + flatObjectMetadata: flatObjectMetadataToCreate, + flatObjectMetadataMaps: + flatObjectMetadataMapsWithTargetRelationFlatFieldMetadatas, + }); + + const impactedObjectMetadataIds = [ + ...new Set( + relationTargetFlatFieldMetadatas.map( + ({ objectMetadataId }) => objectMetadataId, + ), + ), + ]; + const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataIds: [ + ...(isDefined( + existingFlatObjectMetadataMaps.byId[flatObjectMetadataToCreate.id], + ) + ? [flatObjectMetadataToCreate.id] + : []), + ...impactedObjectMetadataIds, + ], + }); await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( { fromFlatObjectMetadataMaps, - toFlatObjectMetadataMaps: - addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: EMPTY_FLAT_OBJECT_METADATA_MAPS, - flatObjectMetadata: flatObjectMetadataToCreate, - }), + toFlatObjectMetadataMaps, buildOptions: { isSystemBuild: false, inferDeletionFromMissingObjectFieldIndex: false, 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 89b487814f..eac968b737 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 @@ -199,7 +199,7 @@ export class ObjectMetadataService extends TypeOrmQueryService field.standardId === CUSTOM_OBJECT_STANDARD_FIELD_IDS.name, )?.id; - if (!labelIdentifierFieldMetadataId) { + if (!isDefined(labelIdentifierFieldMetadataId)) { throw new ObjectMetadataException( 'Label identifier field metadata not created properly', ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts index 0a911b750e..58a2f91ce9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts @@ -28,7 +28,7 @@ import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sy import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { createRelationDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util'; -const DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS = [ +export const DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS = [ STANDARD_OBJECT_IDS.timelineActivity, STANDARD_OBJECT_IDS.favorite, STANDARD_OBJECT_IDS.attachment, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util.ts index 30d880205b..993c20328f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util.ts @@ -2,12 +2,10 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { v4 } from 'uuid'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { BASE_OBJECT_STANDARD_FIELD_IDS, CUSTOM_OBJECT_STANDARD_FIELD_IDS, } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util'; export const buildDefaultFieldsForCustomObject = ( workspaceId: string, @@ -116,268 +114,3 @@ export const buildDefaultFieldsForCustomObject = ( defaultValue: 0, }, ]; - -type BuildDefaultFlatFieldMetadataForCustomObjectArgs = { - workspaceId: string; - objectMetadataId: string; -}; - -export const buildDefaultFlatFieldMetadataForCustomObject = ({ - workspaceId, - objectMetadataId, -}: BuildDefaultFlatFieldMetadataForCustomObjectArgs) => { - const createdAt = new Date(); - const idField: FlatFieldMetadata = { - type: FieldMetadataType.UUID, - id: v4(), - isLabelSyncedWithName: false, - isUnique: true, - objectMetadataId, - uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.id, - workspaceId, - standardId: BASE_OBJECT_STANDARD_FIELD_IDS.id, - name: 'id', - label: 'Id', - icon: 'Icon123', - description: 'Id', - isNullable: false, - isActive: true, - isCustom: false, - isSystem: true, - defaultValue: 'uuid', - - createdAt, - updatedAt: createdAt, - flatRelationTargetFieldMetadata: null, - flatRelationTargetObjectMetadata: null, - options: null, - standardOverrides: null, - relationTargetFieldMetadataId: null, - relationTargetObjectMetadataId: null, - settings: null, - }; - - const nameField: FlatFieldMetadata = { - type: FieldMetadataType.TEXT, - id: v4(), - isLabelSyncedWithName: false, - isUnique: false, - objectMetadataId, - uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name, - workspaceId, - standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name, - name: 'name', - label: 'Name', - icon: 'IconAbc', - description: 'Name', - isNullable: false, - isActive: true, - isCustom: false, - isSystem: false, - defaultValue: "'Untitled'", - - createdAt, - updatedAt: createdAt, - flatRelationTargetFieldMetadata: null, - flatRelationTargetObjectMetadata: null, - options: null, - standardOverrides: null, - relationTargetFieldMetadataId: null, - relationTargetObjectMetadataId: null, - settings: null, - }; - - const createdAtField: FlatFieldMetadata = { - type: FieldMetadataType.DATE_TIME, - id: v4(), - isLabelSyncedWithName: false, - isUnique: false, - objectMetadataId, - uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt, - workspaceId, - standardId: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt, - name: 'createdAt', - label: 'Creation date', - icon: 'IconCalendar', - description: 'Creation date', - isNullable: false, - isActive: true, - isCustom: false, - isSystem: false, - defaultValue: 'now', - - createdAt, - updatedAt: createdAt, - flatRelationTargetFieldMetadata: null, - flatRelationTargetObjectMetadata: null, - options: null, - standardOverrides: null, - relationTargetFieldMetadataId: null, - relationTargetObjectMetadataId: null, - settings: null, - }; - - const updatedAtField: FlatFieldMetadata = { - type: FieldMetadataType.DATE_TIME, - id: v4(), - isLabelSyncedWithName: false, - isUnique: false, - objectMetadataId, - uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt, - workspaceId, - standardId: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt, - name: 'updatedAt', - label: 'Last update', - icon: 'IconCalendarClock', - description: 'Last time the record was changed', - isNullable: false, - isActive: true, - isCustom: false, - isSystem: false, - defaultValue: 'now', - - createdAt, - updatedAt: createdAt, - flatRelationTargetFieldMetadata: null, - flatRelationTargetObjectMetadata: null, - options: null, - standardOverrides: null, - relationTargetFieldMetadataId: null, - relationTargetObjectMetadataId: null, - settings: null, - }; - - const deletedAtField: FlatFieldMetadata = { - type: FieldMetadataType.DATE_TIME, - id: v4(), - isLabelSyncedWithName: false, - isUnique: false, - objectMetadataId, - uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt, - workspaceId, - standardId: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt, - name: 'deletedAt', - label: 'Deleted at', - icon: 'IconCalendarClock', - description: 'Deletion date', - isNullable: true, - isActive: true, - isCustom: false, - isSystem: false, - defaultValue: null, - - createdAt, - updatedAt: createdAt, - flatRelationTargetFieldMetadata: null, - flatRelationTargetObjectMetadata: null, - options: null, - standardOverrides: null, - relationTargetFieldMetadataId: null, - relationTargetObjectMetadataId: null, - settings: null, - }; - - const createdByField: FlatFieldMetadata = { - type: FieldMetadataType.ACTOR, - id: v4(), - isLabelSyncedWithName: false, - isUnique: false, - objectMetadataId, - uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy, - workspaceId, - standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy, - name: 'createdBy', - label: 'Created by', - icon: 'IconCreativeCommonsSa', - description: 'The creator of the record', - isNullable: false, - isActive: true, - isCustom: false, - isSystem: false, - defaultValue: { name: "''", source: "'MANUAL'" }, - - createdAt, - updatedAt: createdAt, - flatRelationTargetFieldMetadata: null, - flatRelationTargetObjectMetadata: null, - options: null, - standardOverrides: null, - relationTargetFieldMetadataId: null, - relationTargetObjectMetadataId: null, - settings: null, - }; - - const positionField: FlatFieldMetadata = { - type: FieldMetadataType.POSITION, - id: v4(), - isLabelSyncedWithName: false, - isUnique: false, - objectMetadataId, - uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position, - workspaceId, - standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position, - name: 'position', - label: 'Position', - icon: 'IconHierarchy2', - description: 'Position', - isNullable: false, - isActive: true, - isCustom: false, - isSystem: true, - defaultValue: 0, - - createdAt, - updatedAt: createdAt, - flatRelationTargetFieldMetadata: null, - flatRelationTargetObjectMetadata: null, - options: null, - standardOverrides: null, - relationTargetFieldMetadataId: null, - relationTargetObjectMetadataId: null, - settings: null, - }; - - const searchVectorField: FlatFieldMetadata = { - type: FieldMetadataType.TS_VECTOR, - id: v4(), - isLabelSyncedWithName: false, - isUnique: false, - objectMetadataId, - uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector, - workspaceId, - standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector, - name: 'searchVector', - label: 'Search vector', - icon: 'IconSearch', - description: 'Search vector', - isNullable: true, - isActive: true, - isCustom: false, - isSystem: true, - defaultValue: null, - - createdAt, - updatedAt: createdAt, - flatRelationTargetFieldMetadata: null, - flatRelationTargetObjectMetadata: null, - options: null, - standardOverrides: null, - relationTargetFieldMetadataId: null, - relationTargetObjectMetadataId: null, - settings: { - asExpression: getTsVectorColumnExpressionFromFields([nameField]), - generatedType: 'STORED', - }, - }; - - return { - idField, - nameField, - createdAtField, - updatedAtField, - deletedAtField, - createdByField, - positionField, - searchVectorField, - } as const; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts new file mode 100644 index 0000000000..0add0a1bd6 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts @@ -0,0 +1,274 @@ +import { FieldMetadataType } from 'twenty-shared/types'; +import { v4 } from 'uuid'; + +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { + BASE_OBJECT_STANDARD_FIELD_IDS, + CUSTOM_OBJECT_STANDARD_FIELD_IDS, +} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util'; + +type BuildDefaultFlatFieldMetadataForCustomObjectArgs = { + workspaceId: string; + objectMetadataId: string; +}; + +export const buildDefaultFlatFieldMetadatasForCustomObject = ({ + workspaceId, + objectMetadataId, +}: BuildDefaultFlatFieldMetadataForCustomObjectArgs) => { + const createdAt = new Date(); + const idField: FlatFieldMetadata = { + type: FieldMetadataType.UUID, + id: v4(), + isLabelSyncedWithName: false, + isUnique: true, + objectMetadataId, + uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.id, + workspaceId, + standardId: BASE_OBJECT_STANDARD_FIELD_IDS.id, + name: 'id', + label: 'Id', + icon: 'Icon123', + description: 'Id', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: true, + defaultValue: 'uuid', + + createdAt, + updatedAt: createdAt, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const nameField: FlatFieldMetadata = { + type: FieldMetadataType.TEXT, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name, + workspaceId, + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name, + name: 'name', + label: 'Name', + icon: 'IconAbc', + description: 'Name', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: false, + defaultValue: "'Untitled'", + + createdAt, + updatedAt: createdAt, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const createdAtField: FlatFieldMetadata = { + type: FieldMetadataType.DATE_TIME, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt, + workspaceId, + standardId: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt, + name: 'createdAt', + label: 'Creation date', + icon: 'IconCalendar', + description: 'Creation date', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: false, + defaultValue: 'now', + + createdAt, + updatedAt: createdAt, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const updatedAtField: FlatFieldMetadata = { + type: FieldMetadataType.DATE_TIME, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt, + workspaceId, + standardId: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt, + name: 'updatedAt', + label: 'Last update', + icon: 'IconCalendarClock', + description: 'Last time the record was changed', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: false, + defaultValue: 'now', + + createdAt, + updatedAt: createdAt, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const deletedAtField: FlatFieldMetadata = { + type: FieldMetadataType.DATE_TIME, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt, + workspaceId, + standardId: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt, + name: 'deletedAt', + label: 'Deleted at', + icon: 'IconCalendarClock', + description: 'Deletion date', + isNullable: true, + isActive: true, + isCustom: false, + isSystem: false, + defaultValue: null, + + createdAt, + updatedAt: createdAt, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const createdByField: FlatFieldMetadata = { + type: FieldMetadataType.ACTOR, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy, + workspaceId, + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy, + name: 'createdBy', + label: 'Created by', + icon: 'IconCreativeCommonsSa', + description: 'The creator of the record', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: false, + defaultValue: { name: "''", source: "'MANUAL'" }, + + createdAt, + updatedAt: createdAt, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const positionField: FlatFieldMetadata = { + type: FieldMetadataType.POSITION, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position, + workspaceId, + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position, + name: 'position', + label: 'Position', + icon: 'IconHierarchy2', + description: 'Position', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: true, + defaultValue: 0, + + createdAt, + updatedAt: createdAt, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + }; + + const searchVectorField: FlatFieldMetadata = { + type: FieldMetadataType.TS_VECTOR, + id: v4(), + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector, + workspaceId, + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector, + name: 'searchVector', + label: 'Search vector', + icon: 'IconSearch', + description: 'Search vector', + isNullable: true, + isActive: true, + isCustom: false, + isSystem: true, + defaultValue: null, + + createdAt, + updatedAt: createdAt, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: { + asExpression: getTsVectorColumnExpressionFromFields([nameField]), + generatedType: 'STORED', + }, + }; + + return { + idField, + nameField, + createdAtField, + updatedAtField, + deletedAtField, + createdByField, + positionField, + searchVectorField, + } as const; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts new file mode 100644 index 0000000000..3d6484fee2 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts @@ -0,0 +1,261 @@ +import { FieldMetadataType } from 'twenty-shared/types'; +import { capitalize, isDefined } from 'twenty-shared/utils'; +import { v4 } from 'uuid'; + +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { + ObjectMetadataException, + ObjectMetadataExceptionCode, +} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; +import { buildDescriptionForRelationFieldMetadataOnFromField } from 'src/engine/metadata-modules/object-metadata/utils/build-description-for-relation-field-on-from-field.util'; +import { buildDescriptionForRelationFieldMetadataOnToField } from 'src/engine/metadata-modules/object-metadata/utils/build-description-for-relation-field-on-to-field.util'; +import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type'; +import { fromFlatObjectMetadataToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util'; +import { + CUSTOM_OBJECT_STANDARD_FIELD_IDS, + STANDARD_OBJECT_FIELD_IDS, +} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons'; +import { type STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { createRelationDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util'; + +type FlatFieldMetadataRelationWithoutRelations = Omit< + FlatFieldMetadata, + 'flatRelationTargetFieldMetadata' | 'flatRelationTargetObjectMetadata' +>; + +const generateSourceFlatFieldMetadata = ({ + workspaceId, + targetFlatObjectMetadata, + sourceFlatObjectMetadata, +}: Omit< + BuildDefaultRelationFieldsForCustomObjectArgs, + 'existingFlatObjectMetadataMaps' +> & { + sourceFlatObjectMetadata: FlatObjectMetadata; + targetFlatObjectMetadata: FlatObjectMetadata; +}): FlatFieldMetadataRelationWithoutRelations => { + const { description } = buildDescriptionForRelationFieldMetadataOnFromField({ + relationObjectMetadataNamePlural: targetFlatObjectMetadata.namePlural, + targetObjectLabelSingular: sourceFlatObjectMetadata.labelSingular, + }); + + const createdAt = new Date(); + const sourceFieldMetadataId = v4(); + const targetFieldMetadataId = v4(); + const icon = + STANDARD_OBJECT_ICONS[ + targetFlatObjectMetadata.nameSingular as keyof typeof STANDARD_OBJECT_ICONS + ] || 'IconBuildingSkyscraper'; + const standardId = + CUSTOM_OBJECT_STANDARD_FIELD_IDS[ + targetFlatObjectMetadata.namePlural as keyof typeof CUSTOM_OBJECT_STANDARD_FIELD_IDS + ]; + + if (!isDefined(standardId)) { + throw new ObjectMetadataException( + `Standard field ID not found for target object ${targetFlatObjectMetadata.namePlural}`, + ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + return { + createdAt, + updatedAt: createdAt, + defaultValue: null, + description: description, + icon, + id: sourceFieldMetadataId, + isActive: true, + isCustom: false, + isLabelSyncedWithName: false, + isNullable: true, + isSystem: true, + isUnique: false, + label: capitalize(targetFlatObjectMetadata.namePlural), + name: targetFlatObjectMetadata.namePlural, + objectMetadataId: sourceFlatObjectMetadata.id, + options: null, + relationTargetFieldMetadataId: targetFieldMetadataId, + relationTargetObjectMetadataId: targetFlatObjectMetadata.id, + settings: { + relationType: RelationType.ONE_TO_MANY, + }, + standardId, + standardOverrides: null, + type: FieldMetadataType.RELATION, + uniqueIdentifier: standardId, + workspaceId, + }; +}; + +const generateTargetFlatFieldMetadata = ({ + sourceFlatObjectMetadata, + targetFlatObjectMetadata, + sourceFlatFieldMetadata, + workspaceId, +}: Omit< + BuildDefaultRelationFieldsForCustomObjectArgs, + 'existingFlatObjectMetadataMaps' +> & { + sourceFlatObjectMetadata: FlatObjectMetadata; + targetFlatObjectMetadata: FlatObjectMetadata; + sourceFlatFieldMetadata: FlatFieldMetadataRelationWithoutRelations; +}): FlatFieldMetadataRelationWithoutRelations => { + const customStandardFieldId = + STANDARD_OBJECT_FIELD_IDS[ + targetFlatObjectMetadata.nameSingular as (typeof DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS)[number] + ].custom; + + const { description } = buildDescriptionForRelationFieldMetadataOnToField({ + relationObjectMetadataNamePlural: targetFlatObjectMetadata.namePlural, + targetObjectLabelSingular: sourceFlatObjectMetadata.labelSingular, + }); + const createdAt = new Date(); + const standardId = createRelationDeterministicUuid({ + objectId: sourceFlatObjectMetadata.id, + standardId: customStandardFieldId, + }); + + return { + id: sourceFlatFieldMetadata.relationTargetFieldMetadataId, + name: sourceFlatObjectMetadata.nameSingular, + label: sourceFlatObjectMetadata.labelSingular, + description, + standardId, + objectMetadataId: targetFlatObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + isSystem: true, + type: FieldMetadataType.RELATION, + icon: 'IconBuildingSkyscraper', + isNullable: true, + settings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: RelationOnDeleteAction.CASCADE, + joinColumnName: `${sourceFlatObjectMetadata.nameSingular}Id`, + }, + createdAt, + updatedAt: createdAt, + defaultValue: null, + isLabelSyncedWithName: false, + isUnique: false, + options: null, + relationTargetFieldMetadataId: sourceFlatFieldMetadata.id, + relationTargetObjectMetadataId: sourceFlatObjectMetadata.id, + standardOverrides: null, + uniqueIdentifier: standardId, + }; +}; + +const DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS = [ + 'timelineActivity', + 'favorite', + 'attachment', + 'noteTarget', + 'taskTarget', +] as const satisfies (keyof typeof STANDARD_OBJECT_IDS)[]; + +export type BuildDefaultRelationFieldsForCustomObjectArgs = { + existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; + workspaceId: string; + sourceFlatObjectMetadata: FlatObjectMetadata; +}; + +type SourceAndTargetFlatFieldMetadatasRecord = { + standardSourceFlatFieldMetadatas: FlatFieldMetadata[]; + standardTargetFlatFieldMetadatas: FlatFieldMetadata[]; +}; +const EMPTY_SOURCE_AND_TARGET_FLAT_FIELD_METADATAS_RECORD: SourceAndTargetFlatFieldMetadatasRecord = + { + standardSourceFlatFieldMetadatas: [], + standardTargetFlatFieldMetadatas: [], + }; + +export const buildDefaultRelationFlatFieldMetadatasForCustomObject = ({ + existingFlatObjectMetadataMaps, + sourceFlatObjectMetadata, + workspaceId, +}: BuildDefaultRelationFieldsForCustomObjectArgs): SourceAndTargetFlatFieldMetadatasRecord => { + return DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS.reduce( + (sourceAndTargetFlatFieldMetadatasRecord, objectMetadataNameSingular) => { + const targetFlatObjectMetadataId = + existingFlatObjectMetadataMaps.idByNameSingular[ + objectMetadataNameSingular + ]; + + if (!isDefined(targetFlatObjectMetadataId)) { + throw new ObjectMetadataException( + `Standard target object metadata id ${targetFlatObjectMetadataId} not found in cache`, + ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + const targetFlatObjectMetadata = + findFlatObjectMetadataInFlatObjectMetadataMaps({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataId: targetFlatObjectMetadataId, + }); + + if (!isDefined(targetFlatObjectMetadata)) { + throw new ObjectMetadataException( + `Standard target object metadata of id ${targetFlatObjectMetadataId} not found in cache`, + ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + const sourceFlatFieldMetadata = generateSourceFlatFieldMetadata({ + sourceFlatObjectMetadata, + targetFlatObjectMetadata: targetFlatObjectMetadata, + workspaceId, + }); + + const targetFlatFieldMetadata = generateTargetFlatFieldMetadata({ + sourceFlatFieldMetadata, + sourceFlatObjectMetadata, + targetFlatObjectMetadata, + workspaceId, + }); + + return { + standardSourceFlatFieldMetadatas: [ + ...sourceAndTargetFlatFieldMetadatasRecord.standardSourceFlatFieldMetadatas, + { + ...sourceFlatFieldMetadata, + flatRelationTargetFieldMetadata: { + ...targetFlatFieldMetadata, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + }, + flatRelationTargetObjectMetadata: + fromFlatObjectMetadataToFlatObjectMetadataWithoutFields( + sourceFlatObjectMetadata, + ), + }, + ], + standardTargetFlatFieldMetadatas: [ + ...sourceAndTargetFlatFieldMetadatasRecord.standardTargetFlatFieldMetadatas, + { + ...targetFlatFieldMetadata, + flatRelationTargetFieldMetadata: { + ...sourceFlatFieldMetadata, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + }, + flatRelationTargetObjectMetadata: + fromFlatObjectMetadataToFlatObjectMetadataWithoutFields( + targetFlatObjectMetadata, + ), + }, + ], + }; + }, + EMPTY_SOURCE_AND_TARGET_FLAT_FIELD_METADATAS_RECORD, + ); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts index 900e71ca66..ce5fd3fe38 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts @@ -28,6 +28,11 @@ type CacheResult = { export const USER_WORKSPACE_ROLE_MAP = 'User workspace role map'; export const ROLES_PERMISSIONS = 'Roles permissions'; +const WORKFLOW_STANDARD_OBJECT_IDS = [ + STANDARD_OBJECT_IDS.workflow, + STANDARD_OBJECT_IDS.workflowRun, + STANDARD_OBJECT_IDS.workflowVersion, +] as const; @Injectable() export class WorkspacePermissionsCacheService { @@ -213,11 +218,9 @@ export class WorkspacePermissionsCacheService { if ( standardId && - [ - STANDARD_OBJECT_IDS.workflow, - STANDARD_OBJECT_IDS.workflowRun, - STANDARD_OBJECT_IDS.workflowVersion, - ].includes(standardId) + WORKFLOW_STANDARD_OBJECT_IDS.includes( + standardId as (typeof WORKFLOW_STANDARD_OBJECT_IDS)[number], + ) ) { const hasWorkflowsPermissions = this.hasWorkflowsPermissions(role); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts index f2f37d4d16..d3ab536299 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts @@ -12,7 +12,7 @@ export const ACTIVITY_TARGET_STANDARD_FIELD_IDS = { company: '20202020-7cc0-44a1-8068-f11171fdd02e', opportunity: '20202020-1fc2-4af1-8c91-7901ee0fd38b', custom: '20202020-7f21-442f-94be-32462281b1ca', -}; +} as const; // TODO: check if this can be deleted export const ACTIVITY_STANDARD_FIELD_IDS = { @@ -27,7 +27,7 @@ export const ACTIVITY_STANDARD_FIELD_IDS = { comments: '20202020-6b2e-4d29-bbd1-ecddb330e71a', author: '20202020-455f-44f2-8e89-1b0ef01cb7fb', assignee: '20202020-4259-48e4-9e77-6b92991906d5', -}; +} as const; export const ATTACHMENT_STANDARD_FIELD_IDS = { name: '20202020-87a5-48f8-bbf7-ade388825a57', @@ -41,26 +41,26 @@ export const ATTACHMENT_STANDARD_FIELD_IDS = { company: '20202020-ceab-4a28-b546-73b06b4c08d5', opportunity: '20202020-7374-499d-bea3-9354890755b5', custom: '20202020-302d-43b3-9aea-aa4f89282a9f', -}; +} as const; export const BASE_OBJECT_STANDARD_FIELD_IDS = { id: '20202020-eda0-4cee-9577-3eb357e3c22b', createdAt: '20202020-66ac-4502-9975-e4d959c50311', updatedAt: '20202020-d767-4622-bdcf-d8a084834d86', deletedAt: '20202020-b9a7-48d8-8387-b9a3090a50ec', -}; +} as const; export const BLOCKLIST_STANDARD_FIELD_IDS = { handle: '20202020-eef3-44ed-aa32-4641d7fd4a3e', workspaceMember: '20202020-548d-4084-a947-fa20a39f7c06', -}; +} as const; export const CALENDAR_CHANNEL_EVENT_ASSOCIATION_STANDARD_FIELD_IDS = { calendarChannel: '20202020-93ee-4da4-8d58-0282c4a9cb7d', calendarEvent: '20202020-5aa5-437e-bb86-f42d457783e3', eventExternalId: '20202020-9ec8-48bb-b279-21d0734a75a1', recurringEventExternalId: '20202020-c58f-4c69-9bf8-9518fa31aa50', -}; +} as const; export const CALENDAR_CHANNEL_STANDARD_FIELD_IDS = { connectedAccount: '20202020-95b1-4f44-82dc-61b042ae2414', @@ -76,7 +76,7 @@ export const CALENDAR_CHANNEL_STANDARD_FIELD_IDS = { syncStage: '20202020-6246-42e6-b5cd-003bd921782c', syncStageStartedAt: '20202020-a934-46f1-a8e7-9568b1e3a53e', syncedAt: '20202020-2ff5-4f70-953a-3d0d36357576', -}; +} as const; export const CALENDAR_EVENT_PARTICIPANT_STANDARD_FIELD_IDS = { calendarEvent: '20202020-fe3a-401c-b889-af4f4657a861', @@ -86,7 +86,7 @@ export const CALENDAR_EVENT_PARTICIPANT_STANDARD_FIELD_IDS = { responseStatus: '20202020-cec0-4be8-8fba-c366abc23147', person: '20202020-5761-4842-8186-e1898ef93966', workspaceMember: '20202020-20e4-4591-93ed-aeb17a4dcbd2', -}; +} as const; export const CALENDAR_EVENT_STANDARD_FIELD_IDS = { title: '20202020-080e-49d1-b21d-9702a7e2525c', @@ -103,14 +103,14 @@ export const CALENDAR_EVENT_STANDARD_FIELD_IDS = { conferenceLink: '20202020-35da-43ef-9ca0-e936e9dc237b', calendarChannelEventAssociations: '20202020-bdf8-4572-a2cc-ecbb6bcc3a02', calendarEventParticipants: '20202020-e07e-4ccb-88f5-6f3d00458eec', -}; +} as const; // TODO: check if this can be deleted export const COMMENT_STANDARD_FIELD_IDS = { body: '20202020-d5eb-49d2-b3e0-1ed04145ebb7', author: '20202020-2ab1-427e-a981-cf089de3a9bd', activity: '20202020-c8d9-4c30-a35e-dc7f44388070', -}; +} as const; export const COMPANY_STANDARD_FIELD_IDS = { name: '20202020-4d99-4e2e-a84c-4a27837b1ece', @@ -135,7 +135,7 @@ export const COMPANY_STANDARD_FIELD_IDS = { attachments: '20202020-c1b5-4120-b0f0-987ca401ed53', timelineActivities: '20202020-0414-4daf-9c0d-64fe7b27f89f', searchVector: '85c71601-72f9-4b7b-b343-d46100b2c74d', -}; +} as const; export const CONNECTED_ACCOUNT_STANDARD_FIELD_IDS = { handle: '20202020-c804-4a50-bb05-b3a9e24f1dec', @@ -150,7 +150,7 @@ export const CONNECTED_ACCOUNT_STANDARD_FIELD_IDS = { handleAliases: '20202020-8a3d-46be-814f-6228af16c47b', scopes: '20202020-8a3d-46be-814f-6228af16c47c', connectionParameters: '20202020-a1b2-46be-814f-6228af16c481', -}; +} as const; export const EVENT_STANDARD_FIELD_IDS = { properties: '20202020-f142-4b04-b91b-6a2b4af3bf10', @@ -159,7 +159,7 @@ export const EVENT_STANDARD_FIELD_IDS = { company: '20202020-04ad-4221-a744-7a8278a5ce20', opportunity: '20202020-7664-4a35-a3df-580d389fd5f0', custom: '20202020-4a71-41b0-9f83-9cdcca3f8b14', -}; +} as const; export const AUDIT_LOGS_STANDARD_FIELD_IDS = { name: '20202020-2462-4b9d-b5d9-745febb3b095', @@ -169,7 +169,7 @@ export const AUDIT_LOGS_STANDARD_FIELD_IDS = { objectMetadataId: '20202020-127b-409d-9864-0ec44aa9ed98', recordId: '20202020-c578-4acf-bf94-eb53b035cea2', workspaceMember: '20202020-6e96-4300-b3f5-67a707147385', -}; +} as const; export const BEHAVIORAL_EVENT_STANDARD_FIELD_IDS = { name: '20202020-2462-4b9d-b5d9-745febb3b095', @@ -177,7 +177,7 @@ export const BEHAVIORAL_EVENT_STANDARD_FIELD_IDS = { context: '20202020-bd62-4b5b-8385-6caeed8f8078', objectName: '20202020-a744-406c-a2e1-9d83d74f4341', recordId: '20202020-6d8b-4ca5-9869-f882cb335673', -}; +} as const; export const TIMELINE_ACTIVITY_STANDARD_FIELD_IDS = { happensAt: '20202020-9526-4993-b339-c4318c4d39f0', @@ -197,7 +197,7 @@ export const TIMELINE_ACTIVITY_STANDARD_FIELD_IDS = { linkedRecordCachedName: '20202020-cfdb-4bef-bbce-a29f41230934', linkedRecordId: '20202020-2e0e-48c0-b445-ee6c1e61687d', linkedObjectMetadataId: '20202020-c595-449d-9f89-562758c9ee69', -}; +} as const; export const FAVORITE_STANDARD_FIELD_IDS = { position: '20202020-dd26-42c6-8c3c-2a7598c204f6', @@ -213,13 +213,13 @@ export const FAVORITE_STANDARD_FIELD_IDS = { view: '20202020-5a93-4fa9-acce-e73481a0bbdf', custom: '20202020-855a-4bc8-9861-79deef37011f', favoriteFolder: '20202020-f658-4d12-8b4d-248356aa4bd9', -}; +} as const; export const FAVORITE_FOLDER_STANDARD_FIELD_IDS = { position: '20202020-5278-4bde-8909-2cec74d43744', name: '20202020-82a3-4537-8ff0-dbce7eec35d6', favorites: '20202020-b5e3-4b42-8af2-03cd4fd2e4d2', -}; +} as const; export const MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_STANDARD_FIELD_IDS = { messageChannel: '20202020-b658-408f-bd46-3bd2d15d7e52', @@ -227,7 +227,7 @@ export const MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_STANDARD_FIELD_IDS = { messageExternalId: '20202020-37d6-438f-b6fd-6503596c8f34', messageThread: '20202020-fac8-42a8-94dd-44dbc920ae16', messageThreadExternalId: '20202020-35fb-421e-afa0-0b8e8f7f9018', -}; +} as const; export const MESSAGE_CHANNEL_STANDARD_FIELD_IDS = { visibility: '20202020-6a6b-4532-9767-cbc61b469453', @@ -247,13 +247,13 @@ export const MESSAGE_CHANNEL_STANDARD_FIELD_IDS = { syncStage: '20202020-7979-4b08-89fe-99cb5e698767', syncStageStartedAt: '20202020-8c61-4a42-ae63-73c1c3c52e06', throttleFailureCount: '20202020-0291-42be-9ad0-d578a51684ab', -}; +} as const; export const MESSAGE_FOLDER_STANDARD_FIELD_IDS = { name: '20202020-7cf8-40bc-a681-b80b771449b7', messageChannel: '20202020-b658-408f-bd46-3bd2d15d7e52', syncCursor: '20202020-98cd-49ed-8dfc-cb5796400e64', -}; +} as const; export const MESSAGE_PARTICIPANT_STANDARD_FIELD_IDS = { message: '20202020-985b-429a-9db9-9e55f4898a2a', @@ -262,18 +262,18 @@ export const MESSAGE_PARTICIPANT_STANDARD_FIELD_IDS = { displayName: '20202020-36dd-4a4f-ac02-228425be9fac', person: '20202020-249d-4e0f-82cd-1b9df5cd3da2', workspaceMember: '20202020-77a7-4845-99ed-1bcbb478be6f', -}; +} as const; export const MESSAGE_THREAD_STANDARD_FIELD_IDS = { messages: '20202020-3115-404f-aade-e1154b28e35a', messageChannelMessageAssociations: '20202020-314e-40a4-906d-a5d5d6c285f6', messageThreadSubscribers: '20202020-3b3b-4b3b-8b3b-7f8d6a1d7d5b', -}; +} as const; export const MESSAGE_THREAD_SUBSCRIBER_STANDARD_FIELD_IDS = { messageThread: '20202020-2c8f-4f3e-8b9a-7f8d6a1c7d5b', workspaceMember: '20202020-7f7b-4b3b-8b3b-7f8d6a1d7d5a', -}; +} as const; export const MESSAGE_STANDARD_FIELD_IDS = { headerMessageId: '20202020-72b5-416d-aed8-b55609067d01', @@ -284,7 +284,7 @@ export const MESSAGE_STANDARD_FIELD_IDS = { receivedAt: '20202020-140a-4a2a-9f86-f13b6a979afc', messageParticipants: '20202020-7cff-4a74-b63c-73228448cbd9', messageChannelMessageAssociations: '20202020-3cef-43a3-82c6-50e7cfbc9ae4', -}; +} as const; export const NOTE_STANDARD_FIELD_IDS = { position: '20202020-368d-4dc2-943f-ed8a49c7fdfb', @@ -297,7 +297,7 @@ export const NOTE_STANDARD_FIELD_IDS = { timelineActivities: '20202020-7030-42f8-929c-1a57b25d6bce', favorites: '20202020-4d1d-41ac-b13b-621631298d67', searchVector: '20202020-7ea8-44d4-9d4c-51dd2a757950', -}; +} as const; export const NOTE_TARGET_STANDARD_FIELD_IDS = { note: '20202020-57f3-4f50-9599-fc0f671df003', @@ -305,7 +305,7 @@ export const NOTE_TARGET_STANDARD_FIELD_IDS = { company: 'c500fbc0-d6f2-4982-a959-5a755431696c', opportunity: '20202020-4e42-417a-a705-76581c9ade79', custom: '20202020-3d12-4579-94ee-7117c1bad492', -}; +} as const; export const OPPORTUNITY_STANDARD_FIELD_IDS = { name: '20202020-8609-4f65-a2d9-44009eb422b5', @@ -325,7 +325,7 @@ export const OPPORTUNITY_STANDARD_FIELD_IDS = { attachments: '20202020-87c7-4118-83d6-2f4031005209', timelineActivities: '20202020-30e2-421f-96c7-19c69d1cf631', searchVector: '428a0da5-4b2e-4ce3-b695-89a8b384e6e3', -}; +} as const; export const PERSON_STANDARD_FIELD_IDS = { name: '20202020-3875-44d5-8c33-a6239011cab8', @@ -352,7 +352,7 @@ export const PERSON_STANDARD_FIELD_IDS = { calendarEventParticipants: '20202020-52ee-45e9-a702-b64b3753e3a9', timelineActivities: '20202020-a43e-4873-9c23-e522de906ce5', searchVector: '57d1d7ad-fa10-44fc-82f3-ad0959ec2534', -}; +} as const; export const TASK_STANDARD_FIELD_IDS = { position: '20202020-7d47-4690-8a98-98b9a0c05dd8', @@ -368,7 +368,7 @@ export const TASK_STANDARD_FIELD_IDS = { timelineActivities: '20202020-c778-4278-99ee-23a2837aee64', favorites: '20202020-4d1d-41ac-b13b-621631298d65', searchVector: '20202020-4746-4e2f-870c-52b02c67c90d', -}; +} as const; export const TASK_TARGET_STANDARD_FIELD_IDS = { task: '20202020-e881-457a-8758-74aaef4ae78a', @@ -376,7 +376,7 @@ export const TASK_TARGET_STANDARD_FIELD_IDS = { company: '20202020-4703-4a4e-948c-487b0c60a92c', opportunity: '20202020-6cb2-4c01-a9a5-aca3dbc11d41', custom: '20202020-41c1-4c9a-8c75-be0971ef89af', -}; +} as const; export const VIEW_FIELD_STANDARD_FIELD_IDS = { fieldMetadataId: '20202020-135f-4c5b-b361-15f24870473c', @@ -385,7 +385,7 @@ export const VIEW_FIELD_STANDARD_FIELD_IDS = { position: '20202020-19e5-4e4c-8c15-3a96d1fd0650', view: '20202020-e8da-4521-afab-d6d231f9fa18', aggregateOperation: '20202020-2cd7-4f94-ae83-4a14f5731a04', -}; +} as const; export const VIEW_GROUP_STANDARD_FIELD_IDS = { fieldMetadataId: '20202020-8f26-46ae-afed-fdacd7778682', @@ -393,7 +393,7 @@ export const VIEW_GROUP_STANDARD_FIELD_IDS = { isVisible: '20202020-0fed-4b44-88fd-a064c4fcfce4', position: '20202020-748e-4645-8f32-84aae7726c04', view: '20202020-5bc7-4110-b23f-fb851fb133b4', -}; +} as const; export const VIEW_FILTER_STANDARD_FIELD_IDS = { fieldMetadataId: '20202020-c9aa-4c94-8d0e-9592f5008fb0', @@ -404,20 +404,20 @@ export const VIEW_FILTER_STANDARD_FIELD_IDS = { viewFilterGroupId: '20202020-2580-420a-8328-cab1635c0296', positionInViewFilterGroup: '20202020-3bb0-4f66-a537-a46fe0dc468f', subFieldName: '20202020-3bb0-4f66-a537-a46fe0dc469a', -}; +} as const; export const VIEW_FILTER_GROUP_STANDARD_FIELD_IDS = { view: '20202020-ff7a-4b54-8be5-aa0249047b74', parentViewFilterGroupId: '20202020-edbf-4929-8ede-64f48d6bf2a7', logicalOperator: '20202020-64d9-4bc5-85ba-c250796ce9aa', positionInViewFilterGroup: '20202020-90d6-4299-ad87-d05ddd3a0a3f', -}; +} as const; export const VIEW_SORT_STANDARD_FIELD_IDS = { fieldMetadataId: '20202020-8240-4657-aee4-7f0df8e94eca', direction: '20202020-b06e-4eb3-9b58-0a62e5d79836', view: '20202020-bd6c-422b-9167-5c105f2d02c8', -}; +} as const; export const VIEW_STANDARD_FIELD_IDS = { name: '20202020-12c6-4f37-b588-c9b9bf57328d', @@ -439,13 +439,13 @@ export const VIEW_STANDARD_FIELD_IDS = { viewSorts: '20202020-891b-45c3-9fe1-80a75b4aa043', favorites: '20202020-c818-4a86-8284-9ec0ef0a59a5', anyFieldFilterValue: '20202020-3143-46c0-bb05-034063ce0703', -}; +} as const; export const WORKFLOW_AUTOMATED_TRIGGER_STANDARD_FIELD_IDS = { type: '20202020-3319-4234-a34c-3f92c1ab56e7', settings: '20202020-3319-4234-a34c-bac8f903de12', workflow: '20202020-3319-4234-a34c-8e1a4d2f7c03', -}; +} as const; export const WORKFLOW_STANDARD_FIELD_IDS = { name: '20202020-b3d3-478f-acc0-5d901e725b20', @@ -460,7 +460,7 @@ export const WORKFLOW_STANDARD_FIELD_IDS = { timelineActivities: '20202020-906e-486a-a798-131a5f081faf', createdBy: '20202020-6007-401a-8aa5-e6f48581a6f3', searchVector: '20202020-535d-4ffa-b7f3-4fa0d5da1b7a', -}; +} as const; export const WORKFLOW_RUN_STANDARD_FIELD_IDS = { name: '20202020-b840-4253-aef9-4e5013694587', @@ -478,7 +478,7 @@ export const WORKFLOW_RUN_STANDARD_FIELD_IDS = { favorites: '20202020-4baf-4604-b899-2f7fcfbbf90d', timelineActivities: '20202020-af4d-4eb0-babc-eb960a45b356', searchVector: '20202020-0b91-4ded-b1ac-cbd5efa58cb9', -}; +} as const; export const WORKFLOW_VERSION_STANDARD_FIELD_IDS = { name: '20202020-a12f-4cca-9937-a2e40cc65509', @@ -491,7 +491,7 @@ export const WORKFLOW_VERSION_STANDARD_FIELD_IDS = { favorites: '20202020-b8e0-4e57-928d-b51671cc71f2', timelineActivities: '20202020-fcb0-4695-b17e-3b43a421c633', searchVector: '20202020-3f17-44ef-b8c1-b282ae8469b2', -}; +} as const; export const WORKSPACE_MEMBER_STANDARD_FIELD_IDS = { position: '20202020-1810-4591-a93c-d0df97dca843', @@ -520,7 +520,7 @@ export const WORKSPACE_MEMBER_STANDARD_FIELD_IDS = { timeFormat: '20202020-8acb-4cf8-a851-a6ed443c8d81', searchVector: '20202020-46d0-4e7f-bc26-74c0edaeb619', calendarStartDay: '20202020-92d0-1d7f-a126-25ededa6b142', -}; +} as const; export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = { name: '20202020-ba07-4ffd-ba63-009491f5749c', @@ -534,7 +534,7 @@ export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = { attachments: '20202020-8d59-46ca-b7b2-73d167712134', timelineActivities: '20202020-f1ef-4ba4-8f33-1a4577afa477', searchVector: '70e56537-18ef-4811-b1c7-0a444006b815', -}; +} as const; export const STANDARD_OBJECT_FIELD_IDS = { activityTarget: ACTIVITY_TARGET_STANDARD_FIELD_IDS, @@ -575,4 +575,4 @@ export const STANDARD_OBJECT_FIELD_IDS = { workflowRun: WORKFLOW_RUN_STANDARD_FIELD_IDS, workflowVersion: WORKFLOW_VERSION_STANDARD_FIELD_IDS, workspaceMember: WORKSPACE_MEMBER_STANDARD_FIELD_IDS, -}; +} as const; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons.ts index 3df2319a2a..a214c7dc8c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons.ts @@ -40,4 +40,4 @@ export const STANDARD_OBJECT_ICONS = { workflowVersion: 'IconVersions', workflowAutomatedTrigger: 'IconSettingsAutomation', workspaceMember: 'IconUserCircle', -}; +} as const; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids.ts index 3358e911bb..59cbbd689f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids.ts @@ -48,4 +48,4 @@ export const STANDARD_OBJECT_IDS = { workflowVersion: '20202020-d65d-4ab9-9344-d77bfb376a3d', workspaceMember: '20202020-3319-4234-a34c-82d5c0e881a6', workflowAutomatedTrigger: '20202020-3319-4234-a34c-7f3b9d2e4d1f', -}; +} as const;