From baa3043954f378e0ad9dddc3762b90332cd858b5 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 28 Feb 2025 19:51:32 +0100 Subject: [PATCH] Refactor upgrade commands (#10592) Simplifying a lot the upgrade system. New way to upgrade: `yarn command:prod upgrade` New way to write upgrade commands (all wrapping is done for you) ``` override async runOnWorkspace({ index, total, workspaceId, options, }: RunOnWorkspaceArgs): Promise {} ``` Also cleaning CommandModule imports to make it lighter --- ...ed-workspaces-migration.command-runner.ts} | 93 ++-- .../migration.command-runner.ts} | 20 +- .../data-seed-demo-workspace-cron-pattern.ts | 1 - ...t-data-seed-demo-workspace.cron.command.ts | 32 -- ...p-data-seed-demo-workspace.cron.command.ts | 25 - .../data-seed-demo-workspace-command.ts | 19 - .../jobs/data-seed-demo-workspace.job.ts | 16 - .../commands/database-command.module.ts | 55 +-- ...ned-workspaces-migration-command.runner.ts | 68 --- .../create-upgrade-all-command.factory.ts | 114 ----- .../decorators/migration-command.decorator.ts | 41 -- .../interfaces/migration-command.interface.ts | 5 - .../migration-command.constants.ts | 1 - .../migration-command.module.ts | 51 -- ...3-add-tasks-assigned-to-me-view.command.ts | 84 +--- ...able-for-custom-object-metadata.command.ts | 51 ++ ...migrate-rich-text-content-patch.command.ts | 160 +++--- ...ector-on-note-and-task-entities.command.ts | 105 ++-- ...ord-opening-on-workflow-objects.command.ts | 81 ++- .../0-43-upgrade-version-command.module.ts | 45 ++ .../0-44-initialize-permissions.command.ts | 98 ++-- ...ate-relations-to-field-metadata.command.ts | 139 ++++++ .../0-44-upgrade-version-command.module.ts | 34 ++ .../upgrade-version-command.module.ts | 19 + .../upgrade.command.ts | 72 +++ ...fix-body-v2-view-field-position.command.ts | 193 -------- .../0-42/0-42-limit-amount-of-view-field.ts | 122 ----- .../0-42-migrate-rich-text-field.command.ts | 462 ------------------ ...ization-of-actor-composite-context-type.ts | 121 ----- .../0-42/0-42-upgrade-version.module.ts | 45 -- ...able-for-custom-object-metadata.command.ts | 74 --- .../0-43/0-43-upgrade-version.module.ts | 47 -- ...ate-relations-to-field-metadata.command.ts | 169 ------- .../0-44/0-44-upgrade-version.module.ts | 43 -- .../billing-sync-customer-data.command.ts | 48 +- .../billing-sync-plans-data.command.ts | 2 +- .../core-modules/message-queue/jobs.module.ts | 2 - .../workspace-metadata-cache.service.ts | 2 - .../workspace-metadata-version.service.ts | 4 - .../factories/workspace-datasource.factory.ts | 4 - .../clean-suspended-workspaces.command.ts | 2 +- .../sync-workspace-metadata.command.ts | 140 ++---- .../workspace-sync-metadata.module.ts | 15 +- .../developers/self-hosting/upgrade-guide.mdx | 2 +- 44 files changed, 714 insertions(+), 2212 deletions(-) rename packages/twenty-server/src/database/commands/{migration-command/maintained-workspaces-migration-command.runner.ts => command-runners/active-or-suspended-workspaces-migration.command-runner.ts} (60%) rename packages/twenty-server/src/database/commands/{migration-command/migration-command.runner.ts => command-runners/migration.command-runner.ts} (72%) delete mode 100644 packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/data-seed-demo-workspace-cron-pattern.ts delete mode 100644 packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command.ts delete mode 100644 packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command.ts delete mode 100644 packages/twenty-server/src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace-command.ts delete mode 100644 packages/twenty-server/src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job.ts delete mode 100644 packages/twenty-server/src/database/commands/migration-command/batch-maintained-workspaces-migration-command.runner.ts delete mode 100644 packages/twenty-server/src/database/commands/migration-command/create-upgrade-all-command.factory.ts delete mode 100644 packages/twenty-server/src/database/commands/migration-command/decorators/migration-command.decorator.ts delete mode 100644 packages/twenty-server/src/database/commands/migration-command/interfaces/migration-command.interface.ts delete mode 100644 packages/twenty-server/src/database/commands/migration-command/migration-command.constants.ts delete mode 100644 packages/twenty-server/src/database/commands/migration-command/migration-command.module.ts rename packages/twenty-server/src/database/commands/{upgrade-version => upgrade-version-command}/0-43/0-43-add-tasks-assigned-to-me-view.command.ts (76%) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command.ts rename packages/twenty-server/src/database/commands/{upgrade-version => upgrade-version-command}/0-43/0-43-migrate-rich-text-content-patch.command.ts (66%) rename packages/twenty-server/src/database/commands/{upgrade-version => upgrade-version-command}/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command.ts (53%) rename packages/twenty-server/src/database/commands/{upgrade-version => upgrade-version-command}/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command.ts (56%) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-upgrade-version-command.module.ts rename packages/twenty-server/src/database/commands/{upgrade-version => upgrade-version-command}/0-44/0-44-initialize-permissions.command.ts (74%) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-migrate-relations-to-field-metadata.command.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-upgrade-version-command.module.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-fix-body-v2-view-field-position.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-limit-amount-of-view-field.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-upgrade-version.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-migrate-relations-to-field-metadata.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-upgrade-version.module.ts diff --git a/packages/twenty-server/src/database/commands/migration-command/maintained-workspaces-migration-command.runner.ts b/packages/twenty-server/src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner.ts similarity index 60% rename from packages/twenty-server/src/database/commands/migration-command/maintained-workspaces-migration-command.runner.ts rename to packages/twenty-server/src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner.ts index 494083f69..4ef60ba75 100644 --- a/packages/twenty-server/src/database/commands/migration-command/maintained-workspaces-migration-command.runner.ts +++ b/packages/twenty-server/src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner.ts @@ -3,24 +3,31 @@ import { Option } from 'nest-commander'; import { WorkspaceActivationStatus } from 'twenty-shared'; import { In, MoreThanOrEqual, Repository } from 'typeorm'; -import { - MigrationCommandOptions, - MigrationCommandRunner, -} from 'src/database/commands/migration-command/migration-command.runner'; +import { MigrationCommandRunner } from 'src/database/commands/command-runners/migration.command-runner'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -export type MaintainedWorkspacesMigrationCommandOptions = - MigrationCommandOptions & { - workspaceId?: string; - startFromWorkspaceId?: string; - workspaceCountLimit?: number; - }; +export type ActiveOrSuspendedWorkspacesMigrationCommandOptions = { + workspaceIds: string[]; + startFromWorkspaceId?: string; + workspaceCountLimit?: number; + dryRun?: boolean; + verbose?: boolean; +}; -export abstract class MaintainedWorkspacesMigrationCommandRunner< +export type RunOnWorkspaceArgs = { + options: ActiveOrSuspendedWorkspacesMigrationCommandOptions; + workspaceId: string; + dataSource: WorkspaceDataSource; + index: number; + total: number; +}; + +export abstract class ActiveOrSuspendedWorkspacesMigrationCommandRunner< Options extends - MaintainedWorkspacesMigrationCommandOptions = MaintainedWorkspacesMigrationCommandOptions, -> extends MigrationCommandRunner { + ActiveOrSuspendedWorkspacesMigrationCommandOptions = ActiveOrSuspendedWorkspacesMigrationCommandOptions, +> extends MigrationCommandRunner { private workspaceIds: string[] = []; private startFromWorkspaceId: string | undefined; private workspaceCountLimit: number | undefined; @@ -97,20 +104,8 @@ export abstract class MaintainedWorkspacesMigrationCommandRunner< return activeWorkspaces.map((workspace) => workspace.id); } - protected logWorkspaceCount(activeWorkspaceIds: string[]): void { - if (!activeWorkspaceIds.length) { - this.logger.log(chalk.yellow('No workspace found')); - } else { - this.logger.log( - chalk.green( - `Running command on ${activeWorkspaceIds.length} workspaces`, - ), - ); - } - } - override async runMigrationCommand( - passedParams: string[], + _passedParams: string[], options: Options, ): Promise { const activeWorkspaceIds = @@ -118,22 +113,44 @@ export abstract class MaintainedWorkspacesMigrationCommandRunner< ? this.workspaceIds : await this.fetchActiveWorkspaceIds(); - this.logWorkspaceCount(activeWorkspaceIds); - if (options.dryRun) { this.logger.log(chalk.yellow('Dry run mode: No changes will be applied')); } - await this.runMigrationCommandOnMaintainedWorkspaces( - passedParams, - options, - activeWorkspaceIds, - ); + try { + for (const [index, workspaceId] of activeWorkspaceIds.entries()) { + this.logger.log( + `Running command on workspace ${workspaceId} ${index + 1}/${activeWorkspaceIds.length}`, + ); + + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( + workspaceId, + false, + ); + + try { + await this.runOnWorkspace({ + options, + workspaceId, + dataSource, + index: index, + total: activeWorkspaceIds.length, + }); + } catch (error) { + this.logger.warn( + chalk.red(`Error in workspace ${workspaceId}: ${error.message}`), + ); + } + + await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( + workspaceId, + ); + } + } catch (error) { + this.logger.error(error); + } } - protected abstract runMigrationCommandOnMaintainedWorkspaces( - passedParams: string[], - options: Options, - activeWorkspaceIds: string[], - ): Promise; + protected abstract runOnWorkspace(args: RunOnWorkspaceArgs): Promise; } diff --git a/packages/twenty-server/src/database/commands/migration-command/migration-command.runner.ts b/packages/twenty-server/src/database/commands/command-runners/migration.command-runner.ts similarity index 72% rename from packages/twenty-server/src/database/commands/migration-command/migration-command.runner.ts rename to packages/twenty-server/src/database/commands/command-runners/migration.command-runner.ts index 801a80e59..ea8d46860 100644 --- a/packages/twenty-server/src/database/commands/migration-command/migration-command.runner.ts +++ b/packages/twenty-server/src/database/commands/command-runners/migration.command-runner.ts @@ -3,23 +3,16 @@ import { Logger } from '@nestjs/common'; import chalk from 'chalk'; import { CommandRunner, Option } from 'nest-commander'; -import { MigrationCommandInterface } from 'src/database/commands/migration-command/interfaces/migration-command.interface'; - import { CommandLogger } from 'src/database/commands/logger'; export type MigrationCommandOptions = { - workspaceId?: string; dryRun?: boolean; verbose?: boolean; }; -export abstract class MigrationCommandRunner< - Options extends MigrationCommandOptions = MigrationCommandOptions, - > - extends CommandRunner - implements MigrationCommandInterface -{ +export abstract class MigrationCommandRunner extends CommandRunner { protected logger: CommandLogger | Logger; + constructor() { super(); this.logger = new CommandLogger({ @@ -46,7 +39,10 @@ export abstract class MigrationCommandRunner< return true; } - override async run(passedParams: string[], options: Options): Promise { + override async run( + passedParams: string[], + options: MigrationCommandOptions, + ): Promise { if (options.verbose) { this.logger = new CommandLogger({ verbose: true, @@ -64,8 +60,8 @@ export abstract class MigrationCommandRunner< } } - abstract runMigrationCommand( + protected abstract runMigrationCommand( passedParams: string[], - options: Options, + options: MigrationCommandOptions, ): Promise; } diff --git a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/data-seed-demo-workspace-cron-pattern.ts b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/data-seed-demo-workspace-cron-pattern.ts deleted file mode 100644 index a8bac100d..000000000 --- a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/data-seed-demo-workspace-cron-pattern.ts +++ /dev/null @@ -1 +0,0 @@ -export const dataSeedDemoWorkspaceCronPattern = '0 22 * * *'; // Every day at 10pm diff --git a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command.ts b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command.ts deleted file mode 100644 index f634f402d..000000000 --- a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Command, CommandRunner } from 'nest-commander'; - -import { dataSeedDemoWorkspaceCronPattern } from 'src/database/commands/data-seed-demo-workspace/crons/data-seed-demo-workspace-cron-pattern'; -import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job'; -import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; -import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; - -@Command({ - name: 'workspace-seed-demo:cron:start', - description: 'Start cron to seed workspace with demo data.', -}) -export class StartDataSeedDemoWorkspaceCronCommand extends CommandRunner { - constructor( - @InjectMessageQueue(MessageQueue.cronQueue) - private readonly messageQueueService: MessageQueueService, - ) { - super(); - } - - async run(): Promise { - await this.messageQueueService.addCron({ - jobName: DataSeedDemoWorkspaceJob.name, - data: undefined, - options: { - repeat: { - pattern: dataSeedDemoWorkspaceCronPattern, - }, - }, - }); - } -} diff --git a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command.ts b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command.ts deleted file mode 100644 index 8f028783a..000000000 --- a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Command, CommandRunner } from 'nest-commander'; - -import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job'; -import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; -import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; - -@Command({ - name: 'workspace-seed-demo:cron:stop', - description: 'Stop cron to seed workspace with demo data.', -}) -export class StopDataSeedDemoWorkspaceCronCommand extends CommandRunner { - constructor( - @InjectMessageQueue(MessageQueue.cronQueue) - private readonly messageQueueService: MessageQueueService, - ) { - super(); - } - - async run(): Promise { - await this.messageQueueService.removeCron({ - jobName: DataSeedDemoWorkspaceJob.name, - }); - } -} diff --git a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace-command.ts b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace-command.ts deleted file mode 100644 index 611932a33..000000000 --- a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace-command.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Command, CommandRunner } from 'nest-commander'; - -import { DataSeedDemoWorkspaceService } from 'src/database/commands/data-seed-demo-workspace/services/data-seed-demo-workspace.service'; - -@Command({ - name: 'workspace:seed:demo', - description: 'Seed workspace with demo data. Use in development only.', -}) -export class DataSeedDemoWorkspaceCommand extends CommandRunner { - constructor( - private readonly dataSeedDemoWorkspaceService: DataSeedDemoWorkspaceService, - ) { - super(); - } - - async run(): Promise { - await this.dataSeedDemoWorkspaceService.seedDemo(); - } -} diff --git a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job.ts b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job.ts deleted file mode 100644 index af8958518..000000000 --- a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { DataSeedDemoWorkspaceService } from 'src/database/commands/data-seed-demo-workspace/services/data-seed-demo-workspace.service'; -import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; -import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; - -@Processor(MessageQueue.cronQueue) -export class DataSeedDemoWorkspaceJob { - constructor( - private readonly dataSeedDemoWorkspaceService: DataSeedDemoWorkspaceService, - ) {} - - @Process(DataSeedDemoWorkspaceJob.name) - async handle(): Promise { - await this.dataSeedDemoWorkspaceService.seedDemo(); - } -} diff --git a/packages/twenty-server/src/database/commands/database-command.module.ts b/packages/twenty-server/src/database/commands/database-command.module.ts index 5c7101c62..736ba0ba2 100644 --- a/packages/twenty-server/src/database/commands/database-command.module.ts +++ b/packages/twenty-server/src/database/commands/database-command.module.ts @@ -1,66 +1,27 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { StartDataSeedDemoWorkspaceCronCommand } from 'src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command'; -import { StopDataSeedDemoWorkspaceCronCommand } from 'src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command'; -import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace-command'; -import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module'; import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command'; import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question'; -import { UpgradeTo0_42CommandModule } from 'src/database/commands/upgrade-version/0-42/0-42-upgrade-version.module'; -import { UpgradeTo0_43CommandModule } from 'src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module'; -import { UpgradeTo0_44CommandModule } from 'src/database/commands/upgrade-version/0-44/0-44-upgrade-version.module'; +import { UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/upgrade-version-command.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; -import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; -import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; -import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; import { SeederModule } from 'src/engine/seeder/seeder.module'; -import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; -import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module'; -import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module'; @Module({ imports: [ + UpgradeVersionCommandModule, + + // Only needed for the data seed command + TypeORMModule, + FieldMetadataModule, + ObjectMetadataModule, SeederModule, WorkspaceManagerModule, DataSourceModule, - TypeORMModule, - TypeOrmModule.forFeature( - [Workspace, BillingSubscription, FeatureFlag], - 'core', - ), - TypeOrmModule.forFeature( - [FieldMetadataEntity, ObjectMetadataEntity], - 'metadata', - ), - WorkspaceModule, - WorkspaceDataSourceModule, - WorkspaceSyncMetadataModule, - ObjectMetadataModule, - FieldMetadataModule, - DataSeedDemoWorkspaceModule, - WorkspaceCacheStorageModule, - WorkspaceMetadataVersionModule, - UpgradeTo0_42CommandModule, - UpgradeTo0_43CommandModule, - UpgradeTo0_44CommandModule, - FeatureFlagModule, - ], - providers: [ - DataSeedWorkspaceCommand, - DataSeedDemoWorkspaceCommand, - ConfirmationQuestion, - StartDataSeedDemoWorkspaceCronCommand, - StopDataSeedDemoWorkspaceCronCommand, ], + providers: [DataSeedWorkspaceCommand, ConfirmationQuestion], }) export class DatabaseCommandModule {} diff --git a/packages/twenty-server/src/database/commands/migration-command/batch-maintained-workspaces-migration-command.runner.ts b/packages/twenty-server/src/database/commands/migration-command/batch-maintained-workspaces-migration-command.runner.ts deleted file mode 100644 index bf72874ca..000000000 --- a/packages/twenty-server/src/database/commands/migration-command/batch-maintained-workspaces-migration-command.runner.ts +++ /dev/null @@ -1,68 +0,0 @@ -import chalk from 'chalk'; -import { Repository } from 'typeorm'; - -import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; - -export abstract class BatchMaintainedWorkspacesMigrationCommandRunner< - Options extends - MaintainedWorkspacesMigrationCommandOptions = MaintainedWorkspacesMigrationCommandOptions, -> extends MaintainedWorkspacesMigrationCommandRunner { - constructor( - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - async runMigrationCommandOnMaintainedWorkspaces( - _passedParams: string[], - _options: Options, - activeWorkspaceIds: string[], - ): Promise { - this.logger.log( - chalk.green(`Running command on ${activeWorkspaceIds.length} workspaces`), - ); - for (const [index, workspaceId] of activeWorkspaceIds.entries()) { - this.logger.log( - chalk.green( - `Processing workspace ${workspaceId} (${index + 1}/${ - activeWorkspaceIds.length - })`, - ), - ); - - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - workspaceId, - false, - ); - - try { - await this.runMigrationCommandOnWorkspace( - workspaceId, - index, - activeWorkspaceIds.length, - dataSource, - ); - } catch (error) { - this.logger.error(`Error in workspace ${workspaceId}: ${error}`); - } - await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( - workspaceId, - ); - } - } - - protected abstract runMigrationCommandOnWorkspace( - workspaceId: string, - index: number, - total: number, - dataSource: WorkspaceDataSource, - ): Promise; -} diff --git a/packages/twenty-server/src/database/commands/migration-command/create-upgrade-all-command.factory.ts b/packages/twenty-server/src/database/commands/migration-command/create-upgrade-all-command.factory.ts deleted file mode 100644 index 5eb173585..000000000 --- a/packages/twenty-server/src/database/commands/migration-command/create-upgrade-all-command.factory.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { Inject } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { MigrationCommandInterface } from 'src/database/commands/migration-command/interfaces/migration-command.interface'; - -import { MaintainedWorkspacesMigrationCommandRunner } from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; -import { MIGRATION_COMMAND_INJECTION_TOKEN } from 'src/database/commands/migration-command/migration-command.constants'; -import { MigrationCommandRunner } from 'src/database/commands/migration-command/migration-command.runner'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { SyncWorkspaceLoggerService } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/services/sync-workspace-logger.service'; -import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service'; - -export function createUpgradeAllCommand( - version: string, -): new (...args: unknown[]) => MigrationCommandRunner { - @Command({ - name: `upgrade-${version}`, - description: `Upgrade to version ${version}`, - }) - class UpgradeCommand extends MaintainedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @Inject(MIGRATION_COMMAND_INJECTION_TOKEN) - private readonly subCommands: MigrationCommandInterface[], - private readonly dataSourceService: DataSourceService, - private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService, - private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - // TODO Remove and avoid duplicated synchronize logic with SyncWorkspaceMetadataCommand after command refactoring - private async synchronizeWorkspaceMetadata({ - workspaceIds, - options, - }: { - workspaceIds: string[]; - options: Record; - }) { - this.logger.log(`Attempting to sync ${workspaceIds.length} workspaces.`); - const errorsDuringSync: string[] = []; - - for (const [index, workspaceId] of workspaceIds.entries()) { - try { - this.logger.log( - `Running workspace sync for workspace: ${workspaceId} (${index + 1} out of ${workspaceIds.length})`, - ); - const dataSourceMetadata = - await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( - workspaceId, - ); - - const { storage, workspaceMigrations } = - await this.workspaceSyncMetadataService.synchronize( - { - workspaceId, - dataSourceId: dataSourceMetadata.id, - }, - { applyChanges: !options.dryRun }, - ); - - if (options.dryRun) { - await this.syncWorkspaceLoggerService.saveLogs( - workspaceId, - storage, - workspaceMigrations, - ); - } - } catch (error) { - const errorMessage = `Failed to synchronize workspace ${workspaceId}: ${error.message}`; - - this.logger.error(errorMessage); - errorsDuringSync.push(errorMessage); - - continue; - } - } - this.logger.log( - `Finished synchronizing all active workspaces (${ - workspaceIds.length - } workspaces). ${ - errorsDuringSync.length > 0 - ? 'Errors during sync:\n' + errorsDuringSync.join('.\n') - : '' - }`, - ); - } - - async runMigrationCommandOnMaintainedWorkspaces( - passedParams: string[], - options: Record, - workspaceIds: string[], - ): Promise { - this.logger.log(`Running upgrade command for version ${version}`); - - for (const command of this.subCommands) { - await command.runMigrationCommand(passedParams, options); - } - - await this.synchronizeWorkspaceMetadata({ options, workspaceIds }); - - this.logger.log(`Upgrade ${version} command completed!`); - } - } - - return UpgradeCommand; -} diff --git a/packages/twenty-server/src/database/commands/migration-command/decorators/migration-command.decorator.ts b/packages/twenty-server/src/database/commands/migration-command/decorators/migration-command.decorator.ts deleted file mode 100644 index 760d08f29..000000000 --- a/packages/twenty-server/src/database/commands/migration-command/decorators/migration-command.decorator.ts +++ /dev/null @@ -1,41 +0,0 @@ -// migration-command.decorator.ts -import { Type } from '@nestjs/common'; - -import { Command, CommandMetadata } from 'nest-commander'; -import 'reflect-metadata'; - -import { MigrationCommandRunner } from 'src/database/commands/migration-command/migration-command.runner'; - -export interface MigrationCommandMetadata extends CommandMetadata { - version: string; -} - -const MIGRATION_COMMANDS = new Map< - string, - Array> ->(); - -export function MigrationCommand( - options: MigrationCommandMetadata, -): >(target: T) => T | void { - return >(target: T): T | void => { - const { version, name, ...commandOptions } = options; - - if (!MIGRATION_COMMANDS.has(version)) { - MIGRATION_COMMANDS.set(version, []); - } - - MIGRATION_COMMANDS.get(version)?.push(target); - - return Command({ - name: `upgrade-${version}:${name}`, - ...commandOptions, - })(target); - }; -} - -export function getMigrationCommandsForVersion( - version: string, -): Array> { - return MIGRATION_COMMANDS.get(version) || []; -} diff --git a/packages/twenty-server/src/database/commands/migration-command/interfaces/migration-command.interface.ts b/packages/twenty-server/src/database/commands/migration-command/interfaces/migration-command.interface.ts deleted file mode 100644 index 8b197cda2..000000000 --- a/packages/twenty-server/src/database/commands/migration-command/interfaces/migration-command.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface MigrationCommandInterface< - Options extends Record = Record, -> { - runMigrationCommand(passedParams: string[], options: Options): Promise; -} diff --git a/packages/twenty-server/src/database/commands/migration-command/migration-command.constants.ts b/packages/twenty-server/src/database/commands/migration-command/migration-command.constants.ts deleted file mode 100644 index b83709364..000000000 --- a/packages/twenty-server/src/database/commands/migration-command/migration-command.constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const MIGRATION_COMMAND_INJECTION_TOKEN = 'MIGRATION_COMMANDS'; diff --git a/packages/twenty-server/src/database/commands/migration-command/migration-command.module.ts b/packages/twenty-server/src/database/commands/migration-command/migration-command.module.ts deleted file mode 100644 index d4958249a..000000000 --- a/packages/twenty-server/src/database/commands/migration-command/migration-command.module.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { DynamicModule, Module, ModuleMetadata } from '@nestjs/common'; - -import { MigrationCommandInterface } from 'src/database/commands/migration-command/interfaces/migration-command.interface'; - -import { createUpgradeAllCommand } from 'src/database/commands/migration-command/create-upgrade-all-command.factory'; -import { getMigrationCommandsForVersion } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; -import { MIGRATION_COMMAND_INJECTION_TOKEN } from 'src/database/commands/migration-command/migration-command.constants'; -import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; -import { SyncWorkspaceLoggerModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/services/sync-workspace-logger.module'; -import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module'; - -@Module({}) -export class MigrationCommandModule { - static register( - version: string, - moduleMetadata: ModuleMetadata, - ): DynamicModule { - const commandClasses = getMigrationCommandsForVersion(version); - const upgradeAllCommand = createUpgradeAllCommand(version); - - return { - module: MigrationCommandModule, - imports: [ - SyncWorkspaceLoggerModule, - ...(moduleMetadata.imports ?? []), - WorkspaceSyncMetadataModule, - DataSourceModule, - ], - providers: [ - ...(moduleMetadata.providers ?? []), - ...commandClasses, - { - provide: MIGRATION_COMMAND_INJECTION_TOKEN, - useFactory: ( - ...instances: MigrationCommandInterface[] - ): MigrationCommandInterface[] => { - return instances; - }, - inject: commandClasses, - }, - upgradeAllCommand, - ], - exports: [ - ...(moduleMetadata.exports ?? []), - MIGRATION_COMMAND_INJECTION_TOKEN, - ...commandClasses, - upgradeAllCommand, - ], - }; - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-add-tasks-assigned-to-me-view.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-add-tasks-assigned-to-me-view.command.ts similarity index 76% rename from packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-add-tasks-assigned-to-me-view.command.ts rename to packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-add-tasks-assigned-to-me-view.command.ts index 9b934d892..ed7696ed2 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-add-tasks-assigned-to-me-view.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-add-tasks-assigned-to-me-view.command.ts @@ -1,15 +1,14 @@ import { InjectRepository } from '@nestjs/typeorm'; import chalk from 'chalk'; +import { Command } from 'nest-commander'; import { Repository } from 'typeorm'; import { v4 } from 'uuid'; -import { isCommandLogger } from 'src/database/commands/logger'; -import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { FieldMetadataDefaultOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -24,12 +23,11 @@ import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/vie import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity'; import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; -@MigrationCommand({ - name: 'add-tasks-assigned-to-me-view', +@Command({ + name: 'upgrade:0-43:add-tasks-assigned-to-me-view', description: 'Add tasks assigned to me view', - version: '0.43', }) -export class AddTasksAssignedToMeViewCommand extends MaintainedWorkspacesMigrationCommandRunner { +export class AddTasksAssignedToMeViewCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { constructor( @InjectRepository(Workspace, 'core') protected readonly workspaceRepository: Repository, @@ -43,57 +41,25 @@ export class AddTasksAssignedToMeViewCommand extends MaintainedWorkspacesMigrati super(workspaceRepository, twentyORMGlobalManager); } - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: MaintainedWorkspacesMigrationCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log('Running command to create many to one relations'); + override async runOnWorkspace({ + index, + total, + workspaceId, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Running command for workspace ${workspaceId} ${index + 1}/${total}`, + ); - if (isCommandLogger(this.logger)) { - this.logger.setVerbose(options.verbose ?? false); - } + await this.createTasksAssignedToMeView(workspaceId); - try { - for (const [index, workspaceId] of workspaceIds.entries()) { - await this.processWorkspace(workspaceId, index, workspaceIds.length); - } - - this.logger.log(chalk.green('Command completed!')); - } catch (error) { - this.logger.log(chalk.red('Error in workspace')); - } - } - - private async processWorkspace( - workspaceId: string, - index: number, - total: number, - ): Promise { - try { - this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - const viewId = await this.createTasksAssignedToMeView(workspaceId); - - await this.createTasksAssignedToMeViewGroups(workspaceId, viewId); - - await this.workspaceMetadataVersionService.incrementMetadataVersion( - workspaceId, - ); - - this.logger.log( - chalk.green(`Command completed for workspace ${workspaceId}.`), - ); - } catch { - this.logger.log(chalk.red(`Error in workspace ${workspaceId}.`)); - } + this.logger.log( + chalk.green(`Command completed for workspace ${workspaceId}.`), + ); } private async createTasksAssignedToMeView( workspaceId: string, - ): Promise { + ): Promise { const objectMetadata = await this.objectMetadataRepository.find({ where: { workspaceId }, relations: ['fields'], @@ -135,9 +101,13 @@ export class AddTasksAssignedToMeViewCommand extends MaintainedWorkspacesMigrati }); if (existingView) { - throw new Error( - `"Assigned to Me" view already exists for workspace ${workspaceId}`, + this.logger.log( + chalk.yellow( + `"Assigned to Me" view already exists for workspace ${workspaceId}`, + ), ); + + return; } const viewDefinition = tasksAssignedToMeView(objectMetadataMap); @@ -191,7 +161,7 @@ export class AddTasksAssignedToMeViewCommand extends MaintainedWorkspacesMigrati await viewFilterRepository.save(viewFilters); } - return insertedView.id; + await this.createTasksAssignedToMeViewGroups(workspaceId, insertedView.id); } private async createTasksAssignedToMeViewGroups( diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command.ts new file mode 100644 index 000000000..3b0b9f515 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command.ts @@ -0,0 +1,51 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +@Command({ + name: 'upgrade:0-43:migrate-is-searchable-for-custom-object-metadata', + description: 'Set isSearchable true for custom object metadata', +}) +export class MigrateIsSearchableForCustomObjectMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + @InjectRepository(ObjectMetadataEntity, 'metadata') + protected readonly objectMetadataRepository: Repository, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace({ + index, + total, + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Running command for workspace ${workspaceId} ${index + 1}/${total}`, + ); + + if (!options.dryRun) { + await this.objectMetadataRepository.update( + { + workspaceId, + isCustom: true, + }, + { + isSearchable: true, + }, + ); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-rich-text-content-patch.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-migrate-rich-text-content-patch.command.ts similarity index 66% rename from packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-rich-text-content-patch.command.ts rename to packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-migrate-rich-text-content-patch.command.ts index eeb9581f0..4bf34aba0 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-rich-text-content-patch.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-migrate-rich-text-content-patch.command.ts @@ -2,15 +2,15 @@ import { InjectRepository } from '@nestjs/typeorm'; import { ServerBlockNoteEditor } from '@blocknote/server-util'; import chalk from 'chalk'; +import { Command } from 'nest-commander'; import { FieldMetadataType } from 'twenty-shared'; import { Repository } from 'typeorm'; -import { isCommandLogger } from 'src/database/commands/logger'; -import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; + ActiveOrSuspendedWorkspacesMigrationCommandOptions, + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -23,6 +23,7 @@ import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/work type MigrateRichTextContentArgs = { richTextFieldsWithObjectMetadata: RichTextFieldsWithObjectMetadata[]; workspaceId: string; + options: ActiveOrSuspendedWorkspacesMigrationCommandOptions; }; type RichTextFieldsWithObjectMetadata = { @@ -30,25 +31,16 @@ type RichTextFieldsWithObjectMetadata = { objectMetadata: ObjectMetadataEntity | null; }; -type ProcessWorkspaceArgs = { - workspaceId: string; - index: number; - total: number; -}; - type ProcessRichTextFieldsArgs = { richTextFields: FieldMetadataEntity[]; workspaceId: string; }; -@MigrationCommand({ - name: 'migrate-rich-text-content-patch', +@Command({ + name: 'upgrade:0-43:migrate-rich-text-content-patch', description: 'Migrate RICH_TEXT content from v1 to v2', - version: '0.43', }) -export class MigrateRichTextContentPatchCommand extends MaintainedWorkspacesMigrationCommandRunner { - private options: MaintainedWorkspacesMigrationCommandOptions; - +export class MigrateRichTextContentPatchCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { constructor( @InjectRepository(Workspace, 'core') protected readonly workspaceRepository: Repository, @@ -64,91 +56,58 @@ export class MigrateRichTextContentPatchCommand extends MaintainedWorkspacesMigr super(workspaceRepository, twentyORMGlobalManager); } - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: MaintainedWorkspacesMigrationCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log( - 'Running command to migrate RICH_TEXT contents from v1 to v2', - ); - - this.options = options; - if (isCommandLogger(this.logger)) { - this.logger.setVerbose(options.verbose ?? false); - } - - for (const [index, workspaceId] of workspaceIds.entries()) { - try { - await this.processWorkspace({ - workspaceId, - index, - total: workspaceIds.length, - }); - } catch (error) { - this.logger.log( - chalk.red(`Error in workspace ${workspaceId}: ${error}`), - ); - } - - await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( - workspaceId, - ); - } - - this.logger.log(chalk.green('Command completed!')); - } - - private async processWorkspace({ + override async runOnWorkspace({ index, total, + options, workspaceId, - }: ProcessWorkspaceArgs): Promise { - try { + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Running MigrateRichTextContentPatchCommand for workspace ${workspaceId} ${index + 1}/${total}`, + ); + + if (await this.hasRichTextV2FeatureFlag(workspaceId)) { this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, + chalk.yellow( + 'Rich text v2 feature flag is enabled, skipping migration', + ), ); - if (await this.hasRichTextV2FeatureFlag(workspaceId)) { - throw new Error( - 'Rich text v2 feature flag is enabled, skipping migration', - ); - } + return; + } - const richTextFields = await this.fieldMetadataRepository.find({ - where: { - workspaceId, - type: FieldMetadataType.RICH_TEXT, - }, - }); + const richTextFields = await this.fieldMetadataRepository.find({ + where: { + workspaceId, + type: FieldMetadataType.RICH_TEXT, + }, + }); - if (!richTextFields.length) { - this.logger.log( - chalk.yellow('No RICH_TEXT fields found in this workspace'), - ); + if (!richTextFields.length) { + this.logger.log( + chalk.yellow('No RICH_TEXT fields found in this workspace'), + ); - return; - } + return; + } - this.logger.log(`Found ${richTextFields.length} RICH_TEXT fields`); + this.logger.log(`Found ${richTextFields.length} RICH_TEXT fields`); - const richTextFieldsWithObjectMetadata = - await this.getRichTextFieldsWithObjectMetadata({ - richTextFields, - workspaceId, - }); - - await this.migrateToNewRichTextFieldsColumn({ - richTextFieldsWithObjectMetadata, + const richTextFieldsWithObjectMetadata = + await this.getRichTextFieldsWithObjectMetadata({ + richTextFields, workspaceId, }); - this.logger.log( - chalk.green(`Command completed for workspace ${workspaceId}`), - ); - } catch (error) { - this.logger.log(chalk.red(`Error in workspace ${workspaceId}: ${error}`)); - } + await this.migrateToNewRichTextFieldsColumn({ + richTextFieldsWithObjectMetadata, + workspaceId, + options, + }); + + this.logger.log( + chalk.green(`Command completed for workspace ${workspaceId}`), + ); } private async hasRichTextV2FeatureFlag( @@ -179,7 +138,7 @@ export class MigrateRichTextContentPatchCommand extends MaintainedWorkspacesMigr }); if (objectMetadata === null) { - this.logger.warn( + this.logger.log( `Object metadata not found for rich text field ${richTextField.name} in workspace ${workspaceId}`, ); } @@ -225,7 +184,7 @@ export class MigrateRichTextContentPatchCommand extends MaintainedWorkspacesMigr } if (!Array.isArray(jsonParsedblocknoteFieldValue)) { - this.logger.warn( + this.logger.log( `blocknoteFieldValue is defined and is not an array got ${blocknoteFieldValue}`, ); @@ -239,7 +198,7 @@ export class MigrateRichTextContentPatchCommand extends MaintainedWorkspacesMigr jsonParsedblocknoteFieldValue, ); } catch (error) { - this.logger.warn( + this.logger.log( `Error converting blocknote to markdown for ${blocknoteFieldValue}`, ); } @@ -250,6 +209,7 @@ export class MigrateRichTextContentPatchCommand extends MaintainedWorkspacesMigr private async migrateToNewRichTextFieldsColumn({ richTextFieldsWithObjectMetadata, workspaceId, + options, }: MigrateRichTextContentArgs) { const serverBlockNoteEditor = ServerBlockNoteEditor.create(); @@ -285,11 +245,19 @@ export class MigrateRichTextContentPatchCommand extends MaintainedWorkspacesMigr serverBlockNoteEditor, }); - if (!this.options.dryRun) { - await workspaceDataSource.query( - `UPDATE "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" SET "${richTextField.name}V2Blocknote" = $1, "${richTextField.name}V2Markdown" = $2 WHERE id = $3`, - [blocknoteFieldValue, markdownFieldValue, row.id], - ); + if (!options.dryRun) { + try { + await workspaceDataSource.query( + `UPDATE "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" SET "${richTextField.name}V2Blocknote" = $1, "${richTextField.name}V2Markdown" = $2 WHERE id = $3`, + [blocknoteFieldValue, markdownFieldValue, row.id], + ); + } catch (error) { + this.logger.log( + chalk.red( + `Error updating rich text field ${richTextField.name} for record ${row.id} in workspace ${workspaceId}`, + ), + ); + } } } } diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command.ts similarity index 53% rename from packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command.ts rename to packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command.ts index 98dca41e8..6e0e5bbed 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command.ts @@ -1,13 +1,12 @@ import { InjectRepository } from '@nestjs/typeorm'; -import chalk from 'chalk'; +import { Command } from 'nest-commander'; import { Repository } from 'typeorm'; -import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -18,12 +17,11 @@ import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/wo import { SEARCH_FIELDS_FOR_NOTES } from 'src/modules/note/standard-objects/note.workspace-entity'; import { SEARCH_FIELDS_FOR_TASKS } from 'src/modules/task/standard-objects/task.workspace-entity'; -@MigrationCommand({ - name: 'migrate-search-vector-on-note-and-task-entities', +@Command({ + name: 'upgrade:0-43:migrate-search-vector-on-note-and-task-entities', description: 'Migrate search vector on note and task entities', - version: '0.43', }) -export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends MaintainedWorkspacesMigrationCommandRunner { +export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { constructor( @InjectRepository(Workspace, 'core') protected readonly workspaceRepository: Repository, @@ -39,73 +37,60 @@ export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends MaintainedW super(workspaceRepository, twentyORMGlobalManager); } - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: MaintainedWorkspacesMigrationCommandOptions, - workspaceIds: string[], - ): Promise { + override async runOnWorkspace({ + index, + total, + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { this.logger.log( - 'Running command to migrate search vector on note and task entities', + `Running command for workspace ${workspaceId} ${index + 1}/${total}`, ); - for (const [index, workspaceId] of workspaceIds.entries()) { - await this.processWorkspace(workspaceId, index, workspaceIds.length); - } - - this.logger.log(chalk.green('Command completed!')); - } - - async processWorkspace( - workspaceId: string, - index: number, - total: number, - ): Promise { - try { - this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - const noteObjectMetadata = - await this.objectMetadataRepository.findOneOrFail({ - select: ['id'], - where: { - workspaceId, - nameSingular: 'note', - }, - }); + const noteObjectMetadata = + await this.objectMetadataRepository.findOneOrFail({ + select: ['id'], + where: { + workspaceId, + nameSingular: 'note', + }, + }); + if (!options.dryRun) { await this.searchService.updateSearchVector( noteObjectMetadata.id, SEARCH_FIELDS_FOR_NOTES, workspaceId, ); + } - const taskObjectMetadata = - await this.objectMetadataRepository.findOneOrFail({ - select: ['id'], - where: { - workspaceId, - nameSingular: 'task', - }, - }); + const taskObjectMetadata = + await this.objectMetadataRepository.findOneOrFail({ + select: ['id'], + where: { + workspaceId, + nameSingular: 'task', + }, + }); + if (!options.dryRun) { await this.searchService.updateSearchVector( taskObjectMetadata.id, SEARCH_FIELDS_FOR_TASKS, workspaceId, ); - - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( - workspaceId, - ); - - await this.workspaceMetadataVersionService.incrementMetadataVersion( - workspaceId, - ); - } catch (error) { - this.logger.log( - chalk.red(`Error in workspace ${workspaceId} - ${error.message}`), - ); } + + await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( + workspaceId, + ); + + await this.workspaceMetadataVersionService.incrementMetadataVersion( + workspaceId, + ); + + this.logger.log( + `Migrated search vector on note and task entities for workspace ${workspaceId}`, + ); } } diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command.ts similarity index 56% rename from packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command.ts rename to packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command.ts index 3524cae7b..23ae31f3e 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command.ts @@ -1,23 +1,25 @@ import { InjectRepository } from '@nestjs/typeorm'; import chalk from 'chalk'; +import { Command } from 'nest-commander'; import { In, Repository } from 'typeorm'; -import { BatchMaintainedWorkspacesMigrationCommandRunner } from 'src/database/commands/migration-command/batch-maintained-workspaces-migration-command.runner'; -import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity'; -@MigrationCommand({ - name: 'update-default-view-record-opening-on-workflow-objects', +@Command({ + name: 'upgrade:0-43:update-default-view-record-opening-on-workflow-objects', description: 'Update default view record opening on workflow objects to record page', - version: '0.43', }) -export class UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand extends BatchMaintainedWorkspacesMigrationCommandRunner { +export class UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { constructor( @InjectRepository(Workspace, 'core') protected readonly workspaceRepository: Repository, @@ -28,51 +30,46 @@ export class UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand extends Batc super(workspaceRepository, twentyORMGlobalManager); } - async runMigrationCommandOnWorkspace( - workspaceId: string, - index: number, - total: number, - ): Promise { - try { + override async runOnWorkspace({ + index, + total, + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Running command for workspace ${workspaceId} ${index + 1}/${total}`, + ); + + const workflowObjectsMetadata = await this.objectMetadataRepository.find({ + select: ['id'], + where: { + workspaceId, + standardId: In([ + STANDARD_OBJECT_IDS.workflow, + STANDARD_OBJECT_IDS.workflowVersion, + STANDARD_OBJECT_IDS.workflowRun, + ]), + }, + }); + + if (workflowObjectsMetadata.length === 0) { this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, + chalk.yellow(`No workflow objects found for workspace ${workspaceId}`), ); - const workflowObjectsMetadata = await this.objectMetadataRepository.find({ - select: ['id'], - where: { - workspaceId, - standardId: In([ - STANDARD_OBJECT_IDS.workflow, - STANDARD_OBJECT_IDS.workflowVersion, - STANDARD_OBJECT_IDS.workflowRun, - ]), - }, - }); - - if (workflowObjectsMetadata.length === 0) { - this.logger.log( - chalk.yellow( - `No workflow objects found for workspace ${workspaceId}`, - ), - ); - - return; - } + return; + } + if (!options.dryRun) { await this.updateDefaultViewsRecordOpening( workflowObjectsMetadata.map((metadata) => metadata.id), workspaceId, ); - - this.logger.log( - chalk.green(`Command completed for workspace ${workspaceId}.`), - ); - } catch (error) { - this.logger.log( - chalk.red(`Error in workspace ${workspaceId} - ${error.message}`), - ); } + + this.logger.log( + chalk.green(`Command completed for workspace ${workspaceId}.`), + ); } private async updateDefaultViewsRecordOpening( diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-upgrade-version-command.module.ts new file mode 100644 index 000000000..cf7419da5 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-43/0-43-upgrade-version-command.module.ts @@ -0,0 +1,45 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-add-tasks-assigned-to-me-view.command'; +import { MigrateIsSearchableForCustomObjectMetadataCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command'; +import { MigrateRichTextContentPatchCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-rich-text-content-patch.command'; +import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command'; +import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command'; +import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { SearchModule } from 'src/engine/metadata-modules/search/search.module'; +import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; +import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; +import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'), + TypeOrmModule.forFeature( + [FieldMetadataEntity, ObjectMetadataEntity], + 'metadata', + ), + WorkspaceDataSourceModule, + SearchModule, + WorkspaceMigrationRunnerModule, + WorkspaceMetadataVersionModule, + ], + providers: [ + MigrateRichTextContentPatchCommand, + MigrateSearchVectorOnNoteAndTaskEntitiesCommand, + UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand, + MigrateIsSearchableForCustomObjectMetadataCommand, + AddTasksAssignedToMeViewCommand, + ], + exports: [ + MigrateRichTextContentPatchCommand, + MigrateSearchVectorOnNoteAndTaskEntitiesCommand, + UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand, + MigrateIsSearchableForCustomObjectMetadataCommand, + AddTasksAssignedToMeViewCommand, + ], +}) +export class V0_43_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-initialize-permissions.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-initialize-permissions.command.ts similarity index 74% rename from packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-initialize-permissions.command.ts rename to packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-initialize-permissions.command.ts index 7dd3669f6..e67879232 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-initialize-permissions.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-initialize-permissions.command.ts @@ -1,14 +1,15 @@ import { InjectRepository } from '@nestjs/typeorm'; import chalk from 'chalk'; +import { Command } from 'nest-commander'; import { isDefined } from 'twenty-shared'; import { IsNull, Repository } from 'typeorm'; -import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; + ActiveOrSuspendedWorkspacesMigrationCommandOptions, + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants'; @@ -17,13 +18,11 @@ import { RoleService } from 'src/engine/metadata-modules/role/role.service'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -@MigrationCommand({ - name: 'initialize-permissions', +@Command({ + name: 'upgrade:0-44:initialize-permissions', description: 'Initialize permissions', - version: '0.44', }) -export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationCommandRunner { - private options: MaintainedWorkspacesMigrationCommandOptions; +export class InitializePermissionsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { constructor( @InjectRepository(Workspace, 'core') protected readonly workspaceRepository: Repository, @@ -36,26 +35,12 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC super(workspaceRepository, twentyORMGlobalManager); } - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: MaintainedWorkspacesMigrationCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log(chalk.green('Running command to initialize permissions')); - - this.options = options; - for (const [index, workspaceId] of workspaceIds.entries()) { - await this.processWorkspace(workspaceId, index, workspaceIds.length); - } - - this.logger.log(chalk.green('Command completed!')); - } - - private async processWorkspace( - workspaceId: string, - index: number, - total: number, - ): Promise { + override async runOnWorkspace({ + index, + total, + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { try { this.logger.log( `Running command for workspace ${workspaceId} ${index + 1}/${total}`, @@ -71,12 +56,16 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC )?.id; if (!isDefined(adminRoleId)) { - adminRoleId = await this.createAdminRole({ workspaceId }); + adminRoleId = await this.createAdminRole({ + workspaceId, + options, + }); } await this.assignAdminRole({ workspaceId, adminRoleId, + options, }); let memberRoleId: string | undefined; @@ -88,17 +77,20 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC if (!isDefined(memberRoleId)) { memberRoleId = await this.createMemberRole({ workspaceId, + options, }); } await this.setMemberRoleAsDefaultRole({ workspaceId, memberRoleId, + options, }); await this.assignMemberRoleToUserWorkspacesWithoutRole({ workspaceId, memberRoleId, + options, }); } catch (error) { this.logger.log( @@ -107,14 +99,18 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC } } - private async createAdminRole({ workspaceId }: { workspaceId: string }) { + private async createAdminRole({ + workspaceId, + options, + }: { + workspaceId: string; + options: ActiveOrSuspendedWorkspacesMigrationCommandOptions; + }) { this.logger.log( - chalk.green( - `Creating admin role ${this.options.dryRun ? '(dry run)' : ''}`, - ), + chalk.green(`Creating admin role ${options.dryRun ? '(dry run)' : ''}`), ); - if (this.options.dryRun) { + if (options.dryRun) { return ''; } @@ -125,14 +121,18 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC return adminRole.id; } - private async createMemberRole({ workspaceId }: { workspaceId: string }) { + private async createMemberRole({ + workspaceId, + options, + }: { + workspaceId: string; + options: ActiveOrSuspendedWorkspacesMigrationCommandOptions; + }) { this.logger.log( - chalk.green( - `Creating member role ${this.options.dryRun ? '(dry run)' : ''}`, - ), + chalk.green(`Creating member role ${options.dryRun ? '(dry run)' : ''}`), ); - if (this.options.dryRun) { + if (options.dryRun) { return ''; } @@ -146,9 +146,11 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC private async setMemberRoleAsDefaultRole({ workspaceId, memberRoleId, + options, }: { workspaceId: string; memberRoleId: string; + options: ActiveOrSuspendedWorkspacesMigrationCommandOptions; }) { const workspaceDefaultRole = await this.workspaceRepository.findOne({ where: { @@ -159,11 +161,11 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC if (!isDefined(workspaceDefaultRole?.defaultRoleId)) { this.logger.log( chalk.green( - `Setting member role as default role ${this.options.dryRun ? '(dry run)' : ''}`, + `Setting member role as default role ${options.dryRun ? '(dry run)' : ''}`, ), ); - if (this.options.dryRun) { + if (options.dryRun) { return; } @@ -176,9 +178,11 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC private async assignAdminRole({ workspaceId, adminRoleId, + options, }: { workspaceId: string; adminRoleId: string; + options: ActiveOrSuspendedWorkspacesMigrationCommandOptions; }) { const oldestUserWorkspace = await this.userWorkspaceRepository.findOne({ where: { @@ -201,11 +205,11 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC this.logger.log( chalk.green( - `Assigning admin role to user ${oldestUserWorkspace.id} ${this.options.dryRun ? '(dry run)' : ''}`, + `Assigning admin role to user ${oldestUserWorkspace.id} ${options.dryRun ? '(dry run)' : ''}`, ), ); - if (this.options.dryRun) { + if (options.dryRun) { return; } @@ -219,9 +223,11 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC private async assignMemberRoleToUserWorkspacesWithoutRole({ workspaceId, memberRoleId, + options, }: { workspaceId: string; memberRoleId: string; + options: ActiveOrSuspendedWorkspacesMigrationCommandOptions; }) { const userWorkspaces = await this.userWorkspaceRepository.find({ where: { @@ -257,11 +263,11 @@ export class InitializePermissionsCommand extends MaintainedWorkspacesMigrationC this.logger.log( chalk.green( - `Assigning member role to user workspace ${userWorkspace.id} ${this.options.dryRun ? '(dry run)' : ''}`, + `Assigning member role to user workspace ${userWorkspace.id} ${options.dryRun ? '(dry run)' : ''}`, ), ); - if (this.options.dryRun) { + if (options.dryRun) { continue; } diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-migrate-relations-to-field-metadata.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-migrate-relations-to-field-metadata.command.ts new file mode 100644 index 000000000..e3d69537a --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-migrate-relations-to-field-metadata.command.ts @@ -0,0 +1,139 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import chalk from 'chalk'; +import { Command } from 'nest-commander'; +import { FieldMetadataType } from 'twenty-shared'; +import { In, Repository } from 'typeorm'; + +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { + RelationDirection, + deduceRelationDirection, +} from 'src/engine/utils/deduce-relation-direction.util'; +import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util'; + +@Command({ + name: 'upgrade:0-44:migrate-relations-to-field-metadata', + description: 'Migrate relations to field metadata', +}) +export class MigrateRelationsToFieldMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + @InjectRepository(FieldMetadataEntity, 'metadata') + private readonly fieldMetadataRepository: Repository, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace({ + index, + total, + workspaceId, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Running command for workspace ${workspaceId} ${index + 1}/${total}`, + ); + + const fieldMetadataCollection = await this.fieldMetadataRepository.find({ + where: { + workspaceId, + type: In([FieldMetadataType.RELATION, FieldMetadataType.UUID]), + }, + relations: ['fromRelationMetadata', 'toRelationMetadata'], + }); + + if (!fieldMetadataCollection.length) { + this.logger.log( + chalk.yellow( + `No relation field metadata found for workspace ${workspaceId}.`, + ), + ); + + return; + } + + const joinColumnFieldMetadataCollection = fieldMetadataCollection.filter( + (fieldMetadata) => + isFieldMetadataOfType(fieldMetadata, FieldMetadataType.UUID), + // TODO: Fix this, it's working in other places but not here + ) as FieldMetadataEntity[]; + + const fieldMetadataToUpdateCollection = fieldMetadataCollection + .filter((fieldMetadata) => + isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION), + ) + .map((fieldMetadata) => + this.updateRelationFieldMetadata( + joinColumnFieldMetadataCollection, + // TODO: Fix this, it's working in other places but not here + fieldMetadata as FieldMetadataEntity, + ), + ); + + if (fieldMetadataToUpdateCollection.length > 0) { + await this.fieldMetadataRepository.save(fieldMetadataToUpdateCollection); + } + + this.logger.log( + chalk.green(`Command completed for workspace ${workspaceId}.`), + ); + } + + private updateRelationFieldMetadata( + joinColumnFieldMetadataCollection: FieldMetadataEntity[], + fieldMetadata: FieldMetadataEntity, + ): FieldMetadataEntity { + const relationMetadata = + fieldMetadata.fromRelationMetadata ?? fieldMetadata.toRelationMetadata; + const joinColumnFieldMetadata = joinColumnFieldMetadataCollection.find( + (joinColumnFieldMetadata) => + // We're deducing the field based on the name of the relation field + // This is not the best way to do this but we don't have a better way + joinColumnFieldMetadata.name === `${fieldMetadata.name}Id`, + ); + + const relationDirection = deduceRelationDirection( + fieldMetadata, + relationMetadata, + ); + let relationType = relationMetadata.relationType as unknown as RelationType; + + if ( + relationDirection === RelationDirection.TO && + relationType === RelationType.ONE_TO_MANY + ) { + relationType = RelationType.MANY_TO_ONE; + } + + const relationTargetFieldMetadataId = + relationDirection === RelationDirection.FROM + ? relationMetadata.toFieldMetadataId + : relationMetadata.fromFieldMetadataId; + + const relationTargetObjectMetadataId = + relationDirection === RelationDirection.FROM + ? relationMetadata.toObjectMetadataId + : relationMetadata.fromObjectMetadataId; + + return { + ...fieldMetadata, + settings: { + relationType, + onDelete: relationMetadata.onDeleteAction, + joinColumnName: joinColumnFieldMetadata?.name, + }, + relationTargetFieldMetadataId, + relationTargetObjectMetadataId, + }; + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-upgrade-version-command.module.ts new file mode 100644 index 000000000..b6ba27811 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-44/0-44-upgrade-version-command.module.ts @@ -0,0 +1,34 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { InitializePermissionsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-initialize-permissions.command'; +import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-migrate-relations-to-field-metadata.command'; +import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { RoleModule } from 'src/engine/metadata-modules/role/role.module'; +import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; +import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Workspace, UserWorkspace], 'core'), + TypeOrmModule.forFeature( + [FieldMetadataEntity, ObjectMetadataEntity], + 'metadata', + ), + WorkspaceDataSourceModule, + RoleModule, + UserRoleModule, + ], + providers: [ + InitializePermissionsCommand, + MigrateRelationsToFieldMetadataCommand, + ], + exports: [ + InitializePermissionsCommand, + MigrateRelationsToFieldMetadataCommand, + ], +}) +export class V0_44_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts new file mode 100644 index 000000000..31de9145d --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { V0_43_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-43/0-43-upgrade-version-command.module'; +import { V0_44_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-44/0-44-upgrade-version-command.module'; +import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Workspace], 'core'), + V0_43_UpgradeVersionCommandModule, + V0_44_UpgradeVersionCommandModule, + WorkspaceSyncMetadataModule, + ], + providers: [UpgradeCommand], +}) +export class UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts new file mode 100644 index 000000000..c2779a740 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -0,0 +1,72 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import chalk from 'chalk'; +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-add-tasks-assigned-to-me-view.command'; +import { MigrateIsSearchableForCustomObjectMetadataCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command'; +import { MigrateRichTextContentPatchCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-rich-text-content-patch.command'; +import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command'; +import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command'; + +@Command({ + name: 'upgrade', + description: 'Upgrade workspaces to the latest version', +}) +export class UpgradeCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + protected readonly migrateRichTextContentPatchCommand: MigrateRichTextContentPatchCommand, + protected readonly addTasksAssignedToMeViewCommand: AddTasksAssignedToMeViewCommand, + protected readonly migrateIsSearchableForCustomObjectMetadataCommand: MigrateIsSearchableForCustomObjectMetadataCommand, + protected readonly updateDefaultViewRecordOpeningOnWorkflowObjectsCommand: UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand, + protected readonly migrateSearchVectorOnNoteAndTaskEntitiesCommand: MigrateSearchVectorOnNoteAndTaskEntitiesCommand, + protected readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace(args: RunOnWorkspaceArgs): Promise { + this.logger.log( + chalk.blue( + `${args.options.dryRun ? '(dry run)' : ''} Upgrading workspace ${args.workspaceId} ${args.index + 1}/${args.total}`, + ), + ); + + await this.migrateRichTextContentPatchCommand.runOnWorkspace(args); + + await this.migrateIsSearchableForCustomObjectMetadataCommand.runOnWorkspace( + args, + ); + + await this.migrateSearchVectorOnNoteAndTaskEntitiesCommand.runOnWorkspace( + args, + ); + + await this.migrateIsSearchableForCustomObjectMetadataCommand.runOnWorkspace( + args, + ); + + await this.syncWorkspaceMetadataCommand.runOnWorkspace(args); + + await this.updateDefaultViewRecordOpeningOnWorkflowObjectsCommand.runOnWorkspace( + args, + ); + + await this.addTasksAssignedToMeViewCommand.runOnWorkspace(args); + + this.logger.log( + chalk.blue(`Upgrade for workspace ${args.workspaceId} completed.`), + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-fix-body-v2-view-field-position.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-fix-body-v2-view-field-position.command.ts deleted file mode 100644 index 24a0ed964..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-fix-body-v2-view-field-position.command.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { In, Repository } from 'typeorm'; - -import { isCommandLogger } from 'src/database/commands/logger'; -import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -@Command({ - name: 'upgrade-0.42:fix-body-v2-view-field-position', - description: 'Make bodyV2 field position to match body field position', -}) -export class FixBodyV2ViewFieldPositionCommand extends MaintainedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: MaintainedWorkspacesMigrationCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log('Running command to fix bodyV2 field position'); - - if (isCommandLogger(this.logger)) { - this.logger.setVerbose(options.verbose ?? false); - } - - try { - for (const [index, workspaceId] of workspaceIds.entries()) { - await this.processWorkspace(workspaceId, index, workspaceIds.length); - } - - this.logger.log(chalk.green('Command completed!')); - } catch (error) { - this.logger.log(chalk.red('Error executing command')); - } - } - - private async processWorkspace( - workspaceId: string, - index: number, - total: number, - ): Promise { - try { - this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - const viewRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'view', - ); - - const viewFieldRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewField', - false, - ); - - const taskAndNoteObjectMetadatas = - await this.objectMetadataRepository.find({ - where: { - workspaceId, - nameSingular: In(['note', 'task']), - }, - relations: ['fields'], - }); - - const taskAndNoteViews = await viewRepository.find({ - where: { - objectMetadataId: In( - taskAndNoteObjectMetadatas.map((object) => object.id), - ), - }, - }); - - const fieldMetadatas = taskAndNoteObjectMetadatas.flatMap( - (objectMetadata) => objectMetadata.fields, - ); - - const fieldNameByMetadataId: Record = - fieldMetadatas.reduce( - (fieldNameByMetadataId, fieldMetadata) => ({ - ...fieldNameByMetadataId, - [fieldMetadata.id]: fieldMetadata.name, - }), - {}, - ); - - for (const view of taskAndNoteViews) { - this.logger.log( - `Updating bodyV2 field position for view ${view.id} - ${view.name}`, - ); - const viewFields = await viewFieldRepository.find({ - where: { - viewId: view.id, - }, - }); - - const bodyViewField = viewFields.find( - (viewField) => - fieldNameByMetadataId[viewField.fieldMetadataId] === 'body', - ); - const bodyV2ViewField = viewFields.find( - (viewField) => - fieldNameByMetadataId[viewField.fieldMetadataId] === 'bodyV2', - ); - - if (bodyViewField && bodyV2ViewField) { - this.logger.log( - `Setting body field position to ${bodyV2ViewField?.position} and bodyV2 field position to ${bodyViewField?.position}`, - ); - - await viewFieldRepository.update( - { id: bodyViewField.id }, - { - position: bodyV2ViewField.position, - isVisible: false, - }, - ); - await viewFieldRepository.update( - { id: bodyV2ViewField.id }, - { - position: bodyViewField.position, - isVisible: bodyViewField.isVisible, - }, - ); - } else if (bodyViewField && !bodyV2ViewField) { - this.logger.log( - `Creating bodyV2 view field for view ${view.id} with position ${viewFields.length}`, - ); - - const bodyV2FieldMetadataId = fieldMetadatas.find( - (field) => field.name === 'bodyV2', - )?.id; - - const viewFieldToCreate = viewFieldRepository.create({ - fieldMetadataId: bodyV2FieldMetadataId, - viewId: view.id, - position: bodyViewField.position, - isVisible: bodyViewField.isVisible, - size: bodyViewField.size, - aggregateOperation: bodyViewField.aggregateOperation, - }); - - await viewFieldRepository.save(viewFieldToCreate); - - await viewFieldRepository.update( - { id: bodyViewField.id }, - { - position: viewFields.length, - isVisible: false, - }, - ); - } - } - - await this.workspaceMetadataVersionService.incrementMetadataVersion( - workspaceId, - ); - - this.logger.log( - chalk.green(`Command completed for workspace ${workspaceId}`), - ); - } catch (error) { - this.logger.log(chalk.red(`Error in workspace ${workspaceId}`)); - } - - await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( - workspaceId, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-limit-amount-of-view-field.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-limit-amount-of-view-field.ts deleted file mode 100644 index 22c791aab..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-limit-amount-of-view-field.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Repository } from 'typeorm'; - -import { CommandLogger } from 'src/database/commands/logger'; -import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; -import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; -import { settings } from 'src/engine/constants/settings'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -@MigrationCommand({ - name: 'limit-amount-of-view-field', - description: 'Limit amount of view field.', - version: '0.42', -}) -export class LimitAmountOfViewFieldCommand extends MaintainedWorkspacesMigrationCommandRunner { - protected readonly logger: CommandLogger; - - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - this.logger = new CommandLogger({ - constructorName: this.constructor.name, - verbose: false, - }); - this.logger.setVerbose(false); - } - - async runOnWorkspace(workspaceId: string, dryRun?: boolean): Promise { - this.logger.log( - `Processing workspace ${workspaceId} for view field limitation`, - ); - try { - const viewRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - ViewWorkspaceEntity, - ); - - const views = await viewRepository.find({}); - - for (const view of views) { - const viewFieldRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - ViewFieldWorkspaceEntity, - ); - - const viewFields = await viewFieldRepository.find({ - where: { - viewId: view.id, - isVisible: true, - }, - order: { - position: 'ASC', - }, - }); - - if (viewFields.length > settings.maxVisibleViewFields) { - const extraFields = viewFields.slice(settings.maxVisibleViewFields); - - for (const field of extraFields) { - this.logger.log( - `Workspace ${workspaceId} - Hiding field ${field.id} in view ${view.id} (position ${field.position})`, - ); - if (!dryRun) { - await viewFieldRepository.update( - { id: field.id }, - { isVisible: false }, - ); - } - } - } - } - } catch (error) { - this.logger.error( - `Error limiting view fields in workspace ${workspaceId}`, - error, - ); - throw error; - } finally { - await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( - workspaceId, - ); - } - } - - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: MaintainedWorkspacesMigrationCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log(`Running limit-amount-of-view-field command`); - - if (options?.dryRun) { - this.logger.log(chalk.yellow('Dry run mode: No changes will be applied')); - } - - for (const [index, workspaceId] of workspaceIds.entries()) { - try { - await this.runOnWorkspace(workspaceId, options?.dryRun); - this.logger.verbose( - `Processed workspace: ${workspaceId} (${index + 1}/${ - workspaceIds.length - })`, - ); - } catch (error) { - this.logger.error(`Error for workspace: ${workspaceId}`, error); - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command.ts deleted file mode 100644 index 616bf2c99..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command.ts +++ /dev/null @@ -1,462 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { ServerBlockNoteEditor } from '@blocknote/server-util'; -import chalk from 'chalk'; -import { Option } from 'nest-commander'; -import { FieldMetadataType, isDefined } from 'twenty-shared'; -import { Repository } from 'typeorm'; - -import { isCommandLogger } from 'src/database/commands/logger'; -import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; -import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; -import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; -import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; -import { - WorkspaceMigrationColumnActionType, - WorkspaceMigrationColumnCreate, - WorkspaceMigrationTableAction, - WorkspaceMigrationTableActionType, -} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; -import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; -import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; -import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; -import { - NOTE_STANDARD_FIELD_IDS, - TASK_STANDARD_FIELD_IDS, -} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; - -type MigrateRichTextContentArgs = { - richTextFieldsWithHasCreatedColumns: RichTextFieldWithHasCreatedColumnsAndObjectMetadata[]; - workspaceId: string; -}; - -type RichTextFieldWithHasCreatedColumnsAndObjectMetadata = { - richTextField: FieldMetadataEntity; - hasCreatedColumns: boolean; - objectMetadata: ObjectMetadataEntity | null; -}; - -type ProcessWorkspaceArgs = { - workspaceId: string; - index: number; - total: number; -}; - -type ProcessRichTextFieldsArgs = { - richTextFields: FieldMetadataEntity[]; - workspaceId: string; -}; - -type MigrateRichTextFieldCommandOptions = - MaintainedWorkspacesMigrationCommandOptions & { - force?: boolean; - }; - -@MigrationCommand({ - name: 'migrate-rich-text-field', - description: 'Migrate RICH_TEXT fields to new composite structure', - version: '0.42', -}) -export class MigrateRichTextFieldCommand extends MaintainedWorkspacesMigrationCommandRunner { - private options: MigrateRichTextFieldCommandOptions; - - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository, - @InjectRepository(FeatureFlag, 'core') - protected readonly featureFlagRepository: Repository, - private readonly workspaceDataSourceService: WorkspaceDataSourceService, - private readonly workspaceMigrationService: WorkspaceMigrationService, - private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, - private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - @Option({ - flags: '-f, --force [boolean]', - description: - 'Force RICH_TEXT_FIELD value update even if column migration has already be run', - required: false, - }) - parseForceValue(val?: boolean): boolean { - return val ?? false; - } - - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: MigrateRichTextFieldCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log( - 'Running command to migrate RICH_TEXT fields to new composite structure', - ); - if (options.force) { - this.logger.warn('Running in force mode'); - } - this.options = options; - if (isCommandLogger(this.logger)) { - this.logger.setVerbose(options.verbose ?? false); - } - - for (const [index, workspaceId] of workspaceIds.entries()) { - try { - await this.processWorkspace({ - workspaceId, - index, - total: workspaceIds.length, - }); - } catch (error) { - this.logger.log( - chalk.red(`Error in workspace ${workspaceId}: ${error}`), - ); - } - - await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( - workspaceId, - ); - } - - this.logger.log(chalk.green('Command completed!')); - } - - private async processWorkspace({ - index, - total, - workspaceId, - }: ProcessWorkspaceArgs): Promise { - try { - this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - const richTextFields = await this.fieldMetadataRepository.find({ - where: { - workspaceId, - type: FieldMetadataType.RICH_TEXT, - }, - }); - - if (!richTextFields.length) { - this.logger.log( - chalk.yellow('No RICH_TEXT fields found in this workspace'), - ); - - return; - } - - this.logger.log(`Found ${richTextFields.length} RICH_TEXT fields`); - - const richTextFieldsWithHasCreatedColumns = - await this.createIfMissingNewRichTextFieldsColumn({ - richTextFields, - workspaceId, - }); - - await this.migrateToNewRichTextFieldsColumn({ - richTextFieldsWithHasCreatedColumns, - workspaceId, - }); - - if (!this.options.dryRun) { - await this.workspaceMetadataVersionService.incrementMetadataVersion( - workspaceId, - ); - } - - this.logger.log( - chalk.green(`Command completed for workspace ${workspaceId}`), - ); - } catch (error) { - this.logger.log(chalk.red(`Error in workspace ${workspaceId}: ${error}`)); - } - } - - private buildRichTextFieldStandardId(richTextField: FieldMetadataEntity) { - switch (true) { - case richTextField.standardId === TASK_STANDARD_FIELD_IDS.body: { - return TASK_STANDARD_FIELD_IDS.bodyV2; - } - case richTextField.standardId === NOTE_STANDARD_FIELD_IDS.body: { - return NOTE_STANDARD_FIELD_IDS.bodyV2; - } - case richTextField.isCustom: { - return null; - } - default: { - throw new Error( - `RICH_TEXT does not belong to a Task or a Note standard objects: ${richTextField.id}`, - ); - } - } - } - - private async createMarkdownBlockNoteV2Columns({ - richTextField, - workspaceId, - objectMetadata, - fieldMetadataAlreadyExisting, - }: { - objectMetadata: ObjectMetadataEntity; - richTextField: FieldMetadataEntity; - workspaceId: string; - fieldMetadataAlreadyExisting: boolean; - }) { - const columnsToCreate: WorkspaceMigrationColumnCreate[] = [ - { - action: WorkspaceMigrationColumnActionType.CREATE, - columnName: `${richTextField.name}V2Blocknote`, - columnType: 'text', - isNullable: true, - defaultValue: null, - }, - { - action: WorkspaceMigrationColumnActionType.CREATE, - columnName: `${richTextField.name}V2Markdown`, - columnType: 'text', - isNullable: true, - defaultValue: null, - }, - ] as const; - - const shouldForceCreateColumns = - this.options.force && fieldMetadataAlreadyExisting; - - if (shouldForceCreateColumns) { - this.logger.warn( - `Force creating V2 columns for workspaceId: ${workspaceId} objectMetadaId: ${objectMetadata.id}`, - ); - } - const shouldCreateColumns = - !fieldMetadataAlreadyExisting || shouldForceCreateColumns; - - if (!this.options.dryRun && shouldCreateColumns) { - await this.workspaceMigrationService.createCustomMigration( - generateMigrationName( - `migrate-rich-text-field-${objectMetadata.nameSingular}-${richTextField.name}`, - ), - workspaceId, - [ - { - name: computeObjectTargetTable(objectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: columnsToCreate, - } satisfies WorkspaceMigrationTableAction, - ], - ); - } - - return shouldCreateColumns; - } - - private async createIfMissingNewRichTextFieldsColumn({ - richTextFields, - workspaceId, - }: ProcessRichTextFieldsArgs): Promise< - RichTextFieldWithHasCreatedColumnsAndObjectMetadata[] - > { - const richTextFieldsWithHasCreatedColumns: RichTextFieldWithHasCreatedColumnsAndObjectMetadata[] = - []; - - for (const richTextField of richTextFields) { - const standardId = this.buildRichTextFieldStandardId(richTextField); - - const newRichTextField: Partial = { - ...richTextField, - name: `${richTextField.name}V2`, - id: undefined, - type: FieldMetadataType.RICH_TEXT_V2, - defaultValue: null, - standardId, - workspaceId, - }; - - const existingFieldMetadata = - await this.fieldMetadataRepository.findOneBy({ - name: newRichTextField.name, - type: newRichTextField.type, - standardId: newRichTextField.standardId ?? undefined, - workspaceId, - }); - const fieldMetadataAlreadyExisting = isDefined(existingFieldMetadata); - - if (fieldMetadataAlreadyExisting) { - this.logger.warn( - `FieldMetadata already exists in fieldMetadataRepository name: ${newRichTextField.name} standardId: ${newRichTextField.standardId} type: ${newRichTextField.type} workspaceId: ${workspaceId}`, - ); - } - - if (!this.options.dryRun && !fieldMetadataAlreadyExisting) { - await this.fieldMetadataRepository.insert(newRichTextField); - } - - const objectMetadata = await this.objectMetadataRepository.findOne({ - where: { id: richTextField.objectMetadataId }, - relations: { - fields: true, - }, - }); - - if (objectMetadata === null) { - this.logger.warn( - `Object metadata not found for rich text field ${richTextField.name} in workspace ${workspaceId}`, - ); - richTextFieldsWithHasCreatedColumns.push({ - hasCreatedColumns: false, - richTextField, - objectMetadata, - }); - continue; - } - - const hasCreatedColumns = await this.createMarkdownBlockNoteV2Columns({ - objectMetadata, - richTextField, - workspaceId, - fieldMetadataAlreadyExisting, - }); - - richTextFieldsWithHasCreatedColumns.push({ - hasCreatedColumns: hasCreatedColumns ?? false, - richTextField, - objectMetadata, - }); - } - - const hasAtLeastOnePendingMigration = - richTextFieldsWithHasCreatedColumns.some( - ({ hasCreatedColumns }) => hasCreatedColumns, - ); - - if (!this.options.dryRun && hasAtLeastOnePendingMigration) { - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( - workspaceId, - ); - } - - return richTextFieldsWithHasCreatedColumns; - } - - private jsonParseOrSilentlyFail(input: string): null | unknown { - try { - return JSON.parse(input); - } catch (e) { - return null; - } - } - - private async getMardownFieldValue({ - blocknoteFieldValue, - serverBlockNoteEditor, - }: { - blocknoteFieldValue: string | null; - serverBlockNoteEditor: ServerBlockNoteEditor; - }): Promise { - const blocknoteFieldValueIsDefined = - blocknoteFieldValue !== null && - blocknoteFieldValue !== undefined && - blocknoteFieldValue !== '{}'; - - if (!blocknoteFieldValueIsDefined) { - return null; - } - - const jsonParsedblocknoteFieldValue = - this.jsonParseOrSilentlyFail(blocknoteFieldValue); - - if (jsonParsedblocknoteFieldValue === null) { - return null; - } - - if (!Array.isArray(jsonParsedblocknoteFieldValue)) { - this.logger.warn( - `blocknoteFieldValue is defined and is not an array got ${blocknoteFieldValue}`, - ); - - return null; - } - - let markdown: string | null = null; - - try { - markdown = await serverBlockNoteEditor.blocksToMarkdownLossy( - jsonParsedblocknoteFieldValue, - ); - } catch (error) { - this.logger.warn( - `Error converting blocknote to markdown for ${blocknoteFieldValue}`, - ); - } - - return markdown; - } - - private async migrateToNewRichTextFieldsColumn({ - richTextFieldsWithHasCreatedColumns, - workspaceId, - }: MigrateRichTextContentArgs) { - const serverBlockNoteEditor = ServerBlockNoteEditor.create(); - - for (const { - richTextField, - hasCreatedColumns, - objectMetadata, - } of richTextFieldsWithHasCreatedColumns) { - if (objectMetadata === null) { - this.logger.log( - `Object metadata not found for rich text field ${richTextField.name} in workspace ${workspaceId}`, - ); - continue; - } - - const schemaName = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - const workspaceDataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - workspaceId, - ); - - const rows = await workspaceDataSource.query( - `SELECT id, "${richTextField.name}" FROM "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}"`, - ); - - this.logger.log(`Generating markdown for ${rows.length} records`); - - for (const row of rows) { - const blocknoteFieldValue = row[richTextField.name]; - const markdownFieldValue = await this.getMardownFieldValue({ - blocknoteFieldValue, - serverBlockNoteEditor, - }); - - if (this.options.force) { - this.logger.warn( - `Force udpate rowId: ${row.id} RICH_TEXT_FIELD ${richTextField.id} objectMetadata ${objectMetadata.id}`, - ); - } - if (!this.options.dryRun && (hasCreatedColumns || this.options.force)) { - await workspaceDataSource.query( - `UPDATE "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" SET "${richTextField.name}V2Blocknote" = $1, "${richTextField.name}V2Markdown" = $2 WHERE id = $3`, - [blocknoteFieldValue, markdownFieldValue, row.id], - ); - } - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type.ts deleted file mode 100644 index 9e215432e..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { FieldMetadataType } from 'twenty-shared'; -import { IsNull, Repository } from 'typeorm'; - -import { CommandLogger } from 'src/database/commands/logger'; -import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; -import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; - -@MigrationCommand({ - name: 'standardization-of-actor-composite-context-type', - description: 'Add context to actor composite type.', - version: '0.42', -}) -export class StandardizationOfActorCompositeContextTypeCommand extends MaintainedWorkspacesMigrationCommandRunner { - protected readonly logger; - - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, - ) { - super(workspaceRepository, twentyORMGlobalManager); - - this.logger = new CommandLogger({ - constructorName: this.constructor.name, - verbose: false, - }); - this.logger.setVerbose(false); - } - - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: MaintainedWorkspacesMigrationCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log(`Running add-context-to-actor-composite-type command`); - - if (options?.dryRun) { - this.logger.log(chalk.yellow('Dry run mode: No changes will be applied')); - } - - for (const [index, workspaceId] of workspaceIds.entries()) { - try { - await this.runOnWorkspace(workspaceId, options?.dryRun); - this.logger.verbose( - `[${index + 1}/${workspaceIds.length}] Added for workspace: ${workspaceId}`, - ); - } catch (error) { - this.logger.error(`Error for workspace: ${workspaceId}`, error); - } - } - } - - private async runOnWorkspace( - workspaceId: string, - dryRun = false, - ): Promise { - this.logger.verbose(`Adding for workspace: ${workspaceId}`); - const actorFields = await this.fieldMetadataRepository.find({ - where: { - type: FieldMetadataType.ACTOR, - workspaceId, - }, - relations: ['object'], - }); - - for (const field of actorFields) { - if (!field || !field.object) { - this.logger.verbose( - 'field.objectMetadata is null', - workspaceId, - field.id, - ); - continue; - } - - const fieldRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - field.object.nameSingular, - ); - - if (!dryRun) { - const rowsToUpdate = await fieldRepository.update( - { - [field.name + 'Context']: IsNull(), - }, - { - [field.name + 'Context']: {}, - }, - ); - - this.logger.verbose( - `updated ${rowsToUpdate ? rowsToUpdate.affected : 0} rows`, - ); - } - } - - if (!dryRun) { - await this.workspaceMetadataVersionService.incrementMetadataVersion( - workspaceId, - ); - } - - await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( - workspaceId, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-upgrade-version.module.ts deleted file mode 100644 index 7774c09eb..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-42/0-42-upgrade-version.module.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { MigrationCommandModule } from 'src/database/commands/migration-command/migration-command.module'; -import { FixBodyV2ViewFieldPositionCommand } from 'src/database/commands/upgrade-version/0-42/0-42-fix-body-v2-view-field-position.command'; -import { LimitAmountOfViewFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-limit-amount-of-view-field'; -import { MigrateRichTextFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command'; -import { StandardizationOfActorCompositeContextTypeCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type'; -import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; -import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; -import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; -import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; -import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module'; - -@Module({ - imports: [ - MigrationCommandModule.register('0.42', { - imports: [ - TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'), - TypeOrmModule.forFeature( - [ObjectMetadataEntity, FieldMetadataEntity], - 'metadata', - ), - WorkspaceSyncMetadataCommandsModule, - WorkspaceMigrationRunnerModule, - WorkspaceMigrationModule, - WorkspaceMetadataVersionModule, - WorkspaceDataSourceModule, - FeatureFlagModule, - ], - providers: [ - MigrateRichTextFieldCommand, - FixBodyV2ViewFieldPositionCommand, - LimitAmountOfViewFieldCommand, - StandardizationOfActorCompositeContextTypeCommand, - ], - }), - ], -}) -export class UpgradeTo0_42CommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command.ts deleted file mode 100644 index dd5a8f666..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Repository } from 'typeorm'; - -import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; -import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; - -@MigrationCommand({ - name: 'migrate-is-searchable-for-custom-object-metadata', - description: 'Set isSearchable true for custom object metadata', - version: '0.43', -}) -export class MigrateIsSearchableForCustomObjectMetadataCommand extends MaintainedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectRepository(ObjectMetadataEntity, 'metadata') - protected readonly objectMetadataRepository: Repository, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - _options: MaintainedWorkspacesMigrationCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log( - chalk.green( - 'Running command to set isSearchable true for custom object metadata', - ), - ); - - for (const [index, workspaceId] of workspaceIds.entries()) { - await this.processWorkspace(workspaceId, index, workspaceIds.length); - } - - this.logger.log(chalk.green('Command completed!')); - } - - private async processWorkspace( - workspaceId: string, - index: number, - total: number, - ): Promise { - try { - this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - await this.objectMetadataRepository.update( - { - workspaceId, - isCustom: true, - }, - { - isSearchable: true, - }, - ); - } catch (error) { - this.logger.log( - chalk.red(`Error in workspace ${workspaceId} - ${error.message}`), - ); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module.ts deleted file mode 100644 index c3578ec22..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-43/0-43-upgrade-version.module.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { MigrationCommandModule } from 'src/database/commands/migration-command/migration-command.module'; -import { StandardizationOfActorCompositeContextTypeCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type'; -import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version/0-43/0-43-add-tasks-assigned-to-me-view.command'; -import { MigrateIsSearchableForCustomObjectMetadataCommand } from 'src/database/commands/upgrade-version/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command'; -import { MigrateRichTextContentPatchCommand } from 'src/database/commands/upgrade-version/0-43/0-43-migrate-rich-text-content-patch.command'; -import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command'; -import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/database/commands/upgrade-version/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command'; -import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { SearchModule } from 'src/engine/metadata-modules/search/search.module'; -import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; -import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; -import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; -import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; - -@Module({ - imports: [ - MigrationCommandModule.register('0.43', { - imports: [ - TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'), - TypeOrmModule.forFeature( - [ObjectMetadataEntity, FieldMetadataEntity], - 'metadata', - ), - SearchModule, - WorkspaceMigrationRunnerModule, - WorkspaceMigrationModule, - WorkspaceMetadataVersionModule, - WorkspaceDataSourceModule, - ], - providers: [ - AddTasksAssignedToMeViewCommand, - MigrateSearchVectorOnNoteAndTaskEntitiesCommand, - MigrateIsSearchableForCustomObjectMetadataCommand, - UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand, - StandardizationOfActorCompositeContextTypeCommand, - MigrateRichTextContentPatchCommand, - ], - }), - ], -}) -export class UpgradeTo0_43CommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-migrate-relations-to-field-metadata.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-migrate-relations-to-field-metadata.command.ts deleted file mode 100644 index 74bb74658..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-migrate-relations-to-field-metadata.command.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { FieldMetadataType } from 'twenty-shared'; -import { In, Repository } from 'typeorm'; - -import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; - -import { isCommandLogger } from 'src/database/commands/logger'; -import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator'; -import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { - RelationDirection, - deduceRelationDirection, -} from 'src/engine/utils/deduce-relation-direction.util'; -import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util'; - -@MigrationCommand({ - name: 'migrate-relations-to-field-metadata', - description: 'Migrate relations to field metadata', - version: '0.44', -}) -export class MigrateRelationsToFieldMetadataCommand extends MaintainedWorkspacesMigrationCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository, twentyORMGlobalManager); - } - - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: MaintainedWorkspacesMigrationCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log('Running command to create many to one relations'); - - if (isCommandLogger(this.logger)) { - this.logger.setVerbose(options.verbose ?? false); - } - - try { - for (const [index, workspaceId] of workspaceIds.entries()) { - await this.processWorkspace(workspaceId, index, workspaceIds.length); - } - - this.logger.log(chalk.green('Command completed!')); - } catch (error) { - this.logger.log(chalk.red('Error in workspace')); - } - } - - private async processWorkspace( - workspaceId: string, - index: number, - total: number, - ): Promise { - try { - this.logger.log( - `Running command for workspace ${workspaceId} ${index + 1}/${total}`, - ); - - const fieldMetadataCollection = await this.fieldMetadataRepository.find({ - where: { - workspaceId, - type: In([FieldMetadataType.RELATION, FieldMetadataType.UUID]), - }, - relations: ['fromRelationMetadata', 'toRelationMetadata'], - }); - - if (!fieldMetadataCollection.length) { - this.logger.log( - chalk.yellow( - `No relation field metadata found for workspace ${workspaceId}.`, - ), - ); - - return; - } - - const joinColumnFieldMetadataCollection = fieldMetadataCollection.filter( - (fieldMetadata) => - isFieldMetadataOfType(fieldMetadata, FieldMetadataType.UUID), - // TODO: Fix this, it's working in other places but not here - ) as FieldMetadataEntity[]; - - const fieldMetadataToUpdateCollection = fieldMetadataCollection - .filter((fieldMetadata) => - isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION), - ) - .map((fieldMetadata) => - this.updateRelationFieldMetadata( - joinColumnFieldMetadataCollection, - // TODO: Fix this, it's working in other places but not here - fieldMetadata as FieldMetadataEntity, - ), - ); - - if (fieldMetadataToUpdateCollection.length > 0) { - await this.fieldMetadataRepository.save( - fieldMetadataToUpdateCollection, - ); - } - - this.logger.log( - chalk.green(`Command completed for workspace ${workspaceId}.`), - ); - } catch { - this.logger.log(chalk.red(`Error in workspace ${workspaceId}.`)); - } - } - - private updateRelationFieldMetadata( - joinColumnFieldMetadataCollection: FieldMetadataEntity[], - fieldMetadata: FieldMetadataEntity, - ): FieldMetadataEntity { - const relationMetadata = - fieldMetadata.fromRelationMetadata ?? fieldMetadata.toRelationMetadata; - const joinColumnFieldMetadata = joinColumnFieldMetadataCollection.find( - (joinColumnFieldMetadata) => - // We're deducing the field based on the name of the relation field - // This is not the best way to do this but we don't have a better way - joinColumnFieldMetadata.name === `${fieldMetadata.name}Id`, - ); - - const relationDirection = deduceRelationDirection( - fieldMetadata, - relationMetadata, - ); - let relationType = relationMetadata.relationType as unknown as RelationType; - - if ( - relationDirection === RelationDirection.TO && - relationType === RelationType.ONE_TO_MANY - ) { - relationType = RelationType.MANY_TO_ONE; - } - - const relationTargetFieldMetadataId = - relationDirection === RelationDirection.FROM - ? relationMetadata.toFieldMetadataId - : relationMetadata.fromFieldMetadataId; - - const relationTargetObjectMetadataId = - relationDirection === RelationDirection.FROM - ? relationMetadata.toObjectMetadataId - : relationMetadata.fromObjectMetadataId; - - return { - ...fieldMetadata, - settings: { - relationType, - onDelete: relationMetadata.onDeleteAction, - joinColumnName: joinColumnFieldMetadata?.name, - }, - relationTargetFieldMetadataId, - relationTargetObjectMetadataId, - }; - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-upgrade-version.module.ts deleted file mode 100644 index 225e0b822..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-44/0-44-upgrade-version.module.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { MigrationCommandModule } from 'src/database/commands/migration-command/migration-command.module'; -import { InitializePermissionsCommand } from 'src/database/commands/upgrade-version/0-44/0-44-initialize-permissions.command'; -import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version/0-44/0-44-migrate-relations-to-field-metadata.command'; -import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; -import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { RoleModule } from 'src/engine/metadata-modules/role/role.module'; -import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; -import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; -import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; -import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; - -@Module({ - imports: [ - MigrationCommandModule.register('0.44', { - imports: [ - TypeOrmModule.forFeature( - [Workspace, FeatureFlag, UserWorkspace], - 'core', - ), - TypeOrmModule.forFeature( - [ObjectMetadataEntity, FieldMetadataEntity], - 'metadata', - ), - WorkspaceMigrationRunnerModule, - WorkspaceMigrationModule, - WorkspaceMetadataVersionModule, - UserRoleModule, - RoleModule, - ], - providers: [ - MigrateRelationsToFieldMetadataCommand, - InitializePermissionsCommand, - ], - }), - ], -}) -export class UpgradeTo0_44CommandModule {} diff --git a/packages/twenty-server/src/engine/core-modules/billing/commands/billing-sync-customer-data.command.ts b/packages/twenty-server/src/engine/core-modules/billing/commands/billing-sync-customer-data.command.ts index b7271eff5..7aa08b306 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/commands/billing-sync-customer-data.command.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/commands/billing-sync-customer-data.command.ts @@ -7,22 +7,19 @@ import { Command } from 'nest-commander'; import { Repository } from 'typeorm'; import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity'; import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -interface SyncCustomerDataCommandOptions - extends MaintainedWorkspacesMigrationCommandOptions {} - @Command({ name: 'billing:sync-customer-data', description: 'Sync customer data from Stripe for all active workspaces', }) -export class BillingSyncCustomerDataCommand extends MaintainedWorkspacesMigrationCommandRunner { +export class BillingSyncCustomerDataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { constructor( @InjectRepository(Workspace, 'core') protected readonly workspaceRepository: Repository, @@ -34,39 +31,10 @@ export class BillingSyncCustomerDataCommand extends MaintainedWorkspacesMigratio super(workspaceRepository, twentyORMGlobalManager); } - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: SyncCustomerDataCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log('Running command to sync customer data'); - - for (const workspaceId of workspaceIds) { - this.logger.log(`Running command for workspace ${workspaceId}`); - - try { - await this.syncCustomerDataForWorkspace(workspaceId, options); - } catch (error) { - this.logger.log( - chalk.red( - `Running command on workspace ${workspaceId} failed with error: ${error}, ${error.stack}`, - ), - ); - continue; - } finally { - this.logger.log( - chalk.green(`Finished running command for workspace ${workspaceId}.`), - ); - } - } - - this.logger.log(chalk.green(`Command completed!`)); - } - - private async syncCustomerDataForWorkspace( - workspaceId: string, - options: SyncCustomerDataCommandOptions, - ): Promise { + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { const billingCustomer = await this.billingCustomerRepository.findOne({ where: { workspaceId, diff --git a/packages/twenty-server/src/engine/core-modules/billing/commands/billing-sync-plans-data.command.ts b/packages/twenty-server/src/engine/core-modules/billing/commands/billing-sync-plans-data.command.ts index c5de0ea44..19e807448 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/commands/billing-sync-plans-data.command.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/commands/billing-sync-plans-data.command.ts @@ -9,7 +9,7 @@ import { Repository } from 'typeorm'; import { MigrationCommandOptions, MigrationCommandRunner, -} from 'src/database/commands/migration-command/migration-command.runner'; +} from 'src/database/commands/command-runners/migration.command-runner'; import { BillingMeter } from 'src/engine/core-modules/billing/entities/billing-meter.entity'; import { BillingPrice } from 'src/engine/core-modules/billing/entities/billing-price.entity'; import { BillingProduct } from 'src/engine/core-modules/billing/entities/billing-product.entity'; diff --git a/packages/twenty-server/src/engine/core-modules/message-queue/jobs.module.ts b/packages/twenty-server/src/engine/core-modules/message-queue/jobs.module.ts index 5b071a0cd..41e569002 100644 --- a/packages/twenty-server/src/engine/core-modules/message-queue/jobs.module.ts +++ b/packages/twenty-server/src/engine/core-modules/message-queue/jobs.module.ts @@ -3,7 +3,6 @@ import { ModuleRef } from '@nestjs/core'; import { TypeOrmModule } from '@nestjs/typeorm'; import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module'; -import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; @@ -62,7 +61,6 @@ import { WorkflowModule } from 'src/modules/workflow/workflow.module'; providers: [ CleanSuspendedWorkspacesJob, EmailSenderJob, - DataSeedDemoWorkspaceJob, UpdateSubscriptionQuantityJob, HandleWorkspaceMemberDeletedJob, CleanWorkspaceDeletionWarningUserVarsJob, diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts index 89f70f2eb..33cccca37 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts @@ -4,7 +4,6 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { generateObjectMetadataMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-maps.util'; import { @@ -25,7 +24,6 @@ export class WorkspaceMetadataCacheService { private readonly objectMetadataRepository: Repository, ) {} - @LogExecutionTime() async recomputeMetadataCache({ workspaceId, ignoreLock = false, diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service.ts index c34f4577f..437031218 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service.ts @@ -4,13 +4,11 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { WorkspaceMetadataVersionException, WorkspaceMetadataVersionExceptionCode, } from 'src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; @Injectable() export class WorkspaceMetadataVersionService { @@ -20,10 +18,8 @@ export class WorkspaceMetadataVersionService { @InjectRepository(Workspace, 'core') private readonly workspaceRepository: Repository, private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} - @LogExecutionTime() async incrementMetadataVersion(workspaceId: string): Promise { const workspace = await this.workspaceRepository.findOne({ where: { id: workspaceId }, diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts index 3fdb6100f..611a6ad88 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts @@ -68,10 +68,6 @@ export class WorkspaceDatasourceFactory { const result = await this.cacheManager.execute( cacheKey, async () => { - this.logger.log( - `Creating workspace data source for workspace ${workspaceId} and metadata version ${cachedWorkspaceMetadataVersion}`, - ); - const dataSourceMetadata = await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId( workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.command.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.command.ts index aa8880336..d8c32fe22 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.command.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.command.ts @@ -7,7 +7,7 @@ import { In, Repository } from 'typeorm'; import { MigrationCommandOptions, MigrationCommandRunner, -} from 'src/database/commands/migration-command/migration-command.runner'; +} from 'src/database/commands/command-runners/migration.command-runner'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { CleanerWorkspaceService } from 'src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service'; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts index 2949b48f2..a2123e89a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts @@ -1,35 +1,28 @@ import { InjectRepository } from '@nestjs/typeorm'; -import { Command, Option } from 'nest-commander'; +import { Command } from 'nest-commander'; import { Repository } from 'typeorm'; import { - MaintainedWorkspacesMigrationCommandOptions, - MaintainedWorkspacesMigrationCommandRunner, -} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner'; + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceHealthService } from 'src/engine/workspace-manager/workspace-health/workspace-health.service'; import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service'; import { SyncWorkspaceLoggerService } from './services/sync-workspace-logger.service'; -interface RunWorkspaceMigrationsOptions - extends MaintainedWorkspacesMigrationCommandOptions { - force?: boolean; -} - @Command({ name: 'workspace:sync-metadata', description: 'Sync metadata', }) -export class SyncWorkspaceMetadataCommand extends MaintainedWorkspacesMigrationCommandRunner { +export class SyncWorkspaceMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { constructor( @InjectRepository(Workspace, 'core') protected readonly workspaceRepository: Repository, private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService, - private readonly workspaceHealthService: WorkspaceHealthService, private readonly dataSourceService: DataSourceService, private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService, protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, @@ -37,109 +30,40 @@ export class SyncWorkspaceMetadataCommand extends MaintainedWorkspacesMigrationC super(workspaceRepository, twentyORMGlobalManager); } - async runMigrationCommandOnMaintainedWorkspaces( - _passedParam: string[], - options: RunWorkspaceMigrationsOptions, - workspaceIds: string[], - ): Promise { - this.logger.log(`Attempting to sync ${workspaceIds.length} workspaces.`); + override async runOnWorkspace({ + workspaceId, + options, + index, + total, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Running workspace sync for workspace: ${workspaceId} (${index} out of ${total})`, + ); - let count = 1; - - const errorsDuringSync: string[] = []; - - for (const workspaceId of workspaceIds) { - this.logger.log( - `Running workspace sync for workspace: ${workspaceId} (${count} out of ${workspaceIds.length})`, + const dataSourceMetadata = + await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( + workspaceId, ); - count++; - if (!options.force) { - try { - const issues = - await this.workspaceHealthService.healthCheck(workspaceId); + const { storage, workspaceMigrations } = + await this.workspaceSyncMetadataService.synchronize( + { + workspaceId, + dataSourceId: dataSourceMetadata.id, + }, + { applyChanges: !options.dryRun }, + ); - // Security: abort if there are issues. - if (issues.length > 0) { - if (!options.force) { - this.logger.error( - `Workspace contains ${issues.length} issues, aborting.`, - ); - - this.logger.log( - 'If you want to force the migration, use --force flag', - ); - this.logger.log( - 'Please use `workspace:health` command to check issues and fix them before running this command.', - ); - - continue; - } - - this.logger.warn( - `Workspace contains ${issues.length} issues, sync has been forced.`, - ); - } - } catch (error) { - if (!options.force) { - throw error; - } - - this.logger.warn( - `Workspace health check failed with error, but sync has been forced.`, - error, - ); - } - } - - try { - const dataSourceMetadata = - await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( - workspaceId, - ); - - const { storage, workspaceMigrations } = - await this.workspaceSyncMetadataService.synchronize( - { - workspaceId, - dataSourceId: dataSourceMetadata.id, - }, - { applyChanges: !options.dryRun }, - ); - - if (options.dryRun) { - await this.syncWorkspaceLoggerService.saveLogs( - workspaceId, - storage, - workspaceMigrations, - ); - } - } catch (error) { - errorsDuringSync.push( - `Failed to synchronize workspace ${workspaceId}: ${error.message}`, - ); - - continue; - } + if (options.dryRun) { + await this.syncWorkspaceLoggerService.saveLogs( + workspaceId, + storage, + workspaceMigrations, + ); } this.logger.log( - `Finished synchronizing all active workspaces (${ - workspaceIds.length - } workspaces). ${ - errorsDuringSync.length > 0 - ? 'Errors during sync:\n' + errorsDuringSync.join('.\n') - : '' - }`, + `Finished synchronizing all active workspaces (${total} workspaces).`, ); } - - @Option({ - flags: '-f, --force', - description: 'Force migration', - required: false, - }) - force(): boolean { - return true; - } } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module.ts index 5eeac0c6a..c7946f630 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module.ts @@ -3,6 +3,8 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; @@ -10,6 +12,8 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { WorkspaceMigrationBuilderModule } from 'src/engine/workspace-manager/workspace-migration-builder/workspace-migration-builder.module'; import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; +import { SyncWorkspaceLoggerService } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/services/sync-workspace-logger.service'; +import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command'; import { workspaceSyncMetadataComparators } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators'; import { workspaceSyncMetadataFactories } from 'src/engine/workspace-manager/workspace-sync-metadata/factories'; import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service'; @@ -34,7 +38,8 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works ], 'metadata', ), - TypeOrmModule.forFeature([FeatureFlag], 'core'), + DataSourceModule, + TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'), WorkspaceMetadataVersionModule, ], providers: [ @@ -47,7 +52,13 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works WorkspaceSyncFieldMetadataService, WorkspaceSyncMetadataService, WorkspaceSyncIndexMetadataService, + SyncWorkspaceLoggerService, + SyncWorkspaceMetadataCommand, + ], + exports: [ + ...workspaceSyncMetadataFactories, + WorkspaceSyncMetadataService, + SyncWorkspaceMetadataCommand, ], - exports: [...workspaceSyncMetadataFactories, WorkspaceSyncMetadataService], }) export class WorkspaceSyncMetadataModule {} diff --git a/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx b/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx index a1c12c423..60af1d409 100644 --- a/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx +++ b/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx @@ -32,7 +32,7 @@ Upgrade your Twenty instance to use v0.43.0 image ``` yarn database:migrate:prod -yarn command:prod upgrade-0.43 +yarn command:prod upgrade ``` ### v0.41.0 to v0.42.0