diff --git a/packages/twenty-server/src/metadata/workspace-migration/utils/generate-migration-name.util.ts b/packages/twenty-server/src/metadata/workspace-migration/utils/generate-migration-name.util.ts index 988ff1be8..2d9057d47 100644 --- a/packages/twenty-server/src/metadata/workspace-migration/utils/generate-migration-name.util.ts +++ b/packages/twenty-server/src/metadata/workspace-migration/utils/generate-migration-name.util.ts @@ -1,3 +1,6 @@ -export function generateMigrationName(name?: string): string { - return `${new Date().getTime()}${name ? `-${name}` : ''}`; +export function generateMigrationName( + name?: string, + addMilliseconds: number = 0, +): string { + return `${new Date().getTime() + addMilliseconds}${name ? `-${name}` : ''}`; } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/field-workspace-migration.factory.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/field-workspace-migration.factory.ts new file mode 100644 index 000000000..caab405af --- /dev/null +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/field-workspace-migration.factory.ts @@ -0,0 +1,133 @@ +import { Injectable } from '@nestjs/common'; + +import { + FieldMetadataEntity, + FieldMetadataType, +} from 'src/metadata/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; +import { + WorkspaceMigrationColumnActionType, + WorkspaceMigrationEntity, + WorkspaceMigrationTableAction, +} from 'src/metadata/workspace-migration/workspace-migration.entity'; +import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util'; +import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory'; +import { generateMigrationName } from 'src/metadata/workspace-migration/utils/generate-migration-name.util'; + +@Injectable() +export class FieldWorkspaceMigrationFactory { + constructor( + private readonly workspaceMigrationFactory: WorkspaceMigrationFactory, + ) {} + + async create( + originalObjectMetadataCollection: ObjectMetadataEntity[], + createFieldMetadataCollection: FieldMetadataEntity[], + deleteFieldMetadataCollection: FieldMetadataEntity[], + ): Promise[]> { + const workspaceMigrations: Partial[] = []; + const originalObjectMetadataMap = originalObjectMetadataCollection.reduce( + (result, currentObject) => { + result[currentObject.id] = currentObject; + + return result; + }, + {} as Record, + ); + + /** + * Create field migrations + */ + if (createFieldMetadataCollection.length > 0) { + const createFieldWorkspaceMigrations = await this.createFieldMigration( + originalObjectMetadataMap, + createFieldMetadataCollection, + ); + + workspaceMigrations.push(...createFieldWorkspaceMigrations); + } + + /** + * Delete field migrations + */ + if (deleteFieldMetadataCollection.length > 0) { + const deleteFieldWorkspaceMigrations = await this.deleteFieldMigration( + originalObjectMetadataMap, + deleteFieldMetadataCollection, + ); + + workspaceMigrations.push(...deleteFieldWorkspaceMigrations); + } + + return workspaceMigrations; + } + + private async createFieldMigration( + originalObjectMetadataMap: Record, + fieldMetadataCollection: FieldMetadataEntity[], + ): Promise[]> { + const workspaceMigrations: Partial[] = []; + + for (const fieldMetadata of fieldMetadataCollection) { + const migrations: WorkspaceMigrationTableAction[] = [ + { + name: computeObjectTargetTable( + originalObjectMetadataMap[fieldMetadata.objectMetadataId], + ), + action: 'alter', + columns: this.workspaceMigrationFactory.createColumnActions( + WorkspaceMigrationColumnActionType.CREATE, + fieldMetadata, + ), + }, + ]; + + workspaceMigrations.push({ + workspaceId: fieldMetadata.workspaceId, + name: generateMigrationName(`create-${fieldMetadata.name}`), + isCustom: false, + migrations, + }); + } + + return workspaceMigrations; + } + + private async deleteFieldMigration( + originalObjectMetadataMap: Record, + fieldMetadataCollection: FieldMetadataEntity[], + ): Promise[]> { + const workspaceMigrations: Partial[] = []; + + for (const fieldMetadata of fieldMetadataCollection) { + // We're skipping relation fields, because they're just representation and not real columns + if (fieldMetadata.type === FieldMetadataType.RELATION) { + continue; + } + + const migrations: WorkspaceMigrationTableAction[] = [ + { + name: computeObjectTargetTable( + originalObjectMetadataMap[fieldMetadata.objectMetadataId], + ), + action: 'alter', + columns: [ + { + action: WorkspaceMigrationColumnActionType.DROP, + columnName: fieldMetadata.name, + }, + ], + }, + ]; + + workspaceMigrations.push({ + workspaceId: fieldMetadata.workspaceId, + name: generateMigrationName(`delete-${fieldMetadata.name}`), + isCustom: false, + migrations, + }); + } + + return workspaceMigrations; + } +} diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/index.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/index.ts index df6a4bfdb..3af888db3 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/index.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/index.ts @@ -1,11 +1,15 @@ import { FeatureFlagFactory } from './feature-flags.factory'; import { StandardObjectFactory } from './standard-object.factory'; import { StandardRelationFactory } from './standard-relation.factory'; -import { WorkspaceSyncFactory } from './workspace-sync.factory'; +import { ObjectWorkspaceMigrationFactory } from './object-workspace-migration.factory'; +import { FieldWorkspaceMigrationFactory } from './field-workspace-migration.factory'; +import { RelationWorkspaceMigrationFactory } from './relation-workspace-migration.factory'; export const workspaceSyncMetadataFactories = [ FeatureFlagFactory, StandardObjectFactory, StandardRelationFactory, - WorkspaceSyncFactory, + ObjectWorkspaceMigrationFactory, + FieldWorkspaceMigrationFactory, + RelationWorkspaceMigrationFactory, ]; diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/object-workspace-migration.factory.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/object-workspace-migration.factory.ts new file mode 100644 index 000000000..a26ed264b --- /dev/null +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/object-workspace-migration.factory.ts @@ -0,0 +1,114 @@ +import { Injectable } from '@nestjs/common'; + +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; +import { + WorkspaceMigrationColumnActionType, + WorkspaceMigrationEntity, + WorkspaceMigrationTableAction, +} from 'src/metadata/workspace-migration/workspace-migration.entity'; +import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util'; +import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory'; +import { generateMigrationName } from 'src/metadata/workspace-migration/utils/generate-migration-name.util'; + +@Injectable() +export class ObjectWorkspaceMigrationFactory { + constructor( + private readonly workspaceMigrationFactory: WorkspaceMigrationFactory, + ) {} + + async create( + createObjectMetadataCollection: ObjectMetadataEntity[], + deleteObjectMetadataCollection: ObjectMetadataEntity[], + ): Promise[]> { + const workspaceMigrations: Partial[] = []; + + /** + * Create object migrations + */ + if (createObjectMetadataCollection.length > 0) { + const createObjectWorkspaceMigrations = await this.createObjectMigration( + createObjectMetadataCollection, + ); + + workspaceMigrations.push(...createObjectWorkspaceMigrations); + } + + /** + * Delete object migrations + */ + if (deleteObjectMetadataCollection.length > 0) { + const deleteObjectWorkspaceMigrations = await this.deleteObjectMigration( + deleteObjectMetadataCollection, + ); + + workspaceMigrations.push(...deleteObjectWorkspaceMigrations); + } + + return workspaceMigrations; + } + + private async createObjectMigration( + objectMetadataCollection: ObjectMetadataEntity[], + ): Promise[]> { + const workspaceMigrations: Partial[] = []; + + for (const objectMetadata of objectMetadataCollection) { + const migrations: WorkspaceMigrationTableAction[] = [ + { + name: computeObjectTargetTable(objectMetadata), + action: 'create', + }, + ]; + + for (const field of objectMetadata.fields) { + if (field.type === FieldMetadataType.RELATION) { + continue; + } + + migrations.push({ + name: computeObjectTargetTable(objectMetadata), + action: 'alter', + columns: this.workspaceMigrationFactory.createColumnActions( + WorkspaceMigrationColumnActionType.CREATE, + field, + ), + }); + } + + workspaceMigrations.push({ + workspaceId: objectMetadata.workspaceId, + name: generateMigrationName(`create-${objectMetadata.nameSingular}`), + isCustom: false, + migrations, + }); + } + + return workspaceMigrations; + } + + private async deleteObjectMigration( + objectMetadataCollection: ObjectMetadataEntity[], + ): Promise[]> { + const workspaceMigrations: Partial[] = []; + + for (const objectMetadata of objectMetadataCollection) { + const migrations: WorkspaceMigrationTableAction[] = [ + { + name: computeObjectTargetTable(objectMetadata), + action: 'drop', + columns: [], + }, + ]; + + workspaceMigrations.push({ + workspaceId: objectMetadata.workspaceId, + name: generateMigrationName(`delete-${objectMetadata.nameSingular}`), + isCustom: false, + migrations, + }); + } + + return workspaceMigrations; + } +} diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/relation-workspace-migration.factory.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/relation-workspace-migration.factory.ts new file mode 100644 index 000000000..1d35319b8 --- /dev/null +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/relation-workspace-migration.factory.ts @@ -0,0 +1,115 @@ +import { Injectable } from '@nestjs/common'; + +import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; +import { + WorkspaceMigrationColumnActionType, + WorkspaceMigrationEntity, + WorkspaceMigrationTableAction, +} from 'src/metadata/workspace-migration/workspace-migration.entity'; +import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util'; +import { + RelationMetadataEntity, + RelationMetadataType, +} from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { camelCase } from 'src/utils/camel-case'; +import { generateMigrationName } from 'src/metadata/workspace-migration/utils/generate-migration-name.util'; + +@Injectable() +export class RelationWorkspaceMigrationFactory { + constructor() {} + + /** + * Deletion of the relation is handled by field deletion + */ + async create( + originalObjectMetadataCollection: ObjectMetadataEntity[], + createRelationMetadataCollection: RelationMetadataEntity[], + ): Promise[]> { + const workspaceMigrations: Partial[] = []; + const originalObjectMetadataMap = originalObjectMetadataCollection.reduce( + (result, currentObject) => { + result[currentObject.id] = currentObject; + + return result; + }, + {} as Record, + ); + + if (createRelationMetadataCollection.length > 0) { + const createRelationWorkspaceMigrations = + await this.createRelationMigration( + originalObjectMetadataMap, + createRelationMetadataCollection, + ); + + workspaceMigrations.push(...createRelationWorkspaceMigrations); + } + + return workspaceMigrations; + } + + private async createRelationMigration( + originalObjectMetadataMap: Record, + relationMetadataCollection: RelationMetadataEntity[], + ): Promise[]> { + const workspaceMigrations: Partial[] = []; + + for (const relationMetadata of relationMetadataCollection) { + const toObjectMetadata = + originalObjectMetadataMap[relationMetadata.toObjectMetadataId]; + const fromObjectMetadata = + originalObjectMetadataMap[relationMetadata.fromObjectMetadataId]; + + if (!toObjectMetadata) { + throw new Error( + `ObjectMetadata with id ${relationMetadata.toObjectMetadataId} not found`, + ); + } + + if (!fromObjectMetadata) { + throw new Error( + `ObjectMetadata with id ${relationMetadata.fromObjectMetadataId} not found`, + ); + } + + const toFieldMetadata = toObjectMetadata.fields.find( + (field) => field.id === relationMetadata.toFieldMetadataId, + ); + + if (!toFieldMetadata) { + throw new Error( + `FieldMetadata with id ${relationMetadata.toFieldMetadataId} not found`, + ); + } + + const migrations: WorkspaceMigrationTableAction[] = [ + { + name: computeObjectTargetTable(toObjectMetadata), + action: 'alter', + columns: [ + { + action: WorkspaceMigrationColumnActionType.RELATION, + columnName: `${camelCase(toFieldMetadata.name)}Id`, + referencedTableName: computeObjectTargetTable(fromObjectMetadata), + referencedTableColumnName: 'id', + isUnique: + relationMetadata.relationType === + RelationMetadataType.ONE_TO_ONE, + }, + ], + }, + ]; + + workspaceMigrations.push({ + workspaceId: relationMetadata.workspaceId, + name: generateMigrationName( + `create-relation-from-${fromObjectMetadata.nameSingular}-to-${toObjectMetadata.nameSingular}`, + ), + isCustom: false, + migrations, + }); + } + + return workspaceMigrations; + } +} diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-object.factory.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-object.factory.ts index fd76c4e06..f1b1b5825 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-object.factory.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-object.factory.ts @@ -6,7 +6,7 @@ import { FeatureFlagMap } from 'src/core/feature-flag/interfaces/feature-flag-ma import { PartialFieldMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-field-metadata.interface'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; -import { standardObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects'; +import { standardObjectMetadataCollection } from 'src/workspace/workspace-sync-metadata/standard-objects'; import { TypedReflect } from 'src/utils/typed-reflect'; import { isGatedAndNotEnabled } from 'src/workspace/workspace-sync-metadata/utils/is-gate-and-not-enabled.util'; @@ -16,7 +16,7 @@ export class StandardObjectFactory { context: WorkspaceSyncContext, workspaceFeatureFlagsMap: FeatureFlagMap, ): PartialObjectMetadata[] { - return standardObjectMetadata + return standardObjectMetadataCollection .map((metadata) => this.createObjectMetadata(metadata, context, workspaceFeatureFlagsMap), ) diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-relation.factory.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-relation.factory.ts index 8d98da4bc..33e2709a5 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-relation.factory.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/standard-relation.factory.ts @@ -4,7 +4,7 @@ import { WorkspaceSyncContext } from 'src/workspace/workspace-sync-metadata/inte import { FeatureFlagMap } from 'src/core/feature-flag/interfaces/feature-flag-map.interface'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; -import { standardObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects'; +import { standardObjectMetadataCollection } from 'src/workspace/workspace-sync-metadata/standard-objects'; import { TypedReflect } from 'src/utils/typed-reflect'; import { isGatedAndNotEnabled } from 'src/workspace/workspace-sync-metadata/utils/is-gate-and-not-enabled.util'; import { assert } from 'src/utils/assert'; @@ -18,7 +18,7 @@ export class StandardRelationFactory { originalObjectMetadataMap: Record, workspaceFeatureFlagsMap: FeatureFlagMap, ): Partial[] { - return standardObjectMetadata.flatMap((standardObjectMetadata) => + return standardObjectMetadataCollection.flatMap((standardObjectMetadata) => this.createRelationMetadata( standardObjectMetadata, context, diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/workspace-sync.factory.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/workspace-sync.factory.ts deleted file mode 100644 index 8a46950a2..000000000 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/factories/workspace-sync.factory.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - FieldMetadataEntity, - FieldMetadataType, -} from 'src/metadata/field-metadata/field-metadata.entity'; -import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; -import { - WorkspaceMigrationColumnActionType, - WorkspaceMigrationColumnRelation, - WorkspaceMigrationEntity, - WorkspaceMigrationTableAction, -} from 'src/metadata/workspace-migration/workspace-migration.entity'; -import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util'; -import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory'; -import { - RelationMetadataEntity, - RelationMetadataType, -} from 'src/metadata/relation-metadata/relation-metadata.entity'; -import { camelCase } from 'src/utils/camel-case'; -import { generateMigrationName } from 'src/metadata/workspace-migration/utils/generate-migration-name.util'; - -@Injectable() -export class WorkspaceSyncFactory { - constructor( - private readonly workspaceMigrationFactory: WorkspaceMigrationFactory, - ) {} - - async createObjectMigration( - originalObjectMetadataCollection: ObjectMetadataEntity[], - createdObjectMetadataCollection: ObjectMetadataEntity[], - objectMetadataDeleteCollection: ObjectMetadataEntity[], - createdFieldMetadataCollection: FieldMetadataEntity[], - fieldMetadataDeleteCollection: FieldMetadataEntity[], - ): Promise[]> { - const workspaceMigrations: Partial[] = []; - - /** - * Create object migrations - */ - if (createdObjectMetadataCollection.length > 0) { - for (const objectMetadata of createdObjectMetadataCollection) { - const migrations = [ - { - name: computeObjectTargetTable(objectMetadata), - action: 'create', - } satisfies WorkspaceMigrationTableAction, - ...objectMetadata.fields - .filter((field) => field.type !== FieldMetadataType.RELATION) - .map( - (field) => - ({ - name: computeObjectTargetTable(objectMetadata), - action: 'alter', - columns: this.workspaceMigrationFactory.createColumnActions( - WorkspaceMigrationColumnActionType.CREATE, - field, - ), - }) satisfies WorkspaceMigrationTableAction, - ), - ]; - - workspaceMigrations.push({ - workspaceId: objectMetadata.workspaceId, - name: generateMigrationName(`create-${objectMetadata.nameSingular}`), - isCustom: false, - migrations, - }); - } - } - - /** - * Delete object migrations - * TODO: handle object delete migrations. - * Note: we need to delete the relation first due to the DB constraint. - */ - // if (objectMetadataDeleteCollection.length > 0) { - // for (const objectMetadata of objectMetadataDeleteCollection) { - // const migrations = [ - // { - // name: computeObjectTargetTable(objectMetadata), - // action: 'drop', - // columns: [], - // } satisfies WorkspaceMigrationTableAction, - // ]; - - // workspaceMigrations.push({ - // workspaceId: objectMetadata.workspaceId, - // isCustom: false, - // migrations, - // }); - // } - // } - - /** - * Create field migrations - */ - const originalObjectMetadataMap = originalObjectMetadataCollection.reduce( - (result, currentObject) => { - result[currentObject.id] = currentObject; - - return result; - }, - {} as Record, - ); - - if (createdFieldMetadataCollection.length > 0) { - for (const fieldMetadata of createdFieldMetadataCollection) { - if (fieldMetadata.type === FieldMetadataType.RELATION) { - continue; - } - - const migrations = [ - { - name: computeObjectTargetTable( - originalObjectMetadataMap[fieldMetadata.objectMetadataId], - ), - action: 'alter', - columns: this.workspaceMigrationFactory.createColumnActions( - WorkspaceMigrationColumnActionType.CREATE, - fieldMetadata, - ), - } satisfies WorkspaceMigrationTableAction, - ]; - - workspaceMigrations.push({ - workspaceId: fieldMetadata.workspaceId, - name: generateMigrationName(`create-${fieldMetadata.name}`), - isCustom: false, - migrations, - }); - } - } - - /** - * Delete field migrations - */ - if (fieldMetadataDeleteCollection.length > 0) { - for (const fieldMetadata of fieldMetadataDeleteCollection) { - const migrations = [ - { - name: computeObjectTargetTable( - originalObjectMetadataMap[fieldMetadata.objectMetadataId], - ), - action: 'alter', - columns: [ - { - action: WorkspaceMigrationColumnActionType.DROP, - columnName: fieldMetadata.name, - }, - ], - } satisfies WorkspaceMigrationTableAction, - ]; - - workspaceMigrations.push({ - workspaceId: fieldMetadata.workspaceId, - name: generateMigrationName(`delete-${fieldMetadata.name}`), - isCustom: false, - migrations, - }); - } - } - - return workspaceMigrations; - } - - async createRelationMigration( - originalObjectMetadataCollection: ObjectMetadataEntity[], - createdRelationMetadataCollection: RelationMetadataEntity[], - // TODO: handle relation deletion - // eslint-disable-next-line @typescript-eslint/no-unused-vars - relationMetadataDeleteCollection: RelationMetadataEntity[], - ): Promise[]> { - const workspaceMigrations: Partial[] = []; - - if (createdRelationMetadataCollection.length > 0) { - for (const relationMetadata of createdRelationMetadataCollection) { - const toObjectMetadata = originalObjectMetadataCollection.find( - (object) => object.id === relationMetadata.toObjectMetadataId, - ); - - const fromObjectMetadata = originalObjectMetadataCollection.find( - (object) => object.id === relationMetadata.fromObjectMetadataId, - ); - - if (!toObjectMetadata) { - throw new Error( - `ObjectMetadata with id ${relationMetadata.toObjectMetadataId} not found`, - ); - } - - if (!fromObjectMetadata) { - throw new Error( - `ObjectMetadata with id ${relationMetadata.fromObjectMetadataId} not found`, - ); - } - - const toFieldMetadata = toObjectMetadata.fields.find( - (field) => field.id === relationMetadata.toFieldMetadataId, - ); - - if (!toFieldMetadata) { - throw new Error( - `FieldMetadata with id ${relationMetadata.toFieldMetadataId} not found`, - ); - } - - const migrations = [ - { - name: computeObjectTargetTable(toObjectMetadata), - action: 'alter', - columns: [ - { - action: WorkspaceMigrationColumnActionType.RELATION, - columnName: `${camelCase(toFieldMetadata.name)}Id`, - referencedTableName: - computeObjectTargetTable(fromObjectMetadata), - referencedTableColumnName: 'id', - isUnique: - relationMetadata.relationType === - RelationMetadataType.ONE_TO_ONE, - } satisfies WorkspaceMigrationColumnRelation, - ], - } satisfies WorkspaceMigrationTableAction, - ]; - - workspaceMigrations.push({ - workspaceId: relationMetadata.workspaceId, - name: generateMigrationName( - `create-relation-from-${fromObjectMetadata.nameSingular}-to-${toObjectMetadata.nameSingular}`, - ), - isCustom: false, - migrations, - }); - } - } - - // if (relationMetadataDeleteCollection.length > 0) { - // for (const relationMetadata of relationMetadataDeleteCollection) { - // const toObjectMetadata = originalObjectMetadataCollection.find( - // (object) => object.id === relationMetadata.toObjectMetadataId, - // ); - - // if (!toObjectMetadata) { - // throw new Error( - // `ObjectMetadata with id ${relationMetadata.toObjectMetadataId} not found`, - // ); - // } - - // const migrations = [ - // { - // name: computeObjectTargetTable(toObjectMetadata), - // action: 'drop', - // columns: [], - // } satisfies WorkspaceMigrationTableAction, - // ]; - - // workspaceMigrations.push({ - // workspaceId: relationMetadata.workspaceId, - // isCustom: false, - // migrations, - // }); - // } - // } - - return workspaceMigrations; - } -} diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service.ts index 1b8f759a0..087733ec3 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service.ts @@ -162,6 +162,7 @@ export class WorkspaceMetadataUpdaterService { const relationMetadataRepository = manager.getRepository( RelationMetadataEntity, ); + const fieldMetadataRepository = manager.getRepository(FieldMetadataEntity); /** * Create relation metadata @@ -182,6 +183,20 @@ export class WorkspaceMetadataUpdaterService { ); } + /** + * Delete related field metadata + */ + const fieldMetadataDeleteCollectionOnlyRelation = + storage.fieldMetadataDeleteCollection.filter( + (field) => field.type === FieldMetadataType.RELATION, + ); + + if (fieldMetadataDeleteCollectionOnlyRelation.length > 0) { + await fieldMetadataRepository.delete( + fieldMetadataDeleteCollectionOnlyRelation.map((field) => field.id), + ); + } + return { createdRelationMetadataCollection, }; diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-object-metadata.service.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-object-metadata.service.ts index a5b177467..88262e722 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-object-metadata.service.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-object-metadata.service.ts @@ -13,8 +13,9 @@ import { StandardObjectFactory } from 'src/workspace/workspace-sync-metadata/fac import { WorkspaceObjectComparator } from 'src/workspace/workspace-sync-metadata/comparators/workspace-object.comparator'; import { WorkspaceFieldComparator } from 'src/workspace/workspace-sync-metadata/comparators/workspace-field.comparator'; import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service'; -import { WorkspaceSyncFactory } from 'src/workspace/workspace-sync-metadata/factories/workspace-sync.factory'; import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage'; +import { ObjectWorkspaceMigrationFactory } from 'src/workspace/workspace-sync-metadata/factories/object-workspace-migration.factory'; +import { FieldWorkspaceMigrationFactory } from 'src/workspace/workspace-sync-metadata/factories/field-workspace-migration.factory'; @Injectable() export class WorkspaceSyncObjectMetadataService { @@ -25,7 +26,8 @@ export class WorkspaceSyncObjectMetadataService { private readonly workspaceObjectComparator: WorkspaceObjectComparator, private readonly workspaceFieldComparator: WorkspaceFieldComparator, private readonly workspaceMetadataUpdaterService: WorkspaceMetadataUpdaterService, - private readonly workspaceSyncFactory: WorkspaceSyncFactory, + private readonly objectWorkspaceMigrationFactory: ObjectWorkspaceMigrationFactory, + private readonly fieldWorkspaceMigrationFactory: FieldWorkspaceMigrationFactory, ) {} async synchronize( @@ -33,12 +35,9 @@ export class WorkspaceSyncObjectMetadataService { manager: EntityManager, storage: WorkspaceSyncStorage, workspaceFeatureFlagsMap: FeatureFlagMap, - ): Promise { + ): Promise[]> { const objectMetadataRepository = manager.getRepository(ObjectMetadataEntity); - const workspaceMigrationRepository = manager.getRepository( - WorkspaceMigrationEntity, - ); // Retrieve object metadata collection from DB const originalObjectMetadataCollection = @@ -141,22 +140,21 @@ export class WorkspaceSyncObjectMetadataService { this.logger.log('Generating migrations'); // Create migrations - const workspaceObjectMigrations = - await this.workspaceSyncFactory.createObjectMigration( - originalObjectMetadataCollection, + const objectWorkspaceMigrations = + await this.objectWorkspaceMigrationFactory.create( metadataObjectUpdaterResult.createdObjectMetadataCollection, storage.objectMetadataDeleteCollection, + ); + + const fieldWorkspaceMigrations = + await this.fieldWorkspaceMigrationFactory.create( + originalObjectMetadataCollection, metadataFieldUpdaterResult.createdFieldMetadataCollection, storage.fieldMetadataDeleteCollection, ); this.logger.log('Saving migrations'); - // Save migrations into DB - const workspaceMigrations = await workspaceMigrationRepository.save( - workspaceObjectMigrations, - ); - - return workspaceMigrations; + return [...objectWorkspaceMigrations, ...fieldWorkspaceMigrations]; } } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts index b3ebe181f..90b48b64d 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts @@ -12,9 +12,9 @@ import { mapObjectMetadataByUniqueIdentifier } from 'src/workspace/workspace-syn import { StandardRelationFactory } from 'src/workspace/workspace-sync-metadata/factories/standard-relation.factory'; import { WorkspaceRelationComparator } from 'src/workspace/workspace-sync-metadata/comparators/workspace-relation.comparator'; import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service'; -import { WorkspaceSyncFactory } from 'src/workspace/workspace-sync-metadata/factories/workspace-sync.factory'; import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity'; import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage'; +import { RelationWorkspaceMigrationFactory } from 'src/workspace/workspace-sync-metadata/factories/relation-workspace-migration.factory'; @Injectable() export class WorkspaceSyncRelationMetadataService { @@ -26,7 +26,7 @@ export class WorkspaceSyncRelationMetadataService { private readonly standardRelationFactory: StandardRelationFactory, private readonly workspaceRelationComparator: WorkspaceRelationComparator, private readonly workspaceMetadataUpdaterService: WorkspaceMetadataUpdaterService, - private readonly workspaceSyncFactory: WorkspaceSyncFactory, + private readonly relationWorkspaceMigrationFactory: RelationWorkspaceMigrationFactory, ) {} async synchronize( @@ -34,12 +34,9 @@ export class WorkspaceSyncRelationMetadataService { manager: EntityManager, storage: WorkspaceSyncStorage, workspaceFeatureFlagsMap: FeatureFlagMap, - ): Promise { + ): Promise[]> { const objectMetadataRepository = manager.getRepository(ObjectMetadataEntity); - const workspaceMigrationRepository = manager.getRepository( - WorkspaceMigrationEntity, - ); // Retrieve object metadata collection from DB const originalObjectMetadataCollection = @@ -99,17 +96,11 @@ export class WorkspaceSyncRelationMetadataService { // Create migrations const workspaceRelationMigrations = - await this.workspaceSyncFactory.createRelationMigration( + await this.relationWorkspaceMigrationFactory.create( originalObjectMetadataCollection, metadataRelationUpdaterResult.createdRelationMetadataCollection, - storage.relationMetadataDeleteCollection, ); - // Save migrations into DB - const workspaceMigrations = await workspaceMigrationRepository.save( - workspaceRelationMigrations, - ); - - return workspaceMigrations; + return workspaceRelationMigrations; } } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts index 955d7727b..ea878e47f 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts @@ -21,7 +21,7 @@ import { ViewObjectMetadata } from 'src/workspace/workspace-sync-metadata/standa import { WebhookObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/webhook.object-metadata'; import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata'; -export const standardObjectMetadata = [ +export const standardObjectMetadataCollection = [ ActivityTargetObjectMetadata, ActivityObjectMetadata, ApiKeyObjectMetadata, diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/workspace-sync-metadata.service.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/workspace-sync-metadata.service.ts index 6996f14fb..5d60c32c1 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/workspace-sync-metadata.service.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/workspace-sync-metadata.service.ts @@ -11,6 +11,7 @@ import { WorkspaceSyncObjectMetadataService } from 'src/workspace/workspace-sync import { WorkspaceSyncRelationMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service'; import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage'; import { WorkspaceLogsService } from 'src/workspace/workspace-sync-metadata/services/workspace-logs.service'; +import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity'; @Injectable() export class WorkspaceSyncMetadataService { @@ -48,6 +49,9 @@ export class WorkspaceSyncMetadataService { try { const storage = new WorkspaceSyncStorage(); + const workspaceMigrationRepository = manager.getRepository( + WorkspaceMigrationEntity, + ); // Retrieve feature flags const workspaceFeatureFlagsMap = @@ -71,13 +75,14 @@ export class WorkspaceSyncMetadataService { workspaceFeatureFlagsMap, ); + // Save workspace migrations into the database + const workspaceMigrations = await workspaceMigrationRepository.save([ + ...workspaceObjectMigrations, + ...workspaceRelationMigrations, + ]); + // If we're running a dry run, rollback the transaction and do not execute migrations if (options?.dryRun) { - const workspaceMigrations = [ - ...workspaceObjectMigrations, - ...workspaceRelationMigrations, - ]; - this.logger.log('Running in dry run mode, rolling back transaction'); await queryRunner.rollbackTransaction();