Refactor upgrade commands (#10592)
Simplifying a lot the upgrade system.
New way to upgrade:
`yarn command:prod upgrade`
New way to write upgrade commands (all wrapping is done for you)
```
override async runOnWorkspace({
index,
total,
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {}
```
Also cleaning CommandModule imports to make it lighter
This commit is contained in:
@ -7,22 +7,19 @@ import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
MaintainedWorkspacesMigrationCommandOptions,
|
||||
MaintainedWorkspacesMigrationCommandRunner,
|
||||
} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner';
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
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 { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
interface SyncCustomerDataCommandOptions
|
||||
extends MaintainedWorkspacesMigrationCommandOptions {}
|
||||
|
||||
@Command({
|
||||
name: 'billing:sync-customer-data',
|
||||
description: 'Sync customer data from Stripe for all active workspaces',
|
||||
})
|
||||
export class BillingSyncCustomerDataCommand extends MaintainedWorkspacesMigrationCommandRunner {
|
||||
export class BillingSyncCustomerDataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
@ -34,39 +31,10 @@ export class BillingSyncCustomerDataCommand extends MaintainedWorkspacesMigratio
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
}
|
||||
|
||||
async runMigrationCommandOnMaintainedWorkspaces(
|
||||
_passedParam: string[],
|
||||
options: SyncCustomerDataCommandOptions,
|
||||
workspaceIds: string[],
|
||||
): Promise<void> {
|
||||
this.logger.log('Running command to sync customer data');
|
||||
|
||||
for (const workspaceId of workspaceIds) {
|
||||
this.logger.log(`Running command for workspace ${workspaceId}`);
|
||||
|
||||
try {
|
||||
await this.syncCustomerDataForWorkspace(workspaceId, options);
|
||||
} catch (error) {
|
||||
this.logger.log(
|
||||
chalk.red(
|
||||
`Running command on workspace ${workspaceId} failed with error: ${error}, ${error.stack}`,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
} finally {
|
||||
this.logger.log(
|
||||
chalk.green(`Finished running command for workspace ${workspaceId}.`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(chalk.green(`Command completed!`));
|
||||
}
|
||||
|
||||
private async syncCustomerDataForWorkspace(
|
||||
workspaceId: string,
|
||||
options: SyncCustomerDataCommandOptions,
|
||||
): Promise<void> {
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const billingCustomer = await this.billingCustomerRepository.findOne({
|
||||
where: {
|
||||
workspaceId,
|
||||
|
||||
@ -9,7 +9,7 @@ import { Repository } from 'typeorm';
|
||||
import {
|
||||
MigrationCommandOptions,
|
||||
MigrationCommandRunner,
|
||||
} from 'src/database/commands/migration-command/migration-command.runner';
|
||||
} from 'src/database/commands/command-runners/migration.command-runner';
|
||||
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 { BillingProduct } from 'src/engine/core-modules/billing/entities/billing-product.entity';
|
||||
|
||||
@ -3,7 +3,6 @@ import { ModuleRef } from '@nestjs/core';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module';
|
||||
import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||
@ -62,7 +61,6 @@ import { WorkflowModule } from 'src/modules/workflow/workflow.module';
|
||||
providers: [
|
||||
CleanSuspendedWorkspacesJob,
|
||||
EmailSenderJob,
|
||||
DataSeedDemoWorkspaceJob,
|
||||
UpdateSubscriptionQuantityJob,
|
||||
HandleWorkspaceMemberDeletedJob,
|
||||
CleanWorkspaceDeletionWarningUserVarsJob,
|
||||
|
||||
@ -4,7 +4,6 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { generateObjectMetadataMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-maps.util';
|
||||
import {
|
||||
@ -25,7 +24,6 @@ export class WorkspaceMetadataCacheService {
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
@LogExecutionTime()
|
||||
async recomputeMetadataCache({
|
||||
workspaceId,
|
||||
ignoreLock = false,
|
||||
|
||||
@ -4,13 +4,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import {
|
||||
WorkspaceMetadataVersionException,
|
||||
WorkspaceMetadataVersionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMetadataVersionService {
|
||||
@ -20,10 +18,8 @@ export class WorkspaceMetadataVersionService {
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
@LogExecutionTime()
|
||||
async incrementMetadataVersion(workspaceId: string): Promise<void> {
|
||||
const workspace = await this.workspaceRepository.findOne({
|
||||
where: { id: workspaceId },
|
||||
|
||||
@ -68,10 +68,6 @@ export class WorkspaceDatasourceFactory {
|
||||
const result = await this.cacheManager.execute(
|
||||
cacheKey,
|
||||
async () => {
|
||||
this.logger.log(
|
||||
`Creating workspace data source for workspace ${workspaceId} and metadata version ${cachedWorkspaceMetadataVersion}`,
|
||||
);
|
||||
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId(
|
||||
workspaceId,
|
||||
|
||||
@ -7,7 +7,7 @@ import { In, Repository } from 'typeorm';
|
||||
import {
|
||||
MigrationCommandOptions,
|
||||
MigrationCommandRunner,
|
||||
} from 'src/database/commands/migration-command/migration-command.runner';
|
||||
} from 'src/database/commands/command-runners/migration.command-runner';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { CleanerWorkspaceService } from 'src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service';
|
||||
|
||||
|
||||
@ -1,35 +1,28 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command, Option } from 'nest-commander';
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
MaintainedWorkspacesMigrationCommandOptions,
|
||||
MaintainedWorkspacesMigrationCommandRunner,
|
||||
} from 'src/database/commands/migration-command/maintained-workspaces-migration-command.runner';
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceHealthService } from 'src/engine/workspace-manager/workspace-health/workspace-health.service';
|
||||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
||||
|
||||
import { SyncWorkspaceLoggerService } from './services/sync-workspace-logger.service';
|
||||
|
||||
interface RunWorkspaceMigrationsOptions
|
||||
extends MaintainedWorkspacesMigrationCommandOptions {
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
@Command({
|
||||
name: 'workspace:sync-metadata',
|
||||
description: 'Sync metadata',
|
||||
})
|
||||
export class SyncWorkspaceMetadataCommand extends MaintainedWorkspacesMigrationCommandRunner {
|
||||
export class SyncWorkspaceMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService,
|
||||
private readonly workspaceHealthService: WorkspaceHealthService,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
@ -37,109 +30,40 @@ export class SyncWorkspaceMetadataCommand extends MaintainedWorkspacesMigrationC
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
}
|
||||
|
||||
async runMigrationCommandOnMaintainedWorkspaces(
|
||||
_passedParam: string[],
|
||||
options: RunWorkspaceMigrationsOptions,
|
||||
workspaceIds: string[],
|
||||
): Promise<void> {
|
||||
this.logger.log(`Attempting to sync ${workspaceIds.length} workspaces.`);
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
index,
|
||||
total,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Running workspace sync for workspace: ${workspaceId} (${index} out of ${total})`,
|
||||
);
|
||||
|
||||
let count = 1;
|
||||
|
||||
const errorsDuringSync: string[] = [];
|
||||
|
||||
for (const workspaceId of workspaceIds) {
|
||||
this.logger.log(
|
||||
`Running workspace sync for workspace: ${workspaceId} (${count} out of ${workspaceIds.length})`,
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
);
|
||||
count++;
|
||||
|
||||
if (!options.force) {
|
||||
try {
|
||||
const issues =
|
||||
await this.workspaceHealthService.healthCheck(workspaceId);
|
||||
const { storage, workspaceMigrations } =
|
||||
await this.workspaceSyncMetadataService.synchronize(
|
||||
{
|
||||
workspaceId,
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
},
|
||||
{ applyChanges: !options.dryRun },
|
||||
);
|
||||
|
||||
// Security: abort if there are issues.
|
||||
if (issues.length > 0) {
|
||||
if (!options.force) {
|
||||
this.logger.error(
|
||||
`Workspace contains ${issues.length} issues, aborting.`,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
'If you want to force the migration, use --force flag',
|
||||
);
|
||||
this.logger.log(
|
||||
'Please use `workspace:health` command to check issues and fix them before running this command.',
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logger.warn(
|
||||
`Workspace contains ${issues.length} issues, sync has been forced.`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!options.force) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.logger.warn(
|
||||
`Workspace health check failed with error, but sync has been forced.`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const { storage, workspaceMigrations } =
|
||||
await this.workspaceSyncMetadataService.synchronize(
|
||||
{
|
||||
workspaceId,
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
},
|
||||
{ applyChanges: !options.dryRun },
|
||||
);
|
||||
|
||||
if (options.dryRun) {
|
||||
await this.syncWorkspaceLoggerService.saveLogs(
|
||||
workspaceId,
|
||||
storage,
|
||||
workspaceMigrations,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
errorsDuringSync.push(
|
||||
`Failed to synchronize workspace ${workspaceId}: ${error.message}`,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
if (options.dryRun) {
|
||||
await this.syncWorkspaceLoggerService.saveLogs(
|
||||
workspaceId,
|
||||
storage,
|
||||
workspaceMigrations,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Finished synchronizing all active workspaces (${
|
||||
workspaceIds.length
|
||||
} workspaces). ${
|
||||
errorsDuringSync.length > 0
|
||||
? 'Errors during sync:\n' + errorsDuringSync.join('.\n')
|
||||
: ''
|
||||
}`,
|
||||
`Finished synchronizing all active workspaces (${total} workspaces).`,
|
||||
);
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-f, --force',
|
||||
description: 'Force migration',
|
||||
required: false,
|
||||
})
|
||||
force(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
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 { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
@ -10,6 +12,8 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work
|
||||
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { WorkspaceMigrationBuilderModule } from 'src/engine/workspace-manager/workspace-migration-builder/workspace-migration-builder.module';
|
||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
||||
import { SyncWorkspaceLoggerService } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/services/sync-workspace-logger.service';
|
||||
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
||||
import { workspaceSyncMetadataComparators } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators';
|
||||
import { workspaceSyncMetadataFactories } from 'src/engine/workspace-manager/workspace-sync-metadata/factories';
|
||||
import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service';
|
||||
@ -34,7 +38,8 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
|
||||
],
|
||||
'metadata',
|
||||
),
|
||||
TypeOrmModule.forFeature([FeatureFlag], 'core'),
|
||||
DataSourceModule,
|
||||
TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'),
|
||||
WorkspaceMetadataVersionModule,
|
||||
],
|
||||
providers: [
|
||||
@ -47,7 +52,13 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
|
||||
WorkspaceSyncFieldMetadataService,
|
||||
WorkspaceSyncMetadataService,
|
||||
WorkspaceSyncIndexMetadataService,
|
||||
SyncWorkspaceLoggerService,
|
||||
SyncWorkspaceMetadataCommand,
|
||||
],
|
||||
exports: [
|
||||
...workspaceSyncMetadataFactories,
|
||||
WorkspaceSyncMetadataService,
|
||||
SyncWorkspaceMetadataCommand,
|
||||
],
|
||||
exports: [...workspaceSyncMetadataFactories, WorkspaceSyncMetadataService],
|
||||
})
|
||||
export class WorkspaceSyncMetadataModule {}
|
||||
|
||||
Reference in New Issue
Block a user