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 9637f05229a8..9974403628bd 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 @@ -20,6 +20,7 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature- import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module'; import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook'; +import { RemoteTableRelationsModule } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module'; import { ObjectMetadataService } from './object-metadata.service'; import { ObjectMetadataEntity } from './object-metadata.entity'; @@ -43,6 +44,7 @@ import { ObjectMetadataDTO } from './dtos/object-metadata.dto'; WorkspaceMigrationRunnerModule, WorkspaceCacheVersionModule, FeatureFlagModule, + RemoteTableRelationsModule, ], services: [ObjectMetadataService], resolvers: [ 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 af23476fd5be..90c9b8fa1047 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 @@ -49,14 +49,13 @@ import { createForeignKeyDeterministicUuid, createRelationDeterministicUuid, } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util'; -import { createWorkspaceMigrationsForCustomObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util'; -import { createWorkspaceMigrationsForRemoteObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util'; +import { buildMigrationsForCustomObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util'; import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { validateObjectMetadataInputOrThrow } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util'; import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; -import { createMigrationToAlterCommentOnForeignKeyDeletion } from 'src/engine/metadata-modules/object-metadata/utils/create-migration-for-foreign-key-comment-alteration.util'; +import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service'; import { ObjectMetadataEntity } from './object-metadata.entity'; @@ -74,6 +73,8 @@ export class ObjectMetadataService extends TypeOrmQueryService, + private readonly remoteTableRelationsService: RemoteTableRelationsService, + private readonly dataSourceService: DataSourceService, private readonly typeORMService: TypeORMService, private readonly workspaceMigrationService: WorkspaceMigrationService, @@ -123,113 +124,18 @@ export class ObjectMetadataService extends TypeOrmQueryService 0) { - await this.relationMetadataRepository.delete( - relationsToDelete.map((relation) => relation.id), + // DELETE RELATIONS + if (objectMetadata.isRemote) { + await this.remoteTableRelationsService.deleteForeignKeysMetadataAndCreateMigrations( + objectMetadata.workspaceId, + objectMetadata, ); - } - - for (const relationToDelete of relationsToDelete) { - const foreignKeyFieldsToDelete = await this.fieldMetadataRepository.find({ - where: { - name: `${relationToDelete.toFieldMetadataName}Id`, - objectMetadataId: relationToDelete.toObjectMetadataId, - workspaceId, - }, - }); - - const foreignKeyFieldsToDeleteIds = foreignKeyFieldsToDelete.map( - (field) => field.id, - ); - - await this.fieldMetadataRepository.delete([ - ...foreignKeyFieldsToDeleteIds, - relationToDelete.fromFieldMetadataId, - relationToDelete.toFieldMetadataId, - ]); - - if (relationToDelete.direction === 'from') { - await this.workspaceMigrationService.createCustomMigration( - generateMigrationName( - `delete-${relationToDelete.fromObjectName}-${relationToDelete.toObjectName}`, - ), - workspaceId, - [ - { - name: computeTableName( - relationToDelete.toObjectName, - relationToDelete.toObjectMetadataIsCustom, - ), - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.DROP, - columnName: computeColumnName( - relationToDelete.toFieldMetadataName, - { isForeignKey: true }, - ), - } satisfies WorkspaceMigrationColumnDrop, - ], - }, - ], - ); - - // for remote objects, we need to update the comment of the foreign key column - if (objectMetadata.isRemote) { - await createMigrationToAlterCommentOnForeignKeyDeletion( - this.dataSourceService, - this.typeORMService, - this.workspaceMigrationService, - workspaceId, - relationToDelete, - ); - } - } + } else { + await this.deleteAllRelationsAndDropTable(objectMetadata, workspaceId); } await this.objectMetadataRepository.delete(objectMetadata.id); - if (!objectMetadata.isRemote) { - // DROP TABLE - await this.workspaceMigrationService.createCustomMigration( - generateMigrationName(`delete-${objectMetadata.nameSingular}`), - workspaceId, - [ - { - name: computeObjectTargetTable(objectMetadata), - action: WorkspaceMigrationTableActionType.DROP, - }, - ], - ); - } - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( workspaceId, ); @@ -371,10 +277,19 @@ export class ObjectMetadataService extends TypeOrmQueryService | undefined, - isRemoteObject: boolean, ) { const activityTargetObjectMetadata = await this.objectMetadataRepository.findOneByOrFail({ @@ -607,70 +506,68 @@ export class ObjectMetadataService extends TypeOrmQueryService { - if (fieldMetadata.type === FieldMetadataType.RELATION) { - acc[fieldMetadata.objectMetadataId] = fieldMetadata; - } - - return acc; - }, - {}, - ); - - await this.relationMetadataRepository.save([ + const activityTargetRelationFieldMetadata = + await this.fieldMetadataRepository.save([ + // FROM { + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.activityTargets, + objectMetadataId: createdObjectMetadata.id, workspaceId: workspaceId, - relationType: RelationMetadataType.ONE_TO_MANY, - fromObjectMetadataId: createdObjectMetadata.id, - toObjectMetadataId: activityTargetObjectMetadata.id, - fromFieldMetadataId: - activityTargetRelationFieldMetadataMap[createdObjectMetadata.id].id, - toFieldMetadataId: - activityTargetRelationFieldMetadataMap[ - activityTargetObjectMetadata.id - ].id, - onDeleteAction: RelationOnDeleteAction.CASCADE, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: 'activityTargets', + label: 'Activities', + description: `Activities tied to the ${createdObjectMetadata.labelSingular}`, + icon: 'IconCheckbox', + isNullable: true, + }, + // TO + { + standardId: createRelationDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: activityTargetObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: createdObjectMetadata.nameSingular, + label: createdObjectMetadata.labelSingular, + description: `ActivityTarget ${createdObjectMetadata.labelSingular}`, + icon: 'IconBuildingSkyscraper', + isNullable: true, }, ]); - } + + const activityTargetRelationFieldMetadataMap = + activityTargetRelationFieldMetadata.reduce( + (acc, fieldMetadata: FieldMetadataEntity) => { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ + { + workspaceId: workspaceId, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: activityTargetObjectMetadata.id, + fromFieldMetadataId: + activityTargetRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + activityTargetRelationFieldMetadataMap[ + activityTargetObjectMetadata.id + ].id, + onDeleteAction: RelationOnDeleteAction.CASCADE, + }, + ]); return { activityTargetObjectMetadata }; } @@ -682,7 +579,6 @@ export class ObjectMetadataService extends TypeOrmQueryService | undefined, - isRemoteObject: boolean, ) { const attachmentObjectMetadata = await this.objectMetadataRepository.findOneByOrFail({ @@ -713,68 +609,66 @@ export class ObjectMetadataService extends TypeOrmQueryService { - if (fieldMetadata.type === FieldMetadataType.RELATION) { - acc[fieldMetadata.objectMetadataId] = fieldMetadata; - } - - return acc; - }, - {}, - ); - - await this.relationMetadataRepository.save([ + const attachmentRelationFieldMetadata = + await this.fieldMetadataRepository.save([ + // FROM + { + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.attachments, + objectMetadataId: createdObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: 'attachments', + label: 'Attachments', + description: `Attachments tied to the ${createdObjectMetadata.labelSingular}`, + icon: 'IconFileImport', + isNullable: true, + }, + // TO { + standardId: createRelationDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: ATTACHMENT_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: attachmentObjectMetadata.id, workspaceId: workspaceId, - relationType: RelationMetadataType.ONE_TO_MANY, - fromObjectMetadataId: createdObjectMetadata.id, - toObjectMetadataId: attachmentObjectMetadata.id, - fromFieldMetadataId: - attachmentRelationFieldMetadataMap[createdObjectMetadata.id].id, - toFieldMetadataId: - attachmentRelationFieldMetadataMap[attachmentObjectMetadata.id].id, - onDeleteAction: RelationOnDeleteAction.CASCADE, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: createdObjectMetadata.nameSingular, + label: createdObjectMetadata.labelSingular, + description: `Attachment ${createdObjectMetadata.labelSingular}`, + icon: 'IconBuildingSkyscraper', + isNullable: true, }, ]); - } + + const attachmentRelationFieldMetadataMap = + attachmentRelationFieldMetadata.reduce( + (acc, fieldMetadata: FieldMetadataEntity) => { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ + { + workspaceId: workspaceId, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: attachmentObjectMetadata.id, + fromFieldMetadataId: + attachmentRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + attachmentRelationFieldMetadataMap[attachmentObjectMetadata.id].id, + onDeleteAction: RelationOnDeleteAction.CASCADE, + }, + ]); return { attachmentObjectMetadata }; } @@ -786,7 +680,6 @@ export class ObjectMetadataService extends TypeOrmQueryService | undefined, - isRemoteObject: boolean, ) { const timelineActivityObjectMetadata = await this.objectMetadataRepository.findOneByOrFail({ @@ -817,71 +710,68 @@ export class ObjectMetadataService extends TypeOrmQueryService { - if (fieldMetadata.type === FieldMetadataType.RELATION) { - acc[fieldMetadata.objectMetadataId] = fieldMetadata; - } - - return acc; - }, - {}, - ); - - await this.relationMetadataRepository.save([ + const timelineActivityRelationFieldMetadata = + await this.fieldMetadataRepository.save([ + // FROM { + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.timelineActivities, + objectMetadataId: createdObjectMetadata.id, workspaceId: workspaceId, - relationType: RelationMetadataType.ONE_TO_MANY, - fromObjectMetadataId: createdObjectMetadata.id, - toObjectMetadataId: timelineActivityObjectMetadata.id, - fromFieldMetadataId: - timelineActivityRelationFieldMetadataMap[createdObjectMetadata.id] - .id, - toFieldMetadataId: - timelineActivityRelationFieldMetadataMap[ - timelineActivityObjectMetadata.id - ].id, - onDeleteAction: RelationOnDeleteAction.CASCADE, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: 'timelineActivities', + label: 'Timeline Activities', + description: `Timeline Activities tied to the ${createdObjectMetadata.labelSingular}`, + icon: 'IconTimeline', + isNullable: true, + }, + // TO + { + standardId: createRelationDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: timelineActivityObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: createdObjectMetadata.nameSingular, + label: createdObjectMetadata.labelSingular, + description: `Timeline Activity ${createdObjectMetadata.labelSingular}`, + icon: 'IconBuildingSkyscraper', + isNullable: true, }, ]); - } + + const timelineActivityRelationFieldMetadataMap = + timelineActivityRelationFieldMetadata.reduce( + (acc, fieldMetadata: FieldMetadataEntity) => { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ + { + workspaceId: workspaceId, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: timelineActivityObjectMetadata.id, + fromFieldMetadataId: + timelineActivityRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + timelineActivityRelationFieldMetadataMap[ + timelineActivityObjectMetadata.id + ].id, + onDeleteAction: RelationOnDeleteAction.CASCADE, + }, + ]); return { timelineActivityObjectMetadata }; } @@ -893,7 +783,6 @@ export class ObjectMetadataService extends TypeOrmQueryService | undefined, - isRemoteObject: boolean, ) { const favoriteObjectMetadata = await this.objectMetadataRepository.findOneByOrFail({ @@ -924,70 +813,165 @@ export class ObjectMetadataService extends TypeOrmQueryService { - if (fieldMetadata.type === FieldMetadataType.RELATION) { - acc[fieldMetadata.objectMetadataId] = fieldMetadata; - } - - return acc; - }, - {}, - ); - - await this.relationMetadataRepository.save([ + const favoriteRelationFieldMetadata = + await this.fieldMetadataRepository.save([ + // FROM + { + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.favorites, + objectMetadataId: createdObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + isSystem: true, + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + description: `Favorites tied to the ${createdObjectMetadata.labelSingular}`, + icon: 'IconHeart', + isNullable: true, + }, + // TO { + standardId: createRelationDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: FAVORITE_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: favoriteObjectMetadata.id, workspaceId: workspaceId, - relationType: RelationMetadataType.ONE_TO_MANY, - fromObjectMetadataId: createdObjectMetadata.id, - toObjectMetadataId: favoriteObjectMetadata.id, - fromFieldMetadataId: - favoriteRelationFieldMetadataMap[createdObjectMetadata.id].id, - toFieldMetadataId: - favoriteRelationFieldMetadataMap[favoriteObjectMetadata.id].id, - onDeleteAction: RelationOnDeleteAction.CASCADE, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: createdObjectMetadata.nameSingular, + label: createdObjectMetadata.labelSingular, + description: `Favorite ${createdObjectMetadata.labelSingular}`, + icon: 'IconBuildingSkyscraper', + isNullable: true, }, ]); - } + + const favoriteRelationFieldMetadataMap = + favoriteRelationFieldMetadata.reduce( + (acc, fieldMetadata: FieldMetadataEntity) => { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ + { + workspaceId: workspaceId, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: favoriteObjectMetadata.id, + fromFieldMetadataId: + favoriteRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + favoriteRelationFieldMetadataMap[favoriteObjectMetadata.id].id, + onDeleteAction: RelationOnDeleteAction.CASCADE, + }, + ]); return { favoriteObjectMetadata }; } + + private async deleteAllRelationsAndDropTable( + objectMetadata: ObjectMetadataEntity, + workspaceId: string, + ) { + const relationsToDelete: RelationToDelete[] = []; + + // TODO: Most of this logic should be moved to relation-metadata.service.ts + for (const relation of [ + ...objectMetadata.fromRelations, + ...objectMetadata.toRelations, + ]) { + relationsToDelete.push({ + id: relation.id, + fromFieldMetadataId: relation.fromFieldMetadata.id, + toFieldMetadataId: relation.toFieldMetadata.id, + fromFieldMetadataName: relation.fromFieldMetadata.name, + toFieldMetadataName: relation.toFieldMetadata.name, + fromObjectMetadataId: relation.fromObjectMetadata.id, + toObjectMetadataId: relation.toObjectMetadata.id, + fromObjectName: relation.fromObjectMetadata.nameSingular, + toObjectName: relation.toObjectMetadata.nameSingular, + toFieldMetadataIsCustom: relation.toFieldMetadata.isCustom, + toObjectMetadataIsCustom: relation.toObjectMetadata.isCustom, + direction: + relation.fromObjectMetadata.nameSingular === + objectMetadata.nameSingular + ? 'from' + : 'to', + }); + } + + if (relationsToDelete.length > 0) { + await this.relationMetadataRepository.delete( + relationsToDelete.map((relation) => relation.id), + ); + } + + for (const relationToDelete of relationsToDelete) { + const foreignKeyFieldsToDelete = await this.fieldMetadataRepository.find({ + where: { + name: `${relationToDelete.toFieldMetadataName}Id`, + objectMetadataId: relationToDelete.toObjectMetadataId, + workspaceId, + }, + }); + + const foreignKeyFieldsToDeleteIds = foreignKeyFieldsToDelete.map( + (field) => field.id, + ); + + await this.fieldMetadataRepository.delete([ + ...foreignKeyFieldsToDeleteIds, + relationToDelete.fromFieldMetadataId, + relationToDelete.toFieldMetadataId, + ]); + + if (relationToDelete.direction === 'from') { + await this.workspaceMigrationService.createCustomMigration( + generateMigrationName( + `delete-${relationToDelete.fromObjectName}-${relationToDelete.toObjectName}`, + ), + workspaceId, + [ + { + name: computeTableName( + relationToDelete.toObjectName, + relationToDelete.toObjectMetadataIsCustom, + ), + action: WorkspaceMigrationTableActionType.ALTER, + columns: [ + { + action: WorkspaceMigrationColumnActionType.DROP, + columnName: computeColumnName( + relationToDelete.toFieldMetadataName, + { isForeignKey: true }, + ), + } satisfies WorkspaceMigrationColumnDrop, + ], + }, + ], + ); + } + } + + // DROP TABLE + await this.workspaceMigrationService.createCustomMigration( + generateMigrationName(`delete-${objectMetadata.nameSingular}`), + workspaceId, + [ + { + name: computeObjectTargetTable(objectMetadata), + action: WorkspaceMigrationTableActionType.DROP, + }, + ], + ); + } } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/create-migration-for-foreign-key-comment-alteration.util.spec.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/create-migration-for-foreign-key-comment-alteration.util.spec.ts deleted file mode 100644 index 5c5a5a6265c9..000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/create-migration-for-foreign-key-comment-alteration.util.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { buildAlteredCommentOnForeignKeyDeletion } from 'src/engine/metadata-modules/object-metadata/utils/create-migration-for-foreign-key-comment-alteration.util'; - -describe('buildAlteredCommentOnForeignKeyDeletion', () => { - const localObjectMetadataName = 'favorite'; - const remoteObjectMetadataName = 'blog'; - const schema = 'schema'; - const workspaceDataSource = { - query: jest.fn(), - }; - - it('should return null if no comment ', async () => { - workspaceDataSource.query.mockResolvedValueOnce([]); - - const result = await buildAlteredCommentOnForeignKeyDeletion( - localObjectMetadataName, - remoteObjectMetadataName, - schema, - workspaceDataSource as any, - ); - - expect(result).toBeNull(); - }); - - it('should return null if the existing comment does not contain foreign keys', async () => { - workspaceDataSource.query.mockResolvedValueOnce([ - { col_description: '@graphql({"totalCount":{"enabled":true}})' }, - ]); - - const result = await buildAlteredCommentOnForeignKeyDeletion( - localObjectMetadataName, - remoteObjectMetadataName, - schema, - workspaceDataSource as any, - ); - - expect(result).toBeNull(); - }); - - it('should return altered comment without foreign key', async () => { - const existingComment = { - col_description: `@graphql({"totalCount":{"enabled":true},"foreign_keys":[{"local_name":"favoriteCollection","local_columns":["${remoteObjectMetadataName}Id"],"foreign_name":"${remoteObjectMetadataName}","foreign_schema":"schema","foreign_table":"${remoteObjectMetadataName}","foreign_columns":["id"]}]})`, - }; - - workspaceDataSource.query.mockResolvedValueOnce([existingComment]); - - const result = await buildAlteredCommentOnForeignKeyDeletion( - localObjectMetadataName, - remoteObjectMetadataName, - schema, - workspaceDataSource as any, - ); - - expect(result).toBe( - '@graphql({"totalCount":{"enabled":true},"foreign_keys":[]})', - ); - }); - - it('should return altered comment without the input foreign key', async () => { - const existingComment = { - col_description: `@graphql({"totalCount":{"enabled":true},"foreign_keys":[{"local_name":"favoriteCollection","local_columns":["${remoteObjectMetadataName}Id"],"foreign_name":"${remoteObjectMetadataName}","foreign_schema":"schema","foreign_table":"${remoteObjectMetadataName}","foreign_columns":["id"]}, {"local_name":"favoriteCollection","local_columns":["testId"],"foreign_name":"test","foreign_schema":"schema","foreign_table":"test","foreign_columns":["id"]}]})`, - }; - - workspaceDataSource.query.mockResolvedValueOnce([existingComment]); - - const result = await buildAlteredCommentOnForeignKeyDeletion( - localObjectMetadataName, - remoteObjectMetadataName, - schema, - workspaceDataSource as any, - ); - - expect(result).toBe( - '@graphql({"totalCount":{"enabled":true},"foreign_keys":[{"local_name":"favoriteCollection","local_columns":["testId"],"foreign_name":"test","foreign_schema":"schema","foreign_table":"test","foreign_columns":["id"]}]})', - ); - }); -}); diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts similarity index 98% rename from packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util.ts rename to packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts index 8c05bcfe43a6..653a53178940 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts @@ -9,7 +9,7 @@ import { } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; -export const createWorkspaceMigrationsForCustomObjectRelations = ( +export const buildMigrationsForCustomObjectRelations = ( createdObjectMetadata: ObjectMetadataEntity, activityTargetObjectMetadata: ObjectMetadataEntity, attachmentObjectMetadata: ObjectMetadataEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-migration-for-foreign-key-comment-alteration.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-migration-for-foreign-key-comment-alteration.util.ts deleted file mode 100644 index f42dcbff672b..000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-migration-for-foreign-key-comment-alteration.util.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { DataSource } from 'typeorm'; - -import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { RelationToDelete } from 'src/engine/metadata-modules/relation-metadata/types/relation-to-delete'; -import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; -import { - WorkspaceMigrationTableActionType, - WorkspaceMigrationColumnActionType, - WorkspaceMigrationCreateComment, -} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; -import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; - -export const buildAlteredCommentOnForeignKeyDeletion = async ( - localObjectMetadataName: string, - remoteObjectMetadataName: string, - schema: string, - workspaceDataSource: DataSource | undefined, -): Promise => { - const existingComment = await workspaceDataSource?.query( - `SELECT col_description('${schema}."${localObjectMetadataName}"'::regclass, 0)`, - ); - - if (!existingComment[0]?.col_description) { - return null; - } - - const commentWithoutGraphQL = existingComment[0].col_description - .replace('@graphql(', '') - .replace(')', ''); - - const parsedComment = JSON.parse(commentWithoutGraphQL); - - const currentForeignKeys = parsedComment.foreign_keys; - - if (!currentForeignKeys) { - return null; - } - - const updatedForeignKeys = currentForeignKeys.filter( - (foreignKey: any) => - foreignKey.foreign_name !== remoteObjectMetadataName && - foreignKey.foreign_table !== remoteObjectMetadataName, - ); - - parsedComment.foreign_keys = updatedForeignKeys; - - return `@graphql(${JSON.stringify(parsedComment)})`; -}; - -export const createMigrationToAlterCommentOnForeignKeyDeletion = async ( - dataSourceService: DataSourceService, - typeORMService: TypeORMService, - workspaceMigrationService: WorkspaceMigrationService, - workspaceId: string, - relationToDelete: RelationToDelete, -) => { - const dataSourceMetadata = - await dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( - workspaceId, - ); - - const workspaceDataSource = - await typeORMService.connectToDataSource(dataSourceMetadata); - - const alteredComment = await buildAlteredCommentOnForeignKeyDeletion( - relationToDelete.toObjectName, - relationToDelete.fromObjectName, - dataSourceMetadata.schema, - workspaceDataSource, - ); - - if (alteredComment) { - await workspaceMigrationService.createCustomMigration( - generateMigrationName( - `alter-comment-${relationToDelete.fromObjectName}-${relationToDelete.toObjectName}`, - ), - workspaceId, - [ - { - name: computeTableName( - relationToDelete.toObjectName, - relationToDelete.toObjectMetadataIsCustom, - ), - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.CREATE_COMMENT, - comment: alteredComment, - } satisfies WorkspaceMigrationCreateComment, - ], - }, - ], - ); - } -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util.ts deleted file mode 100644 index d817fc54358e..000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { - WorkspaceMigrationTableAction, - WorkspaceMigrationColumnActionType, - WorkspaceMigrationColumnCreate, - WorkspaceMigrationTableActionType, -} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; -import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; - -export const createWorkspaceMigrationsForRemoteObjectRelations = async ( - createdObjectMetadata: ObjectMetadataEntity, - activityTargetObjectMetadata: ObjectMetadataEntity, - attachmentObjectMetadata: ObjectMetadataEntity, - timelineActivityObjectMetadata: ObjectMetadataEntity, - favoriteObjectMetadata: ObjectMetadataEntity, - primaryKeyColumnType: string, -): Promise => { - return [ - { - name: computeObjectTargetTable(activityTargetObjectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.CREATE, - columnName: computeColumnName(createdObjectMetadata.nameSingular, { - isForeignKey: true, - }), - columnType: primaryKeyColumnType, - isNullable: true, - } satisfies WorkspaceMigrationColumnCreate, - ], - }, - { - name: computeObjectTargetTable(attachmentObjectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.CREATE, - columnName: computeColumnName(createdObjectMetadata.nameSingular, { - isForeignKey: true, - }), - columnType: primaryKeyColumnType, - isNullable: true, - } satisfies WorkspaceMigrationColumnCreate, - ], - }, - { - name: computeObjectTargetTable(timelineActivityObjectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.CREATE, - columnName: computeColumnName(createdObjectMetadata.nameSingular, { - isForeignKey: true, - }), - columnType: primaryKeyColumnType, - isNullable: true, - } satisfies WorkspaceMigrationColumnCreate, - ], - }, - { - name: computeObjectTargetTable(favoriteObjectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.CREATE, - columnName: computeColumnName(createdObjectMetadata.nameSingular, { - isForeignKey: true, - }), - columnType: primaryKeyColumnType, - isNullable: true, - } satisfies WorkspaceMigrationColumnCreate, - ], - }, - ]; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module.ts new file mode 100644 index 000000000000..0fa3b6e47196 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service'; +import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature( + [ObjectMetadataEntity, FieldMetadataEntity], + 'metadata', + ), + WorkspaceMigrationModule, + ], + providers: [RemoteTableRelationsService], + exports: [RemoteTableRelationsService], +}) +export class RemoteTableRelationsModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service.ts new file mode 100644 index 000000000000..96fc92d53d95 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service.ts @@ -0,0 +1,327 @@ +import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common'; + +import { In, Repository } from 'typeorm'; + +import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; + +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 { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; +import { + FieldMetadataEntity, + FieldMetadataType, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { createForeignKeyDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util'; +import { + ACTIVITY_TARGET_STANDARD_FIELD_IDS, + ATTACHMENT_STANDARD_FIELD_IDS, + FAVORITE_STANDARD_FIELD_IDS, + TIMELINE_ACTIVITY_STANDARD_FIELD_IDS, +} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; +import { buildMigrationsToCreateRemoteTableRelations } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-create-remote-table-relations.util'; +import { buildMigrationsToRemoveRemoteTableRelations } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-remove-remote-table-relations.util'; +import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util'; +import { createRelationForeignKeyFieldMetadataName } from 'src/engine/metadata-modules/relation-metadata/utils/create-relation-foreign-key-field-metadata-name.util'; + +@Injectable() +export class RemoteTableRelationsService { + constructor( + @InjectRepository(ObjectMetadataEntity, 'metadata') + private readonly objectMetadataRepository: Repository, + + @InjectRepository(FieldMetadataEntity, 'metadata') + private readonly fieldMetadataRepository: Repository, + private readonly workspaceMigrationService: WorkspaceMigrationService, + ) {} + + public async createForeignKeysMetadataAndMigrations( + workspaceId: string, + remoteObjectMetadata: ObjectMetadataEntity, + objectPrimaryKeyFieldSettings: + | FieldMetadataSettings + | undefined, + objectPrimaryKeyColumnType?: string, + ) { + const objectPrimaryKeyFieldType = mapUdtNameToFieldType( + objectPrimaryKeyColumnType ?? 'uuid', + ); + + const favoriteObjectMetadata = await this.createFavoriteRelation( + workspaceId, + remoteObjectMetadata, + objectPrimaryKeyFieldType, + objectPrimaryKeyFieldSettings, + ); + + const activityTargetObjectMetadata = + await this.createActivityTargetRelation( + workspaceId, + remoteObjectMetadata, + objectPrimaryKeyFieldType, + objectPrimaryKeyFieldSettings, + ); + + const attachmentObjectMetadata = await this.createAttachmentRelation( + workspaceId, + remoteObjectMetadata, + objectPrimaryKeyFieldType, + objectPrimaryKeyFieldSettings, + ); + + const timelineActivityObjectMetadata = + await this.createTimelineActivityRelation( + workspaceId, + remoteObjectMetadata, + objectPrimaryKeyFieldType, + objectPrimaryKeyFieldSettings, + ); + + // create migration to add foreign key columns + await this.workspaceMigrationService.createCustomMigration( + generateMigrationName( + `add-foreign-keys-${remoteObjectMetadata.nameSingular}`, + ), + workspaceId, + buildMigrationsToCreateRemoteTableRelations( + remoteObjectMetadata.nameSingular, + [ + favoriteObjectMetadata, + activityTargetObjectMetadata, + attachmentObjectMetadata, + timelineActivityObjectMetadata, + ], + objectPrimaryKeyColumnType ?? 'uuid', + ), + ); + } + + public async deleteForeignKeysMetadataAndCreateMigrations( + workspaceId: string, + remoteObjectMetadata: ObjectMetadataEntity, + ) { + // find favorite, activityTarget, attachment, timelineActivity objects + const favoriteObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'favorite', + workspaceId: workspaceId, + }); + + const activityTargetObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'activityTarget', + workspaceId: workspaceId, + }); + + const attachmentObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'attachment', + workspaceId: workspaceId, + }); + + const timelineActivityObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'timelineActivity', + workspaceId: workspaceId, + }); + + // compute the target column name + const targetColumnName = createRelationForeignKeyFieldMetadataName( + remoteObjectMetadata.nameSingular, + ); + + // find the foreign key fields to delete + const foreignKeyFieldsToDelete = await this.fieldMetadataRepository.find({ + where: { + name: targetColumnName, + objectMetadataId: In([ + favoriteObjectMetadata.id, + activityTargetObjectMetadata.id, + attachmentObjectMetadata.id, + timelineActivityObjectMetadata.id, + ]), + workspaceId, + }, + }); + + const foreignKeyFieldsToDeleteIds = foreignKeyFieldsToDelete.map( + (field) => field.id, + ); + + await this.fieldMetadataRepository.delete(foreignKeyFieldsToDeleteIds); + + // create migration to drop foreign key columns + await this.workspaceMigrationService.createCustomMigration( + generateMigrationName( + `delete-foreign-keys-${remoteObjectMetadata.nameSingular}`, + ), + workspaceId, + buildMigrationsToRemoveRemoteTableRelations(targetColumnName, [ + favoriteObjectMetadata, + activityTargetObjectMetadata, + attachmentObjectMetadata, + timelineActivityObjectMetadata, + ]), + ); + } + + private async createActivityTargetRelation( + workspaceId: string, + createdObjectMetadata: ObjectMetadataEntity, + objectPrimaryKeyType: FieldMetadataType, + objectPrimaryKeyFieldSettings: + | FieldMetadataSettings + | undefined, + ) { + const activityTargetObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'activityTarget', + workspaceId: workspaceId, + }); + + await this.fieldMetadataRepository.save( + // Foreign key + { + standardId: createForeignKeyDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: activityTargetObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: objectPrimaryKeyType, + name: `${createdObjectMetadata.nameSingular}Id`, + label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, + description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`, + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + }, + ); + + return activityTargetObjectMetadata; + } + + private async createAttachmentRelation( + workspaceId: string, + createdObjectMetadata: ObjectMetadataEntity, + objectPrimaryKeyType: FieldMetadataType, + objectPrimaryKeyFieldSettings: + | FieldMetadataSettings + | undefined, + ) { + const attachmentObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'attachment', + workspaceId: workspaceId, + }); + + await this.fieldMetadataRepository.save( + // Foreign key + { + standardId: createForeignKeyDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: ATTACHMENT_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: attachmentObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: objectPrimaryKeyType, + name: `${createdObjectMetadata.nameSingular}Id`, + label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, + description: `Attachment ${createdObjectMetadata.labelSingular} id foreign key`, + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + }, + ); + + return attachmentObjectMetadata; + } + + private async createTimelineActivityRelation( + workspaceId: string, + createdObjectMetadata: ObjectMetadataEntity, + objectPrimaryKeyType: FieldMetadataType, + objectPrimaryKeyFieldSettings: + | FieldMetadataSettings + | undefined, + ) { + const timelineActivityObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'timelineActivity', + workspaceId: workspaceId, + }); + + await this.fieldMetadataRepository.save( + // Foreign key + { + standardId: createForeignKeyDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: timelineActivityObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: objectPrimaryKeyType, + name: `${createdObjectMetadata.nameSingular}Id`, + label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, + description: `Timeline Activity ${createdObjectMetadata.labelSingular} id foreign key`, + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + }, + ); + + return timelineActivityObjectMetadata; + } + + private async createFavoriteRelation( + workspaceId: string, + createdObjectMetadata: ObjectMetadataEntity, + objectPrimaryKeyType: FieldMetadataType, + objectPrimaryKeyFieldSettings: + | FieldMetadataSettings + | undefined, + ) { + const favoriteObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'favorite', + workspaceId: workspaceId, + }); + + await this.fieldMetadataRepository.save( + // Foreign key + { + standardId: createForeignKeyDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: FAVORITE_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: favoriteObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: objectPrimaryKeyType, + name: `${createdObjectMetadata.nameSingular}Id`, + label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, + description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`, + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + }, + ); + + return favoriteObjectMetadata; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-create-remote-table-relations.util.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-create-remote-table-relations.util.ts new file mode 100644 index 000000000000..1cf526edf74e --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-create-remote-table-relations.util.ts @@ -0,0 +1,29 @@ +import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { + WorkspaceMigrationTableAction, + WorkspaceMigrationColumnActionType, + WorkspaceMigrationColumnCreate, + WorkspaceMigrationTableActionType, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; +import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; + +export const buildMigrationsToCreateRemoteTableRelations = ( + createdObjectNameSingular: string, + targetObjectMetadataList: ObjectMetadataEntity[], + primaryKeyColumnType: string, +): WorkspaceMigrationTableAction[] => + targetObjectMetadataList.map((targetObjectMetadata) => ({ + name: computeObjectTargetTable(targetObjectMetadata), + action: WorkspaceMigrationTableActionType.ALTER, + columns: [ + { + action: WorkspaceMigrationColumnActionType.CREATE, + columnName: computeColumnName(createdObjectNameSingular, { + isForeignKey: true, + }), + columnType: primaryKeyColumnType, + isNullable: true, + } satisfies WorkspaceMigrationColumnCreate, + ], + })); diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-remove-remote-table-relations.util.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-remove-remote-table-relations.util.ts new file mode 100644 index 000000000000..a928570d77dc --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-remove-remote-table-relations.util.ts @@ -0,0 +1,26 @@ +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { + WorkspaceMigrationTableAction, + WorkspaceMigrationTableActionType, + WorkspaceMigrationColumnActionType, + WorkspaceMigrationColumnDrop, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; + +export const buildMigrationsToRemoveRemoteTableRelations = ( + targetColumnName: string, + targetObjectMetadataList: ObjectMetadataEntity[], +): WorkspaceMigrationTableAction[] => + targetObjectMetadataList.map((objectMetadata) => ({ + name: computeTableName( + objectMetadata.nameSingular, + objectMetadata.isCustom, + ), + action: WorkspaceMigrationTableActionType.ALTER, + columns: [ + { + action: WorkspaceMigrationColumnActionType.DROP, + columnName: targetColumnName, + } satisfies WorkspaceMigrationColumnDrop, + ], + }));