File storage cleaning (#18381)
- Remove feature flag - Remove legacy methods in file-upload and file-service - Migrate AI Chat to new file management --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
-531
@@ -1,531 +0,0 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import {
|
||||
LogicFunctionEntity,
|
||||
LogicFunctionRuntime,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
|
||||
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
import {
|
||||
DEFAULT_BUILT_HANDLER_PATH,
|
||||
DEFAULT_HANDLER_NAME,
|
||||
DEFAULT_SOURCE_HANDLER_PATH,
|
||||
} from 'src/engine/metadata-modules/logic-function/constants/handler.contant';
|
||||
|
||||
const OLD_BUILT_FOLDER = 'built-function';
|
||||
const OLD_SOURCE_FOLDER = 'serverless-function';
|
||||
const SEED_VERSION_DRAFT = 'draft';
|
||||
const SEED_VERSION_PUBLISHED = '1';
|
||||
|
||||
const OUTPUT_SCHEMA_LINK = {
|
||||
link: {
|
||||
tab: 'test',
|
||||
icon: 'IconVariable',
|
||||
label: 'Generate Function Output',
|
||||
isLeaf: true,
|
||||
},
|
||||
_outputSchemaType: 'LINK',
|
||||
} as const;
|
||||
|
||||
const OUTPUT_SCHEMA_MESSAGE = {
|
||||
message: {
|
||||
type: 'string',
|
||||
label: 'message',
|
||||
value: 'Hello, input: null and null',
|
||||
isLeaf: true,
|
||||
},
|
||||
} as const;
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-17:seed-workflow-v1-16',
|
||||
description:
|
||||
'[Temporary] Clean existing workflow runs, workflow versions, workflows, logic functions and old file storage, then seed 3 scenarios: (1) draft+active with LINK outputSchema, (2) draft-only with message outputSchema, (3) draft+active with mixed outputSchema (message on draft, LINK on active). For testing the 1-17 migrate-workflow-code-steps command.',
|
||||
})
|
||||
export class SeedWorkflowV1_16Command extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
protected readonly logger = new Logger(SeedWorkflowV1_16Command.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectRepository(LogicFunctionEntity)
|
||||
private readonly logicFunctionRepository: Repository<LogicFunctionEntity>,
|
||||
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly recordPositionService: RecordPositionService,
|
||||
) {
|
||||
super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(`Seeding workflow v1.16 data for workspace ${workspaceId}`);
|
||||
|
||||
await this.cleanWorkflowsAndOldFileStorage(workspaceId);
|
||||
|
||||
const workflowRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
'workflow',
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
await this.seedScenarioDraftAndActiveLink(
|
||||
workspaceId,
|
||||
workflowRepository,
|
||||
workflowVersionRepository,
|
||||
);
|
||||
await this.seedScenarioDraftOnlyMessage(
|
||||
workspaceId,
|
||||
workflowRepository,
|
||||
workflowVersionRepository,
|
||||
);
|
||||
await this.seedScenarioDraftAndActiveMixedOutputSchema(
|
||||
workspaceId,
|
||||
workflowRepository,
|
||||
workflowVersionRepository,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Seeded 3 workflows (draft+active LINK, draft-only message, draft+active mixed outputSchema) in workspace ${workspaceId}. Run upgrade:1-17:migrate-workflow-code-steps to test migration.`,
|
||||
);
|
||||
}
|
||||
|
||||
private buildCodeStep(
|
||||
logicFunctionId: string,
|
||||
version: string,
|
||||
outputSchema: object,
|
||||
stepName: string,
|
||||
) {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
name: stepName,
|
||||
type: WorkflowActionType.CODE,
|
||||
settings: {
|
||||
input: {
|
||||
serverlessFunctionId: logicFunctionId,
|
||||
serverlessFunctionInput: {},
|
||||
serverlessFunctionVersion: version,
|
||||
},
|
||||
outputSchema,
|
||||
},
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
|
||||
private buildTrigger(): {
|
||||
name: string;
|
||||
type: string;
|
||||
settings: { outputSchema: object };
|
||||
} {
|
||||
return {
|
||||
name: 'trigger',
|
||||
type: WorkflowTriggerType.MANUAL,
|
||||
settings: { outputSchema: {} },
|
||||
};
|
||||
}
|
||||
|
||||
private async seedScenarioDraftAndActiveLink(
|
||||
workspaceId: string,
|
||||
workflowRepository: Awaited<
|
||||
ReturnType<GlobalWorkspaceOrmManager['getRepository']>
|
||||
>,
|
||||
workflowVersionRepository: Awaited<
|
||||
ReturnType<GlobalWorkspaceOrmManager['getRepository']>
|
||||
>,
|
||||
): Promise<void> {
|
||||
const logicFunctionId = await this.insertLogicFunctionRow(
|
||||
workspaceId,
|
||||
'Seed (draft+active LINK)',
|
||||
);
|
||||
|
||||
await this.writeOldFormatFiles(logicFunctionId, SEED_VERSION_DRAFT);
|
||||
await this.writeOldFormatFiles(logicFunctionId, SEED_VERSION_PUBLISHED);
|
||||
|
||||
const workflowId = uuidv4();
|
||||
const draftVersionId = uuidv4();
|
||||
const activeVersionId = uuidv4();
|
||||
|
||||
const workflowPosition =
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflow',
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
const draftVersionPosition =
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
const activeVersionPosition =
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'last',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await workflowRepository.insert({
|
||||
id: workflowId,
|
||||
name: 'Seed draft+active (LINK)',
|
||||
statuses: [WorkflowStatus.DRAFT],
|
||||
position: workflowPosition,
|
||||
});
|
||||
|
||||
const trigger = this.buildTrigger();
|
||||
const draftSteps = [
|
||||
this.buildCodeStep(
|
||||
logicFunctionId,
|
||||
SEED_VERSION_DRAFT,
|
||||
OUTPUT_SCHEMA_LINK,
|
||||
'Code step (draft)',
|
||||
),
|
||||
];
|
||||
const activeSteps = [
|
||||
this.buildCodeStep(
|
||||
logicFunctionId,
|
||||
SEED_VERSION_PUBLISHED,
|
||||
OUTPUT_SCHEMA_LINK,
|
||||
'Code step (v1)',
|
||||
),
|
||||
];
|
||||
|
||||
await workflowVersionRepository.insert({
|
||||
id: draftVersionId,
|
||||
workflowId,
|
||||
name: 'v1',
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
trigger,
|
||||
steps: draftSteps,
|
||||
position: draftVersionPosition,
|
||||
});
|
||||
await workflowVersionRepository.insert({
|
||||
id: activeVersionId,
|
||||
workflowId,
|
||||
name: 'v2',
|
||||
status: WorkflowVersionStatus.ACTIVE,
|
||||
trigger,
|
||||
steps: activeSteps,
|
||||
position: activeVersionPosition,
|
||||
});
|
||||
await workflowRepository.update(workflowId, {
|
||||
lastPublishedVersionId: activeVersionId,
|
||||
statuses: [WorkflowStatus.ACTIVE],
|
||||
});
|
||||
}
|
||||
|
||||
private async seedScenarioDraftOnlyMessage(
|
||||
workspaceId: string,
|
||||
workflowRepository: Awaited<
|
||||
ReturnType<GlobalWorkspaceOrmManager['getRepository']>
|
||||
>,
|
||||
workflowVersionRepository: Awaited<
|
||||
ReturnType<GlobalWorkspaceOrmManager['getRepository']>
|
||||
>,
|
||||
): Promise<void> {
|
||||
const logicFunctionId = await this.insertLogicFunctionRow(
|
||||
workspaceId,
|
||||
'Seed (draft-only message)',
|
||||
);
|
||||
|
||||
await this.writeOldFormatFiles(logicFunctionId, SEED_VERSION_DRAFT);
|
||||
|
||||
const workflowId = uuidv4();
|
||||
const draftVersionId = uuidv4();
|
||||
|
||||
const workflowPosition =
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflow',
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
const draftVersionPosition =
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await workflowRepository.insert({
|
||||
id: workflowId,
|
||||
name: 'Seed draft-only (message)',
|
||||
statuses: [WorkflowStatus.DRAFT],
|
||||
position: workflowPosition,
|
||||
});
|
||||
|
||||
const trigger = this.buildTrigger();
|
||||
const draftSteps = [
|
||||
this.buildCodeStep(
|
||||
logicFunctionId,
|
||||
SEED_VERSION_DRAFT,
|
||||
OUTPUT_SCHEMA_MESSAGE,
|
||||
'Code step (draft)',
|
||||
),
|
||||
];
|
||||
|
||||
await workflowVersionRepository.insert({
|
||||
id: draftVersionId,
|
||||
workflowId,
|
||||
name: 'v1',
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
trigger,
|
||||
steps: draftSteps,
|
||||
position: draftVersionPosition,
|
||||
});
|
||||
}
|
||||
|
||||
private async seedScenarioDraftAndActiveMixedOutputSchema(
|
||||
workspaceId: string,
|
||||
workflowRepository: Awaited<
|
||||
ReturnType<GlobalWorkspaceOrmManager['getRepository']>
|
||||
>,
|
||||
workflowVersionRepository: Awaited<
|
||||
ReturnType<GlobalWorkspaceOrmManager['getRepository']>
|
||||
>,
|
||||
): Promise<void> {
|
||||
const logicFunctionId = await this.insertLogicFunctionRow(
|
||||
workspaceId,
|
||||
'Seed (draft+active mixed)',
|
||||
);
|
||||
|
||||
await this.writeOldFormatFiles(logicFunctionId, SEED_VERSION_DRAFT);
|
||||
await this.writeOldFormatFiles(logicFunctionId, SEED_VERSION_PUBLISHED);
|
||||
|
||||
const workflowId = uuidv4();
|
||||
const draftVersionId = uuidv4();
|
||||
const activeVersionId = uuidv4();
|
||||
|
||||
const workflowPosition =
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflow',
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
const draftVersionPosition =
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
const activeVersionPosition =
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'last',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await workflowRepository.insert({
|
||||
id: workflowId,
|
||||
name: 'Seed draft+active (mixed)',
|
||||
statuses: [WorkflowStatus.DRAFT],
|
||||
position: workflowPosition,
|
||||
});
|
||||
|
||||
const trigger = this.buildTrigger();
|
||||
const draftSteps = [
|
||||
this.buildCodeStep(
|
||||
logicFunctionId,
|
||||
SEED_VERSION_DRAFT,
|
||||
OUTPUT_SCHEMA_MESSAGE,
|
||||
'Code step (draft, message)',
|
||||
),
|
||||
];
|
||||
const activeSteps = [
|
||||
this.buildCodeStep(
|
||||
logicFunctionId,
|
||||
SEED_VERSION_PUBLISHED,
|
||||
OUTPUT_SCHEMA_LINK,
|
||||
'Code step (v1, LINK)',
|
||||
),
|
||||
];
|
||||
|
||||
await workflowVersionRepository.insert({
|
||||
id: draftVersionId,
|
||||
workflowId,
|
||||
name: 'v1',
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
trigger,
|
||||
steps: draftSteps,
|
||||
position: draftVersionPosition,
|
||||
});
|
||||
await workflowVersionRepository.insert({
|
||||
id: activeVersionId,
|
||||
workflowId,
|
||||
name: 'v2',
|
||||
status: WorkflowVersionStatus.ACTIVE,
|
||||
trigger,
|
||||
steps: activeSteps,
|
||||
position: activeVersionPosition,
|
||||
});
|
||||
await workflowRepository.update(workflowId, {
|
||||
lastPublishedVersionId: activeVersionId,
|
||||
statuses: [WorkflowStatus.ACTIVE],
|
||||
});
|
||||
}
|
||||
|
||||
private async insertLogicFunctionRow(
|
||||
workspaceId: string,
|
||||
name: string = 'Seed code step (v1.16)',
|
||||
): Promise<string> {
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
const applicationId = workspaceCustomFlatApplication.id;
|
||||
const id = uuidv4();
|
||||
const universalIdentifier = uuidv4();
|
||||
const now = new Date();
|
||||
|
||||
await this.logicFunctionRepository.insert({
|
||||
id,
|
||||
workspaceId,
|
||||
universalIdentifier,
|
||||
applicationId,
|
||||
name,
|
||||
description: 'Temporary logic function for 1.17 migration testing',
|
||||
sourceHandlerPath: DEFAULT_SOURCE_HANDLER_PATH,
|
||||
builtHandlerPath: DEFAULT_BUILT_HANDLER_PATH,
|
||||
handlerName: DEFAULT_HANDLER_NAME,
|
||||
runtime: LogicFunctionRuntime.NODE22,
|
||||
timeoutSeconds: 300,
|
||||
checksum: null,
|
||||
toolInputSchema: null,
|
||||
isTool: false,
|
||||
cronTriggerSettings: null,
|
||||
databaseEventTriggerSettings: null,
|
||||
httpRouteTriggerSettings: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private async cleanWorkflowsAndOldFileStorage(
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
'workflowRun',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
const workflowRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
'workflow',
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
|
||||
const deletedRuns = await workflowRunRepository.delete({});
|
||||
const deletedVersions = await workflowVersionRepository.delete({});
|
||||
const deletedWorkflows = await workflowRepository.delete({});
|
||||
|
||||
const deletedLogicFunctions = await this.logicFunctionRepository.delete({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Cleaned workspace ${workspaceId}: ${deletedRuns.affected ?? 0} workflow run(s), ${deletedVersions.affected ?? 0} workflow version(s), ${deletedWorkflows.affected ?? 0} workflow(s), ${deletedLogicFunctions.affected ?? 0} logic function(s)`,
|
||||
);
|
||||
|
||||
try {
|
||||
await this.fileStorageService.deleteLegacy({
|
||||
folderPath: OLD_BUILT_FOLDER,
|
||||
});
|
||||
await this.fileStorageService.deleteLegacy({
|
||||
folderPath: OLD_SOURCE_FOLDER,
|
||||
});
|
||||
this.logger.log(
|
||||
`Cleaned old file storage: ${OLD_BUILT_FOLDER}, ${OLD_SOURCE_FOLDER}`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`Old file storage cleanup skipped (folders may not exist): ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async writeOldFormatFiles(
|
||||
logicFunctionId: string,
|
||||
version: string,
|
||||
): Promise<void> {
|
||||
const builtFolder = `${OLD_BUILT_FOLDER}/${logicFunctionId}/${version}`;
|
||||
const sourceFolder = `${OLD_SOURCE_FOLDER}/${logicFunctionId}/${version}`;
|
||||
|
||||
const builtSources = {
|
||||
'index.mjs':
|
||||
'export default async function main() { return { message: "ok" }; }\n',
|
||||
};
|
||||
const sourceSources = {
|
||||
src: {
|
||||
'index.ts':
|
||||
'export default async function main(): Promise<{ message: string }> {\n return { message: "ok" };\n}\n',
|
||||
},
|
||||
};
|
||||
|
||||
await this.fileStorageService.writeFolderLegacy(builtSources, builtFolder);
|
||||
await this.fileStorageService.writeFolderLegacy(
|
||||
sourceSources,
|
||||
sourceFolder,
|
||||
);
|
||||
}
|
||||
}
|
||||
-3
@@ -11,7 +11,6 @@ import { MigrateDateTimeIsFilterValuesCommand } from 'src/database/commands/upgr
|
||||
import { MigrateNoteTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-note-target-to-morph-relations.command';
|
||||
import { MigrateSendEmailRecipientsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-send-email-recipients.command';
|
||||
import { MigrateTaskTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-task-target-to-morph-relations.command';
|
||||
import { SeedWorkflowV1_16Command } from 'src/database/commands/upgrade-version-command/1-17/1-17-seed-workflow-v1-16.command';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
@@ -76,7 +75,6 @@ import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/tas
|
||||
DeleteFileRecordsAndUpdateTableCommand,
|
||||
MigrateSendEmailRecipientsCommand,
|
||||
MigrateDateTimeIsFilterValuesCommand,
|
||||
SeedWorkflowV1_16Command,
|
||||
BackfillApplicationPackageFilesCommand,
|
||||
],
|
||||
exports: [
|
||||
@@ -89,7 +87,6 @@ import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/tas
|
||||
MigrateSendEmailRecipientsCommand,
|
||||
MigrateDateTimeIsFilterValuesCommand,
|
||||
DeleteFileRecordsAndUpdateTableCommand,
|
||||
SeedWorkflowV1_16Command,
|
||||
BackfillApplicationPackageFilesCommand,
|
||||
],
|
||||
})
|
||||
|
||||
+14
-26
@@ -3,7 +3,7 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import { FileFolder, FeatureFlagKey } from 'twenty-shared/types';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import {
|
||||
extractFolderPathFilenameAndTypeOrThrow,
|
||||
isDefined,
|
||||
@@ -14,7 +14,6 @@ import { v4 } from 'uuid';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
|
||||
@@ -36,7 +35,7 @@ type RichTextBlock = Record<string, unknown>;
|
||||
@Command({
|
||||
name: 'upgrade:1-18:migrate-activity-rich-text-attachment-file-ids',
|
||||
description:
|
||||
'Migrate activity rich text blocks to include attachmentFileId from attachment.file field',
|
||||
'[DEPRECATED] Migrate activity rich text blocks - this migration is now complete and no longer needed',
|
||||
})
|
||||
export class MigrateActivityRichTextAttachmentFileIdsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@@ -44,7 +43,6 @@ export class MigrateActivityRichTextAttachmentFileIdsCommand extends ActiveOrSus
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
@@ -57,23 +55,20 @@ export class MigrateActivityRichTextAttachmentFileIdsCommand extends ActiveOrSus
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const isFilesFieldMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_FILES_FIELD_MIGRATED,
|
||||
workspaceId,
|
||||
this.logger.log(
|
||||
`[DEPRECATED] Activity rich text attachment file IDs migration is no longer needed for workspace ${workspaceId}. ` +
|
||||
`The IS_FILES_FIELD_MIGRATED feature flag has been removed as all workspaces are now migrated.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isFilesFieldMigrated) {
|
||||
this.logger.log(
|
||||
`Files field already migrated for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private _deprecatedMigrationLogic = async ({
|
||||
workspaceId,
|
||||
isDryRun,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
isDryRun: boolean;
|
||||
}) => {
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting activity rich text attachment file IDs migration for workspace ${workspaceId}`,
|
||||
);
|
||||
@@ -171,18 +166,11 @@ export class MigrateActivityRichTextAttachmentFileIdsCommand extends ActiveOrSus
|
||||
isDryRun,
|
||||
});
|
||||
|
||||
if (!isDryRun) {
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_FILES_FIELD_MIGRATED],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Completed activity rich text attachment file IDs migration for workspace ${workspaceId}`,
|
||||
);
|
||||
}, systemAuthContext);
|
||||
}
|
||||
};
|
||||
|
||||
private async migrateActivityTable({
|
||||
activityRepository,
|
||||
|
||||
+14
-23
@@ -3,11 +3,7 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
FileFolder,
|
||||
FeatureFlagKey,
|
||||
} from 'twenty-shared/types';
|
||||
import { FieldMetadataType, FileFolder } from 'twenty-shared/types';
|
||||
import {
|
||||
extractFolderPathFilenameAndTypeOrThrow,
|
||||
isDefined,
|
||||
@@ -19,7 +15,6 @@ import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -35,7 +30,7 @@ import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objec
|
||||
@Command({
|
||||
name: 'upgrade:1-18:migrate-attachment-files',
|
||||
description:
|
||||
'Migrate attachment files to file field: copy files and create file records',
|
||||
'[DEPRECATED] Migrate attachment files to file field - this migration is now complete and no longer needed',
|
||||
})
|
||||
export class MigrateAttachmentFilesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@@ -43,7 +38,6 @@ export class MigrateAttachmentFilesCommand extends ActiveOrSuspendedWorkspacesMi
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly fieldMetadataService: FieldMetadataService,
|
||||
@@ -56,23 +50,20 @@ export class MigrateAttachmentFilesCommand extends ActiveOrSuspendedWorkspacesMi
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_FILES_FIELD_MIGRATED,
|
||||
workspaceId,
|
||||
this.logger.log(
|
||||
`[DEPRECATED] Attachment files migration is no longer needed for workspace ${workspaceId}. ` +
|
||||
`The IS_FILES_FIELD_MIGRATED feature flag has been removed as all workspaces are now migrated.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(
|
||||
`Attachment files migration already completed for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private _deprecatedMigrationLogic = async ({
|
||||
workspaceId,
|
||||
isDryRun,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
isDryRun: boolean;
|
||||
}) => {
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting attachment files migration for workspace ${workspaceId}`,
|
||||
);
|
||||
@@ -302,5 +293,5 @@ export class MigrateAttachmentFilesCommand extends ActiveOrSuspendedWorkspacesMi
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Completed attachment files migration for workspace ${workspaceId}`,
|
||||
);
|
||||
}, systemAuthContext);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+14
-23
@@ -3,11 +3,7 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
FileFolder,
|
||||
FeatureFlagKey,
|
||||
} from 'twenty-shared/types';
|
||||
import { FieldMetadataType, FileFolder } from 'twenty-shared/types';
|
||||
import {
|
||||
assertIsDefinedOrThrow,
|
||||
extractFolderPathFilenameAndTypeOrThrow,
|
||||
@@ -20,7 +16,6 @@ import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -36,7 +31,7 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso
|
||||
@Command({
|
||||
name: 'upgrade:1-18:migrate-person-avatar-files',
|
||||
description:
|
||||
'Migrate person avatarUrl files to file field: copy files and create file records',
|
||||
'[DEPRECATED] Migrate person avatarUrl files to file field - this migration is now complete and no longer needed',
|
||||
})
|
||||
export class MigratePersonAvatarFilesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@@ -44,7 +39,6 @@ export class MigratePersonAvatarFilesCommand extends ActiveOrSuspendedWorkspaces
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly fieldMetadataService: FieldMetadataService,
|
||||
@@ -57,23 +51,20 @@ export class MigratePersonAvatarFilesCommand extends ActiveOrSuspendedWorkspaces
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_FILES_FIELD_MIGRATED,
|
||||
workspaceId,
|
||||
this.logger.log(
|
||||
`[DEPRECATED] Person avatar files migration is no longer needed for workspace ${workspaceId}. ` +
|
||||
`The IS_FILES_FIELD_MIGRATED feature flag has been removed as all workspaces are now migrated.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(
|
||||
`Person avatar files migration already completed for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private _deprecatedMigrationLogic = async ({
|
||||
workspaceId,
|
||||
isDryRun,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
isDryRun: boolean;
|
||||
}) => {
|
||||
this.logger.log(
|
||||
`${
|
||||
isDryRun ? '[DRY RUN] ' : ''
|
||||
@@ -304,5 +295,5 @@ export class MigratePersonAvatarFilesCommand extends ActiveOrSuspendedWorkspaces
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Completed person avatar files migration for workspace ${workspaceId}`,
|
||||
);
|
||||
}, systemAuthContext);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+16
-18
@@ -3,14 +3,13 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Command } from 'nest-commander';
|
||||
import { FileFolder, FeatureFlagKey } from 'twenty-shared/types';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { isDefined, isNonEmptyArray } from 'twenty-shared/utils';
|
||||
import { DataSource, In, Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -47,7 +46,7 @@ type SendEmailStep = {
|
||||
@Command({
|
||||
name: 'upgrade:1-18:migrate-workflow-send-email-attachments',
|
||||
description:
|
||||
'Migrate workflow send email attachments to FileFolder.Workflow and update payload paths',
|
||||
'[DEPRECATED] Migrate workflow send email attachments - this migration is now complete and no longer needed',
|
||||
})
|
||||
export class MigrateWorkflowSendEmailAttachmentsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
protected readonly logger = new Logger(
|
||||
@@ -59,7 +58,6 @@ export class MigrateWorkflowSendEmailAttachmentsCommand extends ActiveOrSuspende
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
@InjectDataSource()
|
||||
@@ -70,14 +68,21 @@ export class MigrateWorkflowSendEmailAttachmentsCommand extends ActiveOrSuspende
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_OTHER_FILE_MIGRATED,
|
||||
workspaceId,
|
||||
this.logger.log(
|
||||
`[DEPRECATED] Workflow send email attachments migration is no longer needed for workspace ${workspaceId}. ` +
|
||||
`The IS_OTHER_FILE_MIGRATED feature flag has been removed as all workspaces are now migrated.`,
|
||||
);
|
||||
}
|
||||
|
||||
private _deprecatedMigrationLogic = async ({
|
||||
workspaceId,
|
||||
isDryRun,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
isDryRun: boolean;
|
||||
}) => {
|
||||
const isMigrated = false;
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(
|
||||
@@ -199,15 +204,8 @@ export class MigrateWorkflowSendEmailAttachmentsCommand extends ActiveOrSuspende
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDryRun) {
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_OTHER_FILE_MIGRATED],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Completed workflow send email attachments migration for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+4
-62
@@ -4,7 +4,7 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import FileType from 'file-type';
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import { FileFolder, FeatureFlagKey } from 'twenty-shared/types';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import {
|
||||
extractFolderPathFilenameAndTypeOrThrow,
|
||||
isDefined,
|
||||
@@ -16,7 +16,6 @@ import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
|
||||
@@ -33,7 +32,7 @@ import { getImageBufferFromUrl } from 'src/utils/image';
|
||||
@Command({
|
||||
name: 'upgrade:1-18:migrate-workspace-pictures',
|
||||
description:
|
||||
'Migrate workspace logos and workspace member avatars to file records',
|
||||
'[DEPRECATED] Migrate workspace logos and workspace member avatars to file records - this migration is now complete and no longer needed',
|
||||
})
|
||||
export class MigrateWorkspacePicturesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@@ -41,7 +40,6 @@ export class MigrateWorkspacePicturesCommand extends ActiveOrSuspendedWorkspaces
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
@@ -55,59 +53,10 @@ export class MigrateWorkspacePicturesCommand extends ActiveOrSuspendedWorkspaces
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_CORE_PICTURE_MIGRATED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(
|
||||
`Workspace pictures migration already completed for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting workspace pictures migration for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const fileRepository = this.coreDataSource.getRepository(FileEntity);
|
||||
|
||||
await this.migrateWorkspaceLogo({
|
||||
workspaceId,
|
||||
isDryRun,
|
||||
workspaceCustomFlatApplication,
|
||||
fileRepository,
|
||||
});
|
||||
|
||||
await this.migrateWorkspaceMemberAvatars({
|
||||
workspaceId,
|
||||
isDryRun,
|
||||
workspaceCustomFlatApplication,
|
||||
fileRepository,
|
||||
});
|
||||
|
||||
if (!isDryRun) {
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_CORE_PICTURE_MIGRATED],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Completed workspace pictures migration for workspace ${workspaceId}`,
|
||||
`[DEPRECATED] Workspace pictures migration is no longer needed for workspace ${workspaceId}. ` +
|
||||
`The IS_CORE_PICTURE_MIGRATED feature flag has been removed as all workspaces are now migrated.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -426,12 +375,5 @@ export class MigrateWorkspacePicturesCommand extends ActiveOrSuspendedWorkspaces
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDryRun) {
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_CORE_PICTURE_MIGRATED],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user