Compare commits

...

17 Commits

Author SHA1 Message Date
prastoin 396efd2d96 generate typeorm migration cli 2026-04-02 16:06:10 +02:00
prastoin d7d279a4b4 wip 2026-04-02 14:46:58 +02:00
prastoin 86a9d3832b remove finally from runners 2026-04-02 13:54:06 +02:00
prastoin dc298bf548 fix merge 2026-04-02 13:46:15 +02:00
prastoin 17ce7ed781 Merge remote-tracking branch 'origin/main' into upgrade-command-refactor 2026-04-02 13:41:59 +02:00
prastoin c2872f8e7d refactor(server): commander options 2026-04-02 13:38:07 +02:00
prastoin ac02aa3ce6 deprecated report 2026-04-02 13:35:05 +02:00
prastoin 3ea466755a naming 2026-04-02 13:22:21 +02:00
prastoin d219c83815 workspace migration runner extends commandRunner directly 2026-04-02 13:04:25 +02:00
prastoin ab16ae6995 review 2026-04-02 12:00:57 +02:00
prastoin 49d19494b9 avoid mutations 2026-04-02 11:45:06 +02:00
prastoin 5172572d15 refactor(server): remove last upgrade command runner extends 2026-04-02 11:39:31 +02:00
prastoin b5492cff64 workspace iterator callback 2026-04-02 11:12:55 +02:00
prastoin 0cb231a407 revert instance commands stuff 2026-04-02 10:49:33 +02:00
prastoin 5484d0b776 feat(server): factorize workspace iterator usage 2026-04-02 10:36:43 +02:00
prastoin bbc4d50d4f feat(server): workspace iterator service 2026-04-02 10:33:18 +02:00
prastoin 10b37a6b39 refactor(server): upgrade and typeorm migrations nesting 2026-04-01 18:39:48 +02:00
50 changed files with 1227 additions and 822 deletions
+8
View File
@@ -219,6 +219,14 @@
"command": "npx nx typeorm -- migration:generate src/database/typeorm/core/migrations/common/{args.migrationName} -d src/database/typeorm/core/core.datasource.ts"
}
},
"database:migrate:generate:versioned": {
"executor": "nx:run-commands",
"dependsOn": ["^build"],
"options": {
"cwd": "packages/twenty-server",
"command": "ts-node --transpile-only -P tsconfig.json scripts/generate-versioned-migration.ts {args.migrationName}"
}
},
"database:migrate:revert": {
"executor": "nx:run-commands",
"dependsOn": ["^build"],
@@ -0,0 +1,146 @@
import * as fs from 'fs';
import * as path from 'path';
import { connectionSource } from 'src/database/typeorm/core/core.datasource';
const SUPPORTED_VERSIONS_FILE = path.resolve(
__dirname,
'../src/engine/constants/upgrade-command-supported-versions.constant.ts',
);
const MIGRATIONS_DIR = path.resolve(
__dirname,
'../src/database/typeorm/core/migrations/common',
);
const toPascalCase = (str: string): string =>
str.replace(/^([A-Z])|[\s-_](\w)/g, (_match, p1, p2) =>
p2 ? p2.toUpperCase() : p1.toLowerCase(),
);
const buildClassName = (name: string, timestamp: number): string =>
// Uppercase first letter to get PascalCase
`${toPascalCase(` ${name}`).trim()}${timestamp}`;
const formatQueryParams = (parameters: unknown[] | undefined): string => {
if (!parameters || !parameters.length) {
return '';
}
return `, ${JSON.stringify(parameters)}`;
};
const resolveLatestVersion = (): string => {
const content = fs.readFileSync(SUPPORTED_VERSIONS_FILE, 'utf-8');
const matches = content.match(/'(\d+\.\d+\.\d+)'/g);
if (!matches || matches.length === 0) {
throw new Error(
`Could not parse versions from ${SUPPORTED_VERSIONS_FILE}`,
);
}
const latestVersion = matches[matches.length - 1].replace(/'/g, '');
return latestVersion;
};
const buildMigrationFileContent = (
className: string,
version: string,
upStatements: string[],
downStatements: string[],
): string =>
`import { MigrationInterface, QueryRunner } from 'typeorm';
import { VersionedMigration } from 'src/database/typeorm/core/decorators/versioned-migration.decorator';
@VersionedMigration('${version}')
export class ${className} implements MigrationInterface {
name = '${className}';
public async up(queryRunner: QueryRunner): Promise<void> {
${upStatements.join('\n')}
}
public async down(queryRunner: QueryRunner): Promise<void> {
${downStatements.join('\n')}
}
}
`;
const main = async (): Promise<void> => {
const migrationName = process.argv[2];
if (!migrationName) {
console.error(
'Usage: ts-node scripts/generate-versioned-migration.ts <migration-name>',
);
console.error('Example: ts-node scripts/generate-versioned-migration.ts add-foo-column');
process.exit(1);
}
const version = resolveLatestVersion();
console.log(`Generating versioned migration for version ${version}...`);
connectionSource.setOptions({
synchronize: false,
migrationsRun: false,
dropSchema: false,
logging: false,
});
await connectionSource.initialize();
try {
const sqlInMemory = await connectionSource.driver
.createSchemaBuilder()
.log();
if (sqlInMemory.upQueries.length === 0) {
console.log(
'No changes in database schema were found - cannot generate a migration.',
);
process.exit(1);
}
const timestamp = Date.now();
const className = buildClassName(migrationName, timestamp);
const upStatements = sqlInMemory.upQueries.map(
(query) =>
` await queryRunner.query(\`${query.query.replace(/`/g, '\\`')}\`${formatQueryParams(query.parameters)});`,
);
const downStatements = sqlInMemory.downQueries
.reverse()
.map(
(query) =>
` await queryRunner.query(\`${query.query.replace(/`/g, '\\`')}\`${formatQueryParams(query.parameters)});`,
);
const fileContent = buildMigrationFileContent(
className,
version,
upStatements,
downStatements,
);
const fileName = `${timestamp}-${migrationName}.ts`;
const filePath = path.join(MIGRATIONS_DIR, fileName);
fs.writeFileSync(filePath, fileContent);
console.log(`Migration generated successfully: ${filePath}`);
console.log(` Class: ${className}`);
console.log(` Version: ${version}`);
} finally {
await connectionSource.destroy();
}
};
main().catch((error) => {
console.error('Error generating migration:', error);
process.exit(1);
});
@@ -8,17 +8,18 @@ import {
import { type Repository } from 'typeorm';
import {
UpgradeCommandOptions,
UpgradeCommandRunner,
type AllCommands,
} from 'src/database/commands/command-runners/upgrade.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
import { VersionedMigrationRegistryService } from 'src/database/commands/core-migration-runner/services/versioned-migration-registry.service';
import { UPGRADE_COMMAND_SUPPORTED_VERSIONS } from 'src/engine/constants/upgrade-command-supported-versions.constant';
import { CoreEngineVersionService } from 'src/engine/core-engine-version/services/core-engine-version.service';
import { type ConfigVariables } from 'src/engine/core-modules/twenty-config/config-variables';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service';
const CURRENT_VERSION =
@@ -66,41 +67,34 @@ const buildUpgradeCommandModule = async ({
appVersion,
commandRunner,
}: BuildUpgradeCommandModuleArgs) => {
const mockDataSourceService = {
getLastDataSourceMetadataFromWorkspaceId: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: commandRunner,
useFactory: (
workspaceRepository: Repository<WorkspaceEntity>,
twentyConfigService: TwentyConfigService,
globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
dataSourceService: DataSourceService,
coreEngineVersionService: CoreEngineVersionService,
workspaceVersionService: WorkspaceVersionService,
coreMigrationRunnerService: CoreMigrationRunnerService,
versionedMigrationRegistryService: VersionedMigrationRegistryService,
workspaceIteratorService: WorkspaceIteratorService,
) => {
return new commandRunner(
workspaceRepository,
twentyConfigService,
globalWorkspaceOrmManager,
dataSourceService,
coreEngineVersionService,
workspaceVersionService,
coreMigrationRunnerService,
versionedMigrationRegistryService,
workspaceIteratorService,
);
},
inject: [
getRepositoryToken(WorkspaceEntity),
TwentyConfigService,
GlobalWorkspaceOrmManager,
DataSourceService,
CoreEngineVersionService,
WorkspaceVersionService,
CoreMigrationRunnerService,
VersionedMigrationRegistryService,
WorkspaceIteratorService,
],
},
{
@@ -131,26 +125,52 @@ const buildUpgradeCommandModule = async ({
}),
},
},
{
provide: GlobalWorkspaceOrmManager,
useValue: {
connect: jest.fn(),
destroyDataSourceForWorkspace: jest.fn(),
getDataSourceForWorkspace: jest.fn(),
executeInWorkspaceContext: jest
.fn()
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
},
},
{
provide: DataSourceService,
useValue: mockDataSourceService,
},
CoreEngineVersionService,
WorkspaceVersionService,
{
provide: CoreMigrationRunnerService,
useValue: { run: jest.fn().mockResolvedValue(undefined) },
useValue: {
runSingleMigration: jest
.fn()
.mockResolvedValue({ status: 'success' }),
},
},
{
provide: VersionedMigrationRegistryService,
useValue: {
getInstanceCommandsForVersion: jest.fn().mockReturnValue([]),
},
},
{
provide: WorkspaceIteratorService,
useValue: {
iterate: jest.fn().mockImplementation(async (args: any) => {
const { callback, ...options } = args;
const workspaceIds =
options.workspaceIds ??
workspaces.map((workspace) => workspace.id);
const report = {
fail: [] as any[],
success: [] as any[],
};
for (const [index, workspaceId] of workspaceIds.entries()) {
try {
await callback({
workspaceId,
index,
total: workspaceIds.length,
});
report.success.push({ workspaceId });
} catch (error) {
report.fail.push({ error, workspaceId });
}
}
return report;
}),
},
},
],
}).compile();
@@ -161,7 +181,6 @@ const buildUpgradeCommandModule = async ({
describe('UpgradeCommandRunner', () => {
let upgradeCommandRunner: BasicUpgradeCommandRunner;
let workspaceRepository: Repository<WorkspaceEntity>;
let coreMigrationRunnerService: CoreMigrationRunnerService;
type BuildModuleAndSetupSpiesArgs = {
numberOfWorkspace?: number;
@@ -197,10 +216,6 @@ describe('UpgradeCommandRunner', () => {
jest.spyOn(upgradeCommandRunner['logger'], 'error').mockImplementation();
jest.spyOn(upgradeCommandRunner['logger'], 'warn').mockImplementation();
jest.spyOn(upgradeCommandRunner, 'runOnWorkspace');
coreMigrationRunnerService = module.get(CoreMigrationRunnerService);
workspaceRepository = module.get<Repository<WorkspaceEntity>>(
getRepositoryToken(WorkspaceEntity),
);
@@ -223,16 +238,6 @@ describe('UpgradeCommandRunner', () => {
// @ts-expect-error legacy noImplicitAny
await upgradeCommandRunner.run(passedParams, options);
const { fail: failReport, success: successReport } =
upgradeCommandRunner.migrationReport;
expect(successReport.length).toBe(1);
expect(failReport.length).toBe(0);
[upgradeCommandRunner.runOnWorkspace].forEach((fn) =>
expect(fn).toHaveBeenCalledTimes(1),
);
[workspaceRepository.update].forEach((fn) =>
expect(fn).not.toHaveBeenCalled(),
);
@@ -251,16 +256,12 @@ describe('UpgradeCommandRunner', () => {
// @ts-expect-error legacy noImplicitAny
await upgradeCommandRunner.run(passedParams, options);
[upgradeCommandRunner.runOnWorkspace].forEach((fn) =>
expect(fn).toHaveBeenCalledTimes(numberOfWorkspace),
);
expect(workspaceRepository.update).toHaveBeenNthCalledWith(
numberOfWorkspace,
{ id: expect.any(String) },
{ version: CURRENT_VERSION },
);
expect(upgradeCommandRunner.migrationReport.success.length).toBe(42);
expect(upgradeCommandRunner.migrationReport.fail.length).toBe(0);
expect(workspaceRepository.update).toHaveBeenCalledTimes(numberOfWorkspace);
});
describe('Workspace upgrade should succeed ', () => {
@@ -308,22 +309,15 @@ describe('UpgradeCommandRunner', () => {
async ({ context: { input } }) => {
await buildModuleAndSetupSpies(input);
// @ts-expect-error legacy noImplicitAny
const passedParams = [];
const options = {};
const passedParams: string[] = [];
const options: UpgradeCommandOptions = {};
// @ts-expect-error legacy noImplicitAny
await upgradeCommandRunner.run(passedParams, options);
const { fail: failReport, success: successReport } =
upgradeCommandRunner.migrationReport;
expect(failReport.length).toBe(0);
expect(successReport.length).toBe(1);
expect(coreMigrationRunnerService.run).toHaveBeenCalledTimes(1);
const { workspaceId } = successReport[0];
expect(workspaceId).toBe('workspace_0');
expect(workspaceRepository.update).toHaveBeenCalledWith(
{ id: 'workspace_0' },
{ version: expect.any(String) },
);
},
);
});
@@ -331,10 +325,7 @@ describe('UpgradeCommandRunner', () => {
describe('Workspace upgrade should fail', () => {
const failingTestUseCases: EachTestingContext<{
input: Omit<BuildModuleAndSetupSpiesArgs, 'numberOfWorkspace'>;
output?: {
failReportWorkspaceId: string;
expectedErrorMessage: string;
};
expectedErrorMessage: string;
}>[] = [
{
title: 'when workspace version is not equal to fromVersion',
@@ -344,10 +335,8 @@ describe('UpgradeCommandRunner', () => {
version: '0.1.0',
},
},
output: {
failReportWorkspaceId: 'workspace_0',
expectedErrorMessage: `Unable to run the upgrade command. Aborting the upgrade process.\nPlease ensure that all workspaces are on at least the previous minor version (${PREVIOUS_VERSION}).\nIf any workspaces are not on the previous minor version, roll back to that version and run the upgrade command again.`,
},
expectedErrorMessage:
'Unable to run the upgrade command. Aborting the upgrade process.',
},
},
{
@@ -358,10 +347,8 @@ describe('UpgradeCommandRunner', () => {
version: null,
},
},
output: {
failReportWorkspaceId: 'workspace_0',
expectedErrorMessage: `Unable to run the upgrade command. Aborting the upgrade process.\nPlease ensure that all workspaces are on at least the previous minor version (${PREVIOUS_VERSION}).\nIf any workspaces are not on the previous minor version, roll back to that version and run the upgrade command again.`,
},
expectedErrorMessage:
'Unable to run the upgrade command. Aborting the upgrade process.',
},
},
{
@@ -370,11 +357,8 @@ describe('UpgradeCommandRunner', () => {
input: {
appVersion: null,
},
output: {
failReportWorkspaceId: 'global',
expectedErrorMessage:
'APP_VERSION is not defined, please double check your env variables',
},
expectedErrorMessage:
'APP_VERSION is not defined, please double check your env variables',
},
},
{
@@ -383,11 +367,8 @@ describe('UpgradeCommandRunner', () => {
input: {
appVersion: '42.0.0',
},
output: {
failReportWorkspaceId: 'global',
expectedErrorMessage:
'No command found for version 42.0.0. Please check the commands record.',
},
expectedErrorMessage:
'No command found for version 42.0.0. Please check the commands record.',
},
},
{
@@ -396,33 +377,22 @@ describe('UpgradeCommandRunner', () => {
input: {
appVersion: UPGRADE_COMMAND_SUPPORTED_VERSIONS[0],
},
output: {
failReportWorkspaceId: 'global',
expectedErrorMessage: `No previous version found for version ${UPGRADE_COMMAND_SUPPORTED_VERSIONS[0]}. Available versions: ${UPGRADE_COMMAND_SUPPORTED_VERSIONS.join(', ')}`,
},
expectedErrorMessage: `No previous version found for version ${UPGRADE_COMMAND_SUPPORTED_VERSIONS[0]}`,
},
},
];
it.each(eachTestingContextFilter(failingTestUseCases))(
'$title',
async ({ context: { input, output } }) => {
async ({ context: { input, expectedErrorMessage } }) => {
await buildModuleAndSetupSpies(input);
const passedParams: string[] = [];
const options = {};
await upgradeCommandRunner.run(passedParams, options);
const { fail: failReport, success: successReport } =
upgradeCommandRunner.migrationReport;
expect(successReport.length).toBe(0);
expect(failReport.length).toBe(1);
const { workspaceId, error } = failReport[0];
expect(workspaceId).toBe(output?.failReportWorkspaceId ?? 'global');
expect(error).toEqual(new Error(output?.expectedErrorMessage ?? ''));
await expect(
upgradeCommandRunner.run(passedParams, options),
).rejects.toThrow(expectedErrorMessage);
},
);
});
@@ -0,0 +1,23 @@
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import {
WorkspaceCommandRunner,
type WorkspaceCommandOptions,
} from 'src/database/commands/command-runners/workspace.command-runner';
import { type WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
export type ActiveOrSuspendedWorkspaceCommandOptions = WorkspaceCommandOptions;
export abstract class ActiveOrSuspendedWorkspaceCommandRunner<
Options extends
ActiveOrSuspendedWorkspaceCommandOptions = ActiveOrSuspendedWorkspaceCommandOptions,
> extends WorkspaceCommandRunner<Options> {
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
) {
super(workspaceIteratorService, [
WorkspaceActivationStatus.ACTIVE,
WorkspaceActivationStatus.SUSPENDED,
]);
}
}
@@ -1,29 +0,0 @@
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { type Repository } from 'typeorm';
import {
WorkspacesMigrationCommandRunner,
type WorkspacesMigrationCommandOptions,
} from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { type DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { type GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
export type ActiveOrSuspendedWorkspacesMigrationCommandOptions =
WorkspacesMigrationCommandOptions;
export abstract class ActiveOrSuspendedWorkspacesMigrationCommandRunner<
Options extends
ActiveOrSuspendedWorkspacesMigrationCommandOptions = ActiveOrSuspendedWorkspacesMigrationCommandOptions,
> extends WorkspacesMigrationCommandRunner<Options> {
constructor(
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
) {
super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService, [
WorkspaceActivationStatus.ACTIVE,
WorkspaceActivationStatus.SUSPENDED,
]);
}
}
@@ -52,11 +52,11 @@ export abstract class MigrationCommandRunner extends CommandRunner {
try {
await this.runMigrationCommand(passedParams, options);
this.logger.log(chalk.blue('Command completed!'));
} catch (error) {
this.logger.error(chalk.red(`Command failed`));
throw error;
} finally {
this.logger.log(chalk.blue('Command completed!'));
}
}
@@ -1,25 +1,26 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { CommandRunner, Option } from 'nest-commander';
import { SemVer } from 'semver';
import { isDefined } from 'twenty-shared/utils';
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import {
ActiveOrSuspendedWorkspacesMigrationCommandOptions,
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
type WorkspaceIteratorContext,
WorkspaceIteratorService,
} from 'src/database/commands/command-runners/workspace-iterator.service';
import {
RunOnWorkspaceArgs,
WorkspacesMigrationCommandRunner,
} from 'src/database/commands/command-runners/workspaces-migration.command-runner';
type RunOnWorkspaceArgs,
WorkspaceCommandRunner,
} from 'src/database/commands/command-runners/workspace.command-runner';
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
import { VersionedMigrationRegistryService } from 'src/database/commands/core-migration-runner/services/versioned-migration-registry.service';
import { CommandLogger } from 'src/database/commands/logger';
import { type UpgradeCommandVersion } from 'src/engine/constants/upgrade-command-supported-versions.constant';
import { CoreEngineVersionService } from 'src/engine/core-engine-version/services/core-engine-version.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { type DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service';
import {
type CompareVersionMajorAndMinorReturnType,
@@ -27,78 +28,123 @@ import {
} from 'src/utils/version/compare-version-minor-and-major';
export type VersionCommands = (
| WorkspacesMigrationCommandRunner
| ActiveOrSuspendedWorkspacesMigrationCommandRunner
| WorkspaceCommandRunner
| ActiveOrSuspendedWorkspaceCommandRunner
)[];
export type AllCommands = Record<UpgradeCommandVersion, VersionCommands>;
export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
private fromWorkspaceVersion: SemVer;
private currentAppVersion: SemVer;
export type UpgradeCommandOptions = {
workspaceId?: Set<string>;
startFromWorkspaceId?: string;
workspaceCountLimit?: number;
dryRun?: boolean;
verbose?: boolean;
};
type VersionContext = {
fromWorkspaceVersion: SemVer;
currentAppVersion: SemVer;
currentVersionMajorMinor: UpgradeCommandVersion;
workspaceCommands: VersionCommands;
};
export abstract class UpgradeCommandRunner extends CommandRunner {
protected logger: CommandLogger;
public abstract allCommands: AllCommands;
public commands: VersionCommands;
public readonly VALIDATE_WORKSPACE_VERSION_FEATURE_FLAG?: true;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyConfigService: TwentyConfigService,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly coreEngineVersionService: CoreEngineVersionService,
protected readonly workspaceVersionService: WorkspaceVersionService,
protected readonly coreMigrationRunnerService: CoreMigrationRunnerService,
protected readonly versionedMigrationRegistryService: VersionedMigrationRegistryService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
) {
super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService);
super();
this.logger = new CommandLogger({
verbose: false,
constructorName: this.constructor.name,
});
}
private setUpgradeContextVersionsAndCommandsForCurrentAppVersion() {
const upgradeContextIsAlreadyDefined = [
this.currentAppVersion,
this.commands,
this.fromWorkspaceVersion,
].every(isDefined);
if (upgradeContextIsAlreadyDefined) {
return;
}
const currentAppVersion = this.coreEngineVersionService.getCurrentVersion();
const currentVersionMajorMinor =
`${currentAppVersion.major}.${currentAppVersion.minor}.0` as UpgradeCommandVersion;
const currentCommands = this.allCommands[currentVersionMajorMinor];
if (!isDefined(currentCommands)) {
throw new Error(
`No command found for version ${currentAppVersion}. Please check the commands record.`,
);
}
const previousVersion = this.coreEngineVersionService.getPreviousVersion();
this.commands = currentCommands;
this.fromWorkspaceVersion = previousVersion;
this.currentAppVersion = currentAppVersion;
const message = [
'Initialized upgrade context with:',
`- currentVersion (migrating to): ${currentAppVersion}`,
`- fromWorkspaceVersion: ${previousVersion}`,
`- ${this.commands.length} commands`,
];
this.logger.log(chalk.blue(message.join('\n ')));
@Option({
flags: '-d, --dry-run',
description: 'Simulate the command without making actual changes',
required: false,
})
parseDryRun(): boolean {
return true;
}
override async runMigrationCommand(
passedParams: string[],
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions,
@Option({
flags: '-v, --verbose',
description: 'Verbose output',
required: false,
})
parseVerbose(): boolean {
return true;
}
@Option({
flags: '-w, --workspace-id [workspace_id]',
description:
'workspace id. Command runs on all active/suspended workspaces if not provided.',
required: false,
})
parseWorkspaceId(val: string, previous?: Set<string>): Set<string> {
const accumulator = previous ?? new Set<string>();
accumulator.add(val);
return accumulator;
}
@Option({
flags: '--start-from-workspace-id [workspace_id]',
description:
'Start from a specific workspace id. Workspaces are processed in ascending order of id.',
required: false,
})
parseStartFromWorkspaceId(val: string): string {
return val;
}
@Option({
flags: '--workspace-count-limit [count]',
description:
'Limit the number of workspaces to process. Workspaces are processed in ascending order of id.',
required: false,
})
parseWorkspaceCountLimit(val: string): number {
const limit = parseInt(val);
if (isNaN(limit)) {
throw new Error('Workspace count limit must be a number');
}
if (limit <= 0) {
throw new Error('Workspace count limit must be greater than 0');
}
return limit;
}
override async run(
_passedParams: string[],
options: UpgradeCommandOptions,
): Promise<void> {
try {
this.setUpgradeContextVersionsAndCommandsForCurrentAppVersion();
if (options.verbose) {
this.logger = new CommandLogger({
verbose: true,
constructorName: this.constructor.name,
});
}
try {
const versionContext = this.resolveVersionContext();
// On fresh installs there are no workspaces yet, so skip the
// per-workspace upgrade loop (core migrations already ran above).
const hasWorkspaces =
await this.workspaceVersionService.hasActiveOrSuspendedWorkspaces();
@@ -110,75 +156,159 @@ export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMi
return;
}
const workspacesThatAreBelowFromWorkspaceVersion =
const workspacesBelowMinimumVersion =
await this.workspaceVersionService.getWorkspacesBelowVersion(
this.fromWorkspaceVersion.version,
versionContext.fromWorkspaceVersion.version,
);
if (workspacesThatAreBelowFromWorkspaceVersion.length > 0) {
this.migrationReport.fail.push(
...workspacesThatAreBelowFromWorkspaceVersion.map((workspace) => ({
error: new Error(
`Unable to run the upgrade command. Aborting the upgrade process.
Please ensure that all workspaces are on at least the previous minor version (${this.fromWorkspaceVersion.version}).
If any workspaces are not on the previous minor version, roll back to that version and run the upgrade command again.`,
),
workspaceId: workspace.id,
})),
if (workspacesBelowMinimumVersion.length > 0) {
const ineligibleIds = workspacesBelowMinimumVersion
.map((workspace) => workspace.id)
.join(', ');
throw new Error(
`Unable to run the upgrade command. Aborting the upgrade process.
Workspaces below minimum version (${versionContext.fromWorkspaceVersion.version}): ${ineligibleIds}.
Please roll back to that version and run the upgrade command again.`,
);
}
} catch (error) {
this.migrationReport.fail.push({
error,
workspaceId: 'global',
});
}
if (this.migrationReport.fail.length > 0) {
this.migrationReport.fail.forEach(({ error, workspaceId }) =>
const instanceCommands =
this.versionedMigrationRegistryService.getInstanceCommandsForVersion(
versionContext.currentVersionMajorMinor,
);
for (const instanceCommand of instanceCommands) {
const migrationName = instanceCommand.constructor.name;
const result =
await this.coreMigrationRunnerService.runSingleMigration(
migrationName,
);
if (result.status === 'fail' && result.error !== 'already-executed') {
this.logger.error(
`Core migration ${migrationName} failed with error: ${result.error}`,
);
return;
}
}
const iteratorReport = await this.workspaceIteratorService.iterate({
workspaceIds:
options.workspaceId && options.workspaceId.size > 0
? Array.from(options.workspaceId)
: undefined,
startFromWorkspaceId: options.startFromWorkspaceId,
workspaceCountLimit: options.workspaceCountLimit,
dryRun: options.dryRun,
callback: async (context) => {
await this.runOnWorkspace(context, options, versionContext);
},
});
if (iteratorReport.fail.length > 0) {
this.logger.error(
`Error in workspace ${workspaceId}: ${error.message}`,
chalk.red(
`Upgrade completed with ${iteratorReport.fail.length} workspace failure(s)`,
),
);
}
this.logger.log(
chalk.blue(
`Upgrade summary: ${iteratorReport.success.length} succeeded, ${iteratorReport.fail.length} failed`,
),
);
return;
this.logger.log(chalk.blue('Command completed!'));
} catch (error) {
this.logger.error(chalk.red(`Upgrade failed: ${error.message}`));
throw error;
}
await this.coreMigrationRunnerService.run();
await super.runMigrationCommand(passedParams, options);
}
override async runOnWorkspace(args: RunOnWorkspaceArgs): Promise<void> {
this.setUpgradeContextVersionsAndCommandsForCurrentAppVersion();
private resolveVersionContext(): VersionContext {
const currentAppVersion = this.coreEngineVersionService.getCurrentVersion();
const currentVersionMajorMinor =
`${currentAppVersion.major}.${currentAppVersion.minor}.0` as UpgradeCommandVersion;
const workspaceCommands = this.allCommands[currentVersionMajorMinor];
const { workspaceId, index, total, options } = args;
if (!isDefined(workspaceCommands)) {
throw new Error(
`No command found for version ${currentAppVersion}. Please check the commands record.`,
);
}
const fromWorkspaceVersion =
this.coreEngineVersionService.getPreviousVersion();
const instanceCommands =
this.versionedMigrationRegistryService.getInstanceCommandsForVersion(
currentVersionMajorMinor,
);
this.logger.log(
chalk.blue(
`${options.dryRun ? '(dry run) ' : ''}Upgrading workspace ${workspaceId} from=${this.fromWorkspaceVersion} to=${this.currentAppVersion} ${index + 1}/${total}`,
[
'Initialized upgrade context with:',
`- currentVersion (migrating to): ${currentAppVersion}`,
`- fromWorkspaceVersion: ${fromWorkspaceVersion}`,
`- ${instanceCommands.length} instance commands (from registry)`,
`- ${workspaceCommands.length} workspace commands`,
].join('\n '),
),
);
const workspaceVersionCompareResult =
await this.retrieveWorkspaceVersionAndCompareToWorkspaceFromVersion(
return {
fromWorkspaceVersion,
currentAppVersion,
currentVersionMajorMinor,
workspaceCommands,
};
}
private async runOnWorkspace(
iteratorContext: WorkspaceIteratorContext,
options: UpgradeCommandOptions,
versionContext: VersionContext,
): Promise<void> {
const { workspaceId, index, total } = iteratorContext;
const { fromWorkspaceVersion, currentAppVersion, workspaceCommands } =
versionContext;
this.logger.log(
chalk.blue(
`${options.dryRun ? '(dry run) ' : ''}Upgrading workspace ${workspaceId} from=${fromWorkspaceVersion} to=${currentAppVersion} ${index + 1}/${total}`,
),
);
const versionCompareResult =
await this.compareWorkspaceVersionToFromVersion(
workspaceId,
fromWorkspaceVersion,
);
switch (workspaceVersionCompareResult) {
switch (versionCompareResult) {
case 'lower': {
throw new Error(
`WORKSPACE_VERSION_MISSMATCH Upgrade for workspace ${workspaceId} failed as its version is beneath fromWorkspaceVersion=${this.fromWorkspaceVersion.version}`,
`WORKSPACE_VERSION_MISSMATCH Upgrade for workspace ${workspaceId} failed as its version is beneath fromWorkspaceVersion=${fromWorkspaceVersion.version}`,
);
}
case 'equal': {
for (const command of this.commands) {
await command.runOnWorkspace(args);
for (const workspaceCommand of workspaceCommands) {
await workspaceCommand.runOnWorkspace({
options: options as RunOnWorkspaceArgs['options'],
workspaceId,
dataSource: iteratorContext.dataSource,
index,
total,
});
}
if (!options.dryRun) {
await this.workspaceRepository.update(
{ id: workspaceId },
{ version: this.currentAppVersion.version },
{ version: currentAppVersion.version },
);
}
@@ -198,15 +328,14 @@ If any workspaces are not on the previous minor version, roll back to that versi
return;
}
default: {
throw new Error(
`Should never occur, encountered unexpected value from retrieveWorkspaceVersionAndCompareToWorkspaceFromVersion ${workspaceVersionCompareResult}`,
);
assertUnreachable(versionCompareResult);
}
}
}
private async retrieveWorkspaceVersionAndCompareToWorkspaceFromVersion(
private async compareWorkspaceVersionToFromVersion(
workspaceId: string,
fromWorkspaceVersion: SemVer,
): Promise<CompareVersionMajorAndMinorReturnType> {
const workspace = await this.workspaceRepository.findOneByOrFail({
id: workspaceId,
@@ -219,7 +348,7 @@ If any workspaces are not on the previous minor version, roll back to that versi
return compareVersionMajorAndMinor(
currentWorkspaceVersion,
this.fromWorkspaceVersion.version,
fromWorkspaceVersion.version,
);
}
}
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
@Module({
imports: [TypeOrmModule.forFeature([WorkspaceEntity]), DataSourceModule],
providers: [WorkspaceIteratorService],
exports: [WorkspaceIteratorService],
})
export class WorkspaceIteratorModule {}
@@ -0,0 +1,139 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { In, MoreThanOrEqual, Repository } from 'typeorm';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
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';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
export type WorkspaceIteratorArgs = {
workspaceIds?: string[];
activationStatuses?: WorkspaceActivationStatus[];
startFromWorkspaceId?: string;
workspaceCountLimit?: number;
dryRun?: boolean;
callback: (context: WorkspaceIteratorContext) => Promise<void>;
};
export type WorkspaceIteratorContext = {
workspaceId: string;
dataSource?: GlobalWorkspaceDataSource;
index: number;
total: number;
};
export type WorkspaceIteratorReport = {
fail: {
workspaceId: string;
error: Error;
}[];
success: {
workspaceId: string;
}[];
};
const DEFAULT_ACTIVATION_STATUSES = [
WorkspaceActivationStatus.ACTIVE,
WorkspaceActivationStatus.SUSPENDED,
];
@Injectable()
export class WorkspaceIteratorService {
private readonly logger = new Logger(WorkspaceIteratorService.name);
constructor(
@InjectRepository(WorkspaceEntity)
private readonly workspaceRepository: Repository<WorkspaceEntity>,
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
private readonly dataSourceService: DataSourceService,
) {}
async iterate(args: WorkspaceIteratorArgs): Promise<WorkspaceIteratorReport> {
const { callback, ...options } = args;
const report: WorkspaceIteratorReport = {
fail: [],
success: [],
};
const workspaceIdsToProcess =
options.workspaceIds && options.workspaceIds.length > 0
? options.workspaceIds
: await this.fetchWorkspaceIds(options);
if (options.dryRun) {
this.logger.log(chalk.yellow('Dry run mode: No changes will be applied'));
}
for (const [index, workspaceId] of workspaceIdsToProcess.entries()) {
this.logger.log(
`Running on workspace ${workspaceId} ${index + 1}/${workspaceIdsToProcess.length}`,
);
try {
const authContext = buildSystemAuthContext(workspaceId);
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
async () => {
const workspaceHasDataSource =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId(
workspaceId,
);
const dataSource = isDefined(workspaceHasDataSource)
? await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource()
: undefined;
await callback({
workspaceId,
dataSource,
index,
total: workspaceIdsToProcess.length,
});
},
authContext,
);
report.success.push({ workspaceId });
} catch (error: unknown) {
report.fail.push({ error: error as Error, workspaceId });
}
}
report.fail.forEach(({ error, workspaceId }) =>
this.logger.error(
`Error in workspace ${workspaceId}: ${error.message}`,
error.stack,
),
);
return report;
}
private async fetchWorkspaceIds(
options: Omit<WorkspaceIteratorArgs, 'callback'>,
): Promise<string[]> {
const activationStatuses =
options.activationStatuses ?? DEFAULT_ACTIVATION_STATUSES;
const workspaces = await this.workspaceRepository.find({
select: ['id'],
where: {
activationStatus: In(activationStatuses),
...(options.startFromWorkspaceId
? { id: MoreThanOrEqual(options.startFromWorkspaceId) }
: {}),
},
order: { id: 'ASC' },
take: options.workspaceCountLimit,
});
return workspaces.map((workspace) => workspace.id);
}
}
@@ -0,0 +1,140 @@
import chalk from 'chalk';
import { CommandRunner, Option } from 'nest-commander';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { CommandLogger } from 'src/database/commands/logger';
import { GlobalWorkspaceDataSource } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource';
export type WorkspaceCommandOptions = {
workspaceId?: Set<string>;
startFromWorkspaceId?: string;
workspaceCountLimit?: number;
dryRun?: boolean;
verbose?: boolean;
};
export type RunOnWorkspaceArgs = {
options: WorkspaceCommandOptions;
workspaceId: string;
dataSource?: GlobalWorkspaceDataSource;
index: number;
total: number;
};
export abstract class WorkspaceCommandRunner<
Options extends WorkspaceCommandOptions = WorkspaceCommandOptions,
> extends CommandRunner {
protected logger: CommandLogger;
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
protected readonly activationStatuses: WorkspaceActivationStatus[],
) {
super();
this.logger = new CommandLogger({
verbose: false,
constructorName: this.constructor.name,
});
}
@Option({
flags: '-d, --dry-run',
description: 'Simulate the command without making actual changes',
required: false,
})
parseDryRun(): boolean {
return true;
}
@Option({
flags: '-v, --verbose',
description: 'Verbose output',
required: false,
})
parseVerbose(): boolean {
return true;
}
@Option({
flags: '--start-from-workspace-id [workspace_id]',
description:
'Start from a specific workspace id. Workspaces are processed in ascending order of id.',
required: false,
})
parseStartFromWorkspaceId(val: string): string {
return val;
}
@Option({
flags: '--workspace-count-limit [count]',
description:
'Limit the number of workspaces to process. Workspaces are processed in ascending order of id.',
required: false,
})
parseWorkspaceCountLimit(val: string): number {
const limit = parseInt(val);
if (isNaN(limit)) {
throw new Error('Workspace count limit must be a number');
}
if (limit <= 0) {
throw new Error('Workspace count limit must be greater than 0');
}
return limit;
}
@Option({
flags: '-w, --workspace-id [workspace_id]',
description:
'workspace id. Command runs on all workspaces matching the activation statuses if not provided.',
required: false,
})
parseWorkspaceId(val: string, previous?: Set<string>): Set<string> {
const accumulator = previous ?? new Set<string>();
accumulator.add(val);
return accumulator;
}
override async run(_passedParams: string[], options: Options): Promise<void> {
if (options.verbose) {
this.logger = new CommandLogger({
verbose: true,
constructorName: this.constructor.name,
});
}
try {
await this.workspaceIteratorService.iterate({
workspaceIds:
options.workspaceId && options.workspaceId.size > 0
? Array.from(options.workspaceId)
: undefined,
activationStatuses: this.activationStatuses,
startFromWorkspaceId: options.startFromWorkspaceId,
workspaceCountLimit: options.workspaceCountLimit,
dryRun: options.dryRun,
callback: async (context) => {
await this.runOnWorkspace({
options,
workspaceId: context.workspaceId,
dataSource: context.dataSource,
index: context.index,
total: context.total,
});
},
});
this.logger.log(chalk.blue('Command completed!'));
} catch (error) {
this.logger.error(chalk.red(`Command failed`));
throw error;
}
}
public abstract runOnWorkspace(args: RunOnWorkspaceArgs): Promise<void>;
}
@@ -1,189 +0,0 @@
import chalk from 'chalk';
import { Option } from 'nest-commander';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { In, MoreThanOrEqual, type Repository } from 'typeorm';
import { MigrationCommandRunner } from 'src/database/commands/command-runners/migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { type DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceDataSource } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource';
import { type GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
export type WorkspacesMigrationCommandOptions = {
workspaceIds: string[];
startFromWorkspaceId?: string;
workspaceCountLimit?: number;
dryRun?: boolean;
verbose?: boolean;
};
export type RunOnWorkspaceArgs = {
options: WorkspacesMigrationCommandOptions;
workspaceId: string;
dataSource?: GlobalWorkspaceDataSource;
index: number;
total: number;
};
export type WorkspaceMigrationReport = {
fail: {
workspaceId: string;
error: Error;
}[];
success: {
workspaceId: string;
}[];
};
export abstract class WorkspacesMigrationCommandRunner<
Options extends
WorkspacesMigrationCommandOptions = WorkspacesMigrationCommandOptions,
> extends MigrationCommandRunner {
protected workspaceIds: Set<string> = new Set();
private startFromWorkspaceId: string | undefined;
private workspaceCountLimit: number | undefined;
public migrationReport: WorkspaceMigrationReport = {
fail: [],
success: [],
};
constructor(
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly activationStatuses: WorkspaceActivationStatus[],
) {
super();
}
@Option({
flags: '--start-from-workspace-id [workspace_id]',
description:
'Start from a specific workspace id. Workspaces are processed in ascending order of id.',
required: false,
})
parseStartFromWorkspaceId(val: string): string {
this.startFromWorkspaceId = val;
return val;
}
@Option({
flags: '--workspace-count-limit [count]',
description:
'Limit the number of workspaces to process. Workspaces are processed in ascending order of id.',
required: false,
})
parseWorkspaceCountLimit(val: string): number {
this.workspaceCountLimit = parseInt(val);
if (isNaN(this.workspaceCountLimit)) {
throw new Error('Workspace count limit must be a number');
}
if (this.workspaceCountLimit <= 0) {
throw new Error('Workspace count limit must be greater than 0');
}
return this.workspaceCountLimit;
}
@Option({
flags: '-w, --workspace-id [workspace_id]',
description:
'workspace id. Command runs on all workspaces matching the activation statuses if not provided.',
required: false,
})
parseWorkspaceId(val: string): Set<string> {
this.workspaceIds.add(val);
return this.workspaceIds;
}
protected async fetchWorkspaceIds(): Promise<string[]> {
const workspaces = await this.workspaceRepository.find({
select: ['id'],
where: {
activationStatus: In(this.activationStatuses),
...(this.startFromWorkspaceId
? { id: MoreThanOrEqual(this.startFromWorkspaceId) }
: {}),
},
order: {
id: 'ASC',
},
take: this.workspaceCountLimit,
});
return workspaces.map((workspace) => workspace.id);
}
override async runMigrationCommand(
_passedParams: string[],
options: Options,
) {
const workspaceIdsToProcess =
this.workspaceIds.size > 0
? Array.from(this.workspaceIds)
: await this.fetchWorkspaceIds();
if (options.dryRun) {
this.logger.log(chalk.yellow('Dry run mode: No changes will be applied'));
}
for (const [index, workspaceId] of workspaceIdsToProcess.entries()) {
this.logger.log(
`Upgrading workspace ${workspaceId} ${index + 1}/${workspaceIdsToProcess.length}`,
);
try {
const authContext = buildSystemAuthContext(workspaceId);
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
async () => {
const workspaceHasDataSource =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId(
workspaceId,
);
const dataSource = isDefined(workspaceHasDataSource)
? await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource()
: undefined;
await this.runOnWorkspace({
options,
workspaceId,
dataSource,
index: index,
total: workspaceIdsToProcess.length,
});
},
authContext,
);
this.migrationReport.success.push({
workspaceId,
});
} catch (error) {
this.migrationReport.fail.push({
error,
workspaceId,
});
this.logger.warn(
chalk.red(`Error in workspace ${workspaceId}: ${error.message}`),
);
}
}
this.migrationReport.fail.forEach(({ error, workspaceId }) =>
this.logger.error(
`Error in workspace ${workspaceId}: ${error.message}`,
error.stack,
),
);
}
public abstract runOnWorkspace(args: RunOnWorkspaceArgs): Promise<void>;
}
@@ -1,9 +1,10 @@
import { Module } from '@nestjs/common';
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
import { VersionedMigrationRegistryService } from 'src/database/commands/core-migration-runner/services/versioned-migration-registry.service';
@Module({
providers: [CoreMigrationRunnerService],
exports: [CoreMigrationRunnerService],
providers: [CoreMigrationRunnerService, VersionedMigrationRegistryService],
exports: [CoreMigrationRunnerService, VersionedMigrationRegistryService],
})
export class CoreMigrationRunnerModule {}
@@ -1,7 +1,17 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { DataSource, MigrationExecutor, type QueryRunner } from 'typeorm';
export type RunSingleMigrationError =
| 'already-executed'
| 'migration-execution-failed'
| 'migration-instance-not-defined'
| 'migration-not-registered';
export type RunSingleMigrationResult =
| { status: 'success' }
| { error: RunSingleMigrationError; status: 'fail' };
@Injectable()
export class CoreMigrationRunnerService {
@@ -12,7 +22,7 @@ export class CoreMigrationRunnerService {
private readonly dataSource: DataSource,
) {}
async run(): Promise<void> {
async runAllPendingMigrations(): Promise<void> {
this.logger.log('Running core datasource migrations...');
try {
@@ -34,4 +44,107 @@ export class CoreMigrationRunnerService {
throw error;
}
}
async runSingleMigration(
migrationName: string,
): Promise<RunSingleMigrationResult> {
this.logger.log(`Running core datasource migration ${migrationName}...`);
const queryRunner = this.dataSource.createQueryRunner();
const migrationExecutor = new MigrationExecutor(
this.dataSource,
queryRunner,
);
try {
await queryRunner.connect();
const pendingMigrations = await migrationExecutor.getPendingMigrations();
const pendingMigration = pendingMigrations.find(
(migration) => migration.name === migrationName,
);
if (!pendingMigration) {
const registeredMigration = (
await migrationExecutor.getAllMigrations()
).find((migration) => migration.name === migrationName);
if (!registeredMigration) {
return {
error: 'migration-not-registered',
status: 'fail',
};
}
this.logger.log(`Migration ${migrationName} has already been executed`);
return {
error: 'already-executed',
status: 'fail',
};
}
if (!pendingMigration.instance) {
return {
error: 'migration-instance-not-defined',
status: 'fail',
};
}
await this.createMetadataTableIfNecessary(queryRunner);
const shouldRunInTransaction =
pendingMigration.instance.transaction ?? true;
await queryRunner.beforeMigration();
try {
if (shouldRunInTransaction) {
await queryRunner.startTransaction();
}
await pendingMigration.instance.up(queryRunner);
await migrationExecutor.insertMigration(pendingMigration);
if (shouldRunInTransaction) {
await queryRunner.commitTransaction();
}
} catch (error) {
if (shouldRunInTransaction && queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction();
}
throw error;
} finally {
await queryRunner.afterMigration();
}
this.logger.log(`Executed migration successfully: ${migrationName}`);
return { status: 'success' };
} catch (error) {
this.logger.error('Error running database migration:', error);
return {
error: 'migration-execution-failed',
status: 'fail',
};
} finally {
await queryRunner.release();
}
}
private async createMetadataTableIfNecessary(
queryRunner: QueryRunner,
): Promise<void> {
const schemaBuilder = this.dataSource.driver.createSchemaBuilder() as {
createMetadataTableIfNecessary?: (
queryRunnerToUse: QueryRunner,
) => Promise<void>;
};
if (schemaBuilder.createMetadataTableIfNecessary) {
await schemaBuilder.createMetadataTableIfNecessary(queryRunner);
}
}
}
@@ -0,0 +1,64 @@
import { Injectable, Logger, type OnModuleInit } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { type MigrationInterface, DataSource } from 'typeorm';
import { getVersionedMigrationVersion } from 'src/database/typeorm/core/decorators/versioned-migration.decorator';
import {
UPGRADE_COMMAND_SUPPORTED_VERSIONS,
type UpgradeCommandVersion,
} from 'src/engine/constants/upgrade-command-supported-versions.constant';
@Injectable()
export class VersionedMigrationRegistryService implements OnModuleInit {
private readonly logger = new Logger(VersionedMigrationRegistryService.name);
private readonly migrationsByVersion = new Map<
UpgradeCommandVersion,
MigrationInterface[]
>();
constructor(
@InjectDataSource()
private readonly dataSource: DataSource,
) {}
onModuleInit(): void {
for (const version of UPGRADE_COMMAND_SUPPORTED_VERSIONS) {
this.migrationsByVersion.set(version, []);
}
// dataSource.migrations is already sorted by timestamp (TypeORM sorts
// ascending by the 13-digit suffix of the class name)
for (const migration of this.dataSource.migrations) {
const constructor = migration.constructor;
const version = getVersionedMigrationVersion(constructor);
if (version === undefined) {
continue;
}
const bucket = this.migrationsByVersion.get(version);
if (!bucket) {
continue;
}
bucket.push(migration);
}
for (const [version, migrations] of this.migrationsByVersion) {
if (migrations.length > 0) {
this.logger.log(
`Registered ${migrations.length} versioned migration(s) for ${version}: ${migrations.map((migration) => migration.constructor.name).join(', ')}`,
);
}
}
}
getInstanceCommandsForVersion(
version: UpgradeCommandVersion,
): MigrationInterface[] {
return this.migrationsByVersion.get(version) ?? [];
}
}
@@ -70,6 +70,6 @@ export class RunTypeormMigrationCommand extends CommandRunner {
}
}
await this.coreMigrationRunnerService.run();
await this.coreMigrationRunnerService.runAllPendingMigrations();
}
}
@@ -1,21 +1,17 @@
import { InjectRepository } from '@nestjs/typeorm';
import { isNonEmptyString } from '@sniptt/guards';
import { Command } from 'nest-commander';
import { FeatureFlagKey } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { v4 as uuidv4 } from 'uuid';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
@@ -38,19 +34,17 @@ import {
description:
'Backfill missing standard and trigger workflow version command menu items for existing workspaces and enable IS_COMMAND_MENU_ITEM_ENABLED feature flag',
})
export class BackfillCommandMenuItemsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class BackfillCommandMenuItemsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
private readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
private readonly applicationService: ApplicationService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly featureFlagService: FeatureFlagService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,32 +1,27 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { makeNavigationMenuItemTypeNotNullQueries } from 'src/database/typeorm/core/migrations/utils/1773681736596-makeNavigationMenuItemTypeNotNull.util';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'upgrade:1-20:backfill-navigation-menu-item-type',
description:
'Backfill navigation menu item type based on existing columns, then apply NOT NULL and CHECK constraints',
})
export class BackfillNavigationMenuItemTypeCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class BackfillNavigationMenuItemTypeCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private hasRunOnce = false;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,31 +1,26 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { DataSource, Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
@Command({
name: 'upgrade:1-20:backfill-select-field-option-ids',
description:
'Backfill missing ids on SELECT and MULTI_SELECT field metadata options',
})
export class BackfillSelectFieldOptionIdsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class BackfillSelectFieldOptionIdsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,4 +1,3 @@
import { Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
@@ -6,33 +5,24 @@ import { In, Repository } from 'typeorm';
import { isDefined } from 'twenty-shared/utils';
import { NavigationMenuItemType } from 'twenty-shared/types';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { NavigationMenuItemEntity } from 'src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
@Command({
name: 'upgrade:1-20:delete-orphan-navigation-menu-items',
description: 'Delete navigation menu items pointing to deleted views',
})
export class DeleteOrphanNavigationMenuItemsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
protected readonly logger = new Logger(
DeleteOrphanNavigationMenuItemsCommand.name,
);
export class DeleteOrphanNavigationMenuItemsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectRepository(NavigationMenuItemEntity)
private readonly navigationMenuItemRepository: Repository<NavigationMenuItemEntity>,
private readonly workspaceCacheService: WorkspaceCacheService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,35 +1,30 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, IsNull, type Repository } from 'typeorm';
import { DataSource, IsNull } from 'typeorm';
import { v4 } from 'uuid';
import { isDefined } from 'twenty-shared/utils';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'upgrade:1-20:identify-field-permission-metadata',
description:
'Identify field permission metadata (backfill universalIdentifier and applicationId)',
})
export class IdentifyFieldPermissionMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class IdentifyFieldPermissionMetadataCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private hasRunOnce = false;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,35 +1,30 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, IsNull, type Repository } from 'typeorm';
import { DataSource, IsNull } from 'typeorm';
import { v4 } from 'uuid';
import { isDefined } from 'twenty-shared/utils';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'upgrade:1-20:identify-object-permission-metadata',
description:
'Identify object permission metadata (backfill universalIdentifier and applicationId)',
})
export class IdentifyObjectPermissionMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class IdentifyObjectPermissionMetadataCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private hasRunOnce = false;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,35 +1,30 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, IsNull, type Repository } from 'typeorm';
import { DataSource, IsNull } from 'typeorm';
import { v4 } from 'uuid';
import { isDefined } from 'twenty-shared/utils';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'upgrade:1-20:identify-permission-flag-metadata',
description:
'Identify permission flag metadata (backfill universalIdentifier and applicationId)',
})
export class IdentifyPermissionFlagMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class IdentifyPermissionFlagMetadataCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private hasRunOnce = false;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,32 +1,27 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, type Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullQueries } from 'src/database/typeorm/core/migrations/utils/1773400000000-make-field-permission-universal-identifier-and-application-id-not-null.util';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'upgrade:1-20:make-field-permission-universal-identifier-and-application-id-not-nullable-migration',
description:
'Set NOT NULL on fieldPermission universalIdentifier and applicationId, add unique index and FK (run identify-field-permission-metadata first)',
})
export class MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private hasRunOnce = false;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,32 +1,27 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, type Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { makeObjectPermissionUniversalIdentifierAndApplicationIdNotNullQueries } from 'src/database/typeorm/core/migrations/utils/1773317160558-make-object-permission-universal-identifier-and-application-id-not-null.util';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'upgrade:1-20:make-object-permission-universal-identifier-and-application-id-not-nullable-migration',
description:
'Set NOT NULL on objectPermission universalIdentifier and applicationId, add unique index and FK (run identify-object-permission-metadata first)',
})
export class MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private hasRunOnce = false;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,32 +1,27 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, type Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { makePermissionFlagUniversalIdentifierAndApplicationIdNotNullQueries } from 'src/database/typeorm/core/migrations/utils/1773232418467-make-permission-flag-universal-identifier-and-application-id-not-null.util';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'upgrade:1-20:make-permission-flag-universal-identifier-and-application-id-not-nullable-migration',
description:
'Set NOT NULL on permissionFlag universalIdentifier and applicationId, add unique index and FK (run identify-permission-flag-metadata first)',
})
export class MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private hasRunOnce = false;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,14 +1,12 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
@@ -16,19 +14,16 @@ import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/works
name: 'upgrade:1-20:make-workflow-searchable',
description: 'Set isSearchable to true on the workflow object metadata',
})
export class MakeWorkflowSearchableCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class MakeWorkflowSearchableCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -5,14 +5,13 @@ import { Command } from 'nest-commander';
import { FeatureFlagKey } from 'twenty-shared/types';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@@ -27,12 +26,9 @@ import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-membe
description:
'Backfill connectedAccount, messageChannel, calendarChannel, and messageFolder to core metadata schema',
})
export class MigrateMessagingInfrastructureToMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class MigrateMessagingInfrastructureToMetadataCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
private readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
@InjectRepository(ConnectedAccountEntity)
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
@InjectRepository(MessageChannelEntity)
@@ -44,8 +40,9 @@ export class MigrateMessagingInfrastructureToMetadataCommand extends ActiveOrSus
@InjectRepository(UserWorkspaceEntity)
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
private readonly featureFlagService: FeatureFlagService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,18 +1,16 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { FeatureFlagKey } from 'twenty-shared/types';
import { DataSource, Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { type WorkspaceCacheKeyName } from 'src/engine/workspace-cache/types/workspace-cache-key.type';
@@ -22,20 +20,17 @@ import { type WorkspaceCacheKeyName } from 'src/engine/workspace-cache/types/wor
description:
'Migrate deprecated RICH_TEXT (V1) to TEXT and rename RICH_TEXT_V2 to RICH_TEXT. The underlying column type is already text, so only the metadata needs updating.',
})
export class MigrateRichTextToTextCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class MigrateRichTextToTextCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
private readonly featureFlagService: FeatureFlagService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,31 +1,23 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
@Command({
name: 'upgrade:1-20:seed-cli-application-registration',
description:
'Seed the Twenty CLI application registration for OAuth-based CLI login',
})
export class SeedCliApplicationRegistrationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class SeedCliApplicationRegistrationCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private hasRun = false;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly applicationRegistrationService: ApplicationRegistrationService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,29 +1,24 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
@Command({
name: 'upgrade:1-20:update-standard-index-view-names',
description:
'Update standard index view names to use translatable template placeholders',
})
export class UpdateStandardIndexViewNamesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class UpdateStandardIndexViewNamesCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
@@ -55,6 +56,7 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
WorkspaceMigrationModule,
FeatureFlagModule,
WorkflowCommonModule,
WorkspaceIteratorModule,
],
providers: [
IdentifyPermissionFlagMetadataCommand,
@@ -1,21 +1,19 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, type Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 { addGlobalKeyValuePairUniqueIndexQueries } from 'src/database/typeorm/core/migrations/utils/1774700000000-add-global-key-value-pair-unique-index.util';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'upgrade:1-21:add-global-key-value-pair-unique-index',
description:
'Deduplicate global keyValuePair rows and add the null/null unique index',
})
export class AddGlobalKeyValuePairUniqueIndexCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class AddGlobalKeyValuePairUniqueIndexCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private hasRunOnce = false;
private async deduplicateGlobalKeyValuePairs(
@@ -44,14 +42,11 @@ export class AddGlobalKeyValuePairUniqueIndexCommand extends ActiveOrSuspendedWo
}
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -5,28 +5,26 @@ import { Command } from 'nest-commander';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'upgrade:1-21:backfill-datasource-to-workspace',
description:
'Backfill workspace.databaseSchema from the dataSource entity for workspaces that have not been migrated yet',
})
export class BackfillDatasourceToWorkspaceCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class BackfillDatasourceToWorkspaceCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
private readonly workspaceRepository: Repository<WorkspaceEntity>,
@InjectRepository(DataSourceEntity)
private readonly dataSourceRepository: Repository<DataSourceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,5 +1,3 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import {
CoreObjectNameSingular,
@@ -9,16 +7,14 @@ import {
ViewType,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
@@ -31,7 +27,6 @@ import { FieldDisplayMode } from 'src/engine/metadata-modules/page-layout-widget
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import {
GRID_POSITIONS,
@@ -91,18 +86,15 @@ const isRelationTargetAvailable = (
description:
'Backfill RECORD_PAGE page layouts, sync FIELDS_WIDGET view fields, create FIELD widgets, and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
})
export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly applicationService: ApplicationService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly featureFlagService: FeatureFlagService,
private readonly workspaceCacheService: WorkspaceCacheService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,15 +1,10 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
@@ -38,17 +33,14 @@ const NEW_UNIVERSAL_IDENTIFIERS = new Set([
description:
'Merge single/multiple record engine command menu items into unified commands (delete, restore, destroy, export)',
})
export class DeduplicateEngineCommandsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class DeduplicateEngineCommandsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly applicationService: ApplicationService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly workspaceCacheService: WorkspaceCacheService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,14 +1,10 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
@@ -45,17 +41,15 @@ const TEXT_AGENT_DEFAULT_OUTPUT_SCHEMA = {
description:
'Migrate AI agents with text response format to JSON with a default response field',
})
export class MigrateAiAgentTextToJsonResponseFormatCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class MigrateAiAgentTextToJsonResponseFormatCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly applicationService: ApplicationService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,13 +1,11 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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';
const EDIT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER =
'd9794c67-1799-424f-8871-5ea771dd4a6d';
@@ -16,16 +14,13 @@ const EDIT_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER =
name: 'upgrade:1-21:update-edit-layout-command-menu-item-label',
description: 'Update Edit Page Layout command menu item label to Edit Layout',
})
export class UpdateEditLayoutCommandMenuItemLabelCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class UpdateEditLayoutCommandMenuItemLabelCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { AddGlobalKeyValuePairUniqueIndexCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-add-global-key-value-pair-unique-index.command';
import { BackfillDatasourceToWorkspaceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-datasource-to-workspace.command';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-page-layouts-and-fields-widget-view-fields.command';
@@ -23,6 +24,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
ApplicationModule,
WorkspaceMigrationModule,
FeatureFlagModule,
WorkspaceIteratorModule,
],
providers: [
AddGlobalKeyValuePairUniqueIndexCommand,
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { CoreMigrationRunnerModule } from 'src/database/commands/core-migration-runner/core-migration-runner.module';
import { V1_20_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-20/1-20-upgrade-version-command.module';
import { V1_21_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-21/1-21-upgrade-version-command.module';
import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command';
import { CoreEngineVersionModule } from 'src/engine/core-engine-version/core-engine-version.module';
import { CoreMigrationRunnerModule } from 'src/database/commands/core-migration-runner/core-migration-runner.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-version/workspace-version.module';
@@ -19,6 +20,7 @@ import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-v
CoreEngineVersionModule,
CoreMigrationRunnerModule,
WorkspaceVersionModule,
WorkspaceIteratorModule,
],
providers: [UpgradeCommand],
})
@@ -3,16 +3,16 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { type Repository } from 'typeorm';
import { type ActiveOrSuspendedWorkspacesMigrationCommandOptions } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import {
type AllCommands,
UpgradeCommandRunner,
type VersionCommands,
} from 'src/database/commands/command-runners/upgrade.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
import { VersionedMigrationRegistryService } from 'src/database/commands/core-migration-runner/services/versioned-migration-registry.service';
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
import { AddGlobalKeyValuePairUniqueIndexCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-add-global-key-value-pair-unique-index.command';
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
import { IdentifyFieldPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-field-permission-metadata.command';
@@ -26,16 +26,14 @@ import { MigrateMessagingInfrastructureToMetadataCommand } from 'src/database/co
import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-rich-text-to-text.command';
import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-seed-cli-application-registration.command';
import { UpdateStandardIndexViewNamesCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-update-standard-index-view-names.command';
import { AddGlobalKeyValuePairUniqueIndexCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-add-global-key-value-pair-unique-index.command';
import { BackfillDatasourceToWorkspaceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-datasource-to-workspace.command';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-page-layouts-and-fields-widget-view-fields.command';
import { DeduplicateEngineCommandsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-deduplicate-engine-commands.command';
import { MigrateAiAgentTextToJsonResponseFormatCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-migrate-ai-agent-text-to-json-response-format.command';
import { UpdateEditLayoutCommandMenuItemLabelCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-update-edit-layout-command-menu-item-label.command';
import { CoreEngineVersionService } from 'src/engine/core-engine-version/services/core-engine-version.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service';
@Command({
@@ -48,46 +46,44 @@ export class UpgradeCommand extends UpgradeCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyConfigService: TwentyConfigService,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly coreEngineVersionService: CoreEngineVersionService,
protected readonly workspaceVersionService: WorkspaceVersionService,
protected readonly coreMigrationRunnerService: CoreMigrationRunnerService,
protected readonly versionedMigrationRegistryService: VersionedMigrationRegistryService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
// 1.20 Commands
protected readonly identifyPermissionFlagMetadataCommand: IdentifyPermissionFlagMetadataCommand,
protected readonly makePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
protected readonly identifyObjectPermissionMetadataCommand: IdentifyObjectPermissionMetadataCommand,
protected readonly makeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
protected readonly identifyFieldPermissionMetadataCommand: IdentifyFieldPermissionMetadataCommand,
protected readonly makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
protected readonly backfillNavigationMenuItemTypeCommand: BackfillNavigationMenuItemTypeCommand,
protected readonly backfillCommandMenuItemsCommand: BackfillCommandMenuItemsCommand,
protected readonly deleteOrphanNavigationMenuItemsCommand: DeleteOrphanNavigationMenuItemsCommand,
protected readonly seedCliApplicationRegistrationCommand: SeedCliApplicationRegistrationCommand,
protected readonly migrateRichTextToTextCommand: MigrateRichTextToTextCommand,
protected readonly migrateMessagingInfrastructureToMetadataCommand: MigrateMessagingInfrastructureToMetadataCommand,
protected readonly backfillSelectFieldOptionIdsCommand: BackfillSelectFieldOptionIdsCommand,
protected readonly updateStandardIndexViewNamesCommand: UpdateStandardIndexViewNamesCommand,
protected readonly makeWorkflowSearchableCommand: MakeWorkflowSearchableCommand,
private readonly identifyPermissionFlagMetadataCommand: IdentifyPermissionFlagMetadataCommand,
private readonly makePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
private readonly identifyObjectPermissionMetadataCommand: IdentifyObjectPermissionMetadataCommand,
private readonly makeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
private readonly identifyFieldPermissionMetadataCommand: IdentifyFieldPermissionMetadataCommand,
private readonly makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
private readonly backfillNavigationMenuItemTypeCommand: BackfillNavigationMenuItemTypeCommand,
private readonly backfillCommandMenuItemsCommand: BackfillCommandMenuItemsCommand,
private readonly deleteOrphanNavigationMenuItemsCommand: DeleteOrphanNavigationMenuItemsCommand,
private readonly seedCliApplicationRegistrationCommand: SeedCliApplicationRegistrationCommand,
private readonly migrateRichTextToTextCommand: MigrateRichTextToTextCommand,
private readonly migrateMessagingInfrastructureToMetadataCommand: MigrateMessagingInfrastructureToMetadataCommand,
private readonly backfillSelectFieldOptionIdsCommand: BackfillSelectFieldOptionIdsCommand,
private readonly updateStandardIndexViewNamesCommand: UpdateStandardIndexViewNamesCommand,
private readonly makeWorkflowSearchableCommand: MakeWorkflowSearchableCommand,
// 1.21 Commands
protected readonly addGlobalKeyValuePairUniqueIndexCommand: AddGlobalKeyValuePairUniqueIndexCommand,
protected readonly backfillDatasourceToWorkspaceCommand: BackfillDatasourceToWorkspaceCommand,
protected readonly backfillPageLayoutsAndFieldsWidgetViewFieldsCommand: BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
protected readonly deduplicateEngineCommandsCommand: DeduplicateEngineCommandsCommand,
protected readonly migrateAiAgentTextToJsonResponseFormatCommand: MigrateAiAgentTextToJsonResponseFormatCommand,
protected readonly updateEditLayoutCommandMenuItemLabelCommand: UpdateEditLayoutCommandMenuItemLabelCommand,
private readonly addGlobalKeyValuePairUniqueIndexCommand: AddGlobalKeyValuePairUniqueIndexCommand,
private readonly backfillDatasourceToWorkspaceCommand: BackfillDatasourceToWorkspaceCommand,
private readonly backfillPageLayoutsAndFieldsWidgetViewFieldsCommand: BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
private readonly deduplicateEngineCommandsCommand: DeduplicateEngineCommandsCommand,
private readonly migrateAiAgentTextToJsonResponseFormatCommand: MigrateAiAgentTextToJsonResponseFormatCommand,
private readonly updateEditLayoutCommandMenuItemLabelCommand: UpdateEditLayoutCommandMenuItemLabelCommand,
) {
super(
workspaceRepository,
twentyConfigService,
globalWorkspaceOrmManager,
dataSourceService,
coreEngineVersionService,
workspaceVersionService,
coreMigrationRunnerService,
versionedMigrationRegistryService,
workspaceIteratorService,
);
const commands_1200: VersionCommands = [
@@ -126,11 +122,4 @@ export class UpgradeCommand extends UpgradeCommandRunner {
'1.21.0': commands_1210,
};
}
override async runMigrationCommand(
passedParams: string[],
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions,
): Promise<void> {
return await super.runMigrationCommand(passedParams, options);
}
}
@@ -0,0 +1,18 @@
import 'reflect-metadata';
import { type UpgradeCommandVersion } from 'src/engine/constants/upgrade-command-supported-versions.constant';
const VERSIONED_MIGRATION_KEY = 'VERSIONED_MIGRATION_VERSION';
// When dropping a version from UPGRADE_COMMAND_SUPPORTED_VERSIONS, also
// remove the @VersionedMigration decorator from its associated migration files.
export const VersionedMigration =
(version: UpgradeCommandVersion): ClassDecorator =>
(target) => {
Reflect.defineMetadata(VERSIONED_MIGRATION_KEY, version, target);
};
export const getVersionedMigrationVersion = (
target: Function,
): UpgradeCommandVersion | undefined =>
Reflect.getMetadata(VERSIONED_MIGRATION_KEY, target);
@@ -3,6 +3,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { BillingGaugeService } from 'src/engine/core-modules/billing/billing-gauge.service';
import { BillingResolver } from 'src/engine/core-modules/billing/billing.resolver';
import { BillingSyncCustomerDataCommand } from 'src/engine/core-modules/billing/commands/billing-sync-customer-data.command';
@@ -68,6 +69,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
DataSourceModule,
MetricsModule,
EnterpriseModule,
WorkspaceIteratorModule,
],
providers: [
BillingSubscriptionService,
@@ -6,29 +6,24 @@ import chalk from 'chalk';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 { BillingCustomerEntity } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'billing:sync-customer-data',
description: 'Sync customer data from Stripe for all active workspaces',
})
export class BillingSyncCustomerDataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class BillingSyncCustomerDataCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly stripeSubscriptionService: StripeSubscriptionService,
@InjectRepository(BillingCustomerEntity)
protected readonly billingCustomerRepository: Repository<BillingCustomerEntity>,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
) {
super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -6,35 +6,30 @@ import { Command, Option } from 'nest-commander';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 { BillingSubscriptionEntity } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
import { StripeSubscriptionItemService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription-item.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@Command({
name: 'billing:update-subscription-price',
description: 'Update subscription price',
})
export class BillingUpdateSubscriptionPriceCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class BillingUpdateSubscriptionPriceCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private stripePriceIdToUpdate: string;
private newStripePriceId: string;
private clearUsage = false;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectRepository(BillingSubscriptionEntity)
protected readonly billingSubscriptionRepository: Repository<BillingSubscriptionEntity>,
private readonly billingSubscriptionService: BillingSubscriptionService,
private readonly stripeSubscriptionItemService: StripeSubscriptionItemService,
protected readonly dataSourceService: DataSourceService,
) {
super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService);
super(workspaceIteratorService);
}
@Option({
@@ -1,47 +1,38 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command, Option } from 'nest-commander';
import {
ALL_METADATA_NAME,
type AllMetadataName,
} from 'twenty-shared/metadata';
import { Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
type ActiveOrSuspendedWorkspacesMigrationCommandOptions,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
ActiveOrSuspendedWorkspaceCommandRunner,
type ActiveOrSuspendedWorkspaceCommandOptions,
} from 'src/database/commands/command-runners/active-or-suspended-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 { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service';
type FlatCacheFlushCommandOptions =
ActiveOrSuspendedWorkspacesMigrationCommandOptions & {
allMetadata?: boolean;
};
type FlatCacheFlushCommandOptions = ActiveOrSuspendedWorkspaceCommandOptions & {
metadataName?: string[];
allMetadata?: boolean;
};
@Command({
name: 'cache:flat-cache-invalidate',
description:
'Flush flat entity cache for specific metadata names and workspaces',
})
export class FlatCacheInvalidateCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner<FlatCacheFlushCommandOptions> {
private metadataNames: string[] = [];
export class FlatCacheInvalidateCommand extends ActiveOrSuspendedWorkspaceCommandRunner<FlatCacheFlushCommandOptions> {
private flatMapsKeysToFlush: (keyof AllFlatEntityMaps)[] = [];
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
) {
super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService);
super(workspaceIteratorService);
}
@Option({
@@ -50,10 +41,12 @@ export class FlatCacheInvalidateCommand extends ActiveOrSuspendedWorkspacesMigra
'Metadata name(s) to flush cache for. Can be specified multiple times.',
required: false,
})
parseMetadataName(val: string): string[] {
this.metadataNames.push(val);
parseMetadataName(val: string, previous?: string[]): string[] {
const accumulator = previous ?? [];
return this.metadataNames;
accumulator.push(val);
return accumulator;
}
@Option({
@@ -66,11 +59,13 @@ export class FlatCacheInvalidateCommand extends ActiveOrSuspendedWorkspacesMigra
return true;
}
override async runMigrationCommand(
override async run(
passedParams: string[],
options: FlatCacheFlushCommandOptions,
): Promise<void> {
if (!options.allMetadata && this.metadataNames.length === 0) {
const metadataNames = options.metadataName ?? [];
if (!options.allMetadata && metadataNames.length === 0) {
this.logger.error(
'Either --all-metadata or at least one --metadataName must be provided.',
);
@@ -79,7 +74,7 @@ export class FlatCacheInvalidateCommand extends ActiveOrSuspendedWorkspacesMigra
}
const validatedMetadataNames = this.validateAndExpandMetadataNames({
inputMetadataNames: this.metadataNames,
inputMetadataNames: metadataNames,
allMetadata: options.allMetadata,
});
@@ -95,7 +90,7 @@ export class FlatCacheInvalidateCommand extends ActiveOrSuspendedWorkspacesMigra
`Will flush cache for the following flat maps keys: ${this.flatMapsKeysToFlush.join(', ')}`,
);
await super.runMigrationCommand(passedParams, options);
await super.run(passedParams, options);
}
override async runOnWorkspace({
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { DiscoveryModule } from '@nestjs/core';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
@@ -27,6 +28,7 @@ import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/wo
WorkspaceCacheStorageModule,
WorkspaceCacheModule,
TypeOrmModule.forFeature([WorkspaceEntity]),
WorkspaceIteratorModule,
],
providers: [
WorkspaceMigrationRunnerService,
@@ -1,28 +1,20 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
@Command({
name: 'messaging:message-cleaner-remove-orphans',
description: 'Remove orphan message and threads from messaging',
})
export class MessagingMessageCleanerRemoveOrphansCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class MessagingMessageCleanerRemoveOrphansCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly messagingMessageCleanerService: MessagingMessageCleanerService,
protected readonly dataSourceService: DataSourceService,
) {
super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService);
super(workspaceIteratorService);
}
override async runOnWorkspace({
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
@@ -21,6 +22,7 @@ import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cl
FeatureFlagModule,
MessagingCommonModule,
MessageChannelDataAccessModule,
WorkspaceIteratorModule,
],
providers: [
MessagingConnectedAccountDeletionCleanupJob,
@@ -1,12 +1,9 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command, Option } from 'nest-commander';
import { LessThan, Repository } from 'typeorm';
import { LessThan } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
import { type WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
@@ -15,16 +12,14 @@ import { type WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/sta
name: 'workflow:delete-workflow-runs',
description: 'Delete all workflow runs',
})
export class DeleteWorkflowRunsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
export class DeleteWorkflowRunsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private createdBeforeDate: string | undefined;
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
) {
super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService);
super(workspaceIteratorService);
}
@Option({
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { CacheLockModule } from 'src/engine/core-modules/cache-lock/cache-lock.module';
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module';
@@ -23,6 +24,7 @@ import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runne
CacheLockModule,
MetricsModule,
DataSourceModule,
WorkspaceIteratorModule,
],
providers: [WorkflowRunWorkspaceService, DeleteWorkflowRunsCommand],
exports: [WorkflowRunWorkspaceService, DeleteWorkflowRunsCommand],