From 42ddc09f746fa8704d58131534691c14370323d9 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Tue, 14 Jan 2025 18:23:42 +0100 Subject: [PATCH] Add command to tag workspace as suspended or as deleted (#9610) In this PR: - remove old versions upgrade commands - add a 0.40 upgrade command to loop over all INACTIVE workspaces and either: update to SUSPENDED (if workspaceSchema exists), update them to SUSPENDED + deletedAt (if workspaceSchema does not exist anymore) Note: why updating the deleted one to SUSPENDED? Because I plan to remove INACTIVE case in the enum in 0.41 Tests made on production like database: - dry-mode - singleWorkspaceId - 3 cases : suspended, deleted+suspended, deleted+suspended+delete all data --- .../commands/database-command.module.ts | 8 - .../src/database/commands/logger.ts | 8 +- .../0-32/0-32-backfill-view-groups.command.ts | 127 ------- ...bhook-operation-into-operations-command.ts | 85 ----- .../0-32-simplify-search-vector-expression.ts | 112 ------ .../0-32/0-32-upgrade-version.command.ts | 65 ---- .../0-32/0-32-upgrade-version.module.ts | 34 -- ...elete-view-fields-without-views.command.ts | 95 ------ ...0-33-enforce-unique-constraints.command.ts | 320 ------------------ ...el-identifier-to-custom-objects.command.ts | 108 ------ ...date-rich-text-search-vector-expression.ts | 107 ------ .../0-33/0-33-upgrade-version.command.ts | 75 ---- .../0-33/0-33-upgrade-version.module.ts | 35 -- .../0-34/0-34-generate-subdomain.command.ts | 143 -------- .../0-34/0-34-upgrade-version.command.ts | 38 --- .../0-34/0-34-upgrade-version.module.ts | 26 -- ...hone-calling-code-create-column.command.ts | 144 -------- ...phone-calling-code-migrate-data.command.ts | 306 ----------------- .../0-35-record-position-backfill.command.ts | 42 --- .../0-35/0-35-upgrade-version.command.ts | 74 ---- .../0-35/0-35-upgrade-version.module.ts | 46 --- ...35-view-group-no-value-backfill.command.ts | 88 ----- ...pdate-inactive-workspace-status.command.ts | 250 ++++++++++++++ .../0-40/0-40-upgrade-version.module.ts | 35 +- .../engine/core-modules/user/user.entity.ts | 3 +- .../workspace/workspace.entity.ts | 3 +- 26 files changed, 290 insertions(+), 2087 deletions(-) delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-backfill-view-groups.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-copy-webhook-operation-into-operations-command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-simplify-search-vector-expression.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-set-missing-label-identifier-to-custom-objects.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-generate-subdomain.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-upgrade-version.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-upgrade-version.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-create-column.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-migrate-data.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-record-position-backfill.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-upgrade-version.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-upgrade-version.module.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-view-group-no-value-backfill.command.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-40/0-40-update-inactive-workspace-status.command.ts 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 e3aee52be..8422f1e9c 100644 --- a/packages/twenty-server/src/database/commands/database-command.module.ts +++ b/packages/twenty-server/src/database/commands/database-command.module.ts @@ -7,10 +7,6 @@ import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-de 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_32CommandModule } from 'src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module'; -import { UpgradeTo0_33CommandModule } from 'src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module'; -import { UpgradeTo0_34CommandModule } from 'src/database/commands/upgrade-version/0-34/0-34-upgrade-version.module'; -import { UpgradeTo0_35CommandModule } from 'src/database/commands/upgrade-version/0-35/0-35-upgrade-version.module'; import { UpgradeTo0_40CommandModule } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; @@ -52,10 +48,6 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp DataSeedDemoWorkspaceModule, WorkspaceCacheStorageModule, WorkspaceMetadataVersionModule, - UpgradeTo0_32CommandModule, - UpgradeTo0_33CommandModule, - UpgradeTo0_34CommandModule, - UpgradeTo0_35CommandModule, UpgradeTo0_40CommandModule, FeatureFlagModule, ], diff --git a/packages/twenty-server/src/database/commands/logger.ts b/packages/twenty-server/src/database/commands/logger.ts index 722cdb224..bb912004f 100644 --- a/packages/twenty-server/src/database/commands/logger.ts +++ b/packages/twenty-server/src/database/commands/logger.ts @@ -28,12 +28,12 @@ export class CommandLogger { this.logger.error(message, stack, context); } - warn(message: string, context?: string) { - this.logger.warn(message, context); + warn(message: string, ...optionalParams: [...any, string?]) { + this.logger.warn(message, ...optionalParams); } - debug(message: string, context?: string) { - this.logger.debug(message, context); + debug(message: string, ...optionalParams: [...any, string?]) { + this.logger.debug(message, ...optionalParams); } verbose(message: string, ...optionalParams: [...any, string?]) { diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-backfill-view-groups.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-backfill-view-groups.command.ts deleted file mode 100644 index ba7c406a4..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-backfill-view-groups.command.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { In, Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -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 { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -@Command({ - name: 'upgrade-0.32:backfill-view-groups', - description: 'Backfill view groups', -}) -export class BackfillViewGroupsCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - _options: ActiveWorkspacesCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log('Running command to fix backfill view groups'); - - for (const workspaceId of workspaceIds) { - this.logger.log(`Running command for workspace ${workspaceId}`); - - try { - const viewRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'view', - ); - - const viewGroupRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewGroup', - ); - - const kanbanViews = await viewRepository.find({ - where: { - type: 'kanban', - }, - }); - - const kanbanFieldMetadataIds = kanbanViews.map( - (view) => view.kanbanFieldMetadataId, - ); - - const kanbanFieldMetadataItems = - await this.fieldMetadataRepository.find({ - where: { - id: In(kanbanFieldMetadataIds), - }, - }); - - for (const kanbanView of kanbanViews) { - const kanbanFieldMetadataItem = kanbanFieldMetadataItems.find( - (item) => item.id === kanbanView.kanbanFieldMetadataId, - ); - - if (!kanbanFieldMetadataItem) { - this.logger.log( - chalk.red( - `Kanban field metadata with id ${kanbanView.kanbanFieldMetadataId} not found`, - ), - ); - continue; - } - - for (const option of kanbanFieldMetadataItem.options) { - const viewGroup = await viewGroupRepository.findOne({ - where: { - fieldMetadataId: kanbanFieldMetadataItem.id, - fieldValue: option.value, - viewId: kanbanView.id, - }, - }); - - if (viewGroup) { - this.logger.log( - chalk.red(`View group with id ${option.value} already exists`), - ); - continue; - } - - await viewGroupRepository.save({ - fieldMetadataId: kanbanFieldMetadataItem.id, - fieldValue: option.value, - isVisible: true, - viewId: kanbanView.id, - position: option.position, - }); - } - } - } catch (error) { - this.logger.log( - chalk.red( - `Running command on workspace ${workspaceId} failed with error: ${error}`, - ), - ); - continue; - } finally { - this.logger.log( - chalk.green(`Finished running command for workspace ${workspaceId}.`), - ); - } - - this.logger.log(chalk.green(`Command completed!`)); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-copy-webhook-operation-into-operations-command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-copy-webhook-operation-into-operations-command.ts deleted file mode 100644 index 6d5ef731c..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-copy-webhook-operation-into-operations-command.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import chalk from 'chalk'; -import { Repository } from 'typeorm'; - -import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; -import { BaseCommandOptions } from 'src/database/commands/base.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; - -@Command({ - name: 'upgrade-0.32:copy-webhook-operation-into-operations', - description: - 'Read, transform and copy webhook from deprecated column operation into newly created column operations', -}) -export class CopyWebhookOperationIntoOperationsCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - passedParams: string[], - options: BaseCommandOptions, - activeWorkspaceIds: string[], - ): Promise { - this.logger.log('Running command to copy operation to operations'); - - for (const workspaceId of activeWorkspaceIds) { - try { - this.logger.log(`Running command for workspace ${workspaceId}`); - - const webhookRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'webhook', - ); - - const webhooks = await webhookRepository.find(); - - for (const webhook of webhooks) { - if ('operation' in webhook) { - let newOpe = webhook.operation; - - newOpe = newOpe.replace(/\bcreate\b(?=\.|$)/g, 'created'); - newOpe = newOpe.replace(/\bupdate\b(?=\.|$)/g, 'updated'); - newOpe = newOpe.replace(/\bdelete\b(?=\.|$)/g, 'deleted'); - newOpe = newOpe.replace(/\bdestroy\b(?=\.|$)/g, 'destroyed'); - - const [firstWebhookPart, lastWebhookPart] = newOpe.split('.'); - - if ( - ['created', 'updated', 'deleted', 'destroyed'].includes( - firstWebhookPart, - ) - ) { - newOpe = `${lastWebhookPart}.${firstWebhookPart}`; - } - - await webhookRepository.update(webhook.id, { - operation: newOpe, - operations: [newOpe], - }); - - this.logger.log( - chalk.yellow( - `Handled webhook operation updates for ${webhook.id}`, - ), - ); - } - } - } catch (e) { - this.logger.log( - chalk.red( - `Error when running command on workspace ${workspaceId}: ${e}`, - ), - ); - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-simplify-search-vector-expression.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-simplify-search-vector-expression.ts deleted file mode 100644 index 7383c5b7e..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-simplify-search-vector-expression.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { FieldMetadataType } from 'twenty-shared'; -import { Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { SearchService } from 'src/engine/metadata-modules/search/search.service'; -import { SEARCH_FIELDS_FOR_CUSTOM_OBJECT } from 'src/engine/twenty-orm/custom.workspace-entity'; -import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; -import { - COMPANY_STANDARD_FIELD_IDS, - CUSTOM_OBJECT_STANDARD_FIELD_IDS, - OPPORTUNITY_STANDARD_FIELD_IDS, - PERSON_STANDARD_FIELD_IDS, -} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util'; -import { SEARCH_FIELDS_FOR_COMPANY } from 'src/modules/company/standard-objects/company.workspace-entity'; -import { SEARCH_FIELDS_FOR_OPPORTUNITY } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity'; -import { SEARCH_FIELDS_FOR_PERSON } from 'src/modules/person/standard-objects/person.workspace-entity'; - -@Command({ - name: 'fix-0.32:simplify-search-vector-expression', - description: 'Replace searchVector with simpler expression', -}) -export class SimplifySearchVectorExpressionCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - private readonly searchService: SearchService, - private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - _options: ActiveWorkspacesCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log('Running command to fix migration'); - - for (const workspaceId of workspaceIds) { - this.logger.log(`Running command for workspace ${workspaceId}`); - - try { - const searchVectorFields = await this.fieldMetadataRepository.findBy({ - workspaceId: workspaceId, - type: FieldMetadataType.TS_VECTOR, - }); - - for (const searchVectorField of searchVectorFields) { - let fieldsUsedForSearch: FieldTypeAndNameMetadata[] = []; - - switch (searchVectorField.standardId) { - case CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector: { - fieldsUsedForSearch = SEARCH_FIELDS_FOR_CUSTOM_OBJECT; - break; - } - case PERSON_STANDARD_FIELD_IDS.searchVector: { - fieldsUsedForSearch = SEARCH_FIELDS_FOR_PERSON; - break; - } - case COMPANY_STANDARD_FIELD_IDS.searchVector: { - fieldsUsedForSearch = SEARCH_FIELDS_FOR_COMPANY; - break; - } - case OPPORTUNITY_STANDARD_FIELD_IDS.searchVector: { - fieldsUsedForSearch = SEARCH_FIELDS_FOR_OPPORTUNITY; - break; - } - } - - if (fieldsUsedForSearch.length === 0) { - continue; - } - - await this.searchService.updateSearchVector( - searchVectorField.objectMetadataId, - fieldsUsedForSearch, - workspaceId, - ); - - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( - workspaceId, - ); - } - } catch (error) { - this.logger.log( - chalk.red( - `Running command on workspace ${workspaceId} failed with error: ${error}`, - ), - ); - continue; - } finally { - this.logger.log( - chalk.green(`Finished running command for workspace ${workspaceId}.`), - ); - } - - this.logger.log(chalk.green(`Command completed!`)); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.command.ts deleted file mode 100644 index 72ba410be..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.command.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; -import { BackfillViewGroupsCommand } from 'src/database/commands/upgrade-version/0-32/0-32-backfill-view-groups.command'; -import { CopyWebhookOperationIntoOperationsCommand } from 'src/database/commands/upgrade-version/0-32/0-32-copy-webhook-operation-into-operations-command'; -import { SimplifySearchVectorExpressionCommand } from 'src/database/commands/upgrade-version/0-32/0-32-simplify-search-vector-expression'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command'; - -interface UpdateTo0_32CommandOptions { - workspaceId?: string; -} - -@Command({ - name: 'upgrade-0.32', - description: 'Upgrade to 0.32', -}) -export class UpgradeTo0_32Command extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand, - private readonly simplifySearchVectorExpressionCommand: SimplifySearchVectorExpressionCommand, - private readonly copyWebhookOperationIntoOperationsCommand: CopyWebhookOperationIntoOperationsCommand, - private readonly backfillViewGroupsCommand: BackfillViewGroupsCommand, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - passedParam: string[], - options: UpdateTo0_32CommandOptions, - workspaceIds: string[], - ): Promise { - await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand( - passedParam, - { - ...options, - force: true, - }, - workspaceIds, - ); - - await this.simplifySearchVectorExpressionCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - - await this.copyWebhookOperationIntoOperationsCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - - await this.backfillViewGroupsCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module.ts deleted file mode 100644 index 63f033a90..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { CopyWebhookOperationIntoOperationsCommand } from 'src/database/commands/upgrade-version/0-32/0-32-copy-webhook-operation-into-operations-command'; -import { SimplifySearchVectorExpressionCommand } from 'src/database/commands/upgrade-version/0-32/0-32-simplify-search-vector-expression'; -import { UpgradeTo0_32Command } from 'src/database/commands/upgrade-version/0-32/0-32-upgrade-version.command'; -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 { 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'; - -import { BackfillViewGroupsCommand } from './0-32-backfill-view-groups.command'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([Workspace], 'core'), - TypeOrmModule.forFeature( - [ObjectMetadataEntity, FieldMetadataEntity], - 'metadata', - ), - WorkspaceSyncMetadataCommandsModule, - SearchModule, - WorkspaceMigrationRunnerModule, - ], - providers: [ - UpgradeTo0_32Command, - BackfillViewGroupsCommand, - CopyWebhookOperationIntoOperationsCommand, - SimplifySearchVectorExpressionCommand, - ], -}) -export class UpgradeTo0_32CommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts deleted file mode 100644 index 790d829a9..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { IsNull, Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; - -interface DeleteViewFieldsWithoutViewsCommandOptions - extends ActiveWorkspacesCommandOptions {} - -@Command({ - name: 'upgrade-0.33:delete-view-fields-without-views', - description: 'Delete ViewFields that do not have a View', -}) -export class DeleteViewFieldsWithoutViewsCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - options: DeleteViewFieldsWithoutViewsCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log( - 'Running command to delete ViewFields that do not have a View', - ); - - for (const workspaceId of workspaceIds) { - this.logger.log(`Running command for workspace ${workspaceId}`); - - try { - await this.deleteViewFieldsWithoutViewsForWorkspace( - 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 deleteViewFieldsWithoutViewsForWorkspace( - workspaceId: string, - options: DeleteViewFieldsWithoutViewsCommandOptions, - ): Promise { - const viewFieldRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewField', - false, - ); - - const viewFieldsWithoutViews = await viewFieldRepository.find({ - where: { - viewId: IsNull(), - }, - }); - - const viewFieldIds = viewFieldsWithoutViews.map((vf) => vf.id); - - if (!options.dryRun && viewFieldIds.length > 0) { - await viewFieldRepository.delete(viewFieldIds); - } - - if (options.verbose) { - this.logger.log( - chalk.yellow( - `Deleted ${viewFieldsWithoutViews.length} ViewFields that do not have a View`, - ), - ); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts deleted file mode 100644 index a5a0acb85..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts +++ /dev/null @@ -1,320 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command, Option } from 'nest-commander'; -import { IsNull, Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; - -interface EnforceUniqueConstraintsCommandOptions - extends ActiveWorkspacesCommandOptions { - person?: boolean; - company?: boolean; - viewField?: boolean; - viewSort?: boolean; -} - -@Command({ - name: 'upgrade-0.33:enforce-unique-constraints', - description: - 'Enforce unique constraints on company domainName, person emailsPrimaryEmail, ViewField, and ViewSort', -}) -export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository); - } - - @Option({ - flags: '--person', - description: 'Enforce unique constraints on person emailsPrimaryEmail', - }) - parsePerson() { - return true; - } - - @Option({ - flags: '--company', - description: 'Enforce unique constraints on company domainName', - }) - parseCompany() { - return true; - } - - @Option({ - flags: '--view-field', - description: 'Enforce unique constraints on ViewField', - }) - parseViewField() { - return true; - } - - @Option({ - flags: '--view-sort', - description: 'Enforce unique constraints on ViewSort', - }) - parseViewSort() { - return true; - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - options: EnforceUniqueConstraintsCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log('Running command to enforce unique constraints'); - - for (const workspaceId of workspaceIds) { - this.logger.log(`Running command for workspace ${workspaceId}`); - - try { - await this.enforceUniqueConstraintsForWorkspace(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 enforceUniqueConstraintsForWorkspace( - workspaceId: string, - options: EnforceUniqueConstraintsCommandOptions, - ): Promise { - if (options.person) { - await this.enforceUniquePersonEmail(workspaceId, options); - } - if (options.company) { - await this.enforceUniqueCompanyDomainName(workspaceId, options); - } - if (options.viewField) { - await this.enforceUniqueViewField(workspaceId, options); - } - if (options.viewSort) { - await this.enforceUniqueViewSort(workspaceId, options); - } - } - - private async enforceUniqueCompanyDomainName( - workspaceId: string, - options: EnforceUniqueConstraintsCommandOptions, - ): Promise { - const companyRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'company', - false, - ); - - const duplicates = await companyRepository - .createQueryBuilder('company') - .select('company.domainNamePrimaryLinkUrl') - .addSelect('COUNT(*)', 'count') - .where('company.deletedAt IS NULL') - .where('company.domainNamePrimaryLinkUrl IS NOT NULL') - .where("company.domainNamePrimaryLinkUrl != ''") - .groupBy('company.domainNamePrimaryLinkUrl') - .having('COUNT(*) > 1') - .getRawMany(); - - for (const duplicate of duplicates) { - const { company_domainNamePrimaryLinkUrl } = duplicate; - const companies = await companyRepository.find({ - where: { - domainName: { - primaryLinkUrl: company_domainNamePrimaryLinkUrl, - }, - deletedAt: IsNull(), - }, - order: { createdAt: 'DESC' }, - }); - - for (let i = 1; i < companies.length; i++) { - const newdomainNamePrimaryLinkUrl = `${company_domainNamePrimaryLinkUrl}${i}`; - - if (!options.dryRun) { - await companyRepository.update(companies[i].id, { - domainNamePrimaryLinkUrl: newdomainNamePrimaryLinkUrl, - }); - } - if (options.verbose) { - this.logger.log( - chalk.yellow( - `Updated company ${companies[i].id} domainName from ${company_domainNamePrimaryLinkUrl} to ${newdomainNamePrimaryLinkUrl}`, - ), - ); - } - } - } - } - - private async enforceUniquePersonEmail( - workspaceId: string, - options: EnforceUniqueConstraintsCommandOptions, - ): Promise { - const personRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'person', - false, - ); - - const duplicates = await personRepository - .createQueryBuilder('person') - .select('person.emailsPrimaryEmail') - .addSelect('COUNT(*)', 'count') - .where('person.deletedAt IS NULL') - .where('person.emailsPrimaryEmail IS NOT NULL') - .where("person.emailsPrimaryEmail != ''") - .groupBy('person.emailsPrimaryEmail') - .having('COUNT(*) > 1') - .getRawMany(); - - for (const duplicate of duplicates) { - const { person_emailsPrimaryEmail } = duplicate; - const persons = await personRepository.find({ - where: { - emails: { - primaryEmail: person_emailsPrimaryEmail, - }, - deletedAt: IsNull(), - }, - order: { createdAt: 'DESC' }, - }); - - for (let i = 1; i < persons.length; i++) { - const newEmail = person_emailsPrimaryEmail?.includes('@') - ? `${person_emailsPrimaryEmail.split('@')[0]}+${i}@${person_emailsPrimaryEmail.split('@')[1]}` - : `${person_emailsPrimaryEmail}+${i}`; - - if (!options.dryRun) { - await personRepository.update(persons[i].id, { - emailsPrimaryEmail: newEmail, - }); - } - if (options.verbose) { - this.logger.log( - chalk.yellow( - `Updated person ${persons[i].id} emailsPrimaryEmail from ${person_emailsPrimaryEmail} to ${newEmail}`, - ), - ); - } - } - } - } - - private async enforceUniqueViewField( - workspaceId: string, - options: EnforceUniqueConstraintsCommandOptions, - ): Promise { - const viewFieldRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewField', - false, - ); - - const duplicates = await viewFieldRepository - .createQueryBuilder('viewField') - .select(['viewField.fieldMetadataId', 'viewField.viewId']) - .addSelect('COUNT(*)', 'count') - .where('viewField.deletedAt IS NULL') - .groupBy('viewField.fieldMetadataId, viewField.viewId') - .having('COUNT(*) > 1') - .getRawMany(); - - for (const duplicate of duplicates) { - const { - viewField_fieldMetadataId: fieldMetadataId, - viewField_viewId: viewId, - } = duplicate; - const viewFields = await viewFieldRepository.find({ - where: { - fieldMetadataId, - viewId, - deletedAt: IsNull(), - }, - order: { createdAt: 'DESC' }, - }); - - for (let i = 1; i < viewFields.length; i++) { - if (!options.dryRun) { - await viewFieldRepository.softDelete(viewFields[i].id); - } - if (options.verbose) { - this.logger.log( - chalk.yellow( - `Soft deleted duplicate ViewField ${viewFields[i].id} for fieldMetadataId ${fieldMetadataId} and viewId ${viewId}`, - ), - ); - } - } - } - } - - private async enforceUniqueViewSort( - workspaceId: string, - options: EnforceUniqueConstraintsCommandOptions, - ): Promise { - const viewSortRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewSort', - false, - ); - - const duplicates = await viewSortRepository - .createQueryBuilder('viewSort') - .select(['viewSort.fieldMetadataId', 'viewSort.viewId']) - .addSelect('COUNT(*)', 'count') - .where('viewSort.deletedAt IS NULL') - .groupBy('viewSort.fieldMetadataId, viewSort.viewId') - .having('COUNT(*) > 1') - .getRawMany(); - - for (const duplicate of duplicates) { - const { - viewSort_fieldMetadataId: fieldMetadataId, - viewSort_viewId: viewId, - } = duplicate; - const viewSorts = await viewSortRepository.find({ - where: { - fieldMetadataId, - viewId, - deletedAt: IsNull(), - }, - order: { createdAt: 'DESC' }, - }); - - for (let i = 1; i < viewSorts.length; i++) { - if (!options.dryRun) { - await viewSortRepository.softDelete(viewSorts[i].id); - } - if (options.verbose) { - this.logger.log( - chalk.yellow( - `Soft deleted duplicate ViewSort ${viewSorts[i].id} for fieldMetadataId ${fieldMetadataId} and viewId ${viewId}`, - ), - ); - } - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-set-missing-label-identifier-to-custom-objects.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-set-missing-label-identifier-to-custom-objects.command.ts deleted file mode 100644 index 6505c748a..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-set-missing-label-identifier-to-custom-objects.command.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { IsNull, Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; - -interface SetMissingLabelIdentifierToCustomObjectsCommandOptions - extends ActiveWorkspacesCommandOptions {} - -@Command({ - name: 'upgrade-0.33:set-missing-label-identifier-to-custom-objects', - description: 'Set missing labelIdentifier to custom objects', -}) -export class SetMissingLabelIdentifierToCustomObjectsCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - options: SetMissingLabelIdentifierToCustomObjectsCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log( - 'Running command to set missing labelIdentifier to custom objects', - ); - - for (const workspaceId of workspaceIds) { - this.logger.log(`Running command for workspace ${workspaceId}`); - - try { - await this.setMissingLabelIdentifierToCustomObjectsForWorkspace( - 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 setMissingLabelIdentifierToCustomObjectsForWorkspace( - workspaceId: string, - options: SetMissingLabelIdentifierToCustomObjectsCommandOptions, - ): Promise { - const customObjects = await this.objectMetadataRepository.find({ - where: { - workspaceId, - labelIdentifierFieldMetadataId: IsNull(), - isCustom: true, - }, - }); - - for (const customObject of customObjects) { - const labelIdentifierFieldMetadata = - await this.fieldMetadataRepository.findOne({ - where: { - workspaceId, - objectMetadataId: customObject.id, - standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name, - }, - }); - - if (labelIdentifierFieldMetadata && !options.dryRun) { - await this.objectMetadataRepository.update(customObject.id, { - labelIdentifierFieldMetadataId: labelIdentifierFieldMetadata.id, - }); - } - - if (options.verbose) { - this.logger.log( - chalk.yellow( - `Set labelIdentifierFieldMetadataId for custom object ${customObject.nameSingular}`, - ), - ); - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression.ts deleted file mode 100644 index 1bcd25fe7..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { FieldMetadataType } from 'twenty-shared'; -import { Repository } from 'typeorm'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { SearchService } from 'src/engine/metadata-modules/search/search.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'; -import { FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util'; -import { SEARCH_FIELDS_FOR_NOTES } from 'src/modules/note/standard-objects/note.workspace-entity'; -import { SEARCH_FIELDS_FOR_TASK } from 'src/modules/task/standard-objects/task.workspace-entity'; - -@Command({ - name: 'fix-0.33:update-rich-text-expression', - description: 'Update rich text (note and task) search vector expressions', -}) -export class UpdateRichTextSearchVectorCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - private readonly searchService: SearchService, - private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - _options: ActiveWorkspacesCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log('Running command to fix migration'); - - for (const workspaceId of workspaceIds) { - this.logger.log(`Running command for workspace ${workspaceId}`); - - try { - const searchVectorFields = await this.fieldMetadataRepository.findBy({ - workspaceId: workspaceId, - type: FieldMetadataType.TS_VECTOR, - }); - - for (const searchVectorField of searchVectorFields) { - let fieldsUsedForSearch: FieldTypeAndNameMetadata[] = []; - let objectNameForLog = ''; - - switch (searchVectorField.standardId) { - case NOTE_STANDARD_FIELD_IDS.searchVector: { - fieldsUsedForSearch = SEARCH_FIELDS_FOR_NOTES; - objectNameForLog = 'Note'; - break; - } - case TASK_STANDARD_FIELD_IDS.searchVector: { - fieldsUsedForSearch = SEARCH_FIELDS_FOR_TASK; - objectNameForLog = 'Task'; - break; - } - } - - if (fieldsUsedForSearch.length === 0) { - continue; - } - - this.logger.log( - `Updating searchVector for ${searchVectorField.objectMetadataId} (${objectNameForLog})...`, - ); - - await this.searchService.updateSearchVector( - searchVectorField.objectMetadataId, - fieldsUsedForSearch, - workspaceId, - ); - - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( - workspaceId, - ); - } - } catch (error) { - this.logger.log( - chalk.red( - `Running command on workspace ${workspaceId} failed with error: ${error}`, - ), - ); - continue; - } finally { - this.logger.log( - chalk.green(`Finished running command for workspace ${workspaceId}.`), - ); - } - - this.logger.log(chalk.green(`Command completed!`)); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts deleted file mode 100644 index 5fbad5ca8..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; -import { DeleteViewFieldsWithoutViewsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command'; -import { EnforceUniqueConstraintsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command'; -import { SetMissingLabelIdentifierToCustomObjectsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-set-missing-label-identifier-to-custom-objects.command'; -import { UpdateRichTextSearchVectorCommand } from 'src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command'; - -interface UpdateTo0_33CommandOptions { - workspaceId?: string; -} - -@Command({ - name: 'upgrade-0.33', - description: 'Upgrade to 0.33', -}) -export class UpgradeTo0_33Command extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - private readonly updateRichTextSearchVectorCommand: UpdateRichTextSearchVectorCommand, - private readonly enforceUniqueConstraintsCommand: EnforceUniqueConstraintsCommand, - private readonly deleteViewFieldsWithoutViewsCommand: DeleteViewFieldsWithoutViewsCommand, - private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand, - private readonly setMissingLabelIdentifierToCustomObjectsCommand: SetMissingLabelIdentifierToCustomObjectsCommand, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - passedParam: string[], - options: UpdateTo0_33CommandOptions, - workspaceIds: string[], - ): Promise { - await this.deleteViewFieldsWithoutViewsCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - await this.enforceUniqueConstraintsCommand.executeActiveWorkspacesCommand( - passedParam, - { - ...options, - company: true, - person: true, - viewField: true, - viewSort: true, - }, - workspaceIds, - ); - await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand( - passedParam, - { - ...options, - force: true, - }, - workspaceIds, - ); - await this.updateRichTextSearchVectorCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - await this.setMissingLabelIdentifierToCustomObjectsCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts deleted file mode 100644 index 9aa09eb69..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { DeleteViewFieldsWithoutViewsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command'; -import { EnforceUniqueConstraintsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command'; -import { SetMissingLabelIdentifierToCustomObjectsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-set-missing-label-identifier-to-custom-objects.command'; -import { UpdateRichTextSearchVectorCommand } from 'src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression'; -import { UpgradeTo0_33Command } from 'src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command'; -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 { 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: [ - TypeOrmModule.forFeature([Workspace], 'core'), - TypeOrmModule.forFeature( - [ObjectMetadataEntity, FieldMetadataEntity], - 'metadata', - ), - WorkspaceSyncMetadataCommandsModule, - SearchModule, - WorkspaceMigrationRunnerModule, - ], - providers: [ - UpgradeTo0_33Command, - UpdateRichTextSearchVectorCommand, - EnforceUniqueConstraintsCommand, - DeleteViewFieldsWithoutViewsCommand, - SetMissingLabelIdentifierToCustomObjectsCommand, - ], -}) -export class UpgradeTo0_33CommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-generate-subdomain.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-generate-subdomain.command.ts deleted file mode 100644 index 4656de25b..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-generate-subdomain.command.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { In, Repository } from 'typeorm'; - -import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; -import { BaseCommandOptions } from 'src/database/commands/base.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; - -// For DX only -type WorkspaceId = string; - -type Subdomain = string; - -@Command({ - name: 'feat-0.34:add-subdomain-to-workspace', - description: 'Add a default subdomain to each workspace', -}) -export class GenerateDefaultSubdomainCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - ) { - super(workspaceRepository); - } - - private generatePayloadForQuery({ - id, - subdomain, - domainName, - displayName, - }: Workspace) { - const result = { id, subdomain }; - - if (domainName) { - const subdomain = domainName.split('.')[0]; - - if (subdomain.length > 0) { - result.subdomain = subdomain; - } - } - - if (!domainName && displayName) { - result.subdomain = this.sanitizeForSubdomain(displayName); - } - - return result; - } - - private sanitizeForSubdomain(input: string) { - const normalized = input.normalize('NFKD').replace(/[\u0300-\u036f]/g, ''); - - const hyphenated = normalized - .replace(/[^a-zA-Z0-9]/g, '-') - .replace(/-+/g, '-') - .replace(/^-+|-+$/g, '') - .toLowerCase(); - - // DNS subdomain limit is 63, but we want to be conservative for duplicates - return hyphenated.substring(0, 60); - } - - private groupBySubdomainName( - acc: Record>, - workspace: Workspace, - ) { - const payload = this.generatePayloadForQuery(workspace); - - acc[payload.subdomain] = acc[payload.subdomain] - ? acc[payload.subdomain].concat([payload.id]) - : [payload.id]; - - return acc; - } - - private async deduplicateAndSave( - subdomain: Subdomain, - workspaceIds: Array, - existingSubdomains: Set, - options: BaseCommandOptions, - ) { - for (const [index, workspaceId] of workspaceIds.entries()) { - const subdomainDeduplicated = - index === 0 - ? existingSubdomains.has(subdomain) - ? `${subdomain}-${index}` - : subdomain - : `${subdomain}-${index}`; - - this.logger.log( - `Updating workspace ${workspaceId} with subdomain ${subdomainDeduplicated}`, - ); - - existingSubdomains.add(subdomainDeduplicated); - - if (!options.dryRun) { - await this.workspaceRepository.update(workspaceId, { - subdomain: subdomainDeduplicated, - }); - } - } - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - options: BaseCommandOptions, - activeWorkspaceIds: string[], - ): Promise { - const workspaces = await this.workspaceRepository.find( - activeWorkspaceIds.length > 0 - ? { - where: { - id: In(activeWorkspaceIds), - }, - } - : undefined, - ); - - if (workspaces.length === 0) { - this.logger.log('No workspaces found'); - - return; - } - - const workspaceBySubdomain = Object.entries( - workspaces.reduce( - (acc, workspace) => this.groupBySubdomainName(acc, workspace), - {} as ReturnType, - ), - ); - - const existingSubdomains: Set = new Set(); - - for (const [subdomain, workspaceIds] of workspaceBySubdomain) { - await this.deduplicateAndSave( - subdomain, - workspaceIds, - existingSubdomains, - options, - ); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-upgrade-version.command.ts deleted file mode 100644 index 4817eb5d1..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-upgrade-version.command.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { GenerateDefaultSubdomainCommand } from 'src/database/commands/upgrade-version/0-34/0-34-generate-subdomain.command'; - -interface UpdateTo0_34CommandOptions { - workspaceId?: string; -} - -@Command({ - name: 'upgrade-0.34', - description: 'Upgrade to 0.34', -}) -export class UpgradeTo0_34Command extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - private readonly generateDefaultSubdomainCommand: GenerateDefaultSubdomainCommand, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - passedParam: string[], - options: UpdateTo0_34CommandOptions, - workspaceIds: string[], - ): Promise { - await this.generateDefaultSubdomainCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-upgrade-version.module.ts deleted file mode 100644 index 2e532cc64..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-34/0-34-upgrade-version.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { GenerateDefaultSubdomainCommand } from 'src/database/commands/upgrade-version/0-34/0-34-generate-subdomain.command'; -import { UpgradeTo0_34Command } from 'src/database/commands/upgrade-version/0-34/0-34-upgrade-version.command'; -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 { 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: [ - TypeOrmModule.forFeature([Workspace], 'core'), - TypeOrmModule.forFeature( - [ObjectMetadataEntity, FieldMetadataEntity], - 'metadata', - ), - WorkspaceSyncMetadataCommandsModule, - SearchModule, - WorkspaceMigrationRunnerModule, - ], - providers: [UpgradeTo0_34Command, GenerateDefaultSubdomainCommand], -}) -export class UpgradeTo0_34CommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-create-column.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-create-column.command.ts deleted file mode 100644 index 433edddfb..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-create-column.command.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command } from 'nest-commander'; -import { FieldMetadataType } from 'twenty-shared'; -import { Repository } from 'typeorm'; -import { v4 } from 'uuid'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { isCommandLogger } from 'src/database/commands/logger'; -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 { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; -import { - WorkspaceMigrationColumnActionType, - WorkspaceMigrationTableAction, - WorkspaceMigrationTableActionType, -} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; -import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory'; -import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; -import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; -import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; -import { isDefined } from 'src/utils/is-defined'; - -@Command({ - name: 'upgrade-0.35:phone-calling-code-create-column', - description: 'Create the callingCode column', -}) -export class PhoneCallingCodeCreateColumnCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - private readonly workspaceMigrationService: WorkspaceMigrationService, - private readonly workspaceMigrationFactory: WorkspaceMigrationFactory, - private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, - private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - options: ActiveWorkspacesCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log( - 'Running command to add calling code and change country code with default one', - ); - if (isCommandLogger(this.logger)) { - this.logger.setVerbose(options.verbose ?? false); - } - - this.logger.verbose(`Part 1 - Workspace`); - let workspaceIterator = 1; - - for (const workspaceId of workspaceIds) { - this.logger.verbose( - `Running command for workspace ${workspaceId} ${workspaceIterator}/${workspaceIds.length}`, - ); - - this.logger.verbose( - `P1 Step 1 - let's find all the fieldsMetadata that have the PHONES type, and extract the objectMetadataId`, - ); - - try { - const phonesFieldMetadata = await this.fieldMetadataRepository.find({ - where: { - workspaceId, - type: FieldMetadataType.PHONES, - }, - relations: ['object'], - }); - - for (const phoneFieldMetadata of phonesFieldMetadata) { - if ( - isDefined(phoneFieldMetadata?.name) && - isDefined(phoneFieldMetadata.object) - ) { - this.logger.verbose( - `P1 Step 1 - Let's find the "nameSingular" of this objectMetadata: ${phoneFieldMetadata.object.nameSingular || 'not found'}`, - ); - - if (!phoneFieldMetadata.object?.nameSingular) continue; - - this.logger.verbose( - `P1 Step 1 - Create migration for field ${phoneFieldMetadata.name}`, - ); - - if (options.dryRun) { - continue; - } - await this.workspaceMigrationService.createCustomMigration( - generateMigrationName( - `create-${phoneFieldMetadata.object.nameSingular}PrimaryPhoneCallingCode-for-field-${phoneFieldMetadata.name}`, - ), - workspaceId, - [ - { - name: computeObjectTargetTable(phoneFieldMetadata.object), - action: WorkspaceMigrationTableActionType.ALTER, - columns: this.workspaceMigrationFactory.createColumnActions( - WorkspaceMigrationColumnActionType.CREATE, - { - id: v4(), - type: FieldMetadataType.TEXT, - name: `${phoneFieldMetadata.name}PrimaryPhoneCallingCode`, - label: `${phoneFieldMetadata.name}PrimaryPhoneCallingCode`, - objectMetadataId: phoneFieldMetadata.object.id, - workspaceId: workspaceId, - isNullable: true, - defaultValue: "''", - } satisfies Partial, - ), - } satisfies WorkspaceMigrationTableAction, - ], - ); - } - } - - this.logger.verbose( - `P1 Step 1 - RUN migration to create callingCodes for ${workspaceId.slice(0, 5)}`, - ); - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( - workspaceId, - ); - - await this.workspaceMetadataVersionService.incrementMetadataVersion( - workspaceId, - ); - } catch (error) { - this.logger.log(`Error in workspace ${workspaceId} : ${error}`); - } - workspaceIterator++; - } - - this.logger.log(chalk.green(`Command completed!`)); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-migrate-data.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-migrate-data.command.ts deleted file mode 100644 index b25756565..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-migrate-data.command.ts +++ /dev/null @@ -1,306 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { getCountries, getCountryCallingCode } from 'libphonenumber-js'; -import { Command } from 'nest-commander'; -import { FieldMetadataType } from 'twenty-shared'; -import { Repository } from 'typeorm'; -import { v4 } from 'uuid'; - -import { - ActiveWorkspacesCommandOptions, - ActiveWorkspacesCommandRunner, -} from 'src/database/commands/active-workspaces.command'; -import { isCommandLogger } from 'src/database/commands/logger'; -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, - WorkspaceMigrationTableAction, - WorkspaceMigrationTableActionType, -} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; -import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory'; -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 { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; -import { isDefined } from 'src/utils/is-defined'; - -const callingCodeToCountryCode = (callingCode: string): string => { - if (!callingCode) { - return ''; - } - let callingCodeSanitized = callingCode; - - if (callingCode.startsWith('+')) { - callingCodeSanitized = callingCode.slice(1); - } - - return ( - getCountries().find( - (countryCode) => - getCountryCallingCode(countryCode) === callingCodeSanitized, - ) || '' - ); -}; - -const isCallingCode = (callingCode: string): boolean => { - return callingCodeToCountryCode(callingCode) !== ''; -}; - -@Command({ - name: 'upgrade-0.35:phone-calling-code-migrate-data', - description: 'Add calling code and change country code with default one', -}) -export class PhoneCallingCodeMigrateDataCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceMigrationService: WorkspaceMigrationService, - private readonly workspaceMigrationFactory: WorkspaceMigrationFactory, - private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, - private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - options: ActiveWorkspacesCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log( - 'Running command to add calling code and change country code with default one', - ); - - if (isCommandLogger(this.logger)) { - this.logger.setVerbose(options.verbose ?? false); - } - this.logger.verbose(`Part 1 - Workspace`); - - let workspaceIterator = 1; - - for (const workspaceId of workspaceIds) { - this.logger.log( - `Running command for workspace ${workspaceId} ${workspaceIterator}/${workspaceIds.length}`, - ); - - this.logger.verbose( - `P1 Step 1 - let's find all the fieldsMetadata that have the PHONES type, and extract the objectMetadataId`, - ); - - try { - const phonesFieldMetadata = await this.fieldMetadataRepository.find({ - where: { - workspaceId, - type: FieldMetadataType.PHONES, - }, - relations: ['object'], - }); - - for (const phoneFieldMetadata of phonesFieldMetadata) { - if ( - isDefined(phoneFieldMetadata?.name) && - isDefined(phoneFieldMetadata.object) - ) { - this.logger.verbose( - `P1 Step 1 - Let's find the "nameSingular" of this objectMetadata: ${phoneFieldMetadata.object.nameSingular || 'not found'}`, - ); - - if (!phoneFieldMetadata.object?.nameSingular) continue; - - this.logger.verbose( - `P1 Step 1 - Create migration for field ${phoneFieldMetadata.name}`, - ); - if (!options.dryRun) { - await this.workspaceMigrationService.createCustomMigration( - generateMigrationName( - `create-${phoneFieldMetadata.object.nameSingular}PrimaryPhoneCallingCode-for-field-${phoneFieldMetadata.name}`, - ), - workspaceId, - [ - { - name: computeObjectTargetTable(phoneFieldMetadata.object), - action: WorkspaceMigrationTableActionType.ALTER, - columns: this.workspaceMigrationFactory.createColumnActions( - WorkspaceMigrationColumnActionType.CREATE, - { - id: v4(), - type: FieldMetadataType.TEXT, - name: `${phoneFieldMetadata.name}PrimaryPhoneCallingCode`, - label: `${phoneFieldMetadata.name}PrimaryPhoneCallingCode`, - objectMetadataId: phoneFieldMetadata.object.id, - workspaceId: workspaceId, - isNullable: true, - defaultValue: "''", - }, - ), - } satisfies WorkspaceMigrationTableAction, - ], - ); - } - } - } - - this.logger.verbose( - `P1 Step 1 - RUN migration to create callingCodes for ${workspaceId.slice(0, 5)}`, - ); - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( - workspaceId, - ); - - await this.workspaceMetadataVersionService.incrementMetadataVersion( - workspaceId, - ); - - this.logger.verbose( - `P1 Step 2 - Migrations for callingCode must be first. Now can use twentyORMGlobalManager to update countryCode`, - ); - - this.logger.verbose( - `P1 Step 3 (same time) - update CountryCode to letters: +33 => FR || +1 => US (if mulitple, first one)`, - ); - - this.logger.verbose( - `P1 Step 4 (same time) - update all additioanl phones to add a country code following the same logic`, - ); - - for (const phoneFieldMetadata of phonesFieldMetadata) { - this.logger.verbose(`P1 Step 2 - for ${phoneFieldMetadata.name}`); - if ( - isDefined(phoneFieldMetadata) && - isDefined(phoneFieldMetadata.name) - ) { - const [objectMetadata] = await this.objectMetadataRepository.find({ - where: { - id: phoneFieldMetadata?.objectMetadataId, - }, - }); - - const repository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - objectMetadata.nameSingular, - ); - const records = await repository.find(); - - for (const record of records) { - if ( - record?.[phoneFieldMetadata.name]?.primaryPhoneCountryCode && - isCallingCode( - record[phoneFieldMetadata.name].primaryPhoneCountryCode, - ) - ) { - let additionalPhones = null; - - if (record[phoneFieldMetadata.name].additionalPhones) { - additionalPhones = record[ - phoneFieldMetadata.name - ].additionalPhones.map((phone) => { - return { - ...phone, - countryCode: callingCodeToCountryCode(phone.callingCode), - }; - }); - } - if (!options.dryRun) { - await repository.update(record.id, { - [`${phoneFieldMetadata.name}PrimaryPhoneCallingCode`]: - record[phoneFieldMetadata.name].primaryPhoneCountryCode, - [`${phoneFieldMetadata.name}PrimaryPhoneCountryCode`]: - callingCodeToCountryCode( - record[phoneFieldMetadata.name].primaryPhoneCountryCode, - ), - [`${phoneFieldMetadata.name}AdditionalPhones`]: - additionalPhones, - }); - } - } - } - } - } - } catch (error) { - this.logger.log(`Error in workspace ${workspaceId} : ${error}`); - } - workspaceIterator++; - } - - this.logger.verbose(` - - Part 2 - FieldMetadata`); - - workspaceIterator = 1; - for (const workspaceId of workspaceIds) { - this.logger.log( - `Running command for workspace ${workspaceId} ${workspaceIterator}/${workspaceIds.length}`, - ); - - this.logger.verbose( - `P2 Step 1 - let's find all the fieldsMetadata that have the PHONES type, and extract the objectMetadataId`, - ); - - try { - const phonesFieldMetadata = await this.fieldMetadataRepository.find({ - where: { - workspaceId, - type: FieldMetadataType.PHONES, - }, - }); - - for (const phoneFieldMetadata of phonesFieldMetadata) { - if ( - !isDefined(phoneFieldMetadata) || - !isDefined(phoneFieldMetadata.defaultValue) - ) - continue; - let defaultValue = phoneFieldMetadata.defaultValue; - - // some cases look like it's an array. let's flatten it (not sure the case is supposed to happen but I saw it in my local db) - if (Array.isArray(defaultValue) && isDefined(defaultValue[0])) - defaultValue = phoneFieldMetadata.defaultValue[0]; - - if (!isDefined(defaultValue)) continue; - if (typeof defaultValue !== 'object') continue; - if (!('primaryPhoneCountryCode' in defaultValue)) continue; - if (!defaultValue.primaryPhoneCountryCode) continue; - - const primaryPhoneCountryCode = defaultValue.primaryPhoneCountryCode; - - const countryCode = callingCodeToCountryCode( - primaryPhoneCountryCode.replace(/["']/g, ''), - ); - - if (!options.dryRun) { - if (!defaultValue.primaryPhoneCallingCode) { - await this.fieldMetadataRepository.update(phoneFieldMetadata.id, { - defaultValue: { - ...defaultValue, - primaryPhoneCountryCode: countryCode - ? `'${countryCode}'` - : "''", - primaryPhoneCallingCode: isCallingCode( - primaryPhoneCountryCode.replace(/["']/g, ''), - ) - ? primaryPhoneCountryCode - : "''", - }, - }); - } - } - } - } catch (error) { - this.logger.log(`Error in workspace ${workspaceId} : ${error}`); - } - workspaceIterator++; - } - this.logger.log(chalk.green(`Command completed!`)); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-record-position-backfill.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-record-position-backfill.command.ts deleted file mode 100644 index 7a342b920..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-record-position-backfill.command.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; -import { BaseCommandOptions } from 'src/database/commands/base.command'; -import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; - -@Command({ - name: 'upgrade-0.35:record-position-backfill', - description: 'Backfill record position', -}) -export class RecordPositionBackfillCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - private readonly recordPositionBackfillService: RecordPositionBackfillService, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - options: BaseCommandOptions, - workspaceIds: string[], - ): Promise { - for (const workspaceId of workspaceIds) { - try { - await this.recordPositionBackfillService.backfill( - workspaceId, - options.dryRun ?? false, - ); - } catch (error) { - this.logger.error( - `Error backfilling record position for workspace ${workspaceId}: ${error}`, - ); - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-upgrade-version.command.ts deleted file mode 100644 index a7ee898f0..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-upgrade-version.command.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; -import { BaseCommandOptions } from 'src/database/commands/base.command'; -import { PhoneCallingCodeCreateColumnCommand } from 'src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-create-column.command'; -import { PhoneCallingCodeMigrateDataCommand } from 'src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-migrate-data.command'; -import { RecordPositionBackfillCommand } from 'src/database/commands/upgrade-version/0-35/0-35-record-position-backfill.command'; -import { ViewGroupNoValueBackfillCommand } from 'src/database/commands/upgrade-version/0-35/0-35-view-group-no-value-backfill.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command'; - -@Command({ - name: 'upgrade-0.35', - description: 'Upgrade to 0.35', -}) -export class UpgradeTo0_35Command extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - private readonly viewGroupNoValueBackfillCommand: ViewGroupNoValueBackfillCommand, - private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand, - private readonly phoneCallingCodeMigrateDataCommand: PhoneCallingCodeMigrateDataCommand, - private readonly phoneCallingCodeCreateColumnCommand: PhoneCallingCodeCreateColumnCommand, - private readonly recordPositionBackfillCommand: RecordPositionBackfillCommand, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - passedParam: string[], - options: BaseCommandOptions, - workspaceIds: string[], - ): Promise { - this.logger.log( - 'Running command to upgrade to 0.35: must start with phone calling code otherwise SyncMetadata will fail', - ); - - await this.recordPositionBackfillCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - - await this.phoneCallingCodeCreateColumnCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - - await this.phoneCallingCodeMigrateDataCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - - await this.viewGroupNoValueBackfillCommand.executeActiveWorkspacesCommand( - passedParam, - options, - workspaceIds, - ); - - await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand( - passedParam, - { - ...options, - force: true, - }, - workspaceIds, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-upgrade-version.module.ts deleted file mode 100644 index aba95d1bb..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-upgrade-version.module.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { PhoneCallingCodeCreateColumnCommand } from 'src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-create-column.command'; -import { PhoneCallingCodeMigrateDataCommand } from 'src/database/commands/upgrade-version/0-35/0-35-phone-calling-code-migrate-data.command'; -import { RecordPositionBackfillCommand } from 'src/database/commands/upgrade-version/0-35/0-35-record-position-backfill.command'; -import { UpgradeTo0_35Command } from 'src/database/commands/upgrade-version/0-35/0-35-upgrade-version.command'; -import { ViewGroupNoValueBackfillCommand } from 'src/database/commands/upgrade-version/0-35/0-35-view-group-no-value-backfill.command'; -import { RecordPositionBackfillModule } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-module'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -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 { SearchModule } from 'src/engine/metadata-modules/search/search.module'; -import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; -import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory'; -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'; -import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([Workspace], 'core'), - TypeOrmModule.forFeature( - [ObjectMetadataEntity, FieldMetadataEntity], - 'metadata', - ), - TypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'), - WorkspaceSyncMetadataCommandsModule, - SearchModule, - WorkspaceMigrationRunnerModule, - WorkspaceMetadataVersionModule, - WorkspaceMigrationModule, - RecordPositionBackfillModule, - FieldMetadataModule, - ], - providers: [ - UpgradeTo0_35Command, - PhoneCallingCodeCreateColumnCommand, - PhoneCallingCodeMigrateDataCommand, - WorkspaceMigrationFactory, - RecordPositionBackfillCommand, - ViewGroupNoValueBackfillCommand, - ], -}) -export class UpgradeTo0_35CommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-view-group-no-value-backfill.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-view-group-no-value-backfill.command.ts deleted file mode 100644 index fabdf1154..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-35/0-35-view-group-no-value-backfill.command.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Command } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; -import { BaseCommandOptions } from 'src/database/commands/base.command'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { FieldMetadataRelatedRecordsService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity'; -import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; - -@Command({ - name: 'migrate-0.35:backfill-view-group-no-value', - description: 'Backfill view group no value', -}) -export class ViewGroupNoValueBackfillCommand extends ActiveWorkspacesCommandRunner { - constructor( - @InjectRepository(Workspace, 'core') - protected readonly workspaceRepository: Repository, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly fieldMetadataRelatedRecordsService: FieldMetadataRelatedRecordsService, - ) { - super(workspaceRepository); - } - - async executeActiveWorkspacesCommand( - _passedParam: string[], - _options: BaseCommandOptions, - workspaceIds: string[], - ): Promise { - for (const workspaceId of workspaceIds) { - try { - const viewRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'view', - ); - - const viewGroupRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'viewGroup', - ); - - const views = await viewRepository.find({ - relations: ['viewGroups'], - }); - - for (const view of views) { - if (view.viewGroups.length === 0) { - continue; - } - - // We're assuming for now that all viewGroups belonging to the same view have the same fieldMetadataId - const viewGroup = view.viewGroups?.[0]; - const fieldMetadataId = viewGroup?.fieldMetadataId; - - if (!fieldMetadataId || !viewGroup) { - continue; - } - - const fieldMetadata = await this.fieldMetadataRepository.findOne({ - where: { id: viewGroup.fieldMetadataId }, - }); - - if (!fieldMetadata) { - continue; - } - - await this.fieldMetadataRelatedRecordsService.syncNoValueViewGroup( - fieldMetadata, - view, - viewGroupRepository, - ); - } - } catch (error) { - this.logger.error( - `Error backfilling view group no value for workspace ${workspaceId}: ${error}`, - ); - } - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-40/0-40-update-inactive-workspace-status.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-40/0-40-update-inactive-workspace-status.command.ts new file mode 100644 index 000000000..3cb37806e --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-40/0-40-update-inactive-workspace-status.command.ts @@ -0,0 +1,250 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import chalk from 'chalk'; +import { Command, Option } from 'nest-commander'; +import { In, Repository } from 'typeorm'; + +import { + BaseCommandOptions, + BaseCommandRunner, +} from 'src/database/commands/base.command'; +import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource'; +import { TypeORMService } from 'src/database/typeorm/typeorm.service'; +import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; +import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum'; +import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity'; +import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { User } from 'src/engine/core-modules/user/user.entity'; +import { + Workspace, + WorkspaceActivationStatus, +} from 'src/engine/core-modules/workspace/workspace.entity'; +import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; +import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; + +type UpdateInactiveWorkspaceStatusOptions = BaseCommandOptions & { + workspaceIds: string[]; +}; + +@Command({ + name: 'upgrade-0.40:update-inactive-workspace-status', + description: + 'Update the status of inactive workspaces to SUSPENDED and delete them', +}) +export class UpdateInactiveWorkspaceStatusCommand extends BaseCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + @InjectRepository(DataSourceEntity, 'metadata') + protected readonly datasourceRepository: Repository, + @InjectRepository(WorkspaceMigrationEntity, 'metadata') + protected readonly workspaceMigrationRepository: Repository, + @InjectRepository(BillingSubscription, 'core') + protected readonly subscriptionRepository: Repository, + @InjectRepository(FeatureFlagEntity, 'core') + protected readonly featureFlagRepository: Repository, + @InjectRepository(KeyValuePair, 'core') + protected readonly keyValuePairRepository: Repository, + @InjectRepository(UserWorkspace, 'core') + protected readonly userWorkspaceRepository: Repository, + @InjectRepository(User, 'core') + protected readonly userRepository: Repository, + private readonly typeORMService: TypeORMService, + ) { + super(); + } + + @Option({ + flags: '-w, --workspace-ids [workspaceIds]', + description: 'Workspace ids to process (comma separated)', + }) + parseWorkspaceIds(val: string): string[] { + return val.split(','); + } + + override async executeBaseCommand( + _passedParams: string[], + options: UpdateInactiveWorkspaceStatusOptions, + ): Promise { + const whereCondition: any = { + activationStatus: WorkspaceActivationStatus.INACTIVE, + }; + + if (options.workspaceIds?.length > 0) { + whereCondition.id = In(options.workspaceIds); + } + + const workspaces = await this.workspaceRepository.find({ + where: whereCondition, + }); + + if (options.dryRun) { + this.logger.log(chalk.yellow('Dry run mode: No changes will be applied')); + } + + this.logger.log( + chalk.blue( + `Found ${workspaces.length} inactive workspace${ + workspaces.length > 1 ? 's' : '' + }`, + ), + ); + + await rawDataSource.initialize(); + + for (const workspace of workspaces) { + this.logger.log( + chalk.blue( + `Processing workspace ${workspace.id} with name ${workspace.displayName}`, + ), + ); + // Check if the workspace has a datasource + const datasource = await this.datasourceRepository.findOne({ + where: { workspaceId: workspace.id }, + }); + + const schemaName = datasource?.schema; + + const postgresSchemaExists = await this.typeORMService + .getMainDataSource() + .query( + `SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = '${schemaName}'`, + ); + + if (!schemaName || !postgresSchemaExists) { + await this.deleteWorkspaceAndMarkAsSuspended(workspace, options); + continue; + } + + const subscriptions = await this.subscriptionRepository.find({ + where: { workspaceId: workspace.id }, + }); + + if (subscriptions.length > 1) { + this.logger.warn(chalk.red('More than one subscription found')); + continue; + } + + const subscription = subscriptions[0]; + + if (!subscription) { + this.logger.log(chalk.red('No subscription found')); + await this.deleteWorkspaceAndMarkAsSuspendedAndDeleteAllData( + workspace, + schemaName, + options, + ); + continue; + } + + const thirtyDaysAgo = new Date(); + + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + + if ( + ([ + SubscriptionStatus.Canceled, + SubscriptionStatus.Incomplete, + SubscriptionStatus.IncompleteExpired, + SubscriptionStatus.Unpaid, + SubscriptionStatus.Paused, + ].includes(subscription.status) && + subscription.canceledAt && + subscription.canceledAt < thirtyDaysAgo) || + (subscription.canceledAt === null && + subscription.updatedAt && + subscription.updatedAt < thirtyDaysAgo) + ) { + await this.deleteWorkspaceAndMarkAsSuspendedAndDeleteAllData( + workspace, + schemaName, + options, + subscription, + ); + continue; + } + + await this.markAsSuspended(workspace, options); + } + } + + private async deleteWorkspaceAndMarkAsSuspended( + workspace: Workspace, + options: UpdateInactiveWorkspaceStatusOptions, + ) { + this.logger.log( + chalk.blue('(!!) Deleting workspace and marking as suspended'), + ); + + if (!options.dryRun) { + await this.workspaceRepository.update(workspace.id, { + activationStatus: WorkspaceActivationStatus.SUSPENDED, + }); + + await this.workspaceRepository.softRemove({ id: workspace.id }); + } + } + + private async deleteWorkspaceAndMarkAsSuspendedAndDeleteAllData( + workspace: Workspace, + schemaName: string, + options: UpdateInactiveWorkspaceStatusOptions, + billingSubscription?: BillingSubscription, + ) { + this.logger.warn( + chalk.blue( + `(!!!) Deleting workspace and marking as suspended and deleting all data for workspace updated at ${workspace.updatedAt} with subscription status ${billingSubscription?.status} and subscription updatedAt ${billingSubscription?.updatedAt} and canceledAt ${billingSubscription?.canceledAt}`, + ), + ); + + if (!options.dryRun) { + await this.workspaceRepository.update(workspace.id, { + activationStatus: WorkspaceActivationStatus.SUSPENDED, + }); + + await this.workspaceRepository.softRemove({ id: workspace.id }); + + await this.datasourceRepository.delete({ workspaceId: workspace.id }); + await this.workspaceMigrationRepository.delete({ + workspaceId: workspace.id, + }); + + await this.featureFlagRepository.delete({ workspaceId: workspace.id }); + await this.keyValuePairRepository.delete({ workspaceId: workspace.id }); + + const userWorkspaces = await this.userWorkspaceRepository.find({ + where: { workspaceId: workspace.id }, + }); + + for (const userWorkspace of userWorkspaces) { + await this.userWorkspaceRepository.delete({ id: userWorkspace.id }); + + const remainingUserWorkspaces = + await this.userWorkspaceRepository.count({ + where: { userId: userWorkspace.userId }, + }); + + if (remainingUserWorkspaces === 0) { + await this.userRepository.softRemove({ id: userWorkspace.userId }); + } + } + + await this.typeORMService + .getMainDataSource() + .query(`DROP SCHEMA IF EXISTS ${schemaName} CASCADE`); + } + } + + private async markAsSuspended( + workspace: Workspace, + options: UpdateInactiveWorkspaceStatusOptions, + ) { + this.logger.log(chalk.blue('(!) Marking as suspended')); + if (!options.dryRun) { + await this.workspaceRepository.update(workspace.id, { + activationStatus: WorkspaceActivationStatus.SUSPENDED, + }); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-40/0-40-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-40/0-40-upgrade-version.module.ts index cc33bd878..c1471e69b 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-40/0-40-upgrade-version.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-40/0-40-upgrade-version.module.ts @@ -2,25 +2,54 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { MigrateAggregateOperationOptionsCommand } from 'src/database/commands/upgrade-version/0-40/0-40-migrate-aggregate-operations-options.command'; +import { UpdateInactiveWorkspaceStatusCommand } from 'src/database/commands/upgrade-version/0-40/0-40-update-inactive-workspace-status.command'; import { UpgradeTo0_40Command } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.command'; +import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; +import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; +import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity'; +import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { User } from 'src/engine/core-modules/user/user.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { 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 { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; 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: [ - TypeOrmModule.forFeature([Workspace], 'core'), TypeOrmModule.forFeature( - [ObjectMetadataEntity, FieldMetadataEntity], + [ + Workspace, + BillingSubscription, + FeatureFlagEntity, + KeyValuePair, + User, + UserWorkspace, + ], + 'core', + ), + TypeOrmModule.forFeature( + [ + ObjectMetadataEntity, + FieldMetadataEntity, + DataSourceEntity, + WorkspaceMigrationEntity, + ], 'metadata', ), WorkspaceMigrationRunnerModule, WorkspaceMigrationModule, WorkspaceMetadataVersionModule, + TypeORMModule, + ], + providers: [ + UpgradeTo0_40Command, + MigrateAggregateOperationOptionsCommand, + UpdateInactiveWorkspaceStatusCommand, ], - providers: [UpgradeTo0_40Command, MigrateAggregateOperationOptionsCommand], }) export class UpgradeTo0_40CommandModule {} diff --git a/packages/twenty-server/src/engine/core-modules/user/user.entity.ts b/packages/twenty-server/src/engine/core-modules/user/user.entity.ts index 03ce52c7b..0a33443bf 100644 --- a/packages/twenty-server/src/engine/core-modules/user/user.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/user/user.entity.ts @@ -4,6 +4,7 @@ import { IDField } from '@ptc-org/nestjs-query-graphql'; import { Column, CreateDateColumn, + DeleteDateColumn, Entity, Index, OneToMany, @@ -77,7 +78,7 @@ export class User { updatedAt: Date; @Field({ nullable: true }) - @Column({ nullable: true, type: 'timestamptz' }) + @DeleteDateColumn({ type: 'timestamptz' }) deletedAt: Date; @OneToMany(() => AppToken, (appToken) => appToken.user, { diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts index 89b776046..8b69c77bb 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts @@ -4,6 +4,7 @@ import { IDField, UnPagedRelation } from '@ptc-org/nestjs-query-graphql'; import { Column, CreateDateColumn, + DeleteDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, @@ -67,7 +68,7 @@ export class Workspace { inviteHash?: string; @Field({ nullable: true }) - @Column({ nullable: true, type: 'timestamptz' }) + @DeleteDateColumn({ type: 'timestamptz' }) deletedAt?: Date; @Field()