From 35717fce8b53d3ac0f4acb85c425e3552af7059d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20M?= Date: Tue, 9 Apr 2024 10:20:34 +0200 Subject: [PATCH] feat: sync command missing ability to rename standard object (#4819) We've introduced in PR #4373 standard ids to be able to rename standard fields and objects. Fields part was working properly, but objects part was not yet implemented. This PR is adding the missing parts to make it work. --- .../field-metadata/field-metadata.service.ts | 7 +- .../object-metadata.service.ts | 5 +- ...space-migrations-for-custom-object.util.ts | 23 +-- ...space-migrations-for-remote-object.util.ts | 25 +-- .../relation-metadata.service.ts | 9 +- .../remote-table/remote-table.service.ts | 5 +- .../workspace-migration.entity.ts | 19 +- .../workspace-migration-field.factory.ts | 7 +- .../workspace-migration-object.factory.ts | 73 +++++++- .../workspace-migration-relation.factory.ts | 7 +- .../workspace-migration-runner.service.ts | 50 ++++- .../sync-workspace-metadata.command.ts | 44 +++-- .../interfaces/comparator.interface.ts | 4 +- .../workspace-metadata-updater.service.ts | 176 +++++++++++------- .../workspace-sync-object-metadata.service.ts | 7 + .../storage/workspace-sync.storage.ts | 9 +- 16 files changed, 322 insertions(+), 148 deletions(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index 670d2c1d8..67ec645e5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -18,6 +18,7 @@ import { WorkspaceMigrationColumnActionType, WorkspaceMigrationColumnDrop, WorkspaceMigrationTableAction, + WorkspaceMigrationTableActionType, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; @@ -152,7 +153,7 @@ export class FieldMetadataService extends TypeOrmQueryService [ { name: computeObjectTargetTable(createdObjectMetadata), - action: 'create', + action: WorkspaceMigrationTableActionType.CREATE, } satisfies WorkspaceMigrationTableAction, // Add activity target relation { name: computeObjectTargetTable(activityTargetObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -36,7 +37,7 @@ export const buildWorkspaceMigrationsForCustomObject = ( }, { name: computeObjectTargetTable(activityTargetObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY, @@ -52,7 +53,7 @@ export const buildWorkspaceMigrationsForCustomObject = ( // Add attachment relation { name: computeObjectTargetTable(attachmentObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -66,7 +67,7 @@ export const buildWorkspaceMigrationsForCustomObject = ( }, { name: computeObjectTargetTable(attachmentObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY, @@ -82,7 +83,7 @@ export const buildWorkspaceMigrationsForCustomObject = ( // Add event relation { name: computeObjectTargetTable(eventObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -96,7 +97,7 @@ export const buildWorkspaceMigrationsForCustomObject = ( }, { name: computeObjectTargetTable(eventObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY, @@ -112,7 +113,7 @@ export const buildWorkspaceMigrationsForCustomObject = ( // Add favorite relation { name: computeObjectTargetTable(favoriteObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -126,7 +127,7 @@ export const buildWorkspaceMigrationsForCustomObject = ( }, { name: computeObjectTargetTable(favoriteObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY, @@ -141,7 +142,7 @@ export const buildWorkspaceMigrationsForCustomObject = ( }, { name: computeObjectTargetTable(createdObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -154,7 +155,7 @@ export const buildWorkspaceMigrationsForCustomObject = ( // This is temporary until we implement mainIdentifier { name: computeObjectTargetTable(createdObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-workspace-migrations-for-remote-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-workspace-migrations-for-remote-object.util.ts index 091b71fc8..51bbdb8e3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-workspace-migrations-for-remote-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-workspace-migrations-for-remote-object.util.ts @@ -6,6 +6,7 @@ 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'; @@ -61,7 +62,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( return [ { name: computeObjectTargetTable(activityTargetObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -75,7 +76,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( }, { name: computeObjectTargetTable(activityTargetObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -88,7 +89,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( }, { name: computeObjectTargetTable(activityTargetObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE_COMMENT, @@ -104,7 +105,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( // Add attachment relation { name: computeObjectTargetTable(attachmentObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -118,7 +119,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( }, { name: computeObjectTargetTable(attachmentObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -131,7 +132,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( }, { name: computeObjectTargetTable(attachmentObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE_COMMENT, @@ -147,7 +148,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( // Add event relation { name: computeObjectTargetTable(eventObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -161,7 +162,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( }, { name: computeObjectTargetTable(eventObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -174,7 +175,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( }, { name: computeObjectTargetTable(eventObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE_COMMENT, @@ -190,7 +191,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( // Add favorite relation { name: computeObjectTargetTable(favoriteObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -204,7 +205,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( }, { name: computeObjectTargetTable(favoriteObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE, @@ -217,7 +218,7 @@ export const buildWorkspaceMigrationsForRemoteObject = async ( }, { name: computeObjectTargetTable(favoriteObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE_COMMENT, diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts index 26287772b..a5a755ad0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts @@ -17,7 +17,10 @@ import { CreateRelationInput } from 'src/engine/metadata-modules/relation-metada import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { WorkspaceMigrationColumnActionType } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; +import { + WorkspaceMigrationColumnActionType, + WorkspaceMigrationTableActionType, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; @@ -171,7 +174,7 @@ export class RelationMetadataService extends TypeOrmQueryService @@ -252,7 +253,7 @@ export class RemoteTableService { [ { name: remoteTableName, - action: 'drop_foreign_table', + action: WorkspaceMigrationTableActionType.DROP_FOREIGN_TABLE, }, ], ); diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.entity.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.entity.ts index 667d0c697..6ed2f58ba 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.entity.ts @@ -80,14 +80,21 @@ export type WorkspaceMigrationColumnAction = { | WorkspaceMigrationCreateComment ); +/** + * Enum values are lowercase to avoid issues with already existing enum values + */ +export enum WorkspaceMigrationTableActionType { + CREATE = 'create', + ALTER = 'alter', + DROP = 'drop', + CREATE_FOREIGN_TABLE = 'create_foreign_table', + DROP_FOREIGN_TABLE = 'drop_foreign_table' +} + export type WorkspaceMigrationTableAction = { name: string; - action: - | 'create' - | 'alter' - | 'drop' - | 'create_foreign_table' - | 'drop_foreign_table'; + newName?: string; + action: WorkspaceMigrationTableActionType; columns?: WorkspaceMigrationColumnAction[]; foreignTable?: WorkspaceMigrationForeignTable; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory.ts index ceecfef00..d764bbc8d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory.ts @@ -11,6 +11,7 @@ import { WorkspaceMigrationColumnActionType, WorkspaceMigrationEntity, WorkspaceMigrationTableAction, + WorkspaceMigrationTableActionType, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory'; @@ -94,7 +95,7 @@ export class WorkspaceMigrationFieldFactory { name: computeObjectTargetTable( originalObjectMetadataMap[fieldMetadata.objectMetadataId], ), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: this.workspaceMigrationFactory.createColumnActions( WorkspaceMigrationColumnActionType.CREATE, fieldMetadata, @@ -132,7 +133,7 @@ export class WorkspaceMigrationFieldFactory { fieldMetadataUpdate.current.objectMetadataId ], ), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: this.workspaceMigrationFactory.createColumnActions( WorkspaceMigrationColumnActionType.ALTER, fieldMetadataUpdate.current, @@ -171,7 +172,7 @@ export class WorkspaceMigrationFieldFactory { name: computeObjectTargetTable( originalObjectMetadataMap[fieldMetadata.objectMetadataId], ), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.DROP, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-object.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-object.factory.ts index e5839b232..3102956c1 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-object.factory.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-object.factory.ts @@ -8,11 +8,17 @@ import { WorkspaceMigrationColumnActionType, WorkspaceMigrationEntity, WorkspaceMigrationTableAction, + WorkspaceMigrationTableActionType, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; +export interface ObjectMetadataUpdate { + current: ObjectMetadataEntity; + altered: ObjectMetadataEntity; +} + @Injectable() export class WorkspaceMigrationObjectFactory { constructor( @@ -21,13 +27,35 @@ export class WorkspaceMigrationObjectFactory { async create( objectMetadataCollection: ObjectMetadataEntity[], + action: + | WorkspaceMigrationBuilderAction.CREATE + | WorkspaceMigrationBuilderAction.DELETE, + ): Promise[]>; + + async create( + objectMetadataUpdateCollection: ObjectMetadataUpdate[], + action: WorkspaceMigrationBuilderAction.UPDATE, + ): Promise[]>; + + async create( + objectMetadataCollectionOrObjectMetadataUpdateCollection: + | ObjectMetadataEntity[] + | ObjectMetadataUpdate[], action: WorkspaceMigrationBuilderAction, ): Promise[]> { switch (action) { case WorkspaceMigrationBuilderAction.CREATE: - return this.createObjectMigration(objectMetadataCollection); + return this.createObjectMigration( + objectMetadataCollectionOrObjectMetadataUpdateCollection as ObjectMetadataEntity[], + ); + case WorkspaceMigrationBuilderAction.UPDATE: + return this.updateObjectMigration( + objectMetadataCollectionOrObjectMetadataUpdateCollection as ObjectMetadataUpdate[], + ); case WorkspaceMigrationBuilderAction.DELETE: - return this.deleteObjectMigration(objectMetadataCollection); + return this.deleteObjectMigration( + objectMetadataCollectionOrObjectMetadataUpdateCollection as ObjectMetadataEntity[], + ); default: return []; } @@ -42,7 +70,7 @@ export class WorkspaceMigrationObjectFactory { const migrations: WorkspaceMigrationTableAction[] = [ { name: computeObjectTargetTable(objectMetadata), - action: 'create', + action: WorkspaceMigrationTableActionType.CREATE, }, ]; @@ -53,7 +81,7 @@ export class WorkspaceMigrationObjectFactory { migrations.push({ name: computeObjectTargetTable(objectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: this.workspaceMigrationFactory.createColumnActions( WorkspaceMigrationColumnActionType.CREATE, field, @@ -72,6 +100,40 @@ export class WorkspaceMigrationObjectFactory { return workspaceMigrations; } + private async updateObjectMigration( + objectMetadataUpdateCollection: ObjectMetadataUpdate[], + ): Promise[]> { + const workspaceMigrations: Partial[] = []; + + for (const objectMetadataUpdate of objectMetadataUpdateCollection) { + const oldTableName = computeObjectTargetTable( + objectMetadataUpdate.current, + ); + const newTableName = computeObjectTargetTable( + objectMetadataUpdate.altered, + ); + + if (oldTableName !== newTableName) { + workspaceMigrations.push({ + workspaceId: objectMetadataUpdate.current.workspaceId, + name: generateMigrationName( + `rename-${objectMetadataUpdate.current.nameSingular}`, + ), + isCustom: false, + migrations: [ + { + name: oldTableName, + newName: newTableName, + action: WorkspaceMigrationTableActionType.ALTER, + }, + ], + }); + } + } + + return workspaceMigrations; + } + private async deleteObjectMigration( objectMetadataCollection: ObjectMetadataEntity[], ): Promise[]> { @@ -81,8 +143,7 @@ export class WorkspaceMigrationObjectFactory { const migrations: WorkspaceMigrationTableAction[] = [ { name: computeObjectTargetTable(objectMetadata), - action: 'drop', - columns: [], + action: WorkspaceMigrationTableActionType.DROP, }, ]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-relation.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-relation.factory.ts index b195479d8..73e8c3ed9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-relation.factory.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-relation.factory.ts @@ -7,6 +7,7 @@ import { WorkspaceMigrationColumnActionType, WorkspaceMigrationEntity, WorkspaceMigrationTableAction, + WorkspaceMigrationTableActionType, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { @@ -90,7 +91,7 @@ export class WorkspaceMigrationRelationFactory { const migrations: WorkspaceMigrationTableAction[] = [ { name: computeObjectTargetTable(toObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY, @@ -100,7 +101,7 @@ export class WorkspaceMigrationRelationFactory { }, { name: computeObjectTargetTable(toObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY, @@ -166,7 +167,7 @@ export class WorkspaceMigrationRelationFactory { const migrations: WorkspaceMigrationTableAction[] = [ { name: computeObjectTargetTable(toObjectMetadata), - action: 'alter', + action: WorkspaceMigrationTableActionType.ALTER, columns: [ { action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts index 867927049..96fbc52eb 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts @@ -18,6 +18,7 @@ import { WorkspaceMigrationColumnCreateRelation, WorkspaceMigrationColumnAlter, WorkspaceMigrationColumnDropRelation, + WorkspaceMigrationTableActionType, WorkspaceMigrationForeignTable, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; @@ -118,18 +119,30 @@ export class WorkspaceMigrationRunnerService { tableMigration: WorkspaceMigrationTableAction, ) { switch (tableMigration.action) { - case 'create': + case WorkspaceMigrationTableActionType.CREATE: await this.createTable(queryRunner, schemaName, tableMigration.name); break; - case 'alter': - await this.handleColumnChanges( - queryRunner, - schemaName, - tableMigration.name, - tableMigration?.columns, - ); + case WorkspaceMigrationTableActionType.ALTER: { + if (tableMigration.newName) { + await this.renameTable( + queryRunner, + schemaName, + tableMigration.name, + tableMigration.newName, + ); + } + + if (tableMigration.columns && tableMigration.columns.length > 0) { + await this.handleColumnChanges( + queryRunner, + schemaName, + tableMigration.newName ?? tableMigration.name, + tableMigration.columns, + ); + } break; - case 'drop': + } + case WorkspaceMigrationTableActionType.DROP: await queryRunner.dropTable(`${schemaName}.${tableMigration.name}`); break; case 'create_foreign_table': @@ -179,6 +192,25 @@ export class WorkspaceMigrationRunnerService { `); } + /** + * Rename a table + * @param queryRunner QueryRunner + * @param schemaName string + * @param oldTableName string + * @param newTableName string + */ + private async renameTable( + queryRunner: QueryRunner, + schemaName: string, + oldTableName: string, + newTableName: string, + ) { + await queryRunner.renameTable( + `${schemaName}.${oldTableName}`, + newTableName, + ); + } + /** * Handles column changes for a given migration * diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts index c43d6f9a4..b6d15d677 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts @@ -42,27 +42,39 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { : await this.workspaceService.getWorkspaceIds(); for (const workspaceId of workspaceIds) { - const issues = await this.workspaceHealthService.healthCheck(workspaceId); + try { + const issues = + await this.workspaceHealthService.healthCheck(workspaceId); - // Security: abort if there are issues. - if (issues.length > 0) { + // Security: abort if there are issues. + if (issues.length > 0) { + if (!options.force) { + this.logger.error( + `Workspace contains ${issues.length} issues, aborting.`, + ); + + this.logger.log( + 'If you want to force the migration, use --force flag', + ); + this.logger.log( + 'Please use `workspace:health` command to check issues and fix them before running this command.', + ); + + return; + } + + this.logger.warn( + `Workspace contains ${issues.length} issues, sync has been forced.`, + ); + } + } catch (error) { if (!options.force) { - this.logger.error( - `Workspace contains ${issues.length} issues, aborting.`, - ); - - this.logger.log( - 'If you want to force the migration, use --force flag', - ); - this.logger.log( - 'Please use `workspace:health` command to check issues and fix them before running this command.', - ); - - return; + throw error; } this.logger.warn( - `Workspace contains ${issues.length} issues, sync has been forced.`, + `Workspace health check failed with error, but sync has been forced.`, + error, ); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface.ts index bb83fc881..c39a440a7 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface.ts @@ -33,7 +33,9 @@ export interface ComparatorDeleteResult { export type ObjectComparatorResult = | ComparatorSkipResult | ComparatorCreateResult - | ComparatorUpdateResult>; + | ComparatorUpdateResult< + Partial & { id: string } + >; export type FieldComparatorResult = | ComparatorSkipResult diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service.ts index 85b09148f..6eb43d925 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service.ts @@ -1,8 +1,13 @@ import { Injectable, Logger } from '@nestjs/common'; -import { EntityManager, In } from 'typeorm'; +import { + EntityManager, + EntityTarget, + FindOptionsWhere, + In, + ObjectLiteral, +} from 'typeorm'; import { v4 as uuidV4 } from 'uuid'; -import omit from 'lodash.omit'; import { PartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface'; @@ -14,6 +19,8 @@ import { import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage'; +import { FieldMetadataUpdate } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory'; +import { ObjectMetadataUpdate } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-object.factory'; @Injectable() export class WorkspaceMetadataUpdaterService { @@ -24,7 +31,7 @@ export class WorkspaceMetadataUpdaterService { storage: WorkspaceSyncStorage, ): Promise<{ createdObjectMetadataCollection: ObjectMetadataEntity[]; - updatedObjectMetadataCollection: ObjectMetadataEntity[]; + updatedObjectMetadataCollection: ObjectMetadataUpdate[]; }> { const objectMetadataRepository = manager.getRepository(ObjectMetadataEntity); @@ -56,10 +63,17 @@ export class WorkspaceMetadataUpdaterService { /** * Update object metadata */ - const updatedObjectMetadataCollection = await objectMetadataRepository.save( - storage.objectMetadataUpdateCollection.map((objectMetadata) => - omit(objectMetadata, ['fields']), - ), + const updatedObjectMetadataCollection = await this.updateEntities( + manager, + ObjectMetadataEntity, + storage.objectMetadataUpdateCollection, + [ + 'fields', + 'dataSourceId', + 'workspaceId', + 'labelIdentifierFieldMetadataId', + 'imageIdentifierFieldMetadataId', + ], ); /** @@ -108,10 +122,7 @@ export class WorkspaceMetadataUpdaterService { storage: WorkspaceSyncStorage, ): Promise<{ createdFieldMetadataCollection: FieldMetadataEntity[]; - updatedFieldMetadataCollection: { - current: FieldMetadataEntity; - altered: FieldMetadataEntity; - }[]; + updatedFieldMetadataCollection: FieldMetadataUpdate[]; }> { const fieldMetadataRepository = manager.getRepository(FieldMetadataEntity); @@ -127,41 +138,12 @@ export class WorkspaceMetadataUpdaterService { /** * Update field metadata */ - const oldFieldMetadataCollection = await fieldMetadataRepository.findBy({ - id: In(storage.fieldMetadataUpdateCollection.map((field) => field.id)), - }); - // Pre-process old collection into a mapping for quick access - const oldFieldMetadataMap = new Map( - oldFieldMetadataCollection.map((field) => [field.id, field]), - ); - // Combine old and new field metadata to get whole updated entities - const fieldMetadataUpdateCollection = - storage.fieldMetadataUpdateCollection.map((updateFieldMetadata) => { - const oldFieldMetadata = oldFieldMetadataMap.get( - updateFieldMetadata.id, - ); - - if (!oldFieldMetadata) { - throw new Error(` - Field ${updateFieldMetadata.id} not found in oldFieldMetadataCollection`); - } - - // TypeORM 😢 - // If we didn't provide the old value, it will be set to null fields that are not in the updateFieldMetadata - // and override the old value with null in the DB. - // Also save method doesn't return the whole entity if you give a partial one. - // https://github.com/typeorm/typeorm/issues/3490 - // To avoid calling update in a for loop, we did this hack. - return { - ...omit(oldFieldMetadata, ['objectMetadataId', 'workspaceId']), - ...omit(updateFieldMetadata, ['objectMetadataId', 'workspaceId']), - options: updateFieldMetadata.options ?? oldFieldMetadata.options, - }; - }); - - const updatedFieldMetadataCollection = await fieldMetadataRepository.save( - fieldMetadataUpdateCollection, - ); + const updatedFieldMetadataCollection = await this.updateEntities< + FieldMetadataEntity<'default'> + >(manager, FieldMetadataEntity, storage.objectMetadataUpdateCollection, [ + 'objectMetadataId', + 'workspaceId', + ]); /** * Delete field metadata @@ -183,28 +165,7 @@ export class WorkspaceMetadataUpdaterService { return { createdFieldMetadataCollection: createdFieldMetadataCollection as FieldMetadataEntity[], - updatedFieldMetadataCollection: updatedFieldMetadataCollection.map( - (alteredFieldMetadata) => { - const oldFieldMetadata = oldFieldMetadataMap.get( - alteredFieldMetadata.id, - ); - - if (!oldFieldMetadata) { - throw new Error(` - Field ${alteredFieldMetadata.id} not found in oldFieldMetadataCollection - `); - } - - return { - current: oldFieldMetadata as FieldMetadataEntity, - altered: { - ...alteredFieldMetadata, - objectMetadataId: oldFieldMetadata.objectMetadataId, - workspaceId: oldFieldMetadata.workspaceId, - } as FieldMetadataEntity, - }; - }, - ), + updatedFieldMetadataCollection, }; } @@ -267,4 +228,83 @@ export class WorkspaceMetadataUpdaterService { updatedRelationMetadataCollection, }; } + + /** + * Update entities in the database + * @param manager EntityManager + * @param entityClass Entity class + * @param updateCollection Update collection + * @param keysToOmit keys to omit in the merge process + * @returns Promise<{ current: Entity; altered: Entity }[]> + */ + private async updateEntities( + manager: EntityManager, + entityClass: EntityTarget, + updateCollection: Array< + DeepPartial> & { id: string } + >, + keysToOmit: (keyof Entity)[] = [], + ): Promise<{ current: Entity; altered: Entity }[]> { + const repository = manager.getRepository(entityClass); + + const oldEntities = await repository.findBy({ + id: In(updateCollection.map((updateItem) => updateItem.id)), + } as FindOptionsWhere); + + // Pre-process old collection into a mapping for quick access + const oldEntitiesMap = new Map( + oldEntities.map((oldEntity) => [oldEntity.id, oldEntity]), + ); + + // Combine old and new field metadata to get whole updated entities + const entityUpdateCollection = updateCollection.map((updateItem) => { + const oldEntity = oldEntitiesMap.get(updateItem.id); + + if (!oldEntity) { + throw new Error(` + Entity ${updateItem.id} not found in oldEntities`); + } + + // TypeORM 😢 + // If we didn't provide the old value, it will be set to null objects that are not in the updateObjectMetadata + // and override the old value with null in the DB. + // Also save method doesn't return the whole entity if you give a partial one. + // https://github.com/typeorm/typeorm/issues/3490 + // To avoid calling update in a for loop, we did this hack. + const mergedUpdate = { + ...oldEntity, + ...updateItem, + }; + + // Omit keys that we don't want to override + keysToOmit.forEach((key) => { + delete mergedUpdate[key]; + }); + + return mergedUpdate; + }); + + const updatedEntities = await repository.save(entityUpdateCollection); + + return updatedEntities.map((updatedEntity) => { + const oldEntity = oldEntitiesMap.get(updatedEntity.id); + + if (!oldEntity) { + throw new Error(` + Entity ${updatedEntity.id} not found in oldEntitiesMap + `); + } + + return { + current: oldEntity, + altered: { + ...updatedEntity, + ...keysToOmit.reduce( + (acc, key) => ({ ...acc, [key]: oldEntity[key] }), + {}, + ), + }, + }; + }); + } } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata.service.ts index 4707c7c45..147389b18 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata.service.ts @@ -154,6 +154,12 @@ export class WorkspaceSyncObjectMetadataService { WorkspaceMigrationBuilderAction.CREATE, ); + const updateObjectWorkspaceMigrations = + await this.workspaceMigrationObjectFactory.create( + metadataObjectUpdaterResult.updatedObjectMetadataCollection, + WorkspaceMigrationBuilderAction.UPDATE, + ); + const deleteObjectWorkspaceMigrations = await this.workspaceMigrationObjectFactory.create( storage.objectMetadataDeleteCollection, @@ -164,6 +170,7 @@ export class WorkspaceSyncObjectMetadataService { return [ ...createObjectWorkspaceMigrations, + ...updateObjectWorkspaceMigrations, ...deleteObjectWorkspaceMigrations, ]; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage.ts index 9c3904054..4aa5c5a08 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage.ts @@ -10,8 +10,9 @@ export class WorkspaceSyncStorage { // Object metadata private readonly _objectMetadataCreateCollection: ComputedPartialObjectMetadata[] = []; - private readonly _objectMetadataUpdateCollection: Partial[] = - []; + private readonly _objectMetadataUpdateCollection: (Partial & { + id: string; + })[] = []; private readonly _objectMetadataDeleteCollection: ObjectMetadataEntity[] = []; // Field metadata @@ -72,7 +73,9 @@ export class WorkspaceSyncStorage { this._objectMetadataCreateCollection.push(object); } - addUpdateObjectMetadata(object: Partial) { + addUpdateObjectMetadata( + object: Partial & { id: string }, + ) { this._objectMetadataUpdateCollection.push(object); }