diff --git a/packages/twenty-server/src/engine/core-modules/workspace/utils/activation-status-in.util.ts b/packages/twenty-server/src/database/commands/command-runners/utils/activation-status-in.util.ts similarity index 78% rename from packages/twenty-server/src/engine/core-modules/workspace/utils/activation-status-in.util.ts rename to packages/twenty-server/src/database/commands/command-runners/utils/activation-status-in.util.ts index c5de1646f6..df2395534d 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/utils/activation-status-in.util.ts +++ b/packages/twenty-server/src/database/commands/command-runners/utils/activation-status-in.util.ts @@ -1,7 +1,7 @@ import { type WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { Raw } from 'typeorm'; -// TODO: drop the ::text cast and use In() once the CREATED enum migration has run everywhere (follow-up PR of #22904) +// TODO: drop the ::text cast and use In() once upgrades from versions before 2.22 (CREATED enum migration) are no longer supported export const activationStatusIn = (statuses: WorkspaceActivationStatus[]) => Raw( (alias) => { diff --git a/packages/twenty-server/src/database/commands/command-runners/workspace-iterator.service.ts b/packages/twenty-server/src/database/commands/command-runners/workspace-iterator.service.ts index afd0f92b46..5eae7b8e33 100644 --- a/packages/twenty-server/src/database/commands/command-runners/workspace-iterator.service.ts +++ b/packages/twenty-server/src/database/commands/command-runners/workspace-iterator.service.ts @@ -10,7 +10,7 @@ import { import { isDefined } from 'twenty-shared/utils'; import { MoreThanOrEqual, Repository } from 'typeorm'; -import { activationStatusIn } from 'src/engine/core-modules/workspace/utils/activation-status-in.util'; +import { activationStatusIn } from 'src/database/commands/command-runners/utils/activation-status-in.util'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { GlobalWorkspaceDataSource } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-instance-command-slow-1784286705000-backfill-created-workspace-activation-status.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-instance-command-slow-1784286705000-backfill-created-workspace-activation-status.ts new file mode 100644 index 0000000000..c5fc54b016 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-instance-command-slow-1784286705000-backfill-created-workspace-activation-status.ts @@ -0,0 +1,49 @@ +import { Logger } from '@nestjs/common'; + +import { DataSource, QueryRunner } from 'typeorm'; + +import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface'; + +const V2_ONBOARDING_RELEASE_DATE = '2026-07-01'; + +@RegisteredInstanceCommand('2.23.0', 1784286705000, { type: 'slow' }) +export class BackfillCreatedWorkspaceActivationStatusSlowInstanceCommand + implements SlowInstanceCommand +{ + private readonly logger = new Logger( + BackfillCreatedWorkspaceActivationStatusSlowInstanceCommand.name, + ); + + constructor(private readonly twentyConfigService: TwentyConfigService) {} + + async runDataMigration(dataSource: DataSource): Promise { + if (!this.twentyConfigService.get('IS_BILLING_ENABLED')) { + return; + } + + const backfilledWorkspaces: { id: string }[] = await dataSource.query( + `UPDATE "core"."workspace" "workspace" + SET "activationStatus" = 'CREATED' + WHERE "workspace"."activationStatus" = 'ACTIVE' + AND "workspace"."deletedAt" IS NULL + AND "workspace"."createdAt" >= $1 + AND NOT EXISTS ( + SELECT 1 + FROM "core"."billingSubscription" "billingSubscription" + WHERE "billingSubscription"."workspaceId" = "workspace"."id" + ) + RETURNING "workspace"."id"`, + [V2_ONBOARDING_RELEASE_DATE], + ); + + this.logger.log( + `Backfilled ${backfilledWorkspaces.length} subscription-less active workspace(s) to CREATED`, + ); + } + + public async up(_queryRunner: QueryRunner): Promise {} + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index 2b57c1fe8a..d26463d630 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -112,6 +112,7 @@ import { BackfillWorkspaceDatabaseSchemaSlowInstanceCommand } from './2-21/2-21- import { AddLogoFileIdToApplicationRegistrationFastInstanceCommand } from './2-21/2-21-instance-command-fast-1783945979243-add-logo-file-id-to-application-registration'; import { AddCalendarEndFieldMetadataIdToViewFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-22/2-22-instance-command-fast-1783956795000-add-calendar-end-field-metadata-id-to-view'; import { AddCreatedWorkspaceActivationStatusSlowInstanceCommand } from './2-22/2-22-instance-command-slow-1784106205000-add-created-workspace-activation-status'; +import { BackfillCreatedWorkspaceActivationStatusSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784286705000-backfill-created-workspace-activation-status'; export const INSTANCE_COMMANDS = [ AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand, @@ -226,4 +227,5 @@ export const INSTANCE_COMMANDS = [ AddLogoFileIdToApplicationRegistrationFastInstanceCommand, AddCalendarEndFieldMetadataIdToViewFastInstanceCommand, AddCreatedWorkspaceActivationStatusSlowInstanceCommand, + BackfillCreatedWorkspaceActivationStatusSlowInstanceCommand, ]; diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts index 2bc2fc38c0..0088f31715 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-subscription.service.ts @@ -407,16 +407,13 @@ export class BillingSubscriptionService { currentBillingSubscription.status, ) ) { - // TODO: drop the ::text cast once the CREATED enum migration has run everywhere (follow-up PR of #22904) - const activationResult = await this.workspaceRepository - .createQueryBuilder() - .update() - .set({ activationStatus: WorkspaceActivationStatus.ACTIVE }) - .where('id = :workspaceId AND "activationStatus"::text = :fromStatus', { - workspaceId, - fromStatus: WorkspaceActivationStatus.CREATED, - }) - .execute(); + const activationResult = await this.workspaceRepository.update( + { + id: workspaceId, + activationStatus: WorkspaceActivationStatus.CREATED, + }, + { activationStatus: WorkspaceActivationStatus.ACTIVE }, + ); if ((activationResult.affected ?? 0) > 0) { await this.coreEntityCacheService.invalidate( diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-status.service.ts b/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-status.service.ts index bc51946c71..a2c98ab8ff 100644 --- a/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-status.service.ts +++ b/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-status.service.ts @@ -12,7 +12,7 @@ import { UpgradeStatusCacheService } from 'src/engine/core-modules/upgrade/servi import { type UpgradeMigrationStatus } from 'src/engine/core-modules/upgrade/upgrade-migration.entity'; import { extractVersionFromCommandName } from 'src/engine/core-modules/upgrade/utils/extract-version-from-command-name.util'; -import { activationStatusIn } from 'src/engine/core-modules/workspace/utils/activation-status-in.util'; +import { activationStatusIn } from 'src/database/commands/command-runners/utils/activation-status-in.util'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { In, Repository } from 'typeorm'; diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts index bb6b722dfd..f046524561 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts @@ -461,6 +461,9 @@ export class WorkspaceService { const executedByVersion = this.twentyConfigService.get('APP_VERSION') ?? 'unknown'; + const hasWorkspaceAnySubscription = + await this.billingService.hasWorkspaceAnySubscription(workspaceId); + const queryRunner = this.coreDataSource.createQueryRunner(); await queryRunner.connect(); @@ -468,7 +471,9 @@ export class WorkspaceService { try { await queryRunner.manager.update(WorkspaceEntity, workspaceId, { - activationStatus: WorkspaceActivationStatus.ACTIVE, + activationStatus: hasWorkspaceAnySubscription + ? WorkspaceActivationStatus.ACTIVE + : WorkspaceActivationStatus.CREATED, }); await this.upgradeMigrationService.markAsWorkspaceInitial({ diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-onboarding-workspaces.command.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-onboarding-workspaces.command.ts index 9f360fb5c8..6ca01feabe 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-onboarding-workspaces.command.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/commands/clean-onboarding-workspaces.command.ts @@ -49,6 +49,7 @@ export class CleanOnboardingWorkspacesCommand extends MigrationCommandRunner { activationStatus: In([ WorkspaceActivationStatus.PENDING_CREATION, WorkspaceActivationStatus.ONGOING_CREATION, + WorkspaceActivationStatus.CREATED, ]), createdAt: LessThan(sevenDaysAgo), }, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/crons/clean-onboarding-workspaces.job.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/crons/clean-onboarding-workspaces.job.ts index e46bd071bc..4405652f44 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/crons/clean-onboarding-workspaces.job.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/crons/clean-onboarding-workspaces.job.ts @@ -35,6 +35,7 @@ export class CleanOnboardingWorkspacesJob { activationStatus: In([ WorkspaceActivationStatus.PENDING_CREATION, WorkspaceActivationStatus.ONGOING_CREATION, + WorkspaceActivationStatus.CREATED, ]), createdAt: LessThan(sevenDaysAgo), }, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service.ts index cf6f1692b2..7233cb8881 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service.ts @@ -300,6 +300,7 @@ export class CleanerWorkspaceService { activationStatus: In([ WorkspaceActivationStatus.PENDING_CREATION, WorkspaceActivationStatus.ONGOING_CREATION, + WorkspaceActivationStatus.CREATED, ]), }, withDeleted: true, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-version/services/workspace-version.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-version/services/workspace-version.service.ts index fd4041d4c7..da8aa26d04 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-version/services/workspace-version.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-version/services/workspace-version.service.ts @@ -4,7 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { PROVISIONED_WORKSPACE_ACTIVATION_STATUSES } from 'twenty-shared/workspace'; import { MoreThanOrEqual, QueryRunner, Repository } from 'typeorm'; -import { activationStatusIn } from 'src/engine/core-modules/workspace/utils/activation-status-in.util'; +import { activationStatusIn } from 'src/database/commands/command-runners/utils/activation-status-in.util'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; @Injectable()