feat: populate relation join column (#10212)

Fix
https://github.com/twentyhq/core-team-issues/issues/241#issue-2793030259
This commit is contained in:
Jérémy M
2025-02-25 11:24:05 +01:00
committed by GitHub
parent dde70ee3b0
commit a1eea40cf7
49 changed files with 677 additions and 496 deletions

View File

@ -4,19 +4,23 @@ import { WorkspaceActivationStatus } from 'twenty-shared';
import { In, MoreThanOrEqual, Repository } from 'typeorm'; import { In, MoreThanOrEqual, Repository } from 'typeorm';
import { import {
BaseCommandOptions, MigrationCommandOptions,
BaseCommandRunner, MigrationCommandRunner,
} from 'src/database/commands/base.command'; } from 'src/database/commands/migration-command/migration-command.runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
export type ActiveWorkspacesCommandOptions = BaseCommandOptions & {
workspaceId?: string;
startFromWorkspaceId?: string;
workspaceCountLimit?: number;
};
export abstract class ActiveWorkspacesCommandRunner extends BaseCommandRunner { export type ActiveWorkspacesMigrationCommandOptions =
MigrationCommandOptions & {
workspaceId?: string;
startFromWorkspaceId?: string;
workspaceCountLimit?: number;
};
export abstract class ActiveWorkspacesMigrationCommandRunner<
Options extends
ActiveWorkspacesMigrationCommandOptions = ActiveWorkspacesMigrationCommandOptions,
> extends MigrationCommandRunner<Options> {
private workspaceIds: string[] = []; private workspaceIds: string[] = [];
private startFromWorkspaceId: string | undefined; private startFromWorkspaceId: string | undefined;
private workspaceCountLimit: number | undefined; private workspaceCountLimit: number | undefined;
@ -105,9 +109,9 @@ export abstract class ActiveWorkspacesCommandRunner extends BaseCommandRunner {
} }
} }
override async executeBaseCommand( override async runMigrationCommand(
passedParams: string[], passedParams: string[],
options: BaseCommandOptions, options: Options,
): Promise<void> { ): Promise<void> {
const activeWorkspaceIds = const activeWorkspaceIds =
this.workspaceIds.length > 0 this.workspaceIds.length > 0
@ -120,64 +124,16 @@ export abstract class ActiveWorkspacesCommandRunner extends BaseCommandRunner {
this.logger.log(chalk.yellow('Dry run mode: No changes will be applied')); this.logger.log(chalk.yellow('Dry run mode: No changes will be applied'));
} }
await this.executeActiveWorkspacesCommand( await this.runMigrationCommandOnActiveWorkspaces(
passedParams, passedParams,
options, options,
activeWorkspaceIds, activeWorkspaceIds,
); );
} }
protected async processEachWorkspaceWithWorkspaceDataSource( protected abstract runMigrationCommandOnActiveWorkspaces(
workspaceIds: string[],
callback: ({
workspaceId,
index,
total,
dataSource,
}: {
workspaceId: string;
index: number;
total: number;
dataSource: WorkspaceDataSource;
}) => Promise<void>,
): Promise<void> {
this.logger.log(
chalk.green(`Running command on ${workspaceIds.length} workspaces`),
);
for (const [index, workspaceId] of workspaceIds.entries()) {
this.logger.log(
chalk.green(
`Processing workspace ${workspaceId} (${index + 1}/${
workspaceIds.length
})`,
),
);
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
workspaceId,
false,
);
try {
await callback({
workspaceId,
index,
total: workspaceIds.length,
dataSource,
});
} catch (error) {
this.logger.error(`Error in workspace ${workspaceId}: ${error}`);
}
await this.twentyORMGlobalManager.destroyDataSourceForWorkspace(
workspaceId,
);
}
}
protected abstract executeActiveWorkspacesCommand(
passedParams: string[], passedParams: string[],
options: BaseCommandOptions, options: Options,
activeWorkspaceIds: string[], activeWorkspaceIds: string[],
): Promise<void>; ): Promise<void>;
} }

View File

