diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts index d9651efd85d..b19f555331c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts @@ -26,7 +26,7 @@ import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadat import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.util'; import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; -import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; @Injectable() export class FieldMetadataServiceV2 { diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts index 5e777940a52..c4aaa5ec8a4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts @@ -23,7 +23,7 @@ import { ObjectMetadataExceptionCode, } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; -import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; @Injectable() export class ObjectMetadataServiceV2 { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service.ts similarity index 97% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service.ts index 9191eda028c..00a269f9e55 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service.ts @@ -5,7 +5,7 @@ import { WorkspaceMigrationBuildArgs, WorkspaceMigrationBuilderV2Service, } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service'; -import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service'; +import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service'; import { WorkspaceMigrationV2Exception, WorkspaceMigrationV2ExceptionCode, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2.ts index dc4cb1a1171..d5e4bcd6ef5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2.ts @@ -8,3 +8,8 @@ export type WorkspaceMigrationActionV2 = | WorkspaceMigrationIndexActionV2; export type WorkspaceMigrationActionTypeV2 = WorkspaceMigrationActionV2['type']; + +export type ExtractAction = Extract< + WorkspaceMigrationActionV2, + { type: T } +>; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module.ts similarity index 100% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.module.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module.ts diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts new file mode 100644 index 00000000000..e06bbffd170 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; +import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; +import { type CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; +import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; +import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util'; +import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util'; +import { + collectEnumOperationsForField, + EnumOperation, + executeBatchEnumOperations, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util'; + +@Injectable() +export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'create_field', +) { + constructor( + private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService, + ) { + super(); + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner } = context; + const fieldMetadataRepository = + queryRunner.manager.getRepository( + FieldMetadataEntity, + ); + + const { flatFieldMetadata } = action; + + await fieldMetadataRepository.save(flatFieldMetadata); + } + + async executeForWorkspaceSchema( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner, flatObjectMetadataMaps, workspaceId } = + context; + const { flatFieldMetadata } = action; + + const flatObjectMetadataWithFlatFieldMaps = + findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps, + objectMetadataId: flatFieldMetadata.objectMetadataId, + }); + + const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({ + workspaceId, + flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps, + }); + + const enumOperations = collectEnumOperationsForField({ + flatFieldMetadata: flatFieldMetadata, + tableName, + operation: EnumOperation.CREATE, + }); + + await executeBatchEnumOperations({ + enumOperations, + queryRunner, + schemaName, + workspaceSchemaManagerService: this.workspaceSchemaManagerService, + }); + + const columnDefinitions = generateColumnDefinitions({ + flatFieldMetadata: flatFieldMetadata, + flatObjectMetadataWithoutFields: flatObjectMetadataWithFlatFieldMaps, + }); + + await this.workspaceSchemaManagerService.columnManager.addColumns({ + queryRunner, + schemaName, + tableName, + columnDefinitions, + }); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service.ts new file mode 100644 index 00000000000..fc457379664 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service.ts @@ -0,0 +1,104 @@ +import { Injectable } from '@nestjs/common'; + +import { In } from 'typeorm'; + +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; +import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; +import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; +import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; +import { type DeleteFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; +import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; +import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util'; +import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util'; +import { + collectEnumOperationsForField, + EnumOperation, + executeBatchEnumOperations, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util'; + +@Injectable() +export class DeleteFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'delete_field', +) { + constructor( + private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService, + ) { + super(); + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerArgs, + ) { + const { action, queryRunner } = context; + const fieldMetadataRepository = + queryRunner.manager.getRepository( + FieldMetadataEntity, + ); + + const { fieldMetadataId } = action; + + await fieldMetadataRepository.delete({ + id: In([fieldMetadataId]), + }); + } + + async executeForWorkspaceSchema( + context: WorkspaceMigrationActionRunnerArgs, + ) { + const { action, queryRunner, flatObjectMetadataMaps, workspaceId } = + context; + const { objectMetadataId, fieldMetadataId } = action; + + const flatObjectMetadataWithFlatFieldMaps = + findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps, + objectMetadataId, + }); + + const fieldMetadata = findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps, + objectMetadataId, + fieldMetadataId, + }); + + const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({ + workspaceId, + flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps, + }); + + const flatObjectMetadata = + findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps, + objectMetadataId, + }); + + const columnDefinitions = generateColumnDefinitions({ + flatFieldMetadata: fieldMetadata, + flatObjectMetadataWithoutFields: flatObjectMetadata, + }); + const columnNamesToDrop = columnDefinitions.map((def) => def.name); + + await this.workspaceSchemaManagerService.columnManager.dropColumns({ + queryRunner, + schemaName, + tableName, + columnNames: columnNamesToDrop, + }); + + const enumOperations = collectEnumOperationsForField({ + flatFieldMetadata: fieldMetadata, + tableName, + operation: EnumOperation.DROP, + }); + + await executeBatchEnumOperations({ + enumOperations, + queryRunner, + schemaName, + workspaceSchemaManagerService: this.workspaceSchemaManagerService, + }); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service.ts similarity index 66% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service.ts index 40bb5c0c5e9..12cf713a3e9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service.ts @@ -1,16 +1,17 @@ import { Injectable } from '@nestjs/common'; -import { type FromTo } from 'twenty-shared/types'; +import { type EnumFieldMetadataType, type FromTo } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type QueryRunner } from 'typeorm'; import { type FieldMetadataDefaultValueForAnyType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; import { - FieldMetadataDefaultOption, type FieldMetadataComplexOption, + type FieldMetadataDefaultOption, } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; -import { EnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/enum-field-metadata-type.type'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { getCompositeTypeOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/get-composite-type-or-throw.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; @@ -19,151 +20,84 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-m import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util'; import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util'; import { isRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-relation-flat-field-metadata.util'; -import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; +import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; +import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; +import { type UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; import { - type CreateFieldAction, - type DeleteFieldAction, - type UpdateFieldAction, - type WorkspaceMigrationFieldActionTypeV2, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; -import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type'; + WorkspaceMigrationRunnerException, + WorkspaceMigrationRunnerExceptionCode, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception'; import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; -import { - WorkspaceSchemaMigrationException, - WorkspaceSchemaMigrationExceptionCode, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception'; -import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util'; - -import { - prepareFieldWorkspaceSchemaContext, - prepareWorkspaceSchemaContext, -} from './utils/workspace-schema-context.util'; +import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util'; +import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util'; +import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util'; import { collectEnumOperationsForField, EnumOperation, executeBatchEnumOperations, -} from './utils/workspace-schema-enum-operations.util'; +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util'; @Injectable() -export class WorkspaceSchemaFieldActionRunnerService - implements - RunnerMethodForActionType -{ +export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'update_field', +) { constructor( private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService, - ) {} + ) { + super(); + } - runDeleteFieldSchemaMigration = async ({ - action, - queryRunner, - flatObjectMetadataMaps, - }: WorkspaceMigrationActionRunnerArgs) => { - const { objectMetadataId, fieldMetadataId } = action; - const { schemaName, tableName, fieldMetadata } = - prepareFieldWorkspaceSchemaContext({ + async executeForMetadata( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner } = context; + const fieldMetadataRepository = + queryRunner.manager.getRepository( + FieldMetadataEntity, + ); + + const { fieldMetadataId } = action; + + await fieldMetadataRepository.update( + fieldMetadataId, + fromWorkspaceMigrationUpdateActionToPartialEntity(action), + ); + } + + async executeForWorkspaceSchema( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner, flatObjectMetadataMaps, workspaceId } = + context; + const { objectMetadataId, fieldMetadataId, updates } = action; + + const flatObjectMetadataWithFlatFieldMaps = + findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps, + objectMetadataId, + }); + + const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({ + workspaceId, + flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps, + }); + + const flatObjectMetadata = + findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps, + objectMetadataId, + }); + + const currentFlatFieldMetadata = + findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ flatObjectMetadataMaps, objectMetadataId, fieldMetadataId, }); - const flatObjectMetadata = - findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps, - objectMetadataId, - }); - - const columnDefinitions = generateColumnDefinitions({ - flatFieldMetadata: fieldMetadata, - flatObjectMetadataWithoutFields: flatObjectMetadata, - }); - const columnNamesToDrop = columnDefinitions.map((def) => def.name); - - await this.workspaceSchemaManagerService.columnManager.dropColumns({ - queryRunner, - schemaName, - tableName, - columnNames: columnNamesToDrop, - }); - - const enumOperations = collectEnumOperationsForField({ - flatFieldMetadata: fieldMetadata, - tableName, - operation: EnumOperation.DROP, - }); - - await executeBatchEnumOperations({ - enumOperations, - queryRunner, - schemaName, - workspaceSchemaManagerService: this.workspaceSchemaManagerService, - }); - - return; - }; - - runCreateFieldSchemaMigration = async ({ - action, - queryRunner, - flatObjectMetadataMaps, - }: WorkspaceMigrationActionRunnerArgs) => { - const { flatFieldMetadata } = action; - const { - schemaName, - tableName, - flatObjectMetadataWithFlatFieldMaps: flatObjectMetadata, - } = prepareWorkspaceSchemaContext({ - flatObjectMetadataMaps, - objectMetadataId: flatFieldMetadata.objectMetadataId, - }); - - const enumOperations = collectEnumOperationsForField({ - flatFieldMetadata: flatFieldMetadata, - tableName, - operation: EnumOperation.CREATE, - }); - - await executeBatchEnumOperations({ - enumOperations, - queryRunner, - schemaName, - workspaceSchemaManagerService: this.workspaceSchemaManagerService, - }); - - const columnDefinitions = generateColumnDefinitions({ - flatFieldMetadata: flatFieldMetadata, - flatObjectMetadataWithoutFields: flatObjectMetadata, - }); - - await this.workspaceSchemaManagerService.columnManager.addColumns({ - queryRunner, - schemaName, - tableName, - columnDefinitions, - }); - - return; - }; - - runUpdateFieldSchemaMigration = async ({ - action, - queryRunner, - flatObjectMetadataMaps, - }: WorkspaceMigrationActionRunnerArgs) => { - const { objectMetadataId, fieldMetadataId, updates } = action; - const { - schemaName, - tableName, - flatObjectMetadataWithFlatFieldMaps: flatObjectMetadata, - fieldMetadata: currentFlatFieldMetadata, - } = prepareFieldWorkspaceSchemaContext({ - flatObjectMetadataMaps, - objectMetadataId, - fieldMetadataId, - }); - let optimisticFlatFieldMetadata = structuredClone(currentFlatFieldMetadata); for (const update of updates) { @@ -202,9 +136,7 @@ export class WorkspaceSchemaFieldActionRunnerService optimisticFlatFieldMetadata.options = update.to ?? []; } } - - return; - }; + } private async handleFieldNameUpdate( queryRunner: QueryRunner, @@ -220,9 +152,9 @@ export class WorkspaceSchemaFieldActionRunnerService for (const property of compositeType.properties) { if (isRelationFieldMetadataType(property.type)) { - throw new WorkspaceSchemaMigrationException( + throw new WorkspaceMigrationRunnerException( 'Relation field metadata in composite type is not supported yet', - WorkspaceSchemaMigrationExceptionCode.NOT_SUPPORTED, + WorkspaceMigrationRunnerExceptionCode.NOT_SUPPORTED, ); } @@ -245,9 +177,9 @@ export class WorkspaceSchemaFieldActionRunnerService } } else { if (isRelationFlatFieldMetadata(flatFieldMetadata)) { - throw new WorkspaceSchemaMigrationException( + throw new WorkspaceMigrationRunnerException( 'Relation field metadata name update is not supported yet', - WorkspaceSchemaMigrationExceptionCode.NOT_SUPPORTED, + WorkspaceMigrationRunnerExceptionCode.NOT_SUPPORTED, ); } await this.workspaceSchemaManagerService.columnManager.renameColumn({ @@ -290,9 +222,9 @@ export class WorkspaceSchemaFieldActionRunnerService for (const property of compositeType.properties) { if (isRelationFieldMetadataType(property.type)) { - throw new WorkspaceSchemaMigrationException( + throw new WorkspaceMigrationRunnerException( 'Relation field metadata in composite type is not supported yet', - WorkspaceSchemaMigrationExceptionCode.NOT_SUPPORTED, + WorkspaceMigrationRunnerExceptionCode.NOT_SUPPORTED, ); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/create-index-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/create-index-action-handler.service.ts new file mode 100644 index 00000000000..6f5f6c21afe --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/create-index-action-handler.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { type CreateIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2'; +import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; + +@Injectable() +export class CreateIndexActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'create_index', +) { + async executeForMetadata( + _context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + throw new Error('Not implemented'); + } + + async executeForWorkspaceSchema( + _context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + throw new Error('Not implemented'); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/delete-index-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/delete-index-action-handler.service.ts new file mode 100644 index 00000000000..366e7efc2db --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/delete-index-action-handler.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { type DeleteIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2'; +import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; + +@Injectable() +export class DeleteIndexActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'delete_index', +) { + async executeForMetadata( + _context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + throw new Error('Not implemented'); + } + + async executeForWorkspaceSchema( + _context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + throw new Error('Not implemented'); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service.ts new file mode 100644 index 00000000000..5917911a282 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service.ts @@ -0,0 +1,119 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util'; +import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; +import { type CreateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; +import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; +import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util'; +import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util'; +import { + collectEnumOperationsForObject, + EnumOperation, + executeBatchEnumOperations, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util'; + +@Injectable() +export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'create_object', +) { + constructor( + private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService, + ) { + super(); + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner } = context; + const { flatObjectMetadataWithoutFields, createFieldActions } = action; + + const objectMetadataRepository = + queryRunner.manager.getRepository( + ObjectMetadataEntity, + ); + + const dataSourceRepository = + queryRunner.manager.getRepository(DataSourceEntity); + + const lastDataSourceMetadata = await dataSourceRepository.findOneOrFail({ + where: { + workspaceId: flatObjectMetadataWithoutFields.workspaceId, + }, + order: { createdAt: 'DESC' }, + }); + + await objectMetadataRepository.save({ + ...flatObjectMetadataWithoutFields, + dataSourceId: lastDataSourceMetadata.id, + targetTableName: 'DEPRECATED', + }); + + const fieldMetadataRepository = + queryRunner.manager.getRepository( + FieldMetadataEntity, + ); + + for (const createFieldAction of createFieldActions) { + const { flatFieldMetadata } = createFieldAction; + + await fieldMetadataRepository.save(flatFieldMetadata); + } + } + + async executeForWorkspaceSchema( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner, workspaceId } = context; + const { flatObjectMetadataWithoutFields, createFieldActions } = action; + + const flatObjectMetadata = flatObjectMetadataWithoutFields; + + const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({ + workspaceId, + flatObjectMetadata: flatObjectMetadata, + }); + + const columnDefinitions = createFieldActions.flatMap((createFieldAction) => + generateColumnDefinitions({ + flatFieldMetadata: createFieldAction.flatFieldMetadata, + flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields, + }), + ); + + const enumOrCompositeFlatFieldMetadatas = createFieldActions + .map((createFieldAction) => createFieldAction.flatFieldMetadata) + .filter((field): field is FlatFieldMetadata => field != null) + .filter( + (field) => + isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field), + ); + + const enumOperations = collectEnumOperationsForObject({ + flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas, + tableName, + operation: EnumOperation.CREATE, + }); + + await executeBatchEnumOperations({ + enumOperations, + queryRunner, + schemaName, + workspaceSchemaManagerService: this.workspaceSchemaManagerService, + }); + + await this.workspaceSchemaManagerService.tableManager.createTable({ + queryRunner, + schemaName, + tableName, + columnDefinitions, + }); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service.ts new file mode 100644 index 00000000000..907ab5df8fe --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service.ts @@ -0,0 +1,90 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util'; +import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util'; +import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; +import { type DeleteObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; +import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; +import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util'; +import { + collectEnumOperationsForObject, + EnumOperation, + executeBatchEnumOperations, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util'; + +@Injectable() +export class DeleteObjectActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'delete_object', +) { + constructor( + private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService, + ) { + super(); + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner } = context; + const { objectMetadataId } = action; + + const objectMetadataRepository = + queryRunner.manager.getRepository( + ObjectMetadataEntity, + ); + + await objectMetadataRepository.delete(objectMetadataId); + } + + async executeForWorkspaceSchema( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner, flatObjectMetadataMaps, workspaceId } = + context; + const { objectMetadataId } = action; + + const flatObjectMetadataWithFlatFieldMaps = + findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps, + objectMetadataId, + }); + + const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({ + workspaceId, + flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps, + }); + + await this.workspaceSchemaManagerService.tableManager.dropTable({ + queryRunner, + schemaName, + tableName, + }); + + const enumOrCompositeFlatFieldMetadatas = Object.values( + flatObjectMetadataWithFlatFieldMaps.fieldsById, + ) + .filter((field): field is FlatFieldMetadata => field != null) + .filter( + (field) => + isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field), + ); + + const enumOperations = collectEnumOperationsForObject({ + flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas, + tableName, + operation: EnumOperation.DROP, + }); + + await executeBatchEnumOperations({ + enumOperations, + queryRunner, + schemaName, + workspaceSchemaManagerService: this.workspaceSchemaManagerService, + }); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service.ts new file mode 100644 index 00000000000..09272041f75 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service.ts @@ -0,0 +1,115 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util'; +import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util'; +import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; +import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; +import { type UpdateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; +import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; +import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util'; +import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util'; +import { + collectEnumOperationsForObject, + EnumOperation, + executeBatchEnumOperations, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util'; + +@Injectable() +export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'update_object', +) { + constructor( + private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService, + ) { + super(); + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner } = context; + + const objectMetadataRepository = + queryRunner.manager.getRepository( + ObjectMetadataEntity, + ); + + await objectMetadataRepository.update( + action.objectMetadataId, + fromWorkspaceMigrationUpdateActionToPartialEntity(action), + ); + } + + async executeForWorkspaceSchema( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner, flatObjectMetadataMaps, workspaceId } = + context; + const { objectMetadataId, updates } = action; + + const flatObjectMetadataWithFlatFieldMaps = + findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps, + objectMetadataId, + }); + + const { schemaName, tableName: currentTableName } = + getWorkspaceSchemaContextForMigration({ + workspaceId, + flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps, + }); + + for (const update of updates) { + if (update.property !== 'nameSingular') { + continue; + } + + const updatedObjectMetadata = { + ...flatObjectMetadataWithFlatFieldMaps, + [update.property]: update.to, + }; + + const newTableName = computeObjectTargetTable(updatedObjectMetadata); + + if (currentTableName !== newTableName) { + await this.workspaceSchemaManagerService.tableManager.renameTable({ + queryRunner, + schemaName, + oldTableName: currentTableName, + newTableName, + }); + + const enumOrCompositeFlatFieldMetadatas = Object.values( + flatObjectMetadataWithFlatFieldMaps.fieldsById, + ) + .filter((field): field is FlatFieldMetadata => field != null) + .filter( + (field) => + isEnumFlatFieldMetadata(field) || + isCompositeFlatFieldMetadata(field), + ); + + const enumOperations = collectEnumOperationsForObject({ + flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas, + tableName: currentTableName, + operation: EnumOperation.RENAME, + options: { + newTableName, + }, + }); + + await executeBatchEnumOperations({ + enumOperations, + queryRunner, + schemaName, + workspaceSchemaManagerService: this.workspaceSchemaManagerService, + }); + } + } + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts new file mode 100644 index 00000000000..a37c07fb7b5 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts @@ -0,0 +1,28 @@ +import { Module } from '@nestjs/common'; + +import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module'; +import { CreateFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service'; +import { DeleteFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service'; +import { UpdateFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service'; +import { CreateIndexActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/create-index-action-handler.service'; +import { DeleteIndexActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/delete-index-action-handler.service'; +import { CreateObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service'; +import { DeleteObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service'; +import { UpdateObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service'; + +@Module({ + imports: [WorkspaceSchemaManagerModule], + providers: [ + CreateFieldActionHandlerService, + UpdateFieldActionHandlerService, + DeleteFieldActionHandlerService, + + CreateObjectActionHandlerService, + UpdateObjectActionHandlerService, + DeleteObjectActionHandlerService, + + CreateIndexActionHandlerService, + DeleteIndexActionHandlerService, + ], +}) +export class WorkspaceSchemaMigrationRunnerActionHandlersModule {} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/constants/workspace-migration-action-handler-metadata-key.constant.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/constants/workspace-migration-action-handler-metadata-key.constant.ts new file mode 100644 index 00000000000..65c7eb6d1eb --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/constants/workspace-migration-action-handler-metadata-key.constant.ts @@ -0,0 +1,2 @@ +export const WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY = + 'workspace_migration_action_handler_metadata_key'; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts similarity index 68% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts index e86f59cb6bf..566b88f5b44 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts @@ -10,4 +10,8 @@ export class WorkspaceMigrationRunnerException extends CustomException< export const WorkspaceMigrationRunnerExceptionCode = appendCommonExceptionCode({ FIELD_METADATA_NOT_FOUND: 'FIELD_METADATA_NOT_FOUND', OBJECT_METADATA_NOT_FOUND: 'OBJECT_METADATA_NOT_FOUND', + ENUM_OPERATION_FAILED: 'ENUM_OPERATION_FAILED', + UNSUPPORTED_COMPOSITE_COLUMN_TYPE: 'UNSUPPORTED_COMPOSITE_COLUMN_TYPE', + NOT_SUPPORTED: 'NOT_SUPPORTED', + INVALID_ACTION_TYPE: 'INVALID_ACTION_TYPE', } as const); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface.ts new file mode 100644 index 00000000000..e23a58c03b4 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface.ts @@ -0,0 +1,51 @@ +import { SetMetadata } from '@nestjs/common'; + +import { + type ExtractAction, + type WorkspaceMigrationActionTypeV2, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; +import { WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/constants/workspace-migration-action-handler-metadata-key.constant'; +import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; + +export interface WorkspaceMigrationRunnerActionHandlerService< + T extends WorkspaceMigrationActionTypeV2, +> { + execute( + context: WorkspaceMigrationActionRunnerArgs>, + ): Promise; +} + +export abstract class BaseWorkspaceMigrationRunnerActionHandlerService< + T extends WorkspaceMigrationActionTypeV2, +> implements WorkspaceMigrationRunnerActionHandlerService +{ + abstract executeForMetadata( + context: WorkspaceMigrationActionRunnerArgs>, + ): Promise; + + abstract executeForWorkspaceSchema( + context: WorkspaceMigrationActionRunnerArgs>, + ): Promise; + + async execute( + context: WorkspaceMigrationActionRunnerArgs>, + ): Promise { + await Promise.all([ + this.executeForMetadata(context), + this.executeForWorkspaceSchema(context), + ]); + } +} + +export function WorkspaceMigrationRunnerActionHandler< + T extends WorkspaceMigrationActionTypeV2, +>(actionType: T): typeof BaseWorkspaceMigrationRunnerActionHandlerService { + abstract class ActionHandlerService extends BaseWorkspaceMigrationRunnerActionHandlerService {} + + SetMetadata( + WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY, + actionType, + )(ActionHandlerService); + + return ActionHandlerService; +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service.ts new file mode 100644 index 00000000000..541b206fdf2 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service.ts @@ -0,0 +1,69 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { DiscoveryService } from '@nestjs/core'; + +import { type WorkspaceMigrationRunnerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { + type WorkspaceMigrationActionTypeV2, + type WorkspaceMigrationActionV2, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; +import { WorkspaceSchemaMigrationRunnerActionHandlersModule } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module'; +import { WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/constants/workspace-migration-action-handler-metadata-key.constant'; +import { + WorkspaceMigrationRunnerException, + WorkspaceMigrationRunnerExceptionCode, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception'; +import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; + +@Injectable() +export class WorkspaceMigrationRunnerActionHandlerRegistryService + implements OnModuleInit +{ + private readonly actionHandlers = new Map< + WorkspaceMigrationActionTypeV2, + WorkspaceMigrationRunnerActionHandlerService + >(); + + constructor(private readonly discoveryService: DiscoveryService) {} + + async onModuleInit() { + this.discoverAndRegisterActionHandlers(); + } + + private discoverAndRegisterActionHandlers(): void { + const providers = this.discoveryService.getProviders({ + include: [WorkspaceSchemaMigrationRunnerActionHandlersModule], + }); + + providers.forEach((wrapper) => { + const { instance, metatype } = wrapper; + + if (!instance || !metatype) return; + + const actionType = Reflect.getMetadata( + WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY, + metatype, + ); + + if (actionType && instance.execute) { + this.actionHandlers.set(actionType, instance); + } + }); + } + + async executeActionHandler( + actionType: WorkspaceMigrationActionTypeV2, + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const handler = this.actionHandlers.get(actionType); + + if (!handler) { + throw new WorkspaceMigrationRunnerException( + `No migration runner action handler found for action: ${actionType}`, + WorkspaceMigrationRunnerExceptionCode.INVALID_ACTION_TYPE, + ); + } + + await handler.execute(context); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts similarity index 74% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts index aaeaa69ce8c..edb6c188324 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts @@ -8,20 +8,18 @@ import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/works import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; import { type WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2'; -import { applyWorkspaceMigrationActionOnFlatObjectMetadataMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps'; -import { WorkspaceMetadataMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service'; -import { WorkspaceSchemaMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service'; +import { WorkspaceMigrationRunnerActionHandlerRegistryService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service'; +import { applyWorkspaceMigrationActionOnFlatObjectMetadataMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps'; @Injectable() export class WorkspaceMigrationRunnerV2Service { constructor( - private readonly workspaceMetadataMigrationRunner: WorkspaceMetadataMigrationRunnerService, - private readonly workspaceSchemaMigrationRunner: WorkspaceSchemaMigrationRunnerService, private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, @InjectDataSource('core') private readonly coreDataSource: DataSource, + private readonly workspaceMigrationRunnerActionHandlerRegistry: WorkspaceMigrationRunnerActionHandlerRegistryService, ) {} run = async ( @@ -44,23 +42,21 @@ export class WorkspaceMigrationRunnerV2Service { try { for (const action of workspaceMigration.actions) { - await Promise.all([ - this.workspaceMetadataMigrationRunner.runWorkspaceMetadataMigration({ + await this.workspaceMigrationRunnerActionHandlerRegistry.executeActionHandler( + action.type, + { + action, flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, queryRunner, - action, - }), - this.workspaceSchemaMigrationRunner.runWorkspaceSchemaMigration({ - queryRunner, - flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, - action, - }), - ]); + workspaceId: workspaceMigration.workspaceId, + }, + ); optimisticFlatObjectMetadataMaps = applyWorkspaceMigrationActionOnFlatObjectMetadataMaps({ action, flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, + workspaceId: workspaceMigration.workspaceId, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type.ts deleted file mode 100644 index 280f98522df..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { type ConvertActionTypeToCamelCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/convert-action-type-to-camel-case.type'; -import { - type WorkspaceMigrationActionTypeV2, - type WorkspaceMigrationActionV2, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; -import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; - -export type RunnerMethodForActionType< - TAction extends WorkspaceMigrationActionTypeV2, - TRunner extends 'metadata' | 'schema', -> = { - [P in TAction as `run${Capitalize>}${Capitalize}Migration`]: ( - arg: WorkspaceMigrationActionRunnerArgs< - Extract - >, - ) => Promise; -}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type.ts index 8a85bb9c288..0355dc1c1f5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type.ts @@ -9,4 +9,5 @@ export type WorkspaceMigrationActionRunnerArgs< queryRunner: QueryRunner; action: T; flatObjectMetadataMaps: FlatObjectMetadataMaps; + workspaceId: string; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type.ts deleted file mode 100644 index b6ff194d149..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { type QueryRunner } from 'typeorm'; - -import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; -import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; - -export type WorkspaceMigrationRunnerArgs = { - action: WorkspaceMigrationActionV2; - queryRunner: QueryRunner; - flatObjectMetadataMaps: FlatObjectMetadataMaps; -}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/__tests__/generate-column-definitions.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/__tests__/generate-column-definitions.util.spec.ts similarity index 98% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/__tests__/generate-column-definitions.util.spec.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/__tests__/generate-column-definitions.util.spec.ts index f36f846e6f8..0d7028c0d25 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/__tests__/generate-column-definitions.util.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/__tests__/generate-column-definitions.util.spec.ts @@ -3,7 +3,7 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock'; import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util'; +import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util'; describe('Generate Column Definitions', () => { const mockObjectMetadata = getFlatObjectMetadataMock({ diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/__tests__/workspace-schema-enum-operations.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/__tests__/workspace-schema-enum-operations.util.spec.ts similarity index 95% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/__tests__/workspace-schema-enum-operations.util.spec.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/__tests__/workspace-schema-enum-operations.util.spec.ts index 3a278661dbb..93d0aa6aa60 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/__tests__/workspace-schema-enum-operations.util.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/__tests__/workspace-schema-enum-operations.util.spec.ts @@ -3,13 +3,13 @@ import { type QueryRunner } from 'typeorm'; import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock'; import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; -import { WorkspaceSchemaMigrationException } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception'; +import { WorkspaceMigrationRunnerException } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception'; import { collectEnumOperationsForField, EnumOperation, type EnumOperationSpec, executeBatchEnumOperations, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/workspace-schema-enum-operations.util'; +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util'; describe('WorkspaceSchemaEnumOperations', () => { let mockSchemaManagerService: jest.Mocked; @@ -61,7 +61,7 @@ describe('WorkspaceSchemaEnumOperations', () => { schemaName: 'test_schema', workspaceSchemaManagerService: mockSchemaManagerService, }), - ).rejects.toThrow(WorkspaceSchemaMigrationException); + ).rejects.toThrow(WorkspaceMigrationRunnerException); // All operations should be attempted in parallel despite failure expect( diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps.ts similarity index 92% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps.ts index 3a32d16336a..1f26b4bf7b3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps.ts @@ -9,18 +9,21 @@ import { findFlatFieldMetadataInFlatObjectMetadataMaps } from 'src/engine/metada import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util'; import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; -import { type WorkspaceMigrationRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type'; -import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util'; +import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; import { WorkspaceMigrationRunnerException, WorkspaceMigrationRunnerExceptionCode, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception'; +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception'; +import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; +import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util'; -export const applyWorkspaceMigrationActionOnFlatObjectMetadataMaps = ({ +export const applyWorkspaceMigrationActionOnFlatObjectMetadataMaps = < + T extends WorkspaceMigrationActionV2, +>({ action, flatObjectMetadataMaps, }: Omit< - WorkspaceMigrationRunnerArgs, + WorkspaceMigrationActionRunnerArgs, 'queryRunner' >): FlatObjectMetadataMaps => { switch (action.type) { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util.ts similarity index 92% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util.ts index fc9bacc903f..df7546d7cbc 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util.ts @@ -18,11 +18,10 @@ import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/works import { type WorkspaceSchemaColumnDefinition } from 'src/engine/twenty-orm/workspace-schema-manager/types/workspace-schema-column-definition.type'; import { computePostgresEnumName } from 'src/engine/workspace-manager/workspace-migration-runner/utils/compute-postgres-enum-name.util'; import { - WorkspaceSchemaMigrationException, - WorkspaceSchemaMigrationExceptionCode, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception'; - -import { getWorkspaceSchemaContextForMigration } from './get-workspace-schema-context-for-migration.util'; + WorkspaceMigrationRunnerException, + WorkspaceMigrationRunnerExceptionCode, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception'; +import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util'; export const generateCompositeColumnDefinition = ({ compositeProperty, @@ -35,16 +34,16 @@ export const generateCompositeColumnDefinition = ({ }): WorkspaceSchemaColumnDefinition => { const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({ workspaceId: flatObjectMetadataWithoutFields.workspaceId, - flatObjectMetadataWithoutFields, + flatObjectMetadata: flatObjectMetadataWithoutFields, }); if ( compositeProperty.type === FieldMetadataType.RELATION || compositeProperty.type === FieldMetadataType.MORPH_RELATION ) { - throw new WorkspaceSchemaMigrationException( + throw new WorkspaceMigrationRunnerException( `Relation type not supported for composite columns`, - WorkspaceSchemaMigrationExceptionCode.UNSUPPORTED_COMPOSITE_COLUMN_TYPE, + WorkspaceMigrationRunnerExceptionCode.UNSUPPORTED_COMPOSITE_COLUMN_TYPE, ); } @@ -156,7 +155,7 @@ export const generateColumnDefinitions = ({ }): WorkspaceSchemaColumnDefinition[] => { const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({ workspaceId: flatObjectMetadataWithoutFields.workspaceId, - flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields, + flatObjectMetadata: flatObjectMetadataWithoutFields, }); if (isCompositeFlatFieldMetadata(flatFieldMetadata)) { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/get-workspace-schema-context-for-migration.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util.ts similarity index 61% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/get-workspace-schema-context-for-migration.util.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util.ts index cbdee84dfdd..73686e2c991 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/get-workspace-schema-context-for-migration.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util.ts @@ -1,4 +1,7 @@ -import { type FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { + type FlatObjectMetadata, + type FlatObjectMetadataWithoutFields, +} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; @@ -9,13 +12,13 @@ export type WorkspaceSchemaContextForMigration = { export const getWorkspaceSchemaContextForMigration = ({ workspaceId, - flatObjectMetadataWithoutFields, + flatObjectMetadata, }: { workspaceId: string; - flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields; + flatObjectMetadata: FlatObjectMetadata | FlatObjectMetadataWithoutFields; }): WorkspaceSchemaContextForMigration => { return { schemaName: getWorkspaceSchemaName(workspaceId), - tableName: computeObjectTargetTable(flatObjectMetadataWithoutFields), + tableName: computeObjectTargetTable(flatObjectMetadata), }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/workspace-schema-enum-operations.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util.ts similarity index 96% rename from packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/workspace-schema-enum-operations.util.ts rename to packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util.ts index 4527baabd80..0311de1cd44 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/workspace-schema-enum-operations.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util.ts @@ -12,9 +12,9 @@ import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field- import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; import { computePostgresEnumName } from 'src/engine/workspace-manager/workspace-migration-runner/utils/compute-postgres-enum-name.util'; import { - WorkspaceSchemaMigrationException, - WorkspaceSchemaMigrationExceptionCode, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception'; + WorkspaceMigrationRunnerException, + WorkspaceMigrationRunnerExceptionCode, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception'; export interface CreateEnumOperationSpec { operation: EnumOperation.CREATE; @@ -246,9 +246,9 @@ export const executeBatchEnumOperations = async ({ await Promise.all(enumPromises); } catch (error) { - throw new WorkspaceSchemaMigrationException( + throw new WorkspaceMigrationRunnerException( `Failed to execute batch enum operations: ${error instanceof Error ? error.message : 'Unknown error'}`, - WorkspaceSchemaMigrationExceptionCode.ENUM_OPERATION_FAILED, + WorkspaceMigrationRunnerExceptionCode.ENUM_OPERATION_FAILED, ); } }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts deleted file mode 100644 index 164b66867e7..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { In } from 'typeorm'; - -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { - type CreateFieldAction, - type DeleteFieldAction, - type UpdateFieldAction, - type WorkspaceMigrationFieldActionTypeV2, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; -import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type'; -import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; -import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util'; - -export class WorkspaceMetadataFieldActionRunnerService - implements - RunnerMethodForActionType -{ - runDeleteFieldMetadataMigration = async ({ - action, - queryRunner, - }: WorkspaceMigrationActionRunnerArgs) => { - const fieldMetadataRepository = - queryRunner.manager.getRepository( - FieldMetadataEntity, - ); - - const { fieldMetadataId } = action; - - await fieldMetadataRepository.delete({ - id: In([fieldMetadataId]), - }); - }; - - runCreateFieldMetadataMigration = async ({ - action, - queryRunner, - }: WorkspaceMigrationActionRunnerArgs) => { - const fieldMetadataRepository = - queryRunner.manager.getRepository( - FieldMetadataEntity, - ); - - const { flatFieldMetadata } = action; - - await fieldMetadataRepository.save(flatFieldMetadata); - }; - runUpdateFieldMetadataMigration = async ({ - action, - queryRunner, - }: WorkspaceMigrationActionRunnerArgs) => { - const fieldMetadataRepository = - queryRunner.manager.getRepository( - FieldMetadataEntity, - ); - - const { fieldMetadataId } = action; - - await fieldMetadataRepository.update( - fieldMetadataId, - fromWorkspaceMigrationUpdateActionToPartialEntity(action), - ); - }; -} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-index-action-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-index-action-runner.service.ts deleted file mode 100644 index 62aabfef062..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-index-action-runner.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - type CreateIndexAction, - type DeleteIndexAction, - type WorkspaceMigrationIndexActionTypeV2, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2'; -import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type'; -import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; - -@Injectable() -export class WorkspaceMetadataIndexActionRunnerService - implements - RunnerMethodForActionType -{ - runDeleteIndexMetadataMigration = async ( - _action: WorkspaceMigrationActionRunnerArgs, - ) => { - return; - }; - runCreateIndexMetadataMigration = async ( - _action: WorkspaceMigrationActionRunnerArgs, - ) => { - return; - }; -} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service.ts deleted file mode 100644 index b7e89c1b5e0..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { assertUnreachable } from 'twenty-shared/utils'; - -import { type WorkspaceMigrationRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type'; -import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service'; -import { WorkspaceMetadataIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-index-action-runner.service'; -import { WorkspaceMetadataObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service'; - -@Injectable() -export class WorkspaceMetadataMigrationRunnerService { - constructor( - private readonly workspaceMetadataObjectMigrationRunnerService: WorkspaceMetadataObjectActionRunnerService, - private readonly workspaceMetadataIndexMigrationRunnerService: WorkspaceMetadataIndexActionRunnerService, - private readonly workspaceMetadataFieldMigrationRunnerService: WorkspaceMetadataFieldActionRunnerService, - ) {} - - runWorkspaceMetadataMigration = async ({ - action, - queryRunner, - flatObjectMetadataMaps, - }: WorkspaceMigrationRunnerArgs) => { - switch (action.type) { - case 'delete_object': { - await this.workspaceMetadataObjectMigrationRunnerService.runDeleteObjectMetadataMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'create_object': { - await this.workspaceMetadataObjectMigrationRunnerService.runCreateObjectMetadataMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'update_object': { - await this.workspaceMetadataObjectMigrationRunnerService.runUpdateObjectMetadataMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'create_field': { - await this.workspaceMetadataFieldMigrationRunnerService.runCreateFieldMetadataMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'update_field': { - await this.workspaceMetadataFieldMigrationRunnerService.runUpdateFieldMetadataMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'delete_field': { - await this.workspaceMetadataFieldMigrationRunnerService.runDeleteFieldMetadataMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'create_index': { - await this.workspaceMetadataIndexMigrationRunnerService.runCreateIndexMetadataMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'delete_index': { - await this.workspaceMetadataIndexMigrationRunnerService.runDeleteIndexMetadataMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - default: { - assertUnreachable( - action, - 'Should never occur, encountered an unsupported workspace migration action type', - ); - } - } - }; -} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts deleted file mode 100644 index b51f00a6efa..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { - type CreateObjectAction, - type DeleteObjectAction, - type UpdateObjectAction, - type WorkspaceMigrationObjectActionTypeV2, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; -import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type'; -import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; -import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util'; -import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service'; - -@Injectable() -export class WorkspaceMetadataObjectActionRunnerService - implements - RunnerMethodForActionType -{ - constructor( - private readonly dataSourceService: DataSourceService, - private readonly workspaceMetadataFieldActionRunnerService: WorkspaceMetadataFieldActionRunnerService, - ) {} - - runDeleteObjectMetadataMigration = async ({ - action: { objectMetadataId }, - queryRunner, - }: WorkspaceMigrationActionRunnerArgs) => { - const objectMetadataRepository = - queryRunner.manager.getRepository( - ObjectMetadataEntity, - ); - - await objectMetadataRepository.delete(objectMetadataId); - }; - - runCreateObjectMetadataMigration = async ({ - action: { flatObjectMetadataWithoutFields, createFieldActions }, - queryRunner, - flatObjectMetadataMaps, - }: WorkspaceMigrationActionRunnerArgs) => { - const objectMetadataRepository = - queryRunner.manager.getRepository( - ObjectMetadataEntity, - ); - const lastDataSourceMetadata = - await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( - flatObjectMetadataWithoutFields.workspaceId, - ); - - await objectMetadataRepository.save({ - ...flatObjectMetadataWithoutFields, - dataSourceId: lastDataSourceMetadata.id, - targetTableName: 'DEPRECATED', - }); - - for (const createFieldAction of createFieldActions) { - await this.workspaceMetadataFieldActionRunnerService.runCreateFieldMetadataMigration( - { - action: createFieldAction, - queryRunner, - flatObjectMetadataMaps, - }, - ); - } - }; - - runUpdateObjectMetadataMigration = async ({ - action, - queryRunner, - }: WorkspaceMigrationActionRunnerArgs) => { - const objectMetadataRepository = - queryRunner.manager.getRepository( - ObjectMetadataEntity, - ); - - await objectMetadataRepository.update( - action.objectMetadataId, - fromWorkspaceMigrationUpdateActionToPartialEntity(action), - ); - }; -} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts index 129a343c507..13b3b0bfcff 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { DiscoveryModule } from '@nestjs/core'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; @@ -6,16 +7,9 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; -import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module'; -import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service'; -import { WorkspaceMetadataIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-index-action-runner.service'; -import { WorkspaceMetadataMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service'; -import { WorkspaceMetadataObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service'; -import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service'; -import { WorkspaceSchemaFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service'; -import { WorkspaceSchemaIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-index-action-runner.service'; -import { WorkspaceSchemaMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service'; -import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service'; +import { WorkspaceSchemaMigrationRunnerActionHandlersModule } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module'; +import { WorkspaceMigrationRunnerActionHandlerRegistryService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service'; +import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service'; @Module({ imports: [ @@ -25,18 +19,12 @@ import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-m WorkspaceMetadataVersionModule, WorkspacePermissionsCacheModule, WorkspaceMetadataCacheModule, - WorkspaceSchemaManagerModule, + WorkspaceSchemaMigrationRunnerActionHandlersModule, + DiscoveryModule, ], providers: [ - WorkspaceMetadataObjectActionRunnerService, - WorkspaceMetadataIndexActionRunnerService, - WorkspaceMetadataFieldActionRunnerService, - WorkspaceSchemaIndexActionRunnerService, - WorkspaceMetadataMigrationRunnerService, - WorkspaceSchemaMigrationRunnerService, WorkspaceMigrationRunnerV2Service, - WorkspaceSchemaObjectActionRunnerService, - WorkspaceSchemaFieldActionRunnerService, + WorkspaceMigrationRunnerActionHandlerRegistryService, ], exports: [WorkspaceMigrationRunnerV2Service], }) diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/__tests__/workspace-schema-field-action-runner.service.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/__tests__/workspace-schema-field-action-runner.service.spec.ts deleted file mode 100644 index 3437861a24f..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/__tests__/workspace-schema-field-action-runner.service.spec.ts +++ /dev/null @@ -1,730 +0,0 @@ -import { FieldMetadataType } from 'twenty-shared/types'; -import { type QueryRunner } from 'typeorm'; - -import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; - -import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock'; -import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; -import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { WorkspaceSchemaFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service'; - -describe('WorkspaceSchemaFieldActionRunner', () => { - let service: WorkspaceSchemaFieldActionRunnerService; - let mockSchemaManagerService: jest.Mocked; - let mockQueryRunner: jest.Mocked; - - const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419'; - const mockSchemaName = getWorkspaceSchemaName(mockWorkspaceId); - const mockObjectMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea418'; - const mockFieldMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea417'; - - const createMockFlatObjectMetadataMaps = ( - objectMetadata: any, - fieldMetadata: any, - ) => ({ - byId: { - [mockObjectMetadataId]: { - ...objectMetadata, - fieldsById: { - [mockFieldMetadataId]: fieldMetadata, - }, - }, - }, - idByNameSingular: { - [objectMetadata.nameSingular]: mockObjectMetadataId, - }, - }); - - beforeEach(() => { - mockSchemaManagerService = { - columnManager: { - dropColumns: jest.fn(), - addColumns: jest.fn(), - renameColumn: jest.fn(), - alterColumnDefault: jest.fn(), - }, - enumManager: { - createEnum: jest.fn(), - dropEnum: jest.fn(), - renameEnum: jest.fn(), - alterEnumValues: jest.fn(), - }, - tableManager: { - createTable: jest.fn(), - dropTable: jest.fn(), - renameTable: jest.fn(), - }, - } as any; - - mockQueryRunner = {} as any; - - service = new WorkspaceSchemaFieldActionRunnerService( - mockSchemaManagerService, - ); - }); - - describe('DELETE Field Migration', () => { - it('should properly delete composite ADDRESS fields with all sub-columns', async () => { - const addressField = getFlatFieldMetadataMock({ - id: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.ADDRESS, - name: 'homeAddress', - uniqueIdentifier: 'homeAddress', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'person', - uniqueIdentifier: 'person', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - addressField, - ); - - await service.runDeleteFieldSchemaMigration({ - action: { - type: 'delete_field', - fieldMetadataId: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // All 8 ADDRESS composite columns must be dropped - expect( - mockSchemaManagerService.columnManager.dropColumns, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_person', - columnNames: [ - 'homeAddressAddressStreet1', - 'homeAddressAddressStreet2', - 'homeAddressAddressCity', - 'homeAddressAddressPostcode', - 'homeAddressAddressState', - 'homeAddressAddressCountry', - 'homeAddressAddressLat', - 'homeAddressAddressLng', - ], - }); - - // No enum operations should be performed for ADDRESS fields - expect( - mockSchemaManagerService.enumManager.dropEnum, - ).not.toHaveBeenCalled(); - }); - - it('should properly delete SELECT fields with enum cleanup', async () => { - const selectField = getFlatFieldMetadataMock({ - id: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'status', - options: [ - { - id: '1', - value: 'ACTIVE', - label: 'Active', - color: 'green', - position: 0, - }, - { - id: '2', - value: 'INACTIVE', - label: 'Inactive', - color: 'red', - position: 1, - }, - ], - uniqueIdentifier: 'status', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'person', - uniqueIdentifier: 'person', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - selectField, - ); - - await service.runDeleteFieldSchemaMigration({ - action: { - type: 'delete_field', - fieldMetadataId: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Column must be dropped first - expect( - mockSchemaManagerService.columnManager.dropColumns, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_person', - columnNames: ['status'], - }); - - // Enum type must be properly cleaned up - expect( - mockSchemaManagerService.enumManager.dropEnum, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - enumName: '_person_status_enum', - }); - }); - - it('should properly delete RELATION fields with foreign key cleanup', async () => { - const relationField = getFlatFieldMetadataMock({ - id: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.RELATION, - name: 'company', - settings: { - relationType: RelationType.MANY_TO_ONE, - joinColumnName: 'companyId', - }, - uniqueIdentifier: 'company', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'person', - uniqueIdentifier: 'person', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - relationField, - ); - - await service.runDeleteFieldSchemaMigration({ - action: { - type: 'delete_field', - fieldMetadataId: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Foreign key column must be dropped - expect( - mockSchemaManagerService.columnManager.dropColumns, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_person', - columnNames: ['companyId'], - }); - - // No enum operations for relation fields - expect( - mockSchemaManagerService.enumManager.dropEnum, - ).not.toHaveBeenCalled(); - }); - }); - - describe('CREATE Field Migration', () => { - it('should create SELECT fields with proper enum-first ordering', async () => { - const selectField = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'priority', - options: [ - { id: '1', value: 'HIGH', label: 'High', color: 'red', position: 0 }, - { id: '2', value: 'LOW', label: 'Low', color: 'blue', position: 1 }, - ], - uniqueIdentifier: 'priority', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'task', - uniqueIdentifier: 'task', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - selectField, - ); - - await service.runCreateFieldSchemaMigration({ - action: { - type: 'create_field', - flatFieldMetadata: selectField, - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Enum must be created BEFORE column that depends on it - const enumCreateMock = mockSchemaManagerService.enumManager - .createEnum as unknown as jest.Mock; - const addColumnsMock = mockSchemaManagerService.columnManager - .addColumns as unknown as jest.Mock; - - const allCalls = [ - ...enumCreateMock.mock.calls.map((call: unknown[]) => ({ - type: 'enum', - call, - })), - ...addColumnsMock.mock.calls.map((call: unknown[]) => ({ - type: 'column', - call, - })), - ]; - - expect(allCalls).toHaveLength(2); - expect(allCalls[0].type).toBe('enum'); - expect(allCalls[1].type).toBe('column'); - - // Enum creation with correct parameters - expect( - mockSchemaManagerService.enumManager.createEnum, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - enumName: '_task_priority_enum', - values: ['HIGH', 'LOW'], - }); - - // Column creation with enum reference - expect( - mockSchemaManagerService.columnManager.addColumns, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_task', - columnDefinitions: [ - { - name: 'priority', - type: `"${mockSchemaName}"."_task_priority_enum"`, - isNullable: true, - isArray: false, - isUnique: false, - default: null, - }, - ], - }); - }); - - it('should create CURRENCY composite fields with proper column generation', async () => { - const currencyField = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.CURRENCY, - name: 'salary', - defaultValue: { - amountMicros: '5000000000', - currencyCode: 'USD', - }, - uniqueIdentifier: 'salary', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'employee', - uniqueIdentifier: 'employee', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - currencyField, - ); - - await service.runCreateFieldSchemaMigration({ - action: { - type: 'create_field', - flatFieldMetadata: currencyField, - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // No enum operations for composite fields - expect( - mockSchemaManagerService.enumManager.createEnum, - ).not.toHaveBeenCalled(); - - // Both composite columns must be created - expect( - mockSchemaManagerService.columnManager.addColumns, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_employee', - columnDefinitions: [ - { - name: 'salaryAmountMicros', - type: 'numeric', - isNullable: true, - isUnique: false, - default: '5000000000', - isArray: false, - }, - { - name: 'salaryCurrencyCode', - type: 'text', - isNullable: true, - isUnique: false, - default: 'USD', - isArray: false, - }, - ], - }); - }); - }); - - describe('UPDATE Field Migration', () => { - it('should handle field name updates with composite field column renaming', async () => { - const addressField = getFlatFieldMetadataMock({ - id: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.ADDRESS, - name: 'oldAddress', - uniqueIdentifier: 'oldAddress', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'company', - uniqueIdentifier: 'company', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - addressField, - ); - - await service.runUpdateFieldSchemaMigration({ - action: { - type: 'update_field', - workspaceId: mockWorkspaceId, - fieldMetadataId: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - updates: [ - { - property: 'name', - from: 'oldAddress', - to: 'newAddress', - }, - ], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // All 8 ADDRESS composite columns must be renamed - const expectedRenameCalls = [ - ['oldAddressAddressStreet1', 'newAddressAddressStreet1'], - ['oldAddressAddressStreet2', 'newAddressAddressStreet2'], - ['oldAddressAddressCity', 'newAddressAddressCity'], - ['oldAddressAddressPostcode', 'newAddressAddressPostcode'], - ['oldAddressAddressState', 'newAddressAddressState'], - ['oldAddressAddressCountry', 'newAddressAddressCountry'], - ['oldAddressAddressLat', 'newAddressAddressLat'], - ['oldAddressAddressLng', 'newAddressAddressLng'], - ]; - - expect( - mockSchemaManagerService.columnManager.renameColumn, - ).toHaveBeenCalledTimes(8); - - expectedRenameCalls.forEach(([fromName, toName]) => { - expect( - mockSchemaManagerService.columnManager.renameColumn, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_company', - oldColumnName: fromName, - newColumnName: toName, - }); - }); - }); - - it('should handle enum field option updates with proper value mapping', async () => { - const selectField = getFlatFieldMetadataMock({ - id: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'status', - options: [ - { - id: '1', - value: 'UPDATED_ACTIVE', - label: 'Updated Active', - color: 'green', - position: 0, - }, - { - id: '2', - value: 'UPDATED_INACTIVE', - label: 'Updated Inactive', - color: 'red', - position: 1, - }, - ], - uniqueIdentifier: 'status', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'person', - uniqueIdentifier: 'person', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - selectField, - ); - - await service.runUpdateFieldSchemaMigration({ - action: { - type: 'update_field', - workspaceId: mockWorkspaceId, - fieldMetadataId: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - updates: [ - { - property: 'options', - from: [ - { - id: '1', - value: 'ACTIVE', - label: 'Active', - color: 'green', - position: 0, - }, - { - id: '2', - value: 'INACTIVE', - label: 'Inactive', - color: 'red', - position: 1, - }, - ], - to: [ - { - id: '1', - value: 'UPDATED_ACTIVE', - label: 'Updated Active', - color: 'green', - position: 0, - }, - { - id: '2', - value: 'UPDATED_INACTIVE', - label: 'Updated Inactive', - color: 'red', - position: 1, - }, - ], - }, - ], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Enum values must be updated with proper mapping - expect( - mockSchemaManagerService.enumManager.alterEnumValues, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_person', - enumValues: ['UPDATED_ACTIVE', 'UPDATED_INACTIVE'], - columnDefinition: { - name: 'status', - type: `"${mockSchemaName}"."_person_status_enum"`, - isNullable: true, - isArray: false, - isUnique: false, - default: null, - }, - oldToNewEnumOptionMap: { - ACTIVE: 'UPDATED_ACTIVE', // Keep original values - INACTIVE: 'UPDATED_INACTIVE', - }, - }); - }); - - it('should handle default value updates for composite fields', async () => { - const currencyField = getFlatFieldMetadataMock({ - id: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.CURRENCY, - name: 'price', - defaultValue: { - amountMicros: "'0'", - currencyCode: "'USD'", - }, - uniqueIdentifier: 'price', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'product', - uniqueIdentifier: 'product', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - currencyField, - ); - - await service.runUpdateFieldSchemaMigration({ - action: { - type: 'update_field', - workspaceId: mockWorkspaceId, - fieldMetadataId: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - updates: [ - { - property: 'defaultValue', - from: { - amountMicros: "'0'", - currencyCode: "'USD'", - }, - to: { - amountMicros: "'100000000'", - currencyCode: "'EUR'", - }, - }, - ], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Both composite column defaults must be updated - expect( - mockSchemaManagerService.columnManager.alterColumnDefault, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_product', - columnName: 'priceAmountMicros', - defaultValue: '100000000', - }); - - expect( - mockSchemaManagerService.columnManager.alterColumnDefault, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_product', - columnName: 'priceCurrencyCode', - defaultValue: 'EUR', - }); - }); - }); - - describe('Error Handling and Edge Cases', () => { - it('should handle fields with no join column settings gracefully', async () => { - const relationFieldWithoutSettings = getFlatFieldMetadataMock({ - id: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.RELATION, - name: 'company', - settings: undefined, - uniqueIdentifier: 'company', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'person', - uniqueIdentifier: 'person', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - relationFieldWithoutSettings, - ); - - await service.runDeleteFieldSchemaMigration({ - action: { - type: 'delete_field', - fieldMetadataId: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Should not attempt to drop columns for relations without join columns - expect( - mockSchemaManagerService.columnManager.dropColumns, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_person', - columnNames: [], // Empty array - no columns to drop - }); - }); - - it('should handle enum fields without options safely', async () => { - const selectFieldWithoutOptions = getFlatFieldMetadataMock({ - id: mockFieldMetadataId, - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'emptyStatus', - options: [], - uniqueIdentifier: 'emptyStatus', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'test', - uniqueIdentifier: 'test', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - selectFieldWithoutOptions, - ); - - await service.runCreateFieldSchemaMigration({ - action: { - type: 'create_field', - flatFieldMetadata: selectFieldWithoutOptions, - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Should still create enum even with empty values - expect( - mockSchemaManagerService.enumManager.createEnum, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - enumName: '_test_emptyStatus_enum', - values: [], // Empty enum values array - }); - }); - }); -}); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/__tests__/workspace-schema-object-action-runner.service.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/__tests__/workspace-schema-object-action-runner.service.spec.ts deleted file mode 100644 index 75f55c096c8..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/__tests__/workspace-schema-object-action-runner.service.spec.ts +++ /dev/null @@ -1,873 +0,0 @@ -import { FieldMetadataType } from 'twenty-shared/types'; -import { type QueryRunner } from 'typeorm'; - -import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock'; -import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; -import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service'; - -describe('WorkspaceSchemaObjectActionRunner', () => { - let service: WorkspaceSchemaObjectActionRunnerService; - let mockSchemaManagerService: jest.Mocked; - let mockQueryRunner: jest.Mocked; - - const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419'; - const mockObjectMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea418'; - const mockSchemaName = getWorkspaceSchemaName(mockWorkspaceId); - - const createMockFlatObjectMetadataMaps = ( - objectMetadata: any, - fields: any[] = [], - ) => { - const fieldsById = fields.reduce((acc, field, index) => { - const fieldId = `field-${index}`; - - acc[fieldId] = { ...field, id: fieldId }; - - return acc; - }, {}); - - return { - byId: { - [mockObjectMetadataId]: { - ...objectMetadata, - fieldsById, - }, - }, - idByNameSingular: { - [objectMetadata.nameSingular]: mockObjectMetadataId, - }, - }; - }; - - beforeEach(() => { - mockSchemaManagerService = { - columnManager: { - dropColumns: jest.fn(), - addColumns: jest.fn(), - renameColumn: jest.fn(), - alterColumnDefault: jest.fn(), - }, - enumManager: { - createEnum: jest.fn(), - dropEnum: jest.fn(), - renameEnum: jest.fn(), - alterEnumValues: jest.fn(), - }, - tableManager: { - createTable: jest.fn(), - dropTable: jest.fn(), - renameTable: jest.fn(), - }, - } as any; - - mockQueryRunner = {} as any; - - service = new WorkspaceSchemaObjectActionRunnerService( - mockSchemaManagerService, - ); - }); - - describe('DELETE Object Migration', () => { - it('should properly delete objects with SELECT field enum cleanup', async () => { - const selectField1 = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'status', - uniqueIdentifier: 'status', - options: [ - { - id: '1', - value: 'ACTIVE', - label: 'Active', - color: 'green', - position: 0, - }, - ], - }); - - const selectField2 = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'priority', - uniqueIdentifier: 'priority', - options: [ - { id: '1', value: 'HIGH', label: 'High', color: 'red', position: 0 }, - ], - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'task', - uniqueIdentifier: 'task', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - [selectField1, selectField2], - ); - - await service.runDeleteObjectSchemaMigration({ - action: { - type: 'delete_object', - objectMetadataId: mockObjectMetadataId, - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Table must be dropped first - expect( - mockSchemaManagerService.tableManager.dropTable, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_task', - }); - - // All enum types associated with the object must be cleaned up - expect( - mockSchemaManagerService.enumManager.dropEnum, - ).toHaveBeenCalledTimes(2); - expect( - mockSchemaManagerService.enumManager.dropEnum, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - enumName: '_task_status_enum', - }); - expect( - mockSchemaManagerService.enumManager.dropEnum, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - enumName: '_task_priority_enum', - }); - }); - - it('should handle object deletion with mixed field types', async () => { - const textField = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.TEXT, - name: 'description', - uniqueIdentifier: 'description', - }); - - const addressField = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.ADDRESS, - name: 'location', - uniqueIdentifier: 'location', - }); - - const multiSelectField = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.MULTI_SELECT, - name: 'tags', - uniqueIdentifier: 'tags', - options: [ - { - id: '1', - value: 'URGENT', - label: 'Urgent', - color: 'red', - position: 0, - }, - ], - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'project', - uniqueIdentifier: 'project', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - [textField, addressField, multiSelectField], - ); - - await service.runDeleteObjectSchemaMigration({ - action: { - type: 'delete_object', - objectMetadataId: mockObjectMetadataId, - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Table must be dropped - expect( - mockSchemaManagerService.tableManager.dropTable, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_project', - }); - - // Only enum fields should trigger enum cleanup - expect( - mockSchemaManagerService.enumManager.dropEnum, - ).toHaveBeenCalledTimes(1); - expect( - mockSchemaManagerService.enumManager.dropEnum, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - enumName: '_project_tags_enum', - }); - }); - - it('should handle object deletion with no enum fields', async () => { - const textField = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.TEXT, - name: 'name', - uniqueIdentifier: 'name', - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'simpleObject', - uniqueIdentifier: 'simpleObject', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - [textField], - ); - - await service.runDeleteObjectSchemaMigration({ - action: { - type: 'delete_object', - objectMetadataId: mockObjectMetadataId, - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Table must be dropped - expect( - mockSchemaManagerService.tableManager.dropTable, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_simpleObject', - }); - - // No enum cleanup should occur - expect( - mockSchemaManagerService.enumManager.dropEnum, - ).not.toHaveBeenCalled(); - }); - }); - - describe('CREATE Object Migration', () => { - it('should create objects with mixed field types and proper column generation', async () => { - const textFieldAction = { - type: 'create_field' as const, - flatFieldMetadata: getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.TEXT, - name: 'title', - uniqueIdentifier: 'title', - }), - }; - - const selectFieldAction = { - type: 'create_field' as const, - flatFieldMetadata: getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'status', - uniqueIdentifier: 'status', - options: [ - { - id: '1', - value: 'DRAFT', - label: 'Draft', - color: 'gray', - position: 0, - }, - { - id: '2', - value: 'PUBLISHED', - label: 'Published', - color: 'green', - position: 1, - }, - ], - }), - }; - - const currencyFieldAction = { - type: 'create_field' as const, - flatFieldMetadata: getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.CURRENCY, - name: 'price', - uniqueIdentifier: 'price', - }), - }; - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'article', - uniqueIdentifier: 'article', - }); - - await service.runCreateObjectSchemaMigration({ - action: { - type: 'create_object', - flatObjectMetadataWithoutFields: objectMetadata, - createFieldActions: [ - textFieldAction, - selectFieldAction, - currencyFieldAction, - ], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps: { - byId: { - [mockObjectMetadataId]: { - ...objectMetadata, - fieldsById: {}, - fieldIdByJoinColumnName: {}, - fieldIdByName: {}, - }, - }, - idByNameSingular: { - [objectMetadata.nameSingular]: mockObjectMetadataId, - }, - }, - }); - - // Table must be created with all field columns and enums - expect( - mockSchemaManagerService.tableManager.createTable, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_article', - columnDefinitions: [ - // TEXT field column - { - name: 'title', - type: 'text', - isNullable: true, - isArray: false, - isUnique: false, - default: null, - }, - // SELECT field column - { - name: 'status', - type: `"${mockSchemaName}"."_article_status_enum"`, - isNullable: true, - isArray: false, - isUnique: false, - default: null, - }, - // CURRENCY field columns (2 composite columns) - { - name: 'priceAmountMicros', - type: 'numeric', - isNullable: true, - isArray: false, - isUnique: false, - default: null, - }, - { - name: 'priceCurrencyCode', - type: 'text', - isNullable: true, - isArray: false, - isUnique: false, - default: null, - }, - ], - }); - - expect( - mockSchemaManagerService.enumManager.createEnum, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - enumName: '_article_status_enum', - values: ['DRAFT', 'PUBLISHED'], - }); - }); - - it('should handle object creation with ADDRESS composite fields', async () => { - const addressFieldAction = { - type: 'create_field' as const, - flatFieldMetadata: getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.ADDRESS, - name: 'headquarters', - isNullable: false, // Test non-nullable composite field - uniqueIdentifier: 'headquarters', - }), - }; - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'company', - uniqueIdentifier: 'company', - }); - - await service.runCreateObjectSchemaMigration({ - action: { - type: 'create_object', - flatObjectMetadataWithoutFields: objectMetadata, - createFieldActions: [addressFieldAction], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps: { - byId: { - [mockObjectMetadataId]: { - ...objectMetadata, - fieldsById: {}, - fieldIdByJoinColumnName: {}, - fieldIdByName: {}, - }, - }, - idByNameSingular: { - [objectMetadata.nameSingular]: mockObjectMetadataId, - }, - }, - }); - - // Table must be created with all 8 ADDRESS composite columns - expect( - mockSchemaManagerService.tableManager.createTable, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_company', - columnDefinitions: [ - { - name: 'headquartersAddressStreet1', - type: 'text', - isNullable: true, - isUnique: false, - default: null, - isArray: false, - }, - { - name: 'headquartersAddressStreet2', - type: 'text', - isNullable: true, - isUnique: false, - default: null, - isArray: false, - }, - { - name: 'headquartersAddressCity', - type: 'text', - isNullable: true, - isUnique: false, - default: null, - isArray: false, - }, - { - name: 'headquartersAddressPostcode', - type: 'text', - isNullable: true, - isUnique: false, - default: null, - isArray: false, - }, - { - name: 'headquartersAddressState', - type: 'text', - isNullable: true, - isUnique: false, - default: null, - isArray: false, - }, - { - name: 'headquartersAddressCountry', - type: 'text', - isNullable: true, - isUnique: false, - default: null, - isArray: false, - }, - { - name: 'headquartersAddressLat', - type: 'numeric', - isNullable: true, - isUnique: false, - default: null, - isArray: false, - }, - { - name: 'headquartersAddressLng', - type: 'numeric', - isNullable: true, - isUnique: false, - default: null, - isArray: false, - }, - ], - }); - }); - - it('should handle object creation with no field actions', async () => { - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'emptyObject', - uniqueIdentifier: 'emptyObject', - }); - - await service.runCreateObjectSchemaMigration({ - action: { - type: 'create_object', - flatObjectMetadataWithoutFields: objectMetadata, - createFieldActions: [], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps: { - byId: { - [mockObjectMetadataId]: { - ...objectMetadata, - fieldsById: {}, - fieldIdByJoinColumnName: {}, - fieldIdByName: {}, - }, - }, - idByNameSingular: { - [objectMetadata.nameSingular]: mockObjectMetadataId, - }, - }, - }); - - // Table must be created even with no columns - expect( - mockSchemaManagerService.tableManager.createTable, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_emptyObject', - columnDefinitions: [], // Empty columns array - }); - }); - }); - - describe('UPDATE Object Migration', () => { - it('should handle object name changes with enum type updates', async () => { - const selectField = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'category', - uniqueIdentifier: 'category', - options: [ - { - id: '1', - value: 'TECH', - label: 'Technology', - color: 'blue', - position: 0, - }, - ], - }); - - const multiSelectField = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.MULTI_SELECT, - name: 'tags', - uniqueIdentifier: 'tags', - options: [ - { - id: '1', - value: 'FEATURED', - label: 'Featured', - color: 'yellow', - position: 0, - }, - ], - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'blogPost', - uniqueIdentifier: 'blogPost', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - [selectField, multiSelectField], - ); - - await service.runUpdateObjectSchemaMigration({ - action: { - type: 'update_object', - objectMetadataId: mockObjectMetadataId, - updates: [ - { - property: 'nameSingular', - from: 'blogPost', - to: 'article', - }, - ], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Table must be renamed - expect( - mockSchemaManagerService.tableManager.renameTable, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - oldTableName: '_blogPost', - newTableName: '_article', - }); - - // All enum types must be renamed to match new table name - expect( - mockSchemaManagerService.enumManager.renameEnum, - ).toHaveBeenCalledTimes(2); - expect( - mockSchemaManagerService.enumManager.renameEnum, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - oldEnumName: '_blogPost_category_enum', - newEnumName: '_article_category_enum', - }); - expect( - mockSchemaManagerService.enumManager.renameEnum, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - oldEnumName: '_blogPost_tags_enum', - newEnumName: '_article_tags_enum', - }); - }); - - it('should handle object name changes with no actual table rename needed', async () => { - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'person', - uniqueIdentifier: 'person', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - [], - ); - - await service.runUpdateObjectSchemaMigration({ - action: { - type: 'update_object', - objectMetadataId: mockObjectMetadataId, - updates: [ - { - property: 'nameSingular', - from: 'person', - to: 'person', // Same name - no change needed - }, - ], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // No table operations should occur when names are the same - expect( - mockSchemaManagerService.tableManager.renameTable, - ).not.toHaveBeenCalled(); - expect( - mockSchemaManagerService.enumManager.renameEnum, - ).not.toHaveBeenCalled(); - }); - - it('should handle object updates with complex field combinations', async () => { - const textField = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.TEXT, - name: 'description', - uniqueIdentifier: 'description', - }); - - const selectField = getFlatFieldMetadataMock({ - objectMetadataId: mockObjectMetadataId, - type: FieldMetadataType.SELECT, - name: 'status', - uniqueIdentifier: 'status', - options: [ - { - id: '1', - value: 'ACTIVE', - label: 'Active', - color: 'green', - position: 0, - }, - ], - }); - - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'oldEntity', - uniqueIdentifier: 'oldEntity', - }); - - const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps( - objectMetadata, - [textField, selectField], - ); - - await service.runUpdateObjectSchemaMigration({ - action: { - type: 'update_object', - objectMetadataId: mockObjectMetadataId, - updates: [ - { - property: 'nameSingular', - from: 'oldEntity', - to: 'newEntity', - }, - ], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Table rename - expect( - mockSchemaManagerService.tableManager.renameTable, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - oldTableName: '_oldEntity', - newTableName: '_newEntity', - }); - - // Only SELECT field should trigger enum rename - expect( - mockSchemaManagerService.enumManager.renameEnum, - ).toHaveBeenCalledTimes(1); - expect( - mockSchemaManagerService.enumManager.renameEnum, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - oldEnumName: '_oldEntity_status_enum', - newEnumName: '_newEntity_status_enum', - }); - }); - }); - - describe('Edge Cases and Error Conditions', () => { - it('should handle objects with undefined fieldsById gracefully', async () => { - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'testObject', - uniqueIdentifier: 'testObject', - }); - - // Create maps with undefined fieldsById - const flatObjectMetadataMaps = { - byId: { - [mockObjectMetadataId]: { - ...objectMetadata, - fieldsById: undefined as unknown as Record, - fieldIdByJoinColumnName: {}, - fieldIdByName: {}, - }, - }, - idByNameSingular: { - [objectMetadata.nameSingular]: mockObjectMetadataId, - }, - }; - - await service.runCreateObjectSchemaMigration({ - action: { - type: 'create_object', - flatObjectMetadataWithoutFields: objectMetadata, - createFieldActions: [], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Should still drop table - expect( - mockSchemaManagerService.tableManager.createTable, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - tableName: '_testObject', - columnDefinitions: [], - }); - }); - - it('should handle empty fieldsById object', async () => { - const objectMetadata = getFlatObjectMetadataMock({ - id: mockObjectMetadataId, - workspaceId: mockWorkspaceId, - nameSingular: 'emptyFieldsObject', - uniqueIdentifier: 'emptyFieldsObject', - }); - - const flatObjectMetadataMaps = { - byId: { - [mockObjectMetadataId]: { - ...objectMetadata, - fieldsById: {}, - fieldIdByJoinColumnName: {}, - fieldIdByName: {}, - }, - }, - idByNameSingular: { - [objectMetadata.nameSingular]: mockObjectMetadataId, - }, - }; - - await service.runUpdateObjectSchemaMigration({ - action: { - type: 'update_object', - objectMetadataId: mockObjectMetadataId, - updates: [ - { - property: 'nameSingular', - from: 'emptyFieldsObject', - to: 'renamedEmptyFieldsObject', - }, - ], - }, - queryRunner: mockQueryRunner, - flatObjectMetadataMaps, - }); - - // Should rename table - expect( - mockSchemaManagerService.tableManager.renameTable, - ).toHaveBeenCalledWith({ - queryRunner: mockQueryRunner, - schemaName: mockSchemaName, - oldTableName: '_emptyFieldsObject', - newTableName: '_renamedEmptyFieldsObject', - }); - - // Should not perform enum operations - expect( - mockSchemaManagerService.enumManager.renameEnum, - ).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception.ts deleted file mode 100644 index e8fdab18baa..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { CustomException } from 'src/utils/custom-exception'; - -export class WorkspaceSchemaMigrationException extends CustomException {} - -export enum WorkspaceSchemaMigrationExceptionCode { - COMPOSITE_TYPE_NOT_FOUND = 'COMPOSITE_TYPE_NOT_FOUND', - INVALID_FIELD_TYPE = 'INVALID_FIELD_TYPE', - ENUM_OPERATION_FAILED = 'ENUM_OPERATION_FAILED', - COLUMN_OPERATION_FAILED = 'COLUMN_OPERATION_FAILED', - TABLE_OPERATION_FAILED = 'TABLE_OPERATION_FAILED', - UNSUPPORTED_COMPOSITE_COLUMN_TYPE = 'UNSUPPORTED_COMPOSITE_COLUMN_TYPE', - NOT_SUPPORTED = 'NOT_SUPPORTED', -} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/workspace-schema-context.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/workspace-schema-context.util.ts deleted file mode 100644 index 936c9590ac1..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/workspace-schema-context.util.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; -import { type FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type'; -import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; -import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; - -import { getWorkspaceSchemaContextForMigration } from './get-workspace-schema-context-for-migration.util'; - -export interface WorkspaceSchemaContext { - flatObjectMetadataWithFlatFieldMaps: FlatObjectMetadataWithFlatFieldMaps; - schemaName: string; - tableName: string; -} - -export const prepareWorkspaceSchemaContext = ({ - flatObjectMetadataMaps, - objectMetadataId, -}: { - flatObjectMetadataMaps: FlatObjectMetadataMaps; - objectMetadataId: string; -}): WorkspaceSchemaContext => { - const flatObjectMetadataWithFlatFieldMaps = - findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps, - objectMetadataId, - }); - - const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({ - workspaceId: flatObjectMetadataWithFlatFieldMaps.workspaceId, - flatObjectMetadataWithoutFields: flatObjectMetadataWithFlatFieldMaps, - }); - - return { - flatObjectMetadataWithFlatFieldMaps, - schemaName, - tableName, - }; -}; - -export const prepareFieldWorkspaceSchemaContext = ({ - flatObjectMetadataMaps, - objectMetadataId, - fieldMetadataId, -}: { - flatObjectMetadataMaps: FlatObjectMetadataMaps; - objectMetadataId: string; - fieldMetadataId: string; -}): WorkspaceSchemaContext & { fieldMetadata: FlatFieldMetadata } => { - const context = prepareWorkspaceSchemaContext({ - flatObjectMetadataMaps, - objectMetadataId, - }); - const fieldMetadata = findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps, - objectMetadataId, - fieldMetadataId, - }); - - return { - ...context, - fieldMetadata, - }; -}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-index-action-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-index-action-runner.service.ts deleted file mode 100644 index 04d2ca8cffe..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-index-action-runner.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - type CreateIndexAction, - type DeleteIndexAction, - type WorkspaceMigrationIndexActionTypeV2, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2'; -import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type'; -import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; - -@Injectable() -export class WorkspaceSchemaIndexActionRunnerService - implements - RunnerMethodForActionType -{ - // TODO: Implement index schema operations - runDeleteIndexSchemaMigration = async ( - _action: WorkspaceMigrationActionRunnerArgs, - ) => { - return; - }; - - runCreateIndexSchemaMigration = async ( - _action: WorkspaceMigrationActionRunnerArgs, - ) => { - return; - }; -} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service.ts deleted file mode 100644 index c6a99ac89ac..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { assertUnreachable } from 'twenty-shared/utils'; - -import { type WorkspaceMigrationRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type'; -import { WorkspaceSchemaFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service'; -import { WorkspaceSchemaIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-index-action-runner.service'; -import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service'; - -@Injectable() -export class WorkspaceSchemaMigrationRunnerService { - constructor( - private readonly workspaceSchemaObjectMigrationRunnerService: WorkspaceSchemaObjectActionRunnerService, - private readonly workspaceSchemaIndexMigrationRunnerService: WorkspaceSchemaIndexActionRunnerService, - private readonly workspaceSchemaFieldMigrationRunnerService: WorkspaceSchemaFieldActionRunnerService, - ) {} - - runWorkspaceSchemaMigration = async ({ - action, - queryRunner, - flatObjectMetadataMaps, - }: WorkspaceMigrationRunnerArgs) => { - switch (action.type) { - case 'delete_object': { - await this.workspaceSchemaObjectMigrationRunnerService.runDeleteObjectSchemaMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'create_object': { - await this.workspaceSchemaObjectMigrationRunnerService.runCreateObjectSchemaMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'update_object': { - await this.workspaceSchemaObjectMigrationRunnerService.runUpdateObjectSchemaMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'create_field': { - await this.workspaceSchemaFieldMigrationRunnerService.runCreateFieldSchemaMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'update_field': { - await this.workspaceSchemaFieldMigrationRunnerService.runUpdateFieldSchemaMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'delete_field': { - await this.workspaceSchemaFieldMigrationRunnerService.runDeleteFieldSchemaMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'create_index': { - await this.workspaceSchemaIndexMigrationRunnerService.runCreateIndexSchemaMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - case 'delete_index': { - await this.workspaceSchemaIndexMigrationRunnerService.runDeleteIndexSchemaMigration( - { action, queryRunner, flatObjectMetadataMaps }, - ); - break; - } - default: { - assertUnreachable( - action, - 'Should never occur, encountered an unsupported workspace migration action type', - ); - } - } - }; -} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service.ts deleted file mode 100644 index 77dd73d6572..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util'; -import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util'; -import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; -import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { - type CreateObjectAction, - type DeleteObjectAction, - type UpdateObjectAction, - type WorkspaceMigrationObjectActionTypeV2, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; -import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type'; -import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; -import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util'; - -import { prepareWorkspaceSchemaContext } from './utils/workspace-schema-context.util'; -import { - collectEnumOperationsForObject, - EnumOperation, - executeBatchEnumOperations, -} from './utils/workspace-schema-enum-operations.util'; - -@Injectable() -export class WorkspaceSchemaObjectActionRunnerService - implements - RunnerMethodForActionType -{ - constructor( - private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService, - ) {} - - runDeleteObjectSchemaMigration = async ({ - action: { objectMetadataId }, - queryRunner, - flatObjectMetadataMaps, - }: WorkspaceMigrationActionRunnerArgs) => { - const { schemaName, tableName, flatObjectMetadataWithFlatFieldMaps } = - prepareWorkspaceSchemaContext({ - flatObjectMetadataMaps, - objectMetadataId, - }); - - await this.workspaceSchemaManagerService.tableManager.dropTable({ - queryRunner, - schemaName, - tableName, - }); - - const enumOrCompositeFlatFieldMetadatas = Object.values( - flatObjectMetadataWithFlatFieldMaps.fieldsById, - ) - .filter((field): field is FlatFieldMetadata => field != null) - .filter( - (field) => - isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field), - ); - - const enumOperations = collectEnumOperationsForObject({ - flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas, - tableName, - operation: EnumOperation.DROP, - }); - - await executeBatchEnumOperations({ - enumOperations, - queryRunner, - schemaName, - workspaceSchemaManagerService: this.workspaceSchemaManagerService, - }); - }; - runCreateObjectSchemaMigration = async ({ - action: { flatObjectMetadataWithoutFields, createFieldActions }, - queryRunner, - }: WorkspaceMigrationActionRunnerArgs) => { - const flatObjectMetadata = flatObjectMetadataWithoutFields; - const schemaName = getWorkspaceSchemaName(flatObjectMetadata.workspaceId); - const tableName = computeObjectTargetTable(flatObjectMetadata); - - const columnDefinitions = createFieldActions.flatMap((createFieldAction) => - generateColumnDefinitions({ - flatFieldMetadata: createFieldAction.flatFieldMetadata, - flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields, - }), - ); - - const enumOrCompositeFlatFieldMetadatas = createFieldActions - .map((createFieldAction) => createFieldAction.flatFieldMetadata) - .filter((field): field is FlatFieldMetadata => field != null) - .filter( - (field) => - isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field), - ); - - const enumOperations = collectEnumOperationsForObject({ - flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas, - tableName, - operation: EnumOperation.CREATE, - }); - - await executeBatchEnumOperations({ - enumOperations, - queryRunner, - schemaName, - workspaceSchemaManagerService: this.workspaceSchemaManagerService, - }); - - await this.workspaceSchemaManagerService.tableManager.createTable({ - queryRunner, - schemaName, - tableName, - columnDefinitions, - }); - }; - - runUpdateObjectSchemaMigration = async ({ - action, - queryRunner, - flatObjectMetadataMaps, - }: WorkspaceMigrationActionRunnerArgs) => { - const { objectMetadataId, updates } = action; - const { - schemaName, - tableName: currentTableName, - flatObjectMetadataWithFlatFieldMaps, - } = prepareWorkspaceSchemaContext({ - flatObjectMetadataMaps, - objectMetadataId, - }); - - for (const update of updates) { - if (update.property !== 'nameSingular') { - continue; - } - - const updatedObjectMetadata = { - ...flatObjectMetadataWithFlatFieldMaps, - [update.property]: update.to, - }; - - const newTableName = computeObjectTargetTable(updatedObjectMetadata); - - if (currentTableName !== newTableName) { - await this.workspaceSchemaManagerService.tableManager.renameTable({ - queryRunner, - schemaName, - oldTableName: currentTableName, - newTableName, - }); - - const enumOrCompositeFlatFieldMetadatas = Object.values( - flatObjectMetadataWithFlatFieldMaps.fieldsById, - ) - .filter((field): field is FlatFieldMetadata => field != null) - .filter( - (field) => - isEnumFlatFieldMetadata(field) || - isCompositeFlatFieldMetadata(field), - ); - - const enumOperations = collectEnumOperationsForObject({ - flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas, - tableName: currentTableName, - operation: EnumOperation.RENAME, - options: { - newTableName, - }, - }); - - await executeBatchEnumOperations({ - enumOperations, - queryRunner, - schemaName, - workspaceSchemaManagerService: this.workspaceSchemaManagerService, - }); - } - } - }; -} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts index 8327c3ec2db..f7f50bb4895 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; -import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.module'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; +import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module'; import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module'; -import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service'; @Module({ imports: [ @@ -10,10 +10,6 @@ import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspa WorkspaceMigrationRunnerV2Module, ], providers: [WorkspaceMigrationValidateBuildAndRunService], - exports: [ - WorkspaceMigrationRunnerV2Module, - WorkspaceMigrationBuilderV2Module, - WorkspaceMigrationValidateBuildAndRunService, - ], + exports: [WorkspaceMigrationValidateBuildAndRunService], }) export class WorkspaceMigrationV2Module {}