diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-instance-command-fast-1784823473532-add-logo-file-id-to-application-registration.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-instance-command-fast-1784823473532-add-logo-file-id-to-application-registration.ts new file mode 100644 index 0000000000..5f53350e09 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-instance-command-fast-1784823473532-add-logo-file-id-to-application-registration.ts @@ -0,0 +1,28 @@ +import { type QueryRunner } from 'typeorm'; + +import { + dropApplicationRegistrationLogoFileIdColumn, + ensureApplicationRegistrationLogoFileIdColumn, +} from 'src/database/commands/upgrade-version-command/2-23/utils/ensure-application-registration-logo-file-id-column.util'; +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { type FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +// Re-slots the 2.21 add-logo-file-id DDL at the end of the 2.23 fast segment: +// the 2.21 command landed after the 2.22 bump and is skipped forever on +// instances whose cursor is already past that slot. +@RegisteredInstanceCommand('2.23.0', 1784823473532) +export class AddLogoFileIdToApplicationRegistration2_23FastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await ensureApplicationRegistrationLogoFileIdColumn((sql) => + queryRunner.query(sql), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await dropApplicationRegistrationLogoFileIdColumn((sql) => + queryRunner.query(sql), + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-workspace-command-1784565137000-upgrade-people-data-labs-application.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-workspace-command-1784565137000-upgrade-people-data-labs-application.command.ts index ce6411397f..c92759f4ec 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-workspace-command-1784565137000-upgrade-people-data-labs-application.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-workspace-command-1784565137000-upgrade-people-data-labs-application.command.ts @@ -1,13 +1,14 @@ -import { InjectRepository } from '@nestjs/typeorm'; +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { Command } from 'nest-commander'; import * as semver from 'semver'; import { isDefined } from 'twenty-shared/utils'; -import { Repository } from 'typeorm'; +import { DataSource, Repository } from 'typeorm'; import { ProvisionedWorkspaceCommandRunner } from 'src/database/commands/command-runners/provisioned-workspace.command-runner'; import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service'; import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner'; +import { ensureApplicationRegistrationLogoFileIdColumn } from 'src/database/commands/upgrade-version-command/2-23/utils/ensure-application-registration-logo-file-id-column.util'; import { ApplicationUpgradeService } from 'src/engine/core-modules/application/application-upgrade/application-upgrade.service'; import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator'; @@ -28,11 +29,16 @@ const PEOPLE_DATA_LABS_TARGET_VERSION = '1.0.9'; 'Upgrade the people-data-labs application to 1.0.9 right after the system relation field universal identifier backfill, so its views reference the re-derived name-free identifiers instead of the stale pre-2.23 ones and its front components run on React 19. Workspaces already at or above 1.0.9 are left untouched.', }) export class UpgradePeopleDataLabsApplicationCommand extends ProvisionedWorkspaceCommandRunner { + // Instance-global DDL: run once per process, not per workspace. + private hasEnsuredLogoFileIdColumn = false; + constructor( protected readonly workspaceIteratorService: WorkspaceIteratorService, private readonly applicationUpgradeService: ApplicationUpgradeService, @InjectRepository(ApplicationEntity) private readonly applicationRepository: Repository, + @InjectDataSource() + private readonly coreDataSource: DataSource, ) { super(workspaceIteratorService); } @@ -43,6 +49,28 @@ export class UpgradePeopleDataLabsApplicationCommand extends ProvisionedWorkspac }: RunOnWorkspaceArgs): Promise { const isDryRun = options.dryRun ?? false; + // The 2.21 logoFileId command was skipped on instances that ran a 2.21 + // binary, so the column is absent and the findOne below crashes on it. + // Repair it here since this is the command that breaks. + if (!this.hasEnsuredLogoFileIdColumn) { + if (isDryRun) { + const columnExists = await this.logoFileIdColumnExists(); + + if (!columnExists) { + this.logger.log( + 'Would repair the missing core."applicationRegistration"."logoFileId" column', + ); + + return; + } + } else { + await ensureApplicationRegistrationLogoFileIdColumn((sql) => + this.coreDataSource.query(sql), + ); + this.hasEnsuredLogoFileIdColumn = true; + } + } + const application = await this.applicationRepository.findOne({ where: { workspaceId, @@ -106,4 +134,19 @@ export class UpgradePeopleDataLabsApplicationCommand extends ProvisionedWorkspac ); } } + + private async logoFileIdColumnExists(): Promise { + // pg_attribute scoped to the single table, not the instance-wide + // information_schema.columns view, which is slow on many-tenant instances. + const rows = await this.coreDataSource.query( + `SELECT EXISTS ( + SELECT 1 FROM pg_attribute + WHERE attrelid = to_regclass('core."applicationRegistration"') + AND attname = 'logoFileId' + AND NOT attisdropped + ) AS "exists"`, + ); + + return rows[0]?.exists === true; + } } diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/utils/ensure-application-registration-logo-file-id-column.util.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/utils/ensure-application-registration-logo-file-id-column.util.ts new file mode 100644 index 0000000000..51aa59b1b3 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-23/utils/ensure-application-registration-logo-file-id-column.util.ts @@ -0,0 +1,36 @@ +type QueryExecutor = (sql: string) => Promise; + +// Verbatim 2.21 add-logo-file-id DDL, shared so both 2.23 repair paths match. +export const ensureApplicationRegistrationLogoFileIdColumn = async ( + query: QueryExecutor, +): Promise => { + await query( + 'ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "logoFileId" uuid', + ); + await query( + 'ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT IF EXISTS "UQ_796819fb23559c233e6ebd49f34"', + ); + await query( + 'ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "UQ_796819fb23559c233e6ebd49f34" UNIQUE ("logoFileId")', + ); + await query( + 'ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT IF EXISTS "FK_796819fb23559c233e6ebd49f34"', + ); + await query( + 'ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "FK_796819fb23559c233e6ebd49f34" FOREIGN KEY ("logoFileId") REFERENCES "core"."file"("id") ON DELETE SET NULL ON UPDATE NO ACTION', + ); +}; + +export const dropApplicationRegistrationLogoFileIdColumn = async ( + query: QueryExecutor, +): Promise => { + await query( + 'ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT IF EXISTS "FK_796819fb23559c233e6ebd49f34"', + ); + await query( + 'ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT IF EXISTS "UQ_796819fb23559c233e6ebd49f34"', + ); + await query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN IF EXISTS "logoFileId"', + ); +}; 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 5d18bc6e74..8a6d9d692b 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 @@ -117,6 +117,7 @@ import { AddCreatedWorkspaceActivationStatusSlowInstanceCommand } from './2-22/2 import { AddKanbanAndCalendarWidgetViewTypesFastInstanceCommand } from './2-23/2-23-instance-command-fast-1784620626405-add-kanban-and-calendar-widget-view-types'; import { WidenViewCalendarIntegrityConstraintFastInstanceCommand } from './2-23/2-23-instance-command-fast-1784620626406-widen-view-calendar-integrity-constraint'; import { AddApplicationIdToKeyValuePairFastInstanceCommand } from './2-23/2-23-instance-command-fast-1784659343818-add-application-id-to-key-value-pair'; +import { AddLogoFileIdToApplicationRegistration2_23FastInstanceCommand } from './2-23/2-23-instance-command-fast-1784823473532-add-logo-file-id-to-application-registration'; import { BackfillCreatedWorkspaceActivationStatusSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784286705000-backfill-created-workspace-activation-status'; import { UnlistUnclaimedNpmApplicationRegistrationsSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784322591746-unlist-unclaimed-npm-application-registrations'; import { AddStatusesToBillingSubscriptionIndexSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784650048045-add-statuses-to-billing-subscription-index'; @@ -242,6 +243,7 @@ export const INSTANCE_COMMANDS = [ AddAutoUpgradeToApplicationFastInstanceCommand, AddApplicationIdToKeyValuePairFastInstanceCommand, AddSdkClientCoreChecksumToApplicationFastInstanceCommand, + AddLogoFileIdToApplicationRegistration2_23FastInstanceCommand, AddStatusesToBillingSubscriptionIndexSlowInstanceCommand, AddOnConnectLogicFunctionToConnectionProviderFastInstanceCommand, ];