Move apikey/webhook migration command from 1.3 to 1.1 (#13146)
This commit is contained in:
+114
@@ -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}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+212
@@ -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`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -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 {}
|
||||
|
||||
Reference in New Issue
Block a user