diff --git a/packages/twenty-server/src/metadata/workspace-migration/workspace-migration.entity.ts b/packages/twenty-server/src/metadata/workspace-migration/workspace-migration.entity.ts index 8e560ef0a..705f1f9bf 100644 --- a/packages/twenty-server/src/metadata/workspace-migration/workspace-migration.entity.ts +++ b/packages/twenty-server/src/metadata/workspace-migration/workspace-migration.entity.ts @@ -9,6 +9,7 @@ export enum WorkspaceMigrationColumnActionType { CREATE = 'CREATE', ALTER = 'ALTER', RELATION = 'RELATION', + DROP = 'DROP', } export type WorkspaceMigrationEnum = string | { from: string; to: string }; @@ -41,12 +42,18 @@ export type WorkspaceMigrationColumnRelation = { isUnique?: boolean; }; +export type WorkspaceMigrationColumnDrop = { + action: WorkspaceMigrationColumnActionType.DROP; + columnName: string; +}; + export type WorkspaceMigrationColumnAction = { action: WorkspaceMigrationColumnActionType; } & ( | WorkspaceMigrationColumnCreate | WorkspaceMigrationColumnAlter | WorkspaceMigrationColumnRelation + | WorkspaceMigrationColumnDrop ); export type WorkspaceMigrationTableAction = { diff --git a/packages/twenty-server/src/workspace/workspace-migration-runner/workspace-migration-runner.service.ts b/packages/twenty-server/src/workspace/workspace-migration-runner/workspace-migration-runner.service.ts index 30b079201..0fe6a210f 100644 --- a/packages/twenty-server/src/workspace/workspace-migration-runner/workspace-migration-runner.service.ts +++ b/packages/twenty-server/src/workspace/workspace-migration-runner/workspace-migration-runner.service.ts @@ -187,6 +187,12 @@ export class WorkspaceMigrationRunnerService { columnMigration, ); break; + case WorkspaceMigrationColumnActionType.DROP: + await queryRunner.dropColumn( + `${schemaName}.${tableName}`, + columnMigration.columnName, + ); + break; default: throw new Error(`Migration column action not supported`); } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/decorators/metadata.decorator.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/decorators/metadata.decorator.ts index 66a48717f..8c4702687 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/decorators/metadata.decorator.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/decorators/metadata.decorator.ts @@ -144,7 +144,8 @@ function generateFieldMetadata( name: fieldKey, ...metadata, targetColumnMap: targetColumnMap, - isNullable: metadata.type === FieldMetadataType.RELATION ? true : isNullable, + isNullable: + metadata.type === FieldMetadataType.RELATION ? true : isNullable, isSystem, isCustom: false, options: null, // TODO: handle options + stringify for the diff. 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 e73e41f33..6a709e19f 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 @@ -125,6 +125,7 @@ export class WorkspaceSyncMetadataService { if (value === null || typeof value !== 'object') { return [key, value]; } + return [ key, filterIgnoredProperties( @@ -134,6 +135,7 @@ export class WorkspaceSyncMetadataService { if (property !== null && typeof property === 'object') { return JSON.stringify(property); } + return property; }, ), @@ -157,6 +159,7 @@ export class WorkspaceSyncMetadataService { // We only handle CHANGE here as REMOVE and CREATE are handled earlier. if (diff.type === 'CHANGE') { const property = diff.path[0]; + objectsToUpdate[objectInDB.id] = { ...objectsToUpdate[objectInDB.id], [property]: diff.value, @@ -166,6 +169,7 @@ export class WorkspaceSyncMetadataService { for (const diff of fieldsDiff) { const fieldName = diff.path[0]; + if (diff.type === 'CREATE') fieldsToCreate.push({ ...standardObjectFields[fieldName], @@ -175,6 +179,7 @@ export class WorkspaceSyncMetadataService { fieldsToDelete.push(objectInDBFields[fieldName]); if (diff.type === 'CHANGE') { const property = diff.path[diff.path.length - 1]; + fieldsToUpdate[objectInDBFields[fieldName].id] = { ...fieldsToUpdate[objectInDBFields[fieldName].id], [property]: diff.value, @@ -221,6 +226,7 @@ export class WorkspaceSyncMetadataService { const fieldsToDeleteWithoutRelationType = fieldsToDelete.filter( (field) => field.type !== FieldMetadataType.RELATION, ); + if (fieldsToDeleteWithoutRelationType.length > 0) { await this.fieldMetadataRepository.delete( fieldsToDeleteWithoutRelationType.map((field) => field.id), @@ -233,6 +239,7 @@ export class WorkspaceSyncMetadataService { objectsToDelete, fieldsToCreate, fieldsToDelete, + objectsInDB, ); // We run syncRelationMetadata after everything to ensure that all objects and fields are @@ -263,7 +270,9 @@ export class WorkspaceSyncMetadataService { objectsInDBByName, ).reduce((result, currentObject) => { const key = `${currentObject.fromObjectMetadataId}->${currentObject.fromFieldMetadataId}`; + result[key] = currentObject; + return result; }, {}); @@ -279,7 +288,9 @@ export class WorkspaceSyncMetadataService { ) .reduce((result, currentObject) => { const key = `${currentObject.fromObjectMetadataId}->${currentObject.fromFieldMetadataId}`; + result[key] = currentObject; + return result; }, {}); @@ -324,11 +335,12 @@ export class WorkspaceSyncMetadataService { private async generateMigrationsFromSync( objectsToCreate: ObjectMetadataEntity[], _objectsToDelete: ObjectMetadataEntity[], - _fieldsToCreate: FieldMetadataEntity[], - _fieldsToDelete: FieldMetadataEntity[], + fieldsToCreate: FieldMetadataEntity[], + fieldsToDelete: FieldMetadataEntity[], + objectsInDB: ObjectMetadataEntity[], ) { const migrationsToSave: Partial[] = []; - + if (objectsToCreate.length > 0) { objectsToCreate.map((object) => { const migrations = [ @@ -359,6 +371,59 @@ export class WorkspaceSyncMetadataService { }); } + // TODO: handle object delete migrations. + // Note: we need to delete the relation first due to the DB constraint. + + const objectsInDbById = objectsInDB.reduce((result, currentObject) => { + result[currentObject.id] = currentObject; + + return result; + }, {}); + + if (fieldsToCreate.length > 0) { + fieldsToCreate.map((field) => { + const migrations = [ + { + name: objectsInDbById[field.objectMetadataId].targetTableName, + action: 'alter', + columns: this.workspaceMigrationFactory.createColumnActions( + WorkspaceMigrationColumnActionType.CREATE, + field, + ), + } satisfies WorkspaceMigrationTableAction, + ]; + + migrationsToSave.push({ + workspaceId: field.workspaceId, + isCustom: false, + migrations, + }); + }); + } + + if (fieldsToDelete.length > 0) { + fieldsToDelete.map((field) => { + const migrations = [ + { + name: objectsInDbById[field.objectMetadataId].targetTableName, + action: 'alter', + columns: [ + { + action: WorkspaceMigrationColumnActionType.DROP, + columnName: field.name, + }, + ], + } satisfies WorkspaceMigrationTableAction, + ]; + + migrationsToSave.push({ + workspaceId: field.workspaceId, + isCustom: false, + migrations, + }); + }); + } + await this.workspaceMigrationRepository.save(migrationsToSave); // TODO: handle delete migrations