Move apikey/webhook migration command from 1.3 to 1.1 (#13146)

This commit is contained in:
Weiko
2025-07-10 11:15:31 +02:00
committed by GitHub
parent 50e402af07
commit 4467de1b5c
6 changed files with 39 additions and 44 deletions
@@ -0,0 +1,114 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import {
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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { WORKFLOW_RUN_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { WorkflowRunStatus } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
@Command({
name: 'upgrade:1-1:add-enqueued-status-to-workflow-run',
description: 'Add enqueued status to workflow run',
})
export class AddEnqueuedStatusToWorkflowRunCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
@InjectRepository(FieldMetadataEntity, 'core')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Adding enqueued status to workflow run for workspace ${workspaceId}`,
);
const workflowRunStatusFieldMetadata =
await this.fieldMetadataRepository.findOne({
where: {
standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.status,
},
});
if (!workflowRunStatusFieldMetadata) {
this.logger.error(
`Workflow run status field metadata not found for workspace ${workspaceId}`,
);
return;
}
const workflowRunStatusFieldMetadataOptions =
workflowRunStatusFieldMetadata.options;
// check if enqueued status is already in the field metadata options
if (
workflowRunStatusFieldMetadataOptions.some(
(option) => option.value === WorkflowRunStatus.ENQUEUED,
)
) {
this.logger.log(
`Workflow run status field metadata options already contain enqueued status for workspace ${workspaceId}`,
);
return;
} else if (options.dryRun) {
this.logger.log(
`Would add enqueued status to workflow run status field metadata for workspace ${workspaceId}`,
);
} else {
workflowRunStatusFieldMetadataOptions.push({
value: WorkflowRunStatus.ENQUEUED,
label: 'Enqueued',
position: 4,
color: 'blue',
});
await this.fieldMetadataRepository.save(workflowRunStatusFieldMetadata);
this.logger.log(
`Enqueued status added to workflow run status field metadata for workspace ${workspaceId}`,
);
}
const schemaName =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const mainDataSource =
await this.workspaceDataSourceService.connectToMainDataSource();
if (options.dryRun) {
this.logger.log(
`Would try to add enqueued status to workflow run status enum for workspace ${workspaceId}`,
);
} else {
try {
await mainDataSource.query(
`ALTER TYPE ${schemaName}."workflowRun_status_enum" ADD VALUE 'ENQUEUED'`,
);
this.logger.log(
`Enqueued status added to workflow run status enum for workspace ${workspaceId}`,
);
} catch (error) {
this.logger.error(
`Error adding enqueued status to workflow run status enum for workspace ${workspaceId}: ${error}`,
);
}
}
}
}
@@ -0,0 +1,212 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { ApiKey } from 'src/engine/core-modules/api-key/api-key.entity';
import { ApiKeyService } from 'src/engine/core-modules/api-key/api-key.service';
import { Webhook } from 'src/engine/core-modules/webhook/webhook.entity';
import { WebhookService } from 'src/engine/core-modules/webhook/webhook.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { ApiKeyWorkspaceEntity } from 'src/modules/api-key/standard-objects/api-key.workspace-entity';
import { WebhookWorkspaceEntity } from 'src/modules/webhook/standard-objects/webhook.workspace-entity';
@Command({
name: 'upgrade:1-1:migrate-api-keys-webhooks-to-core',
description:
'Migrate API keys and webhooks from workspace schemas to core schema',
})
export class MigrateApiKeysWebhooksToCoreCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(ApiKey, 'core')
private readonly coreApiKeyRepository: Repository<ApiKey>,
@InjectRepository(Webhook, 'core')
private readonly coreWebhookRepository: Repository<Webhook>,
private readonly apiKeyService: ApiKeyService,
private readonly webhookService: WebhookService,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Migrating API keys and webhooks for workspace ${workspaceId} ${index + 1}/${total}`,
);
try {
await this.migrateApiKeys(workspaceId, options.dryRun);
await this.migrateWebhooks(workspaceId, options.dryRun);
this.logger.log(
`Successfully migrated API keys and webhooks for workspace ${workspaceId}`,
);
} catch (error) {
this.logger.error(
`Failed to migrate API keys and webhooks for workspace ${workspaceId}: ${error.message}`,
);
throw error;
}
}
private async migrateApiKeys(
workspaceId: string,
dryRun?: boolean,
): Promise<void> {
const workspaceApiKeyRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ApiKeyWorkspaceEntity>(
workspaceId,
'apiKey',
{ shouldBypassPermissionChecks: true },
);
const workspaceApiKeys = await workspaceApiKeyRepository.find({
withDeleted: true,
});
if (workspaceApiKeys.length === 0) {
this.logger.log(`No API keys to migrate for workspace ${workspaceId}`);
return;
}
this.logger.log(
`${dryRun ? 'DRY RUN: ' : ''}Found ${workspaceApiKeys.length} API keys to migrate for workspace ${workspaceId}`,
);
if (dryRun) {
workspaceApiKeys.forEach((apiKey) => {
const deletedStatus = apiKey.deletedAt ? ' (DELETED)' : '';
this.logger.log(
`DRY RUN: Would migrate API key ${apiKey.id} (${apiKey.name})${deletedStatus} from workspace ${workspaceId}`,
);
});
return;
}
const existingCoreApiKeys = await this.coreApiKeyRepository.find({
where: { workspaceId },
select: ['id'],
withDeleted: true,
});
const existingApiKeyIds = new Set(existingCoreApiKeys.map((ak) => ak.id));
for (const workspaceApiKey of workspaceApiKeys) {
if (existingApiKeyIds.has(workspaceApiKey.id)) {
this.logger.warn(
`API key ${workspaceApiKey.id} already exists in core schema for workspace ${workspaceId}, skipping`,
);
continue;
}
await this.apiKeyService.create({
id: workspaceApiKey.id,
name: workspaceApiKey.name,
expiresAt: workspaceApiKey.expiresAt,
revokedAt: workspaceApiKey.revokedAt
? new Date(workspaceApiKey.revokedAt)
: workspaceApiKey.deletedAt
? new Date(workspaceApiKey.deletedAt)
: undefined,
workspaceId,
createdAt: new Date(workspaceApiKey.createdAt),
updatedAt: new Date(workspaceApiKey.updatedAt),
});
const deletedStatus = workspaceApiKey.deletedAt ? ' (DELETED)' : '';
this.logger.log(
`Migrated API key ${workspaceApiKey.id} (${workspaceApiKey.name})${deletedStatus} to core schema`,
);
}
}
private async migrateWebhooks(
workspaceId: string,
dryRun?: boolean,
): Promise<void> {
const workspaceWebhookRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WebhookWorkspaceEntity>(
workspaceId,
'webhook',
{ shouldBypassPermissionChecks: true },
);
const workspaceWebhooks = await workspaceWebhookRepository.find({
withDeleted: true,
});
if (workspaceWebhooks.length === 0) {
this.logger.log(`No webhooks to migrate for workspace ${workspaceId}`);
return;
}
this.logger.log(
`${dryRun ? 'DRY RUN: ' : ''}Found ${workspaceWebhooks.length} webhooks to migrate for workspace ${workspaceId}`,
);
if (dryRun) {
workspaceWebhooks.forEach((webhook) => {
const deletedStatus = webhook.deletedAt ? ' (DELETED)' : '';
this.logger.log(
`DRY RUN: Would migrate webhook ${webhook.id} (${webhook.targetUrl})${deletedStatus} from workspace ${workspaceId}`,
);
});
return;
}
const existingCoreWebhooks = await this.coreWebhookRepository.find({
where: { workspaceId },
select: ['id'],
withDeleted: true,
});
const existingWebhookIds = new Set(existingCoreWebhooks.map((wh) => wh.id));
for (const workspaceWebhook of workspaceWebhooks) {
if (existingWebhookIds.has(workspaceWebhook.id)) {
this.logger.warn(
`Webhook ${workspaceWebhook.id} already exists in core schema for workspace ${workspaceId}, skipping`,
);
continue;
}
await this.webhookService.create({
id: workspaceWebhook.id,
targetUrl: workspaceWebhook.targetUrl,
operations: workspaceWebhook.operations,
description: workspaceWebhook.description,
secret: workspaceWebhook.secret,
workspaceId,
createdAt: new Date(workspaceWebhook.createdAt),
updatedAt: new Date(workspaceWebhook.updatedAt),
deletedAt: workspaceWebhook.deletedAt
? new Date(workspaceWebhook.deletedAt)
: undefined,
});
const deletedStatus = workspaceWebhook.deletedAt ? ' (DELETED)' : '';
this.logger.log(
`Migrated webhook ${workspaceWebhook.id} (${workspaceWebhook.targetUrl})${deletedStatus} to core schema`,
);
}
}
}
@@ -1,12 +1,19 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AddEnqueuedStatusToWorkflowRunCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-add-enqueued-status-to-workflow-run.command';
import { FixSchemaArrayTypeCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command';
import { FixUpdateStandardFieldsIsLabelSyncedWithName } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command';
import { MigrateApiKeysWebhooksToCoreCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-migrate-api-keys-webhooks-to-core.command';
import { MigrateWorkflowRunStatesCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-migrate-workflow-run-state.command';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { ApiKey } from 'src/engine/core-modules/api-key/api-key.entity';
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Webhook } from 'src/engine/core-modules/webhook/webhook.entity';
import { WebhookModule } from 'src/engine/core-modules/webhook/webhook.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -14,7 +21,6 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module';
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
import { MigrateWorkflowRunStatesCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-migrate-workflow-run-state.command';
@Module({
imports: [
@@ -26,6 +32,8 @@ import { MigrateWorkflowRunStatesCommand } from 'src/database/commands/upgrade-v
UserWorkspace,
FieldMetadataEntity,
ObjectMetadataEntity,
ApiKey,
Webhook,
],
'core',
),
@@ -34,16 +42,22 @@ import { MigrateWorkflowRunStatesCommand } from 'src/database/commands/upgrade-v
WorkspaceMetadataVersionModule,
WorkspaceHealthModule,
TypeORMModule,
ApiKeyModule,
WebhookModule,
],
providers: [
FixUpdateStandardFieldsIsLabelSyncedWithName,
FixSchemaArrayTypeCommand,
MigrateWorkflowRunStatesCommand,
MigrateApiKeysWebhooksToCoreCommand,
AddEnqueuedStatusToWorkflowRunCommand,
],
exports: [
FixUpdateStandardFieldsIsLabelSyncedWithName,
FixSchemaArrayTypeCommand,
MigrateWorkflowRunStatesCommand,
MigrateApiKeysWebhooksToCoreCommand,
AddEnqueuedStatusToWorkflowRunCommand,
],
})
export class V1_1_UpgradeVersionCommandModule {}