From c9fd19469516527de690d9bfaaeefea0c3e32ad6 Mon Sep 17 00:00:00 2001 From: Weiko Date: Thu, 28 Nov 2024 10:21:03 +0100 Subject: [PATCH] Fix index renaming (#8771) Fixes https://github.com/twentyhq/twenty/issues/8760 ## Context Index names are based on table names and column names, which means renaming an object (or a field) should also trigger a recompute of index names. As of today it raises a bug where you can't create an object with a name that was previously used. I also took the occasion to refactor a bit the part where we create and update (after renaming) relations. Basically the only relations we want to affect are standard relations so I've aligned the logic with sync-metadata which uses standardId as a source of truth to simplify the code. Note: We don't create index for custom relations Next step should be to do that and update that code to update the index name as well. Also note that we need to update the sync-metadata logic for that as well --- .../index-metadata/index-metadata.service.ts | 101 +++++ .../object-metadata/object-metadata.module.ts | 2 + .../object-metadata.service.ts | 113 +++--- .../object-metadata-migration.service.ts | 201 ++------- .../object-metadata-relation.service.ts | 383 +++++++++++++----- 5 files changed, 487 insertions(+), 313 deletions(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts index e6d095e9e..c0ab6d405 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts @@ -13,6 +13,7 @@ import { generateDeterministicIndexName } from 'src/engine/metadata-modules/inde import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; import { + WorkspaceMigrationIndexAction, WorkspaceMigrationIndexActionType, WorkspaceMigrationTableAction, WorkspaceMigrationTableActionType, @@ -103,6 +104,55 @@ export class IndexMetadataService { ); } + async recomputeIndexMetadataForObject( + workspaceId: string, + updatedObjectMetadata: ObjectMetadataEntity, + ) { + const indexesToRecompute = await this.indexMetadataRepository.find({ + where: { + objectMetadataId: updatedObjectMetadata.id, + workspaceId, + }, + relations: ['indexFieldMetadatas.fieldMetadata'], + }); + + const recomputedIndexes: { + indexMetadata: IndexMetadataEntity; + previousName: string; + newName: string; + }[] = []; + + for (const index of indexesToRecompute) { + const previousIndexName = index.name; + const tableName = computeObjectTargetTable(updatedObjectMetadata); + + const indexFieldsMetadataOrdered = index.indexFieldMetadatas.sort( + (a, b) => a.order - b.order, + ); + + const columnNames = indexFieldsMetadataOrdered.map( + (indexFieldMetadata) => indexFieldMetadata.fieldMetadata.name, + ); + + const newIndexName = `IDX_${generateDeterministicIndexName([ + tableName, + ...columnNames, + ])}`; + + await this.indexMetadataRepository.update(index.id, { + name: newIndexName, + }); + + recomputedIndexes.push({ + indexMetadata: index, + previousName: previousIndexName, + newName: newIndexName, + }); + } + + return recomputedIndexes; + } + async deleteIndexMetadata( workspaceId: string, objectMetadata: ObjectMetadataEntity, @@ -179,4 +229,55 @@ export class IndexMetadataService { [migration], ); } + + async createIndexRecomputeMigrations( + workspaceId: string, + objectMetadata: ObjectMetadataEntity, + recomputedIndexes: { + indexMetadata: IndexMetadataEntity; + previousName: string; + newName: string; + }[], + ) { + for (const recomputedIndex of recomputedIndexes) { + const { previousName, newName, indexMetadata } = recomputedIndex; + + const tableName = computeObjectTargetTable(objectMetadata); + + const indexFieldsMetadataOrdered = indexMetadata.indexFieldMetadatas.sort( + (a, b) => a.order - b.order, + ); + + const columnNames = indexFieldsMetadataOrdered.map( + (indexFieldMetadata) => indexFieldMetadata.fieldMetadata.name, + ); + + const migration = { + name: tableName, + action: WorkspaceMigrationTableActionType.ALTER_INDEXES, + indexes: [ + { + action: WorkspaceMigrationIndexActionType.DROP, + name: previousName, + columns: [], + isUnique: indexMetadata.isUnique, + } satisfies WorkspaceMigrationIndexAction, + { + action: WorkspaceMigrationIndexActionType.CREATE, + columns: columnNames, + name: newName, + isUnique: indexMetadata.isUnique, + where: indexMetadata.indexWhereClause, + type: indexMetadata.indexType, + } satisfies WorkspaceMigrationIndexAction, + ], + } satisfies WorkspaceMigrationTableAction; + + await this.workspaceMigrationService.createCustomMigration( + generateMigrationName(`update-${objectMetadata.nameSingular}-index`), + workspaceId, + [migration], + ); + } + } } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts index 6b0074d1e..2c2cac74a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts @@ -13,6 +13,7 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature- import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module'; import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook'; import { ObjectMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor'; import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metadata/object-metadata.resolver'; @@ -49,6 +50,7 @@ import { UpdateObjectPayload } from './dtos/update-object.input'; WorkspaceMetadataVersionModule, RemoteTableRelationsModule, SearchModule, + IndexMetadataModule, ], services: [ ObjectMetadataService, 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 9a82a9515..cd5c8e377 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 @@ -10,6 +10,7 @@ import { FindManyOptions, FindOneOptions, In, Not, Repository } from 'typeorm'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service'; import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input'; import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; import { @@ -26,7 +27,6 @@ import { validateObjectMetadataInputOrThrow, } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service'; -import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util'; import { SearchService } from 'src/engine/metadata-modules/search/search.service'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; @@ -55,6 +55,7 @@ export class ObjectMetadataService extends TypeOrmQueryService - this.objectMetadataRelationService.createMetadata( - objectMetadataInput.workspaceId, - createdObjectMetadata, - mapUdtNameToFieldType( - objectMetadataInput.primaryKeyColumnType ?? 'uuid', - ), - objectMetadataInput.primaryKeyFieldMetadataSettings, - relationType, - ), - ), - ); - - await this.objectMetadataMigrationService.createRelationMigrations( - createdObjectMetadata, - createdRelatedObjectMetadata, - ); - } - private async handleObjectNameAndLabelUpdates( existingObjectMetadata: ObjectMetadataEntity, objectMetadataForUpdate: ObjectMetadataEntity, @@ -447,17 +430,37 @@ export class ObjectMetadataService extends TypeOrmQueryService - relations.map((relation) => relation.toObjectMetadataId), - ); - - const foreignKeyFieldMetadataForStandardRelation = - await this.fieldMetadataRepository.find({ - where: { - isCustom: false, - settings: { - isForeignKey: true, - }, - name: `${existingObjectMetadata.nameSingular}Id`, - workspaceId: workspaceId, - }, - }); - - await Promise.all( - foreignKeyFieldMetadataForStandardRelation.map( - async (foreignKeyFieldMetadata) => { - if ( - relatedObjectsIds.includes( - foreignKeyFieldMetadata.objectMetadataId, - ) - ) { - const relatedObject = - await this.objectMetadataRepository.findOneBy({ - id: foreignKeyFieldMetadata.objectMetadataId, - workspaceId: workspaceId, - }); - - if (relatedObject) { - // 1. Update to and from relation fieldMetadata - const toFieldRelationFieldMetadataId = - await this.fieldMetadataRepository - .findOneByOrFail({ - name: existingObjectMetadata.nameSingular, - objectMetadataId: relatedObject.id, - workspaceId: workspaceId, - }) - .then((field) => field.id); - - const { description: descriptionForToField } = - buildDescriptionForRelationFieldMetadataOnToField({ - relationObjectMetadataNamePlural: relatedObject.namePlural, - targetObjectLabelSingular: - updatedObjectMetadata.labelSingular, - }); - - await this.fieldMetadataRepository.update( - toFieldRelationFieldMetadataId, - { - name: updatedObjectMetadata.nameSingular, - label: updatedObjectMetadata.labelSingular, - description: descriptionForToField, - }, - ); - - const fromFieldRelationFieldMetadataId = - await this.relationMetadataRepository - .findOneByOrFail({ - fromObjectMetadataId: existingObjectMetadata.id, - toObjectMetadataId: relatedObject.id, - toFieldMetadataId: toFieldRelationFieldMetadataId, - workspaceId, - }) - .then((relation) => relation?.fromFieldMetadataId); - - await this.fieldMetadataRepository.update( - fromFieldRelationFieldMetadataId, - { - description: - buildDescriptionForRelationFieldMetadataOnFromField({ - relationObjectMetadataNamePlural: - relatedObject.namePlural, - targetObjectLabelSingular: - updatedObjectMetadata.labelSingular, - }).description, - }, - ); - - // 2. Update foreign key fieldMetadata - const { - name: updatedNameForForeignKeyFieldMetadata, - label: updatedLabelForForeignKeyFieldMetadata, - description: updatedDescriptionForForeignKeyFieldMetadata, - } = buildNameLabelAndDescriptionForForeignKeyFieldMetadata({ - targetObjectNameSingular: updatedObjectMetadata.nameSingular, - targetObjectLabelSingular: - updatedObjectMetadata.labelSingular, - relatedObjectLabelSingular: relatedObject.labelSingular, - }); - - await this.fieldMetadataRepository.update( - foreignKeyFieldMetadata.id, - { - name: updatedNameForForeignKeyFieldMetadata, - label: updatedLabelForForeignKeyFieldMetadata, - description: updatedDescriptionForForeignKeyFieldMetadata, - }, - ); - - const relatedObjectTableName = - computeObjectTargetTable(relatedObject); - const columnName = `${existingObjectMetadata.nameSingular}Id`; - const columnType = fieldMetadataTypeToColumnType( - foreignKeyFieldMetadata.type, - ); - - await this.workspaceMigrationService.createCustomMigration( - generateMigrationName( - `rename-${existingObjectMetadata.nameSingular}-to-${updatedObjectMetadata.nameSingular}-in-${relatedObject.nameSingular}`, - ), - workspaceId, - [ - { - name: relatedObjectTableName, - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.ALTER, - currentColumnDefinition: { - columnName, - columnType, - isNullable: true, - defaultValue: null, - }, - alteredColumnDefinition: { - columnName: `${updatedObjectMetadata.nameSingular}Id`, - columnType, - isNullable: true, - defaultValue: null, - }, - }, - ], - }, - ], - ); - } - } - }, + await this.workspaceMigrationService.createCustomMigration( + generateMigrationName( + `rename-${existingObjectMetadata.nameSingular}-to-${updatedObjectMetadata.nameSingular}-in-${relatedObjectMetadata.nameSingular}`, ), + workspaceId, + [ + { + name: relatedObjectTableName, + action: WorkspaceMigrationTableActionType.ALTER, + columns: [ + { + action: WorkspaceMigrationColumnActionType.ALTER, + currentColumnDefinition: { + columnName, + columnType, + isNullable: true, + defaultValue: null, + }, + alteredColumnDefinition: { + columnName: `${updatedObjectMetadata.nameSingular}Id`, + columnType, + isNullable: true, + defaultValue: null, + }, + }, + ], + }, + ], ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service.ts index 74dac774e..a720a59e8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service.ts @@ -18,17 +18,27 @@ import { RelationMetadataType, RelationOnDeleteAction, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.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 { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { createForeignKeyDeterministicUuid, createRelationDeterministicUuid, } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util'; import { capitalize } from 'src/utils/capitalize'; +const DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS = [ + STANDARD_OBJECT_IDS.timelineActivity, + STANDARD_OBJECT_IDS.favorite, + STANDARD_OBJECT_IDS.attachment, + STANDARD_OBJECT_IDS.noteTarget, + STANDARD_OBJECT_IDS.taskTarget, +]; + @Injectable() export class ObjectMetadataRelationService { constructor( @@ -40,46 +50,63 @@ export class ObjectMetadataRelationService { private readonly relationMetadataRepository: Repository, ) {} - public async createMetadata( + public async createRelationsAndForeignKeysMetadata( + workspaceId: string, + createdObjectMetadata: ObjectMetadataEntity, + { primaryKeyFieldMetadataSettings, primaryKeyColumnType }, + ) { + const relatedObjectMetadataCollection = await Promise.all( + DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS.map( + async (relationObjectMetadataStandardId) => + this.createRelationAndForeignKeyMetadata( + workspaceId, + createdObjectMetadata, + mapUdtNameToFieldType(primaryKeyColumnType ?? 'uuid'), + primaryKeyFieldMetadataSettings, + relationObjectMetadataStandardId, + ), + ), + ); + + return relatedObjectMetadataCollection; + } + + private async createRelationAndForeignKeyMetadata( workspaceId: string, createdObjectMetadata: ObjectMetadataEntity, objectPrimaryKeyType: FieldMetadataType, objectPrimaryKeyFieldSettings: | FieldMetadataSettings | undefined, - relatedObjectMetadataName: string, + relationObjectMetadataStandardId: string, ) { const relatedObjectMetadata = await this.objectMetadataRepository.findOneByOrFail({ - nameSingular: relatedObjectMetadataName, + standardId: relationObjectMetadataStandardId, workspaceId: workspaceId, + isCustom: false, }); - await this.createForeignKeyFieldMetadata( - workspaceId, - createdObjectMetadata, - relatedObjectMetadata, - objectPrimaryKeyType, - objectPrimaryKeyFieldSettings, - ); + const relationFieldMetadataCollection = + await this.createRelationFieldMetadas( + workspaceId, + createdObjectMetadata, + relatedObjectMetadata, + objectPrimaryKeyType, + objectPrimaryKeyFieldSettings, + ); - const relationFieldMetadata = await this.createRelationFields( + await this.createRelationMetadataFromFieldMetadatas( workspaceId, createdObjectMetadata, relatedObjectMetadata, - ); - - await this.createRelationMetadata( - workspaceId, - createdObjectMetadata, - relatedObjectMetadata, - relationFieldMetadata, + relationFieldMetadataCollection, ); return relatedObjectMetadata; } - private async createForeignKeyFieldMetadata( + private async createRelationFieldMetadas( workspaceId: string, createdObjectMetadata: ObjectMetadataEntity, relatedObjectMetadata: ObjectMetadataEntity, @@ -88,99 +115,187 @@ export class ObjectMetadataRelationService { | FieldMetadataSettings | undefined, ) { - const customStandardFieldId = - STANDARD_OBJECT_FIELD_IDS[relatedObjectMetadata.nameSingular].custom; - - if (!customStandardFieldId) { - throw new Error( - `Custom standard field ID not found for ${relatedObjectMetadata.nameSingular}`, - ); - } - - const { name, label, description } = - buildNameLabelAndDescriptionForForeignKeyFieldMetadata({ - targetObjectNameSingular: createdObjectMetadata.nameSingular, - targetObjectLabelSingular: createdObjectMetadata.labelSingular, - relatedObjectLabelSingular: relatedObjectMetadata.labelSingular, - }); - - await this.fieldMetadataRepository.save({ - standardId: createForeignKeyDeterministicUuid({ - objectId: createdObjectMetadata.id, - standardId: customStandardFieldId, - }), - objectMetadataId: relatedObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: objectPrimaryKeyType, - name, - label, - description, - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, - }); - } - - private async createRelationFields( - workspaceId: string, - createdObjectMetadata: ObjectMetadataEntity, - relatedObjectMetadata: ObjectMetadataEntity, - ) { - return await this.fieldMetadataRepository.save([ - this.createFromField( + return this.fieldMetadataRepository.save([ + this.buildFromFieldMetadata( workspaceId, createdObjectMetadata, relatedObjectMetadata, ), - this.createToField( + this.buildToFieldMetadata( workspaceId, createdObjectMetadata, relatedObjectMetadata, ), + this.buildForeignKeyFieldMetadata( + workspaceId, + createdObjectMetadata, + relatedObjectMetadata, + objectPrimaryKeyType, + objectPrimaryKeyFieldSettings, + ), ]); } - private createFromField( + public async updateRelationsAndForeignKeysMetadata( workspaceId: string, - createdObjectMetadata: ObjectMetadataEntity, + updatedObjectMetadata: ObjectMetadataEntity, + ): Promise< + { + relatedObjectMetadata: ObjectMetadataEntity; + foreignKeyFieldMetadata: FieldMetadataEntity; + toFieldMetadata: FieldMetadataEntity; + fromFieldMetadata: FieldMetadataEntity; + }[] + > { + return await Promise.all( + DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS.map( + async (relationObjectMetadataStandardId) => + this.updateRelationAndForeignKeyMetadata( + workspaceId, + updatedObjectMetadata, + relationObjectMetadataStandardId, + ), + ), + ); + } + + private async updateRelationAndForeignKeyMetadata( + workspaceId: string, + updatedObjectMetadata: ObjectMetadataEntity, + relationObjectMetadataStandardId: string, + ) { + const relatedObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + standardId: relationObjectMetadataStandardId, + workspaceId: workspaceId, + isCustom: false, + }); + + const toFieldMetadataUpdateCriteria = { + standardId: createRelationDeterministicUuid({ + objectId: updatedObjectMetadata.id, + standardId: + STANDARD_OBJECT_FIELD_IDS[relatedObjectMetadata.nameSingular].custom, + }), + objectMetadataId: relatedObjectMetadata.id, + workspaceId: workspaceId, + }; + const toFieldMetadataUpdateData = this.buildToFieldMetadata( + workspaceId, + updatedObjectMetadata, + relatedObjectMetadata, + true, + ); + const toFieldMetadataToUpdate = + await this.fieldMetadataRepository.findOneBy( + toFieldMetadataUpdateCriteria, + ); + const toFieldMetadata = await this.fieldMetadataRepository.save({ + ...toFieldMetadataToUpdate, + ...toFieldMetadataUpdateData, + }); + + const fromFieldMetadataUpdateCriteria = { + standardId: + CUSTOM_OBJECT_STANDARD_FIELD_IDS[relatedObjectMetadata.namePlural], + objectMetadataId: updatedObjectMetadata.id, + workspaceId: workspaceId, + }; + const fromFieldMetadataUpdateData = this.buildFromFieldMetadata( + workspaceId, + updatedObjectMetadata, + relatedObjectMetadata, + true, + ); + const fromFieldMetadataToUpdate = + await this.fieldMetadataRepository.findOneBy( + fromFieldMetadataUpdateCriteria, + ); + const fromFieldMetadata = await this.fieldMetadataRepository.save({ + ...fromFieldMetadataToUpdate, + ...fromFieldMetadataUpdateData, + }); + + const foreignKeyFieldMetadataUpdateCriteria = { + standardId: createForeignKeyDeterministicUuid({ + objectId: updatedObjectMetadata.id, + standardId: + STANDARD_OBJECT_FIELD_IDS[relatedObjectMetadata.nameSingular].custom, + }), + objectMetadataId: relatedObjectMetadata.id, + workspaceId: workspaceId, + }; + const foreignKeyFieldMetadataUpdateData = this.buildForeignKeyFieldMetadata( + workspaceId, + updatedObjectMetadata, + relatedObjectMetadata, + FieldMetadataType.UUID, + undefined, + true, + ); + const foreignKeyFieldMetadataToUpdate = + await this.fieldMetadataRepository.findOneBy( + foreignKeyFieldMetadataUpdateCriteria, + ); + const foreignKeyFieldMetadata = await this.fieldMetadataRepository.save({ + ...foreignKeyFieldMetadataToUpdate, + ...foreignKeyFieldMetadataUpdateData, + }); + + return { + relatedObjectMetadata, + foreignKeyFieldMetadata, + toFieldMetadata, + fromFieldMetadata, + }; + } + + private buildFromFieldMetadata( + workspaceId: string, + objectMetadata: ObjectMetadataEntity, relatedObjectMetadata: ObjectMetadataEntity, + isUpdate = false, ) { const relationObjectMetadataNamePlural = relatedObjectMetadata.namePlural; const { description } = buildDescriptionForRelationFieldMetadataOnFromField( { relationObjectMetadataNamePlural, - targetObjectLabelSingular: createdObjectMetadata.labelSingular, + targetObjectLabelSingular: objectMetadata.labelSingular, }, ); return { - standardId: - CUSTOM_OBJECT_STANDARD_FIELD_IDS[relationObjectMetadataNamePlural], - objectMetadataId: createdObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - isSystem: true, - type: FieldMetadataType.RELATION, - name: relatedObjectMetadata.namePlural, - label: capitalize(relationObjectMetadataNamePlural), description, - icon: - STANDARD_OBJECT_ICONS[relatedObjectMetadata.nameSingular] || - 'IconBuildingSkyscraper', - isNullable: true, + ...(!isUpdate + ? { + standardId: + CUSTOM_OBJECT_STANDARD_FIELD_IDS[ + relationObjectMetadataNamePlural + ], + objectMetadataId: objectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + isSystem: true, + type: FieldMetadataType.RELATION, + name: relatedObjectMetadata.namePlural, + label: capitalize(relationObjectMetadataNamePlural), + description, + icon: + STANDARD_OBJECT_ICONS[relatedObjectMetadata.nameSingular] || + 'IconBuildingSkyscraper', + isNullable: true, + } + : {}), }; } - private createToField( + private buildToFieldMetadata( workspaceId: string, - createdObjectMetadata: ObjectMetadataEntity, + objectMetadata: ObjectMetadataEntity, relatedObjectMetadata: ObjectMetadataEntity, + isUpdate = false, ) { const customStandardFieldId = STANDARD_OBJECT_FIELD_IDS[relatedObjectMetadata.nameSingular].custom; @@ -193,35 +308,96 @@ export class ObjectMetadataRelationService { const { description } = buildDescriptionForRelationFieldMetadataOnToField({ relationObjectMetadataNamePlural: relatedObjectMetadata.namePlural, - targetObjectLabelSingular: createdObjectMetadata.labelSingular, + targetObjectLabelSingular: objectMetadata.labelSingular, }); return { - standardId: createRelationDeterministicUuid({ - objectId: createdObjectMetadata.id, - standardId: customStandardFieldId, - }), - objectMetadataId: relatedObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - isSystem: true, - type: FieldMetadataType.RELATION, - name: createdObjectMetadata.nameSingular, - label: createdObjectMetadata.labelSingular, + name: objectMetadata.nameSingular, + label: objectMetadata.labelSingular, description, - icon: 'IconBuildingSkyscraper', - isNullable: true, + ...(!isUpdate + ? { + standardId: createRelationDeterministicUuid({ + objectId: objectMetadata.id, + standardId: customStandardFieldId, + }), + objectMetadataId: relatedObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + isSystem: true, + type: FieldMetadataType.RELATION, + name: objectMetadata.nameSingular, + label: objectMetadata.labelSingular, + description, + icon: 'IconBuildingSkyscraper', + isNullable: true, + } + : {}), }; } - private async createRelationMetadata( + private buildForeignKeyFieldMetadata( + workspaceId: string, + objectMetadata: ObjectMetadataEntity, + relatedObjectMetadata: ObjectMetadataEntity, + objectPrimaryKeyType: FieldMetadataType, + objectPrimaryKeyFieldSettings: + | FieldMetadataSettings + | undefined, + isUpdate = false, + ) { + const customStandardFieldId = + STANDARD_OBJECT_FIELD_IDS[relatedObjectMetadata.nameSingular].custom; + + if (!customStandardFieldId) { + throw new Error( + `Custom standard field ID not found for ${relatedObjectMetadata.nameSingular}`, + ); + } + + const { name, label, description } = + buildNameLabelAndDescriptionForForeignKeyFieldMetadata({ + targetObjectNameSingular: objectMetadata.nameSingular, + targetObjectLabelSingular: objectMetadata.labelSingular, + relatedObjectLabelSingular: relatedObjectMetadata.labelSingular, + }); + + return { + name, + label, + description, + ...(!isUpdate + ? { + standardId: createForeignKeyDeterministicUuid({ + objectId: objectMetadata.id, + standardId: customStandardFieldId, + }), + objectMetadataId: relatedObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: objectPrimaryKeyType, + name, + label, + description, + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + } + : {}), + }; + } + + private async createRelationMetadataFromFieldMetadatas( workspaceId: string, createdObjectMetadata: ObjectMetadataEntity, relatedObjectMetadata: ObjectMetadataEntity, - relationFieldMetadata: FieldMetadataEntity[], + relationFieldMetadataCollection: FieldMetadataEntity[], ) { - const relationFieldMetadataMap = relationFieldMetadata.reduce( + const relationFieldMetadataMap = relationFieldMetadataCollection.reduce( (acc, fieldMetadata: FieldMetadataEntity) => { if (fieldMetadata.type === FieldMetadataType.RELATION) { acc[fieldMetadata.objectMetadataId] = fieldMetadata; @@ -247,7 +423,10 @@ export class ObjectMetadataRelationService { ]); } - async updateObjectRelationships(objectMetadataId: string, isActive: boolean) { + async updateObjectRelationshipsActivationStatus( + objectMetadataId: string, + isActive: boolean, + ) { const affectedRelations = await this.relationMetadataRepository.find({ where: [ { fromObjectMetadataId: objectMetadataId },