@ -0,0 +1,68 @@
import chalk from 'chalk';
import { Repository } from 'typeorm';
import {
ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
export abstract class BatchActiveWorkspacesMigrationCommandRunner<
Options extends
ActiveWorkspacesMigrationCommandOptions = ActiveWorkspacesMigrationCommandOptions,
> extends ActiveWorkspacesMigrationCommandRunner<Options> {
constructor(
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
async runMigrationCommandOnActiveWorkspaces(
_passedParams: string[],
_options: Options,
activeWorkspaceIds: string[],
): Promise<void> {
this.logger.log(
chalk.green(`Running command on ${activeWorkspaceIds.length} workspaces`),
);
for (const [index, workspaceId] of activeWorkspaceIds.entries()) {
this.logger.log(
chalk.green(
`Processing workspace ${workspaceId} (${index + 1}/${
activeWorkspaceIds.length
})`,
),
);
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
workspaceId,
false,
);
try {
await this.runMigrationCommandOnWorkspace(
workspaceId,
index,
activeWorkspaceIds.length,
dataSource,
);
} catch (error) {
this.logger.error(`Error in workspace ${workspaceId}: ${error}`);
}
await this.twentyORMGlobalManager.destroyDataSourceForWorkspace(
workspaceId,
);
}
}
protected abstract runMigrationCommandOnWorkspace(
workspaceId: string,
index: number,
total: number,
dataSource: WorkspaceDataSource,
): Promise<void>;
}

View File

@ -0,0 +1,40 @@
import { Inject } from '@nestjs/common';
import { Command } from 'nest-commander';
import { MigrationCommandInterface } from 'src/database/commands/migration-command/interfaces/migration-command.interface';
import { MIGRATION_COMMAND_INJECTION_TOKEN } from 'src/database/commands/migration-command/migration-command.constants';
import { MigrationCommandRunner } from 'src/database/commands/migration-command/migration-command.runner';
export function createUpgradeAllCommand(
version: string,
): new (...args: unknown[]) => MigrationCommandRunner {
@Command({
name: `upgrade-${version}`,
description: `Upgrade to version ${version}`,
})
class UpgradeCommand extends MigrationCommandRunner {
constructor(
@Inject(MIGRATION_COMMAND_INJECTION_TOKEN)
private readonly subCommands: MigrationCommandInterface[],
) {
super();
}
async runMigrationCommand(
passedParams: string[],
options: Record<string, unknown>,
): Promise<void> {
this.logger.log(`Running upgrade command for version ${version}`);
for (const command of this.subCommands) {
await command.runMigrationCommand(passedParams, options);
}
this.logger.log(`Upgrade ${version} command completed!`);
}
}
return UpgradeCommand;
}

View File

@ -0,0 +1,41 @@
// migration-command.decorator.ts
import { Type } from '@nestjs/common';
import { Command, CommandMetadata } from 'nest-commander';
import 'reflect-metadata';
import { MigrationCommandRunner } from 'src/database/commands/migration-command/migration-command.runner';
export interface MigrationCommandMetadata extends CommandMetadata {
version: string;
}
const MIGRATION_COMMANDS = new Map<
string,
Array<Type<MigrationCommandRunner>>
>();
export function MigrationCommand(
options: MigrationCommandMetadata,
): <T extends Type<MigrationCommandRunner>>(target: T) => T | void {
return <T extends Type<MigrationCommandRunner>>(target: T): T | void => {
const { version, name, ...commandOptions } = options;
if (!MIGRATION_COMMANDS.has(version)) {
MIGRATION_COMMANDS.set(version, []);
}
MIGRATION_COMMANDS.get(version)?.push(target);
return Command({
name: `upgrade-${version}:${name}`,
...commandOptions,
})(target);
};
}
export function getMigrationCommandsForVersion(
version: string,
): Array<Type<MigrationCommandRunner>> {
return MIGRATION_COMMANDS.get(version) || [];
}

View File

@ -0,0 +1,5 @@
export interface MigrationCommandInterface<
Options extends Record<string, unknown> = Record<string, unknown>,
> {
runMigrationCommand(passedParams: string[], options: Options): Promise<void>;
}

View File

@ -0,0 +1 @@
export const MIGRATION_COMMAND_INJECTION_TOKEN = 'MIGRATION_COMMANDS';

View File

@ -3,14 +3,22 @@ import { Logger } from '@nestjs/common';
import chalk from 'chalk'; import chalk from 'chalk';
import { CommandRunner, Option } from 'nest-commander'; import { CommandRunner, Option } from 'nest-commander';
import { CommandLogger } from './logger'; import { MigrationCommandInterface } from 'src/database/commands/migration-command/interfaces/migration-command.interface';
export type BaseCommandOptions = {
import { CommandLogger } from 'src/database/commands/logger';
export type MigrationCommandOptions = {
workspaceId?: string; workspaceId?: string;
dryRun?: boolean; dryRun?: boolean;
verbose?: boolean; verbose?: boolean;
}; };
export abstract class BaseCommandRunner extends CommandRunner { export abstract class MigrationCommandRunner<
Options extends MigrationCommandOptions = MigrationCommandOptions,
>
extends CommandRunner
implements MigrationCommandInterface<Options>
{
protected logger: CommandLogger | Logger; protected logger: CommandLogger | Logger;
constructor() { constructor() {
super(); super();
@ -38,10 +46,7 @@ export abstract class BaseCommandRunner extends CommandRunner {
return true; return true;
} }
override async run( override async run(passedParams: string[], options: Options): Promise<void> {
passedParams: string[],
options: BaseCommandOptions,
): Promise<void> {
if (options.verbose) { if (options.verbose) {
this.logger = new CommandLogger({ this.logger = new CommandLogger({
verbose: true, verbose: true,
@ -50,7 +55,7 @@ export abstract class BaseCommandRunner extends CommandRunner {
} }
try { try {
await this.executeBaseCommand(passedParams, options); await this.runMigrationCommand(passedParams, options);
} catch (error) { } catch (error) {
this.logger.error(chalk.red(`Command failed`)); this.logger.error(chalk.red(`Command failed`));
throw error; throw error;
@ -59,8 +64,8 @@ export abstract class BaseCommandRunner extends CommandRunner {
} }
} }
protected abstract executeBaseCommand( abstract runMigrationCommand(
passedParams: string[], passedParams: string[],
options: BaseCommandOptions, options: Options,
): Promise<void>; ): Promise<void>;
} }

View File

@ -0,0 +1,43 @@
import { DynamicModule, Module, ModuleMetadata } from '@nestjs/common';
import { MigrationCommandInterface } from 'src/database/commands/migration-command/interfaces/migration-command.interface';
import { createUpgradeAllCommand } from 'src/database/commands/migration-command/create-upgrade-all-command.factory';
import { getMigrationCommandsForVersion } from 'src/database/commands/migration-command/decorators/migration-command.decorator';
import { MIGRATION_COMMAND_INJECTION_TOKEN } from 'src/database/commands/migration-command/migration-command.constants';
@Module({})
export class MigrationCommandModule {
static register(
version: string,
moduleMetadata: ModuleMetadata,
): DynamicModule {
const commandClasses = getMigrationCommandsForVersion(version);
const upgradeAllCommand = createUpgradeAllCommand(version);
return {
module: MigrationCommandModule,
imports: moduleMetadata.imports,
providers: [
...(moduleMetadata.providers ?? []),
...commandClasses,
{
provide: MIGRATION_COMMAND_INJECTION_TOKEN,
useFactory: (
...instances: MigrationCommandInterface[]
): MigrationCommandInterface[] => {
return instances;
},
inject: commandClasses,
},
upgradeAllCommand,
],
exports: [
...(moduleMetadata.exports ?? []),
MIGRATION_COMMAND_INJECTION_TOKEN,
...commandClasses,
upgradeAllCommand,
],
};
}
}

View File

@ -4,11 +4,11 @@ import chalk from 'chalk';
import { Command } from 'nest-commander'; import { Command } from 'nest-commander';
import { In, Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { isCommandLogger } from 'src/database/commands/logger'; import { isCommandLogger } from 'src/database/commands/logger';
import {
ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
@ -20,7 +20,7 @@ import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.work
name: 'upgrade-0.42:fix-body-v2-view-field-position', name: 'upgrade-0.42:fix-body-v2-view-field-position',
description: 'Make bodyV2 field position to match body field position', description: 'Make bodyV2 field position to match body field position',
}) })
export class FixBodyV2ViewFieldPositionCommand extends ActiveWorkspacesCommandRunner { export class FixBodyV2ViewFieldPositionCommand extends ActiveWorkspacesMigrationCommandRunner {
constructor( constructor(
@InjectRepository(Workspace, 'core') @InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>, protected readonly workspaceRepository: Repository<Workspace>,
@ -32,9 +32,9 @@ export class FixBodyV2ViewFieldPositionCommand extends ActiveWorkspacesCommandRu
super(workspaceRepository, twentyORMGlobalManager); super(workspaceRepository, twentyORMGlobalManager);
} }
async executeActiveWorkspacesCommand( async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[], _passedParam: string[],
options: ActiveWorkspacesCommandOptions, options: ActiveWorkspacesMigrationCommandOptions,
workspaceIds: string[], workspaceIds: string[],
): Promise<void> { ): Promise<void> {
this.logger.log('Running command to fix bodyV2 field position'); this.logger.log('Running command to fix bodyV2 field position');

View File

@ -1,25 +1,26 @@
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk'; import chalk from 'chalk';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { CommandLogger } from 'src/database/commands/logger'; import { CommandLogger } from 'src/database/commands/logger';
import {
ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator';
import { settings } from 'src/engine/constants/settings'; import { settings } from 'src/engine/constants/settings';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
@Command({ @MigrationCommand({
name: 'upgrade-0.42:limit-amount-of-view-field', name: 'limit-amount-of-view-field',
description: 'Limit amount of view field.', description: 'Limit amount of view field.',
version: '0.42',
}) })
export class LimitAmountOfViewFieldCommand extends ActiveWorkspacesCommandRunner { export class LimitAmountOfViewFieldCommand extends ActiveWorkspacesMigrationCommandRunner {
protected readonly logger: CommandLogger; protected readonly logger: CommandLogger;
constructor( constructor(
@ -35,7 +36,7 @@ export class LimitAmountOfViewFieldCommand extends ActiveWorkspacesCommandRunner
this.logger.setVerbose(false); this.logger.setVerbose(false);
} }
async execute(workspaceId: string, dryRun?: boolean): Promise<void> { async runOnWorkspace(workspaceId: string, dryRun?: boolean): Promise<void> {
this.logger.log( this.logger.log(
`Processing workspace ${workspaceId} for view field limitation`, `Processing workspace ${workspaceId} for view field limitation`,
); );
@ -94,9 +95,9 @@ export class LimitAmountOfViewFieldCommand extends ActiveWorkspacesCommandRunner
} }
} }
async executeActiveWorkspacesCommand( async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[], _passedParam: string[],
options: ActiveWorkspacesCommandOptions, options: ActiveWorkspacesMigrationCommandOptions,
workspaceIds: string[], workspaceIds: string[],
): Promise<void> { ): Promise<void> {
this.logger.log(`Running limit-amount-of-view-field command`); this.logger.log(`Running limit-amount-of-view-field command`);
@ -107,7 +108,7 @@ export class LimitAmountOfViewFieldCommand extends ActiveWorkspacesCommandRunner
for (const [index, workspaceId] of workspaceIds.entries()) { for (const [index, workspaceId] of workspaceIds.entries()) {
try { try {
await this.execute(workspaceId, options?.dryRun); await this.runOnWorkspace(workspaceId, options?.dryRun);
this.logger.verbose( this.logger.verbose(
`Processed workspace: ${workspaceId} (${index + 1}/${ `Processed workspace: ${workspaceId} (${index + 1}/${
workspaceIds.length workspaceIds.length

View File

@ -2,13 +2,16 @@ import { InjectRepository } from '@nestjs/typeorm';
import { ServerBlockNoteEditor } from '@blocknote/server-util'; import { ServerBlockNoteEditor } from '@blocknote/server-util';
import chalk from 'chalk'; import chalk from 'chalk';
import { Command, Option } from 'nest-commander'; import { Option } from 'nest-commander';
import { FieldMetadataType, isDefined } from 'twenty-shared'; import { FieldMetadataType, isDefined } from 'twenty-shared';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import { isCommandLogger } from 'src/database/commands/logger'; import { isCommandLogger } from 'src/database/commands/logger';
import { Upgrade042CommandOptions } from 'src/database/commands/upgrade-version/0-42/0-42-upgrade-version.command'; import {
ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@ -55,12 +58,19 @@ type ProcessRichTextFieldsArgs = {
workspaceId: string; workspaceId: string;
}; };
@Command({ type MigrateRichTextFieldCommandOptions =
name: 'upgrade-0.42:migrate-rich-text-field', ActiveWorkspacesMigrationCommandOptions & {
force?: boolean;
};
@MigrationCommand({
name: 'migrate-rich-text-field',
description: 'Migrate RICH_TEXT fields to new composite structure', description: 'Migrate RICH_TEXT fields to new composite structure',
version: '0.42',
}) })
export class MigrateRichTextFieldCommand extends ActiveWorkspacesCommandRunner { export class MigrateRichTextFieldCommand extends ActiveWorkspacesMigrationCommandRunner<MigrateRichTextFieldCommandOptions> {
private options: Upgrade042CommandOptions; private options: MigrateRichTextFieldCommandOptions;
constructor( constructor(
@InjectRepository(Workspace, 'core') @InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>, protected readonly workspaceRepository: Repository<Workspace>,
@ -89,9 +99,9 @@ export class MigrateRichTextFieldCommand extends ActiveWorkspacesCommandRunner {
return val ?? false; return val ?? false;
} }
async executeActiveWorkspacesCommand( async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[], _passedParam: string[],
options: Upgrade042CommandOptions, options: MigrateRichTextFieldCommandOptions,
workspaceIds: string[], workspaceIds: string[],
): Promise<void> { ): Promise<void> {
this.logger.log( this.logger.log(
@ -343,7 +353,7 @@ export class MigrateRichTextFieldCommand extends ActiveWorkspacesCommandRunner {
}); });
richTextFieldsWithHasCreatedColumns.push({ richTextFieldsWithHasCreatedColumns.push({
hasCreatedColumns, hasCreatedColumns: hasCreatedColumns ?? false,
richTextField, richTextField,
objectMetadata, objectMetadata,
}); });

View File

@ -1,25 +1,26 @@
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk'; import chalk from 'chalk';
import { Command } from 'nest-commander';
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType } from 'twenty-shared';
import { IsNull, Repository } from 'typeorm'; import { IsNull, Repository } from 'typeorm';
import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { CommandLogger } from 'src/database/commands/logger'; import { CommandLogger } from 'src/database/commands/logger';
import {
ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Command({ @MigrationCommand({
name: 'upgrade-0.42:standardization-of-actor-composite-context-type', name: 'standardization-of-actor-composite-context-type',
description: 'Add context to actor composite type.', description: 'Add context to actor composite type.',
version: '0.42',
}) })
export class StandardizationOfActorCompositeContextTypeCommand extends ActiveWorkspacesCommandRunner { export class StandardizationOfActorCompositeContextTypeCommand extends ActiveWorkspacesMigrationCommandRunner {
protected readonly logger; protected readonly logger;
constructor( constructor(
@ -39,9 +40,9 @@ export class StandardizationOfActorCompositeContextTypeCommand extends ActiveWor
this.logger.setVerbose(false); this.logger.setVerbose(false);
} }
async executeActiveWorkspacesCommand( async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[], _passedParam: string[],
options: ActiveWorkspacesCommandOptions, options: ActiveWorkspacesMigrationCommandOptions,
workspaceIds: string[], workspaceIds: string[],
): Promise<void> { ): Promise<void> {
this.logger.log(`Running add-context-to-actor-composite-type command`); this.logger.log(`Running add-context-to-actor-composite-type command`);
@ -52,7 +53,7 @@ export class StandardizationOfActorCompositeContextTypeCommand extends ActiveWor
for (const [index, workspaceId] of workspaceIds.entries()) { for (const [index, workspaceId] of workspaceIds.entries()) {
try { try {
await this.execute(workspaceId, options?.dryRun); await this.runOnWorkspace(workspaceId, options?.dryRun);
this.logger.verbose( this.logger.verbose(
`[${index + 1}/${workspaceIds.length}] Added for workspace: ${workspaceId}`, `[${index + 1}/${workspaceIds.length}] Added for workspace: ${workspaceId}`,
); );
@ -62,7 +63,10 @@ export class StandardizationOfActorCompositeContextTypeCommand extends ActiveWor
} }
} }
private async execute(workspaceId: string, dryRun = false): Promise<void> { private async runOnWorkspace(
workspaceId: string,
dryRun = false,
): Promise<void> {
this.logger.verbose(`Adding for workspace: ${workspaceId}`); this.logger.verbose(`Adding for workspace: ${workspaceId}`);
const actorFields = await this.fieldMetadataRepository.find({ const actorFields = await this.fieldMetadataRepository.find({
where: { where: {

View File

@ -1,89 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command, Option } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import { BaseCommandOptions } from 'src/database/commands/base.command';
import { FixBodyV2ViewFieldPositionCommand } from 'src/database/commands/upgrade-version/0-42/0-42-fix-body-v2-view-field-position.command';
import { LimitAmountOfViewFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-limit-amount-of-view-field';
import { MigrateRichTextFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command';
import { StandardizationOfActorCompositeContextTypeCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
type Upgrade042CommandCustomOptions = {
force: boolean;
};
export type Upgrade042CommandOptions = BaseCommandOptions &
Upgrade042CommandCustomOptions;
@Command({
name: 'upgrade-0.42',
description: 'Upgrade to 0.42',
})
export class UpgradeTo0_42Command extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly migrateRichTextFieldCommand: MigrateRichTextFieldCommand,
private readonly fixBodyV2ViewFieldPositionCommand: FixBodyV2ViewFieldPositionCommand,
private readonly limitAmountOfViewFieldCommand: LimitAmountOfViewFieldCommand,
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
private readonly standardizationOfActorCompositeContextType: StandardizationOfActorCompositeContextTypeCommand,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
@Option({
flags: '-f, --force [boolean]',
description:
'Force RICH_TEXT_FIELD value update even if column migration has already be run',
required: false,
})
parseForceValue(val?: boolean): boolean {
return val ?? false;
}
async executeActiveWorkspacesCommand(
passedParam: string[],
options: Upgrade042CommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log('Running command to upgrade to 0.42');
await this.migrateRichTextFieldCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
await this.fixBodyV2ViewFieldPositionCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
await this.limitAmountOfViewFieldCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand(
passedParam,
{
...options,
force: true,
},
workspaceIds,
);
await this.standardizationOfActorCompositeContextType.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
}
}

View File

@ -1,11 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { MigrationCommandModule } from 'src/database/commands/migration-command/miration-command.module';
import { FixBodyV2ViewFieldPositionCommand } from 'src/database/commands/upgrade-version/0-42/0-42-fix-body-v2-view-field-position.command'; import { FixBodyV2ViewFieldPositionCommand } from 'src/database/commands/upgrade-version/0-42/0-42-fix-body-v2-view-field-position.command';
import { LimitAmountOfViewFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-limit-amount-of-view-field'; import { LimitAmountOfViewFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-limit-amount-of-view-field';
import { MigrateRichTextFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command'; import { MigrateRichTextFieldCommand } from 'src/database/commands/upgrade-version/0-42/0-42-migrate-rich-text-field.command';
import { StandardizationOfActorCompositeContextTypeCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type'; import { StandardizationOfActorCompositeContextTypeCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type';
import { UpgradeTo0_42Command } from 'src/database/commands/upgrade-version/0-42/0-42-upgrade-version.command';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@ -19,24 +19,27 @@ import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manage
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'), MigrationCommandModule.register('0.42', {
TypeOrmModule.forFeature( imports: [
[ObjectMetadataEntity, FieldMetadataEntity], TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'),
'metadata', TypeOrmModule.forFeature(
), [ObjectMetadataEntity, FieldMetadataEntity],
WorkspaceSyncMetadataCommandsModule, 'metadata',
WorkspaceMigrationRunnerModule, ),
WorkspaceMigrationModule, WorkspaceSyncMetadataCommandsModule,
WorkspaceMetadataVersionModule, WorkspaceMigrationRunnerModule,
WorkspaceDataSourceModule, WorkspaceMigrationModule,
FeatureFlagModule, WorkspaceMetadataVersionModule,
], WorkspaceDataSourceModule,
providers: [ FeatureFlagModule,
UpgradeTo0_42Command, ],
MigrateRichTextFieldCommand, providers: [
FixBodyV2ViewFieldPositionCommand, MigrateRichTextFieldCommand,
LimitAmountOfViewFieldCommand, FixBodyV2ViewFieldPositionCommand,
StandardizationOfActorCompositeContextTypeCommand, LimitAmountOfViewFieldCommand,
StandardizationOfActorCompositeContextTypeCommand,
],
}),
], ],
}) })
export class UpgradeTo0_42CommandModule {} export class UpgradeTo0_42CommandModule {}

View File

@ -1,15 +1,15 @@
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk'; import chalk from 'chalk';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { isCommandLogger } from 'src/database/commands/logger'; import { isCommandLogger } from 'src/database/commands/logger';
import {
ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataDefaultOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; import { FieldMetadataDefaultOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@ -24,11 +24,12 @@ import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/vie
import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity'; import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity';
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
@Command({ @MigrationCommand({
name: 'upgrade-0.43:add-tasks-assigned-to-me-view', name: 'add-tasks-assigned-to-me-view',
description: 'Add tasks assigned to me view', description: 'Add tasks assigned to me view',
version: '0.43',
}) })
export class AddTasksAssignedToMeViewCommand extends ActiveWorkspacesCommandRunner { export class AddTasksAssignedToMeViewCommand extends ActiveWorkspacesMigrationCommandRunner {
constructor( constructor(
@InjectRepository(Workspace, 'core') @InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>, protected readonly workspaceRepository: Repository<Workspace>,
@ -42,9 +43,9 @@ export class AddTasksAssignedToMeViewCommand extends ActiveWorkspacesCommandRunn
super(workspaceRepository, twentyORMGlobalManager); super(workspaceRepository, twentyORMGlobalManager);
} }
async executeActiveWorkspacesCommand( async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[], _passedParam: string[],
options: ActiveWorkspacesCommandOptions, options: ActiveWorkspacesMigrationCommandOptions,
workspaceIds: string[], workspaceIds: string[],
): Promise<void> { ): Promise<void> {
this.logger.log('Running command to create many to one relations'); this.logger.log('Running command to create many to one relations');

View File

@ -0,0 +1,169 @@
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import { FieldMetadataType } from 'twenty-shared';
import { In, Repository } from 'typeorm';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { isCommandLogger } from 'src/database/commands/logger';
import {
ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import {
RelationDirection,
deduceRelationDirection,
} from 'src/engine/utils/deduce-relation-direction.util';
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
@MigrationCommand({
name: 'migrate-relations-to-field-metadata',
description: 'Migrate relations to field metadata',
version: '0.43',
})
export class MigrateRelationsToFieldMetadataCommand extends ActiveWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[],
options: ActiveWorkspacesMigrationCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log('Running command to create many to one relations');
if (isCommandLogger(this.logger)) {
this.logger.setVerbose(options.verbose ?? false);
}
try {
for (const [index, workspaceId] of workspaceIds.entries()) {
await this.processWorkspace(workspaceId, index, workspaceIds.length);
}
this.logger.log(chalk.green('Command completed!'));
} catch (error) {
this.logger.log(chalk.red('Error in workspace'));
}
}
private async processWorkspace(
workspaceId: string,
index: number,
total: number,
): Promise<void> {
try {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
const fieldMetadataCollection = await this.fieldMetadataRepository.find({
where: {
workspaceId,
type: In([FieldMetadataType.RELATION, FieldMetadataType.UUID]),
},
relations: ['fromRelationMetadata', 'toRelationMetadata'],
});
if (!fieldMetadataCollection.length) {
this.logger.log(
chalk.yellow(
`No relation field metadata found for workspace ${workspaceId}.`,
),
);
return;
}
const joinColumnFieldMetadataCollection = fieldMetadataCollection.filter(
(fieldMetadata) =>
isFieldMetadataOfType(fieldMetadata, FieldMetadataType.UUID),
// TODO: Fix this, it's working in other places but not here
) as FieldMetadataEntity<FieldMetadataType.UUID>[];
const fieldMetadataToUpdateCollection = fieldMetadataCollection
.filter((fieldMetadata) =>
isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION),
)
.map((fieldMetadata) =>
this.updateRelationFieldMetadata(
joinColumnFieldMetadataCollection,
// TODO: Fix this, it's working in other places but not here
fieldMetadata as FieldMetadataEntity<FieldMetadataType.RELATION>,
),
);
if (fieldMetadataToUpdateCollection.length > 0) {
await this.fieldMetadataRepository.save(
fieldMetadataToUpdateCollection,
);
}
this.logger.log(
chalk.green(`Command completed for workspace ${workspaceId}.`),
);
} catch {
this.logger.log(chalk.red(`Error in workspace ${workspaceId}.`));
}
}
private updateRelationFieldMetadata(
joinColumnFieldMetadataCollection: FieldMetadataEntity<FieldMetadataType.UUID>[],
fieldMetadata: FieldMetadataEntity<FieldMetadataType.RELATION>,
): FieldMetadataEntity<FieldMetadataType.RELATION> {
const relationMetadata =
fieldMetadata.fromRelationMetadata ?? fieldMetadata.toRelationMetadata;
const joinColumnFieldMetadata = joinColumnFieldMetadataCollection.find(
(joinColumnFieldMetadata) =>
// We're deducing the field based on the name of the relation field
// This is not the best way to do this but we don't have a better way
joinColumnFieldMetadata.name === `${fieldMetadata.name}Id`,
);
const relationDirection = deduceRelationDirection(
fieldMetadata,
relationMetadata,
);
let relationType = relationMetadata.relationType as unknown as RelationType;
if (
relationDirection === RelationDirection.TO &&
relationType === RelationType.ONE_TO_MANY
) {
relationType = RelationType.MANY_TO_ONE;
}
const relationTargetFieldMetadataId =
relationDirection === RelationDirection.FROM
? relationMetadata.toFieldMetadataId
: relationMetadata.fromFieldMetadataId;
const relationTargetObjectMetadataId =
relationDirection === RelationDirection.FROM
? relationMetadata.toObjectMetadataId
: relationMetadata.fromObjectMetadataId;
return {
...fieldMetadata,
settings: {
relationType,
onDelete: relationMetadata.onDeleteAction,
joinColumnName: joinColumnFieldMetadata?.name,
},
relationTargetFieldMetadataId,
relationTargetObjectMetadataId,
};
}
}

View File

@ -1,13 +1,13 @@
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk'; import chalk from 'chalk';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { import {
ActiveWorkspacesCommandOptions, ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesCommandRunner, ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/active-workspaces.command'; } from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@ -19,11 +19,12 @@ import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/wo
import { SEARCH_FIELDS_FOR_NOTES } from 'src/modules/note/standard-objects/note.workspace-entity'; import { SEARCH_FIELDS_FOR_NOTES } from 'src/modules/note/standard-objects/note.workspace-entity';
import { SEARCH_FIELDS_FOR_TASKS } from 'src/modules/task/standard-objects/task.workspace-entity'; import { SEARCH_FIELDS_FOR_TASKS } from 'src/modules/task/standard-objects/task.workspace-entity';
@Command({ @MigrationCommand({
name: 'upgrade-0.43:migrate-search-vector-on-note-and-task-entities', name: 'migrate-search-vector-on-note-and-task-entities',
description: 'Migrate search vector on note and task entities', description: 'Migrate search vector on note and task entities',
version: '0.43',
}) })
export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveWorkspacesCommandRunner { export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveWorkspacesMigrationCommandRunner {
constructor( constructor(
@InjectRepository(Workspace, 'core') @InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>, protected readonly workspaceRepository: Repository<Workspace>,
@ -39,9 +40,9 @@ export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveWorks
super(workspaceRepository, twentyORMGlobalManager); super(workspaceRepository, twentyORMGlobalManager);
} }
async executeActiveWorkspacesCommand( async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[], _passedParam: string[],
options: ActiveWorkspacesCommandOptions, options: ActiveWorkspacesMigrationCommandOptions,
workspaceIds: string[], workspaceIds: string[],
): Promise<void> { ): Promise<void> {
this.logger.log( this.logger.log(

View File

@ -1,59 +1,34 @@
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk'; import chalk from 'chalk';
import { Command } from 'nest-commander';
import { In, Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { import { BatchActiveWorkspacesMigrationCommandRunner } from 'src/database/commands/migration-command/batch-active-workspaces-migration-command.runner';
ActiveWorkspacesCommandOptions, import { MigrationCommand } from 'src/database/commands/migration-command/decorators/migration-command.decorator';
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity'; import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity';
@Command({ @MigrationCommand({
name: 'upgrade-0.43:update-default-view-record-opening-on-workflow-objects', name: 'update-default-view-record-opening-on-workflow-objects',
description: description:
'Update default view record opening on workflow objects to record page', 'Update default view record opening on workflow objects to record page',
version: '0.43',
}) })
export class UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand extends ActiveWorkspacesCommandRunner { export class UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand extends BatchActiveWorkspacesMigrationCommandRunner {
constructor( constructor(
@InjectRepository(Workspace, 'core') @InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>, protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(ObjectMetadataEntity, 'metadata') @InjectRepository(ObjectMetadataEntity, 'metadata')
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) { ) {
super(workspaceRepository, twentyORMGlobalManager); super(workspaceRepository, twentyORMGlobalManager);
} }
async executeActiveWorkspacesCommand( async runMigrationCommandOnWorkspace(
_passedParam: string[],
_options: ActiveWorkspacesCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log(
'Running command to update default view record opening on workflow objects to record page',
);
this.processEachWorkspaceWithWorkspaceDataSource(
workspaceIds,
async ({ workspaceId, index, total }) => {
await this.processWorkspace(workspaceId, index, total);
},
);
this.logger.log(chalk.green('Command completed!'));
}
async processWorkspace(
workspaceId: string, workspaceId: string,
index: number, index: number,
total: number, total: number,

View File

@ -1,64 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import { BaseCommandOptions } from 'src/database/commands/base.command';
import { StandardizationOfActorCompositeContextTypeCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type';
import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version/0-43/0-43-add-tasks-assigned-to-me-view.command';
import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command';
import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/database/commands/upgrade-version/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Command({
name: 'upgrade-0.43',
description: 'Upgrade to 0.43',
})
export class UpgradeTo0_43Command extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly addTasksAssignedToMeViewCommand: AddTasksAssignedToMeViewCommand,
private readonly migrateSearchVectorOnNoteAndTaskEntitiesCommand: MigrateSearchVectorOnNoteAndTaskEntitiesCommand,
private readonly updateDefaultViewRecordOpeningOnWorkflowObjectsCommand: UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand,
private readonly standardizationOfActorCompositeContextTypeCommand: StandardizationOfActorCompositeContextTypeCommand,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
async executeActiveWorkspacesCommand(
passedParam: string[],
options: BaseCommandOptions,
workspaceIds: string[],
): Promise<void> {
this.logger.log('Running command to upgrade to 0.43');
await this.addTasksAssignedToMeViewCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
await this.migrateSearchVectorOnNoteAndTaskEntitiesCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
await this.updateDefaultViewRecordOpeningOnWorkflowObjectsCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
// Note: Introduced in 0.42, ran manually on prod. Introduced to self-host globally on 0.43
await this.standardizationOfActorCompositeContextTypeCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
}
}

View File

@ -1,11 +1,12 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { MigrationCommandModule } from 'src/database/commands/migration-command/miration-command.module';
import { StandardizationOfActorCompositeContextTypeCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type'; import { StandardizationOfActorCompositeContextTypeCommand } from 'src/database/commands/upgrade-version/0-42/0-42-standardization-of-actor-composite-context-type';
import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version/0-43/0-43-add-tasks-assigned-to-me-view.command'; import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version/0-43/0-43-add-tasks-assigned-to-me-view.command';
import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version/0-43/0-43-migrate-relations-to-field-metadata.command';
import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command'; import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command';
import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/database/commands/upgrade-version/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command'; import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/database/commands/upgrade-version/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command';
import { UpgradeTo0_43Command } from 'src/database/commands/upgrade-version/0-43/0-43-upgrade-version.command';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@ -17,22 +18,26 @@ import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/wor
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'), MigrationCommandModule.register('0.43', {
TypeOrmModule.forFeature( imports: [
[ObjectMetadataEntity, FieldMetadataEntity], TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'),
'metadata', TypeOrmModule.forFeature(
), [ObjectMetadataEntity, FieldMetadataEntity],
SearchModule, 'metadata',
WorkspaceMigrationRunnerModule, ),
WorkspaceMigrationModule, SearchModule,
WorkspaceMetadataVersionModule, WorkspaceMigrationRunnerModule,
], WorkspaceMigrationModule,
providers: [ WorkspaceMetadataVersionModule,
UpgradeTo0_43Command, ],
AddTasksAssignedToMeViewCommand, providers: [
MigrateSearchVectorOnNoteAndTaskEntitiesCommand, AddTasksAssignedToMeViewCommand,
UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand, MigrateSearchVectorOnNoteAndTaskEntitiesCommand,
StandardizationOfActorCompositeContextTypeCommand, UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand,
StandardizationOfActorCompositeContextTypeCommand,
MigrateRelationsToFieldMetadataCommand,
],
}),
], ],
}) })
export class UpgradeTo0_43CommandModule {} export class UpgradeTo0_43CommandModule {}

View File

@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { FieldMetadataType } from 'twenty-shared';
import { import {
DataSource, DataSource,
FindOptionsRelations, FindOptionsRelations,
@ -22,7 +23,7 @@ import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/typ
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { isRelationFieldMetadata } from 'src/engine/utils/is-relation-field-metadata.util'; import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
@Injectable() @Injectable()
export class ProcessNestedRelationsV2Helper { export class ProcessNestedRelationsV2Helper {
@ -96,7 +97,9 @@ export class ProcessNestedRelationsV2Helper {
const sourceFieldMetadata = const sourceFieldMetadata =
parentObjectMetadataItem.fieldsByName[sourceFieldName]; parentObjectMetadataItem.fieldsByName[sourceFieldName];
if (!isRelationFieldMetadata(sourceFieldMetadata)) { if (
!isFieldMetadataOfType(sourceFieldMetadata, FieldMetadataType.RELATION)
) {
// TODO: Maybe we should throw an error here ? // TODO: Maybe we should throw an error here ?
return; return;
} }

View File

@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { isDefined } from 'twenty-shared'; import { FieldMetadataType, isDefined } from 'twenty-shared';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value'; import { QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
@ -21,7 +21,7 @@ import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/work
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { isRelationFieldMetadata } from 'src/engine/utils/is-relation-field-metadata.util'; import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
// TODO: find a way to prevent conflict between handlers executing logic on object relations // TODO: find a way to prevent conflict between handlers executing logic on object relations
// And this factory that is also executing logic on object relations // And this factory that is also executing logic on object relations
@ -151,7 +151,9 @@ export class QueryResultGettersFactory {
objectMetadataMapItem.fieldsByName[recordFieldName], objectMetadataMapItem.fieldsByName[recordFieldName],
) )
.filter(isDefined) .filter(isDefined)
.filter((fieldMetadata) => isRelationFieldMetadata(fieldMetadata)); .filter((fieldMetadata) =>
isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION),
);
const relationFieldsProcessedMap = {} as Record< const relationFieldsProcessedMap = {} as Record<
string, string,

View File

@ -5,6 +5,7 @@ import {
GraphQLFieldConfigMap, GraphQLFieldConfigMap,
GraphQLObjectType, GraphQLObjectType,
} from 'graphql'; } from 'graphql';
import { FieldMetadataType } from 'twenty-shared';
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface'; import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
@ -14,7 +15,7 @@ import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-b
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage'; import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util'; import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field'; import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
import { isRelationFieldMetadata } from 'src/engine/utils/is-relation-field-metadata.util'; import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { ArgsFactory } from './args.factory'; import { ArgsFactory } from './args.factory';
@ -107,7 +108,7 @@ export class ExtendObjectTypeDefinitionV2Factory {
for (const fieldMetadata of objectMetadata.fields) { for (const fieldMetadata of objectMetadata.fields) {
// Ignore non-relation fields as they are already defined // Ignore non-relation fields as they are already defined
if (!isRelationFieldMetadata(fieldMetadata)) { if (!isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION)) {
continue; continue;
} }

View File

@ -45,7 +45,7 @@ export interface TypeOptions<T = any> {
isArray?: boolean; isArray?: boolean;
arrayDepth?: number; arrayDepth?: number;
defaultValue?: T; defaultValue?: T;
settings?: FieldMetadataSettings<FieldMetadataType | 'default'>; settings?: FieldMetadataSettings<FieldMetadataType>;
isIdField?: boolean; isIdField?: boolean;
} }
@ -55,7 +55,7 @@ const StringArrayScalarType = new GraphQLList(GraphQLString);
export class TypeMapperService { export class TypeMapperService {
mapToScalarType( mapToScalarType(
fieldMetadataType: FieldMetadataType, fieldMetadataType: FieldMetadataType,
settings?: FieldMetadataSettings<FieldMetadataType | 'default'>, settings?: FieldMetadataSettings<FieldMetadataType>,
isIdField?: boolean, isIdField?: boolean,
): GraphQLScalarType | undefined { ): GraphQLScalarType | undefined {
if (isIdField || settings?.isForeignKey) { if (isIdField || settings?.isForeignKey) {
@ -90,7 +90,7 @@ export class TypeMapperService {
mapToFilterType( mapToFilterType(
fieldMetadataType: FieldMetadataType, fieldMetadataType: FieldMetadataType,
settings?: FieldMetadataSettings<FieldMetadataType | 'default'>, settings?: FieldMetadataSettings<FieldMetadataType>,
isIdField?: boolean, isIdField?: boolean,
): GraphQLInputObjectType | GraphQLScalarType | undefined { ): GraphQLInputObjectType | GraphQLScalarType | undefined {
if (isIdField || settings?.isForeignKey) { if (isIdField || settings?.isForeignKey) {

View File

@ -7,22 +7,22 @@ import { Command } from 'nest-commander';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { import {
ActiveWorkspacesCommandOptions, ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesCommandRunner, ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/active-workspaces.command'; } from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity'; import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service'; import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
interface SyncCustomerDataCommandOptions interface SyncCustomerDataCommandOptions
extends ActiveWorkspacesCommandOptions {} extends ActiveWorkspacesMigrationCommandOptions {}
@Command({ @Command({
name: 'billing:sync-customer-data', name: 'billing:sync-customer-data',
description: 'Sync customer data from Stripe for all active workspaces', description: 'Sync customer data from Stripe for all active workspaces',
}) })
export class BillingSyncCustomerDataCommand extends ActiveWorkspacesCommandRunner { export class BillingSyncCustomerDataCommand extends ActiveWorkspacesMigrationCommandRunner {
constructor( constructor(
@InjectRepository(Workspace, 'core') @InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>, protected readonly workspaceRepository: Repository<Workspace>,
@ -34,7 +34,7 @@ export class BillingSyncCustomerDataCommand extends ActiveWorkspacesCommandRunne
super(workspaceRepository, twentyORMGlobalManager); super(workspaceRepository, twentyORMGlobalManager);
} }
async executeActiveWorkspacesCommand( async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[], _passedParam: string[],
options: SyncCustomerDataCommandOptions, options: SyncCustomerDataCommandOptions,
workspaceIds: string[], workspaceIds: string[],

View File

@ -7,9 +7,9 @@ import Stripe from 'stripe';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { import {
BaseCommandOptions, MigrationCommandOptions,
BaseCommandRunner, MigrationCommandRunner,
} from 'src/database/commands/base.command'; } from 'src/database/commands/migration-command/migration-command.runner';
import { BillingMeter } from 'src/engine/core-modules/billing/entities/billing-meter.entity'; import { BillingMeter } from 'src/engine/core-modules/billing/entities/billing-meter.entity';
import { BillingPrice } from 'src/engine/core-modules/billing/entities/billing-price.entity'; import { BillingPrice } from 'src/engine/core-modules/billing/entities/billing-price.entity';
import { BillingProduct } from 'src/engine/core-modules/billing/entities/billing-product.entity'; import { BillingProduct } from 'src/engine/core-modules/billing/entities/billing-product.entity';
@ -25,7 +25,7 @@ import { transformStripeProductToDatabaseProduct } from 'src/engine/core-modules
description: description:
'Fetches from stripe the plans data (meter, product and price) and upserts it into the database', 'Fetches from stripe the plans data (meter, product and price) and upserts it into the database',
}) })
export class BillingSyncPlansDataCommand extends BaseCommandRunner { export class BillingSyncPlansDataCommand extends MigrationCommandRunner {
private readonly batchSize = 5; private readonly batchSize = 5;
constructor( constructor(
@InjectRepository(BillingPrice, 'core') @InjectRepository(BillingPrice, 'core')
@ -43,7 +43,7 @@ export class BillingSyncPlansDataCommand extends BaseCommandRunner {
private async upsertMetersRepositoryData( private async upsertMetersRepositoryData(
meters: Stripe.Billing.Meter[], meters: Stripe.Billing.Meter[],
options: BaseCommandOptions, options: MigrationCommandOptions,
) { ) {
meters.map(async (meter) => { meters.map(async (meter) => {
try { try {
@ -64,7 +64,7 @@ export class BillingSyncPlansDataCommand extends BaseCommandRunner {
private async upsertProductRepositoryData( private async upsertProductRepositoryData(
product: Stripe.Product, product: Stripe.Product,
options: BaseCommandOptions, options: MigrationCommandOptions,
) { ) {
try { try {
if (!options.dryRun) { if (!options.dryRun) {
@ -83,7 +83,7 @@ export class BillingSyncPlansDataCommand extends BaseCommandRunner {
private async getBillingPrices( private async getBillingPrices(
products: Stripe.Product[], products: Stripe.Product[],
options: BaseCommandOptions, options: MigrationCommandOptions,
): Promise<Stripe.Price[][]> { ): Promise<Stripe.Price[][]> {
return await Promise.all( return await Promise.all(
products.map(async (product) => { products.map(async (product) => {
@ -113,7 +113,7 @@ export class BillingSyncPlansDataCommand extends BaseCommandRunner {
private async processBillingPricesByProductBatches( private async processBillingPricesByProductBatches(
products: Stripe.Product[], products: Stripe.Product[],
options: BaseCommandOptions, options: MigrationCommandOptions,
) { ) {
const prices: Stripe.Price[][] = []; const prices: Stripe.Price[][] = [];
@ -135,9 +135,9 @@ export class BillingSyncPlansDataCommand extends BaseCommandRunner {
return prices; return prices;
} }
override async executeBaseCommand( override async runMigrationCommand(
passedParams: string[], passedParams: string[],
options: BaseCommandOptions, options: MigrationCommandOptions,
): Promise<void> { ): Promise<void> {
const billingMeters = await this.stripeBillingMeterService.getAllMeters(); const billingMeters = await this.stripeBillingMeterService.getAllMeters();

View File

@ -64,9 +64,7 @@ registerEnumType(FieldMetadataType, {
@Relation('object', () => ObjectMetadataDTO, { @Relation('object', () => ObjectMetadataDTO, {
nullable: true, nullable: true,
}) })
export class FieldMetadataDTO< export class FieldMetadataDTO<T extends FieldMetadataType = FieldMetadataType> {
T extends FieldMetadataType | 'default' = 'default',
> {
@IsUUID() @IsUUID()
@IsNotEmpty() @IsNotEmpty()
@IDField(() => UUIDScalarType) @IDField(() => UUIDScalarType)
@ -75,7 +73,7 @@ export class FieldMetadataDTO<
@IsEnum(FieldMetadataType) @IsEnum(FieldMetadataType)
@IsNotEmpty() @IsNotEmpty()
@Field(() => FieldMetadataType) @Field(() => FieldMetadataType)
type: FieldMetadataType; type: T;
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()

View File

@ -44,7 +44,7 @@ class TextSettingsValidation {
@Injectable() @Injectable()
export class FieldMetadataValidationService< export class FieldMetadataValidationService<
T extends FieldMetadataType | 'default' = 'default', T extends FieldMetadataType = FieldMetadataType,
> { > {
constructor() {} constructor() {}

View File

@ -36,7 +36,7 @@ import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-met
'relationTargetObjectMetadataId', 'relationTargetObjectMetadataId',
]) ])
export class FieldMetadataEntity< export class FieldMetadataEntity<
T extends FieldMetadataType | 'default' = 'default', T extends FieldMetadataType = FieldMetadataType,
> implements FieldMetadataInterface<T> > implements FieldMetadataInterface<T>
{ {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')
@ -59,7 +59,7 @@ export class FieldMetadataEntity<
nullable: false, nullable: false,
type: 'varchar', type: 'varchar',
}) })
type: FieldMetadataType; type: T;
@Column({ nullable: false }) @Column({ nullable: false })
name: string; name: string;

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType, IsExactly } from 'twenty-shared';
import { import {
FieldMetadataDefaultActor, FieldMetadataDefaultActor,
@ -60,17 +60,14 @@ export type FieldMetadataFunctionDefaultValue = ExtractValueType<
FieldMetadataDefaultValueUuidFunction | FieldMetadataDefaultValueNowFunction FieldMetadataDefaultValueUuidFunction | FieldMetadataDefaultValueNowFunction
>; >;
type DefaultValueByFieldMetadata<T extends FieldMetadataType | 'default'> = [
T,
] extends [keyof FieldMetadataDefaultValueMapping]
? ExtractValueType<FieldMetadataDefaultValueMapping[T]> | null
: T extends 'default'
? ExtractValueType<UnionOfValues<FieldMetadataDefaultValueMapping>> | null
: never;
export type FieldMetadataDefaultValue< export type FieldMetadataDefaultValue<
T extends FieldMetadataType | 'default' = 'default', T extends FieldMetadataType = FieldMetadataType,
> = DefaultValueByFieldMetadata<T>; > =
IsExactly<T, FieldMetadataType> extends true
? ExtractValueType<UnionOfValues<FieldMetadataDefaultValueMapping>> | null
: T extends keyof FieldMetadataDefaultValueMapping
? ExtractValueType<FieldMetadataDefaultValueMapping[T]> | null
: never;
type FieldMetadataDefaultValueExtractedTypes = { type FieldMetadataDefaultValueExtractedTypes = {
[K in keyof FieldMetadataDefaultValueMapping]: ExtractValueType< [K in keyof FieldMetadataDefaultValueMapping]: ExtractValueType<

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType, IsExactly } from 'twenty-shared';
import { import {
FieldMetadataComplexOption, FieldMetadataComplexOption,
@ -11,13 +11,11 @@ type FieldMetadataOptionsMapping = {
[FieldMetadataType.MULTI_SELECT]: FieldMetadataComplexOption[]; [FieldMetadataType.MULTI_SELECT]: FieldMetadataComplexOption[];
}; };
type OptionsByFieldMetadata<T extends FieldMetadataType | 'default'> =
T extends keyof FieldMetadataOptionsMapping
? FieldMetadataOptionsMapping[T]
: T extends 'default'
? FieldMetadataDefaultOption[] | FieldMetadataComplexOption[]
: never;
export type FieldMetadataOptions< export type FieldMetadataOptions<
T extends FieldMetadataType | 'default' = 'default', T extends FieldMetadataType = FieldMetadataType,
> = OptionsByFieldMetadata<T>; > =
IsExactly<T, FieldMetadataType> extends true
? FieldMetadataDefaultOption[] | FieldMetadataComplexOption[]
: T extends keyof FieldMetadataOptionsMapping
? FieldMetadataOptionsMapping[T]
: never;

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType, IsExactly } from 'twenty-shared';
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface'; import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
@ -36,6 +36,7 @@ export type FieldMetadataDateTimeSettings = {
export type FieldMetadataRelationSettings = { export type FieldMetadataRelationSettings = {
relationType: RelationType; relationType: RelationType;
onDelete?: RelationOnDeleteAction; onDelete?: RelationOnDeleteAction;
joinColumnName?: string;
}; };
type FieldMetadataSettingsMapping = { type FieldMetadataSettingsMapping = {
@ -46,13 +47,11 @@ type FieldMetadataSettingsMapping = {
[FieldMetadataType.RELATION]: FieldMetadataRelationSettings; [FieldMetadataType.RELATION]: FieldMetadataRelationSettings;
}; };
type SettingsByFieldMetadata<T extends FieldMetadataType | 'default'> =
T extends keyof FieldMetadataSettingsMapping
? FieldMetadataSettingsMapping[T] & FieldMetadataDefaultSettings
: T extends 'default'
? FieldMetadataDefaultSettings
: never;
export type FieldMetadataSettings< export type FieldMetadataSettings<
T extends FieldMetadataType | 'default' = 'default', T extends FieldMetadataType = FieldMetadataType,
> = SettingsByFieldMetadata<T>; > =
IsExactly<T, FieldMetadataType> extends true
? FieldMetadataDefaultSettings
: T extends keyof FieldMetadataSettingsMapping
? FieldMetadataSettingsMapping[T] & FieldMetadataDefaultSettings
: never;

View File

@ -9,10 +9,10 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
export interface FieldMetadataInterface< export interface FieldMetadataInterface<
T extends FieldMetadataType | 'default' = 'default', T extends FieldMetadataType = FieldMetadataType,
> { > {
id: string; id: string;
type: FieldMetadataType; type: T;
name: string; name: string;
label: string; label: string;
defaultValue?: FieldMetadataDefaultValue<T>; defaultValue?: FieldMetadataDefaultValue<T>;

View File

@ -21,12 +21,12 @@ export function computeColumnName(
fieldName: string, fieldName: string,
options?: ComputeColumnNameOptions, options?: ComputeColumnNameOptions,
): string; ): string;
export function computeColumnName<T extends FieldMetadataType | 'default'>( export function computeColumnName<T extends FieldMetadataType>(
fieldMetadata: FieldMetadataInterface<T>, fieldMetadata: FieldMetadataInterface<T>,
ioptions?: ComputeColumnNameOptions, ioptions?: ComputeColumnNameOptions,
): string; ): string;
// TODO: If we need to implement custom name logic for columns, we can do it here // TODO: If we need to implement custom name logic for columns, we can do it here
export function computeColumnName<T extends FieldMetadataType | 'default'>( export function computeColumnName<T extends FieldMetadataType>(
fieldMetadataOrFieldName: FieldMetadataInterface<T> | string, fieldMetadataOrFieldName: FieldMetadataInterface<T> | string,
options?: ComputeColumnNameOptions, options?: ComputeColumnNameOptions,
): string { ): string {
@ -51,15 +51,11 @@ export function computeCompositeColumnName(
fieldName: string, fieldName: string,
compositeProperty: CompositeProperty, compositeProperty: CompositeProperty,
): string; ): string;
export function computeCompositeColumnName< export function computeCompositeColumnName<T extends FieldMetadataType>(
T extends FieldMetadataType | 'default',
>(
fieldMetadata: FieldTypeAndNameMetadata | FieldMetadataInterface<T>, fieldMetadata: FieldTypeAndNameMetadata | FieldMetadataInterface<T>,
compositeProperty: CompositeProperty, compositeProperty: CompositeProperty,
): string; ): string;
export function computeCompositeColumnName< export function computeCompositeColumnName<T extends FieldMetadataType>(
T extends FieldMetadataType | 'default',
>(
fieldMetadataOrFieldName: fieldMetadataOrFieldName:
| FieldTypeAndNameMetadata | FieldTypeAndNameMetadata
| FieldMetadataInterface<T> | FieldMetadataInterface<T>

View File

@ -67,9 +67,7 @@ export class CreateObjectInput {
@IsOptional() @IsOptional()
@Field(() => GraphQLJSON, { nullable: true }) @Field(() => GraphQLJSON, { nullable: true })
primaryKeyFieldMetadataSettings?: FieldMetadataSettings< primaryKeyFieldMetadataSettings?: FieldMetadataSettings<FieldMetadataType>;
FieldMetadataType | 'default'
>;
@IsBoolean() @IsBoolean()
@IsOptional() @IsOptional()

View File

@ -73,7 +73,7 @@ export class ObjectMetadataRelationService {
createdObjectMetadata: ObjectMetadataEntity, createdObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType, objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings: objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'> | FieldMetadataSettings<FieldMetadataType>
| undefined, | undefined,
relationObjectMetadataStandardId: string, relationObjectMetadataStandardId: string,
) { ) {
@ -109,7 +109,7 @@ export class ObjectMetadataRelationService {
relatedObjectMetadata: ObjectMetadataEntity, relatedObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType, objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings: objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'> | FieldMetadataSettings<FieldMetadataType>
| undefined, | undefined,
) { ) {
return this.fieldMetadataRepository.save([ return this.fieldMetadataRepository.save([
@ -340,7 +340,7 @@ export class ObjectMetadataRelationService {
relatedObjectMetadata: ObjectMetadataEntity, relatedObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType, objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings: objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'> | FieldMetadataSettings<FieldMetadataType>
| undefined, | undefined,
isUpdate = false, isUpdate = false,
) { ) {

View File

@ -147,7 +147,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
toObjectMetadata, toObjectMetadata,
[ [
foreignKeyFieldMetadata, foreignKeyFieldMetadata,
deletedAtFieldMetadata as FieldMetadataEntity<'default'>, deletedAtFieldMetadata as FieldMetadataEntity<FieldMetadataType>,
], ],
false, false,
false, false,
@ -451,7 +451,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
relationMetadata.toObjectMetadata, relationMetadata.toObjectMetadata,
[ [
foreignKeyFieldMetadata, foreignKeyFieldMetadata,
deletedAtFieldMetadata as FieldMetadataEntity<'default'>, deletedAtFieldMetadata as FieldMetadataEntity<FieldMetadataType>,
], ],
); );
@ -570,7 +570,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
} }
private throwIfDeletedAtFieldMetadataNotFound( private throwIfDeletedAtFieldMetadataNotFound(
deletedAtFieldMetadata?: FieldMetadataEntity<'default'> | null, deletedAtFieldMetadata?: FieldMetadataEntity<FieldMetadataType> | null,
) { ) {
if (!isDefined(deletedAtFieldMetadata)) { if (!isDefined(deletedAtFieldMetadata)) {
throw new RelationMetadataException( throw new RelationMetadataException(

View File

@ -36,7 +36,7 @@ export class RemoteTableRelationsService {
workspaceId: string, workspaceId: string,
remoteObjectMetadata: ObjectMetadataEntity, remoteObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyFieldSettings: objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'> | FieldMetadataSettings<FieldMetadataType>
| undefined, | undefined,
objectPrimaryKeyColumnType?: string, objectPrimaryKeyColumnType?: string,
) { ) {
@ -150,7 +150,7 @@ export class RemoteTableRelationsService {
createdObjectMetadata: ObjectMetadataEntity, createdObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType, objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings: objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'> | FieldMetadataSettings<FieldMetadataType>
| undefined, | undefined,
) { ) {
const attachmentObjectMetadata = const attachmentObjectMetadata =
@ -190,7 +190,7 @@ export class RemoteTableRelationsService {
createdObjectMetadata: ObjectMetadataEntity, createdObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType, objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings: objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'> | FieldMetadataSettings<FieldMetadataType>
| undefined, | undefined,
) { ) {
const timelineActivityObjectMetadata = const timelineActivityObjectMetadata =
@ -230,7 +230,7 @@ export class RemoteTableRelationsService {
createdObjectMetadata: ObjectMetadataEntity, createdObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType, objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings: objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'> | FieldMetadataSettings<FieldMetadataType>
| undefined, | undefined,
) { ) {
const favoriteObjectMetadata = const favoriteObjectMetadata =

View File

@ -37,12 +37,12 @@ export const mapUdtNameToFieldSettings = (
case 'int4': case 'int4':
return { return {
dataType: NumberDataType.INT, dataType: NumberDataType.INT,
} satisfies FieldMetadataSettings<FieldMetadataType.NUMBER>; } as FieldMetadataSettings<FieldMetadataType.NUMBER>;
case 'int8': case 'int8':
case 'bigint': case 'bigint':
return { return {
dataType: NumberDataType.BIGINT, dataType: NumberDataType.BIGINT,
} satisfies FieldMetadataSettings<FieldMetadataType.NUMBER>; } as FieldMetadataSettings<FieldMetadataType.NUMBER>;
default: default:
return undefined; return undefined;
} }

View File

@ -18,9 +18,8 @@ import {
WorkspaceMigrationExceptionCode, WorkspaceMigrationExceptionCode,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception'; } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
export class ColumnActionAbstractFactory< export class ColumnActionAbstractFactory<T extends FieldMetadataType>
T extends FieldMetadataType | 'default', implements WorkspaceColumnActionFactory<T>
> implements WorkspaceColumnActionFactory<T>
{ {
protected readonly logger = new Logger(ColumnActionAbstractFactory.name); protected readonly logger = new Logger(ColumnActionAbstractFactory.name);

View File

@ -8,9 +8,7 @@ import {
WorkspaceMigrationColumnActionType, WorkspaceMigrationColumnActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
export interface WorkspaceColumnActionFactory< export interface WorkspaceColumnActionFactory<T extends FieldMetadataType> {
T extends FieldMetadataType | 'default',
> {
create( create(
action: action:
| WorkspaceMigrationColumnActionType.CREATE | WorkspaceMigrationColumnActionType.CREATE

View File

@ -1,7 +1,6 @@
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
import { import {
FieldMetadataNumberSettings, NumberDataType
FieldMetadataTextSettings,
NumberDataType,
} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed'; import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed';
@ -22,8 +21,8 @@ export const SURVEY_RESULTS_METADATA_SEEDS: ObjectMetadataSeed = {
dataType: NumberDataType.FLOAT, dataType: NumberDataType.FLOAT,
decimals: 3, decimals: 3,
type: 'number', type: 'number',
} as FieldMetadataNumberSettings, },
}, } as FieldMetadataDTO<FieldMetadataType.NUMBER>,
{ {
type: FieldMetadataType.NUMBER, type: FieldMetadataType.NUMBER,
label: 'Percentage of completion (Float 3 decimals + percentage)', label: 'Percentage of completion (Float 3 decimals + percentage)',
@ -32,8 +31,8 @@ export const SURVEY_RESULTS_METADATA_SEEDS: ObjectMetadataSeed = {
dataType: NumberDataType.FLOAT, dataType: NumberDataType.FLOAT,
decimals: 6, decimals: 6,
type: 'percentage', type: 'percentage',
} as FieldMetadataNumberSettings, },
}, } as FieldMetadataDTO<FieldMetadataType.NUMBER>,
{ {
type: FieldMetadataType.NUMBER, type: FieldMetadataType.NUMBER,
label: 'Participants (Int)', label: 'Participants (Int)',
@ -41,8 +40,8 @@ export const SURVEY_RESULTS_METADATA_SEEDS: ObjectMetadataSeed = {
settings: { settings: {
dataType: NumberDataType.INT, dataType: NumberDataType.INT,
type: 'number', type: 'number',
} as FieldMetadataNumberSettings, },
}, } as FieldMetadataDTO<FieldMetadataType.NUMBER>,
{ {
type: FieldMetadataType.NUMBER, type: FieldMetadataType.NUMBER,
label: 'Average estimated number of atoms in the universe (BigInt)', label: 'Average estimated number of atoms in the universe (BigInt)',
@ -50,23 +49,23 @@ export const SURVEY_RESULTS_METADATA_SEEDS: ObjectMetadataSeed = {
settings: { settings: {
dataType: NumberDataType.BIGINT, dataType: NumberDataType.BIGINT,
type: 'number', type: 'number',
} as FieldMetadataNumberSettings, },
}, } as FieldMetadataDTO<FieldMetadataType.NUMBER>,
{ {
type: FieldMetadataType.TEXT, type: FieldMetadataType.TEXT,
label: 'Comments (Max 5 rows)', label: 'Comments (Max 5 rows)',
name: 'comments', name: 'comments',
settings: { settings: {
displayedMaxRows: 5, displayedMaxRows: 5,
} as FieldMetadataTextSettings, },
}, } as FieldMetadataDTO<FieldMetadataType.TEXT>,
{ {
type: FieldMetadataType.TEXT, type: FieldMetadataType.TEXT,
label: 'Short notes (Max 1 row)', label: 'Short notes (Max 1 row)',
name: 'shortNotes', name: 'shortNotes',
settings: { settings: {
displayedMaxRows: 1, displayedMaxRows: 1,
} as FieldMetadataTextSettings, },
}, } as FieldMetadataDTO<FieldMetadataType.TEXT>,
], ],
}; };

View File

@ -11,7 +11,7 @@ import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args
import { TypedReflect } from 'src/utils/typed-reflect'; import { TypedReflect } from 'src/utils/typed-reflect';
export interface WorkspaceFieldOptions< export interface WorkspaceFieldOptions<
T extends FieldMetadataType | 'default', T extends FieldMetadataType = FieldMetadataType,
> { > {
standardId: string; standardId: string;
type: T; type: T;

View File

@ -0,0 +1,28 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export function isFieldMetadataOfType<
Field extends FieldMetadataInterface<FieldMetadataType>,
Type extends FieldMetadataType,
>(
fieldMetadata: Field,
type: Type,
): fieldMetadata is Field & FieldMetadataInterface<Type>;
export function isFieldMetadataOfType<
Field extends FieldMetadataEntity<FieldMetadataType>,
Type extends FieldMetadataType,
>(
fieldMetadata: Field,
type: Type,
): fieldMetadata is Field & FieldMetadataEntity<Type>;
export function isFieldMetadataOfType<
Field extends
| FieldMetadataInterface<FieldMetadataType>
| FieldMetadataEntity<FieldMetadataType>,
Type extends FieldMetadataType,
>(fieldMetadata: Field, type: Type): boolean {
return fieldMetadata.type === type;
}

View File

@ -1,9 +0,0 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
export const isRelationFieldMetadata = (
fieldMetadata: FieldMetadataInterface<'default' | FieldMetadataType.RELATION>,
): fieldMetadata is FieldMetadataInterface<FieldMetadataType.RELATION> => {
return fieldMetadata.type === FieldMetadataType.RELATION;
};

View File

@ -5,9 +5,9 @@ import { WorkspaceActivationStatus } from 'twenty-shared';
import { In, Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { import {
BaseCommandOptions, MigrationCommandOptions,
BaseCommandRunner, MigrationCommandRunner,
} from 'src/database/commands/base.command'; } from 'src/database/commands/migration-command/migration-command.runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { CleanerWorkspaceService } from 'src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service'; import { CleanerWorkspaceService } from 'src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service';
@ -15,7 +15,7 @@ import { CleanerWorkspaceService } from 'src/engine/workspace-manager/workspace-
name: 'workspace:clean', name: 'workspace:clean',
description: 'Clean suspended workspace', description: 'Clean suspended workspace',
}) })
export class CleanSuspendedWorkspacesCommand extends BaseCommandRunner { export class CleanSuspendedWorkspacesCommand extends MigrationCommandRunner {
private workspaceIds: string[] = []; private workspaceIds: string[] = [];
constructor( constructor(
@ -50,9 +50,9 @@ export class CleanSuspendedWorkspacesCommand extends BaseCommandRunner {
return suspendedWorkspaces.map((workspace) => workspace.id); return suspendedWorkspaces.map((workspace) => workspace.id);
} }
override async executeBaseCommand( override async runMigrationCommand(
_passedParams: string[], _passedParams: string[],
options: BaseCommandOptions, options: MigrationCommandOptions,
): Promise<void> { ): Promise<void> {
const { dryRun } = options; const { dryRun } = options;

View File

@ -3,7 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Command, Option } from 'nest-commander'; import { Command, Option } from 'nest-commander';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; import {
ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@ -12,18 +15,16 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
import { SyncWorkspaceLoggerService } from './services/sync-workspace-logger.service'; import { SyncWorkspaceLoggerService } from './services/sync-workspace-logger.service';
// TODO: implement dry-run interface RunWorkspaceMigrationsOptions
interface RunWorkspaceMigrationsOptions { extends ActiveWorkspacesMigrationCommandOptions {
dryRun?: boolean;
force?: boolean; force?: boolean;
workspaceId?: string;
} }
@Command({ @Command({
name: 'workspace:sync-metadata', name: 'workspace:sync-metadata',
description: 'Sync metadata', description: 'Sync metadata',
}) })
export class SyncWorkspaceMetadataCommand extends ActiveWorkspacesCommandRunner { export class SyncWorkspaceMetadataCommand extends ActiveWorkspacesMigrationCommandRunner {
constructor( constructor(
@InjectRepository(Workspace, 'core') @InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>, protected readonly workspaceRepository: Repository<Workspace>,
@ -36,7 +37,7 @@ export class SyncWorkspaceMetadataCommand extends ActiveWorkspacesCommandRunner
super(workspaceRepository, twentyORMGlobalManager); super(workspaceRepository, twentyORMGlobalManager);
} }
async executeActiveWorkspacesCommand( async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[], _passedParam: string[],
options: RunWorkspaceMigrationsOptions, options: RunWorkspaceMigrationsOptions,
workspaceIds: string[], workspaceIds: string[],
@ -133,15 +134,6 @@ export class SyncWorkspaceMetadataCommand extends ActiveWorkspacesCommandRunner
); );
} }
@Option({
flags: '-d, --dry-run',
description: 'Dry run without applying changes',
required: false,
})
dryRun(): boolean {
return true;
}
@Option({ @Option({
flags: '-f, --force', flags: '-f, --force',
description: 'Force migration', description: 'Force migration',

View File

@ -0,0 +1,5 @@
export type IsExactly<T, U> = [T] extends [U]
? [U] extends [T]
? true
: false
: false;

View File

@ -1,2 +1,4 @@
export * from './ConnectedAccountProvider'; export * from './ConnectedAccountProvider';
export * from './FieldMetadataType'; export * from './FieldMetadataType';
export * from './IsExactly';