Continue migration schema from metadata to core (#12168)

2nd part of the migration - this time we're migrating all the tables

---------

Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
Félix Malfait
2025-05-21 15:44:13 +02:00
committed by GitHub
parent 8c20bada79
commit 7f1d6f5c7f
62 changed files with 487 additions and 293 deletions

View File

@ -1,8 +1,14 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { exec } from 'child_process';
import { promisify } from 'util';
import { Command } from 'nest-commander';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { In, Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandOptions } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import {
AllCommands,
UpgradeCommandRunner,
@ -23,12 +29,131 @@ import { CopyTypeormMigrationsCommand } from 'src/database/commands/upgrade-vers
import { MigrateWorkflowEventListenersToAutomatedTriggersCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-migrate-workflow-event-listeners-to-automated-triggers.command';
import { RemoveRelationForeignKeyFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-remove-relation-foreign-key-field-metadata.command';
import { UpgradeSearchVectorOnPersonEntityCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-upgrade-search-vector-on-person-entity.command';
import { FixCreatedByDefaultValueCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-created-by-default-value.command';
import { FixStandardSelectFieldsPositionCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-fix-standard-select-fields-position.command';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
import { FixCreatedByDefaultValueCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-created-by-default-value.command';
import { compareVersionMajorAndMinor } from 'src/utils/version/compare-version-minor-and-major';
const execPromise = promisify(exec);
@Injectable()
export class DatabaseMigrationService {
private logger = new Logger(DatabaseMigrationService.name);
constructor(
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
) {}
// TODO centralize with ActiveOrSuspendedRunner method
private async loadActiveOrSuspendedWorkspace() {
return await this.workspaceRepository.find({
select: ['id', 'version'],
where: {
activationStatus: In([
WorkspaceActivationStatus.ACTIVE,
WorkspaceActivationStatus.SUSPENDED,
]),
},
order: {
id: 'ASC',
},
});
}
async shouldRunMigrationsIfAllWorkspaceAreAboveVersion0_53(): Promise<boolean> {
const coreWorkspaceSchemaExists = await this.checkCoreWorkspaceExists();
if (!coreWorkspaceSchemaExists) {
this.logger.log(
'core.workspace does not exist. Running migrations for fresh installation.',
);
return true;
}
this.logger.log('Not a first installation, checking workspace versions...');
return await this.areAllWorkspacesAboveVersion0_53();
}
async runMigrations(): Promise<void> {
this.logger.log('Running global database migrations');
try {
this.logger.log('Running metadata datasource migrations...');
const metadataResult = await execPromise(
'npx -y typeorm migration:run -d dist/src/database/typeorm/metadata/metadata.datasource',
);
this.logger.log(metadataResult.stdout);
this.logger.log('Running core datasource migrations...');
const coreResult = await execPromise(
'npx -y typeorm migration:run -d dist/src/database/typeorm/core/core.datasource',
);
this.logger.log(coreResult.stdout);
this.logger.log('Database migrations completed successfully');
} catch (error) {
this.logger.error('Error running database migrations:', error);
throw error;
}
}
private async checkCoreWorkspaceExists(): Promise<boolean> {
try {
const result = await this.workspaceRepository.query(`
SELECT EXISTS (
SELECT 1
FROM information_schema.tables
WHERE table_schema = 'core'
AND table_name = 'workspace'
);
`);
return result[0].exists;
} catch (error) {
this.logger.error('Error checking core.workspace existence:', error);
return false;
}
}
private async areAllWorkspacesAboveVersion0_53(): Promise<boolean> {
try {
const allActiveOrSuspendedWorkspaces =
await this.loadActiveOrSuspendedWorkspace();
if (allActiveOrSuspendedWorkspaces.length === 0) {
this.logger.log(
'No workspaces found. Running migrations for fresh installation.',
);
return true;
}
const workspacesBelowVersion = allActiveOrSuspendedWorkspaces.filter(
({ version }) =>
version === null ||
compareVersionMajorAndMinor(version, '0.53.0') === 'lower',
);
this.logger.log(
`Found ${workspacesBelowVersion.length} active or suspended workspaces that are below version 0.53.0 \n${workspacesBelowVersion.map((el) => el.id).join('\n')}`,
);
return workspacesBelowVersion.length === 0;
} catch (error) {
this.logger.error('Error checking workspaces below version:', error);
throw error;
}
}
}
@Command({
name: 'upgrade',
@ -44,6 +169,8 @@ export class UpgradeCommand extends UpgradeCommandRunner {
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
protected readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
private readonly databaseMigrationService: DatabaseMigrationService,
// 0.43 Commands
protected readonly migrateRichTextContentPatchCommand: MigrateRichTextContentPatchCommand,
protected readonly addTasksAssignedToMeViewCommand: AddTasksAssignedToMeViewCommand,
@ -146,4 +273,22 @@ export class UpgradeCommand extends UpgradeCommandRunner {
'0.54.0': commands_054,
};
}
override async runMigrationCommand(
passedParams: string[],
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions,
): Promise<void> {
// Only run migrations if we have at least one workspace on version 0.53
const shouldRunMigrateAsPartOfUpgrade =
await this.databaseMigrationService.shouldRunMigrationsIfAllWorkspaceAreAboveVersion0_53();
if (shouldRunMigrateAsPartOfUpgrade) {
await this.databaseMigrationService.runMigrations();
} else {
this.logger.log('Skipping database migrations.');
}
// Continue with the regular upgrade process
await super.runMigrationCommand(passedParams, options);
}
}