diff --git a/.gitignore b/.gitignore index ac1000ee9..945b498dd 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,6 @@ !.yarn/sdks !.yarn/versions coverage -.vercel \ No newline at end of file +.vercel + +**/**/logs/** \ No newline at end of file diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts index a6672e7ee..949cbb716 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts @@ -6,6 +6,7 @@ import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metad // TODO: implement dry-run interface RunWorkspaceMigrationsOptions { workspaceId: string; + dryRun?: boolean; } @Command({ @@ -35,6 +36,7 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { workspaceId: options.workspaceId, dataSourceId: dataSourceMetadata.id, }, + { dryRun: options.dryRun }, ); } @@ -46,4 +48,13 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { parseWorkspaceId(value: string): string { return value; } + + @Option({ + flags: '-d, --dry-run', + description: 'Dry run without applying changes', + required: false, + }) + dryRun(): boolean { + return true; + } } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-logs.service.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-logs.service.ts new file mode 100644 index 000000000..2f62dc8f3 --- /dev/null +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/services/workspace-logs.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; + +import fs from 'fs/promises'; + +import { WorkspaceSyncStorage } from 'src/workspace/workspace-sync-metadata/storage/workspace-sync.storage'; +import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity'; + +@Injectable() +export class WorkspaceLogsService { + constructor() {} + + async saveLogs( + storage: WorkspaceSyncStorage, + workspaceMigrations: WorkspaceMigrationEntity[], + ) { + // Save workspace migrations + await fs.writeFile( + './logs/workspace-migrations.json', + JSON.stringify(workspaceMigrations, null, 2), + ); + + // Save object metadata create collection + await fs.writeFile( + './logs/object-metadata-create-collection.json', + JSON.stringify(storage.objectMetadataCreateCollection, null, 2), + ); + + // Save object metadata update collection + await fs.writeFile( + './logs/object-metadata-update-collection.json', + JSON.stringify(storage.objectMetadataUpdateCollection, null, 2), + ); + + // Save object metadata delete collection + await fs.writeFile( + './logs/object-metadata-delete-collection.json', + JSON.stringify(storage.objectMetadataDeleteCollection, null, 2), + ); + + // Save field metadata create collection + await fs.writeFile( + './logs/field-metadata-create-collection.json', + JSON.stringify(storage.fieldMetadataCreateCollection, null, 2), + ); + + // Save field metadata update collection + await fs.writeFile( + './logs/field-metadata-update-collection.json', + JSON.stringify(storage.fieldMetadataUpdateCollection, null, 2), + ); + + // Save field metadata delete collection + await fs.writeFile( + './logs/field-metadata-delete-collection.json', + JSON.stringify(storage.fieldMetadataDeleteCollection, null, 2), + ); + + // Save relation metadata create collection + await fs.writeFile( + './logs/relation-metadata-create-collection.json', + JSON.stringify(storage.relationMetadataCreateCollection, null, 2), + ); + + // Save relation metadata delete collection + await fs.writeFile( + './logs/relation-metadata-delete-collection.json', + JSON.stringify(storage.relationMetadataDeleteCollection, null, 2), + ); + } +} 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 d120ea42a..a5b177467 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 @@ -33,7 +33,7 @@ export class WorkspaceSyncObjectMetadataService { manager: EntityManager, storage: WorkspaceSyncStorage, workspaceFeatureFlagsMap: FeatureFlagMap, - ): Promise { + ): Promise { const objectMetadataRepository = manager.getRepository(ObjectMetadataEntity); const workspaceMigrationRepository = manager.getRepository( @@ -153,6 +153,10 @@ export class WorkspaceSyncObjectMetadataService { this.logger.log('Saving migrations'); // Save migrations into DB - await workspaceMigrationRepository.save(workspaceObjectMigrations); + const workspaceMigrations = await workspaceMigrationRepository.save( + workspaceObjectMigrations, + ); + + return workspaceMigrations; } } 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 29c55abc6..b3ebe181f 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 @@ -34,7 +34,7 @@ export class WorkspaceSyncRelationMetadataService { manager: EntityManager, storage: WorkspaceSyncStorage, workspaceFeatureFlagsMap: FeatureFlagMap, - ): Promise { + ): Promise { const objectMetadataRepository = manager.getRepository(ObjectMetadataEntity); const workspaceMigrationRepository = manager.getRepository( @@ -106,6 +106,10 @@ export class WorkspaceSyncRelationMetadataService { ); // Save migrations into DB - await workspaceMigrationRepository.save(workspaceRelationMigrations); + const workspaceMigrations = await workspaceMigrationRepository.save( + workspaceRelationMigrations, + ); + + return workspaceMigrations; } } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/workspace-sync-metadata.module.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/workspace-sync-metadata.module.ts index c7cf9f25d..fc3c8f71a 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/workspace-sync-metadata.module.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/workspace-sync-metadata.module.ts @@ -14,6 +14,7 @@ import { workspaceSyncMetadataComparators } from 'src/workspace/workspace-sync-m import { WorkspaceMetadataUpdaterService } from 'src/workspace/workspace-sync-metadata/services/workspace-metadata-updater.service'; import { WorkspaceSyncObjectMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-object-metadata.service'; import { WorkspaceSyncRelationMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-relation-metadata.service'; +import { WorkspaceLogsService } from 'src/workspace/workspace-sync-metadata/services/workspace-logs.service'; @Module({ imports: [ @@ -37,6 +38,7 @@ import { WorkspaceSyncRelationMetadataService } from 'src/workspace/workspace-sy WorkspaceSyncObjectMetadataService, WorkspaceSyncRelationMetadataService, WorkspaceSyncMetadataService, + WorkspaceLogsService, ], exports: [WorkspaceSyncMetadataService], }) 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 59510304f..6996f14fb 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 @@ -10,6 +10,7 @@ import { FeatureFlagFactory } from 'src/workspace/workspace-sync-metadata/factor import { WorkspaceSyncObjectMetadataService } from 'src/workspace/workspace-sync-metadata/services/workspace-sync-object-metadata.service'; 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'; @Injectable() export class WorkspaceSyncMetadataService { @@ -22,6 +23,7 @@ export class WorkspaceSyncMetadataService { private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, private readonly workspaceSyncObjectMetadataService: WorkspaceSyncObjectMetadataService, private readonly workspaceSyncRelationMetadataService: WorkspaceSyncRelationMetadataService, + private readonly workspaceLogsService: WorkspaceLogsService, ) {} /** @@ -34,6 +36,7 @@ export class WorkspaceSyncMetadataService { */ public async syncStandardObjectsAndFieldsMetadata( context: WorkspaceSyncContext, + options?: { dryRun?: boolean }, ) { this.logger.log('Syncing standard objects and fields metadata'); const queryRunner = this.metadataDataSource.createQueryRunner(); @@ -52,19 +55,37 @@ export class WorkspaceSyncMetadataService { this.logger.log('Syncing standard objects and fields metadata'); - await this.workspaceSyncObjectMetadataService.synchronize( - context, - manager, - storage, - workspaceFeatureFlagsMap, - ); + const workspaceObjectMigrations = + await this.workspaceSyncObjectMetadataService.synchronize( + context, + manager, + storage, + workspaceFeatureFlagsMap, + ); - await this.workspaceSyncRelationMetadataService.synchronize( - context, - manager, - storage, - workspaceFeatureFlagsMap, - ); + const workspaceRelationMigrations = + await this.workspaceSyncRelationMetadataService.synchronize( + context, + manager, + storage, + workspaceFeatureFlagsMap, + ); + + // 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(); + + await this.workspaceLogsService.saveLogs(storage, workspaceMigrations); + + return; + } await queryRunner.commitTransaction();