File - Migrate core pictures (workspace and member logo) + workflow attachments (#17924)
- Create a common file-by-id download controller - Create core picture module with resolver and logic to handle workspaceLogo and workspaceMemberProfilePicture update - Create workflow file module (same) - Data migration
This commit is contained in:
+9
-5
@@ -18,7 +18,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
|
||||
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 { FilesFieldService } from 'src/engine/core-modules/file/files-field/files-field.service';
|
||||
import { FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
|
||||
import { extractFileIdFromUrl } from 'src/engine/core-modules/file/files-field/utils/extract-file-id-from-url.util';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
@@ -48,7 +48,7 @@ export class MigrateActivityRichTextAttachmentFileIdsCommand extends ActiveOrSus
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly filesFieldService: FilesFieldService,
|
||||
private readonly fileUrlService: FileUrlService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
@@ -237,7 +237,10 @@ export class MigrateActivityRichTextAttachmentFileIdsCommand extends ActiveOrSus
|
||||
const props = (block.props as Record<string, unknown>) || {};
|
||||
const url = props.url as string | undefined;
|
||||
|
||||
return isDefined(url) && !isDefined(extractFileIdFromUrl(url));
|
||||
return (
|
||||
isDefined(url) &&
|
||||
!isDefined(extractFileIdFromUrl(url, FileFolder.FilesField))
|
||||
);
|
||||
});
|
||||
|
||||
if (!needsMigration) {
|
||||
@@ -259,7 +262,7 @@ export class MigrateActivityRichTextAttachmentFileIdsCommand extends ActiveOrSus
|
||||
|
||||
if (
|
||||
!isDefined(url) ||
|
||||
isDefined(extractFileIdFromUrl(url)) ||
|
||||
isDefined(extractFileIdFromUrl(url, FileFolder.FilesField)) ||
|
||||
!url.includes('/files/attachment/')
|
||||
) {
|
||||
enrichedBlocknote.push(block);
|
||||
@@ -292,9 +295,10 @@ export class MigrateActivityRichTextAttachmentFileIdsCommand extends ActiveOrSus
|
||||
);
|
||||
hasChanges = true;
|
||||
|
||||
const signedUrl = this.filesFieldService.signFileUrl({
|
||||
const signedUrl = this.fileUrlService.signFileByIdUrl({
|
||||
fileId,
|
||||
workspaceId,
|
||||
fileFolder: FileFolder.FilesField,
|
||||
});
|
||||
|
||||
enrichedBlocknote.push({
|
||||
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Command } from 'nest-commander';
|
||||
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 { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
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';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import {
|
||||
WorkflowVersionStatus,
|
||||
WorkflowVersionWorkspaceEntity,
|
||||
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
|
||||
|
||||
type WorkflowFile = {
|
||||
id: string;
|
||||
path?: string;
|
||||
name: string;
|
||||
size: number;
|
||||
type: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
type SendEmailStep = {
|
||||
id: string;
|
||||
type: WorkflowActionType.SEND_EMAIL;
|
||||
settings: {
|
||||
input: {
|
||||
files?: WorkflowFile[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-18:migrate-workflow-send-email-attachments',
|
||||
description:
|
||||
'Migrate workflow send email attachments to FileFolder.Workflow and update payload paths',
|
||||
})
|
||||
export class MigrateWorkflowSendEmailAttachmentsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
protected readonly logger = new Logger(
|
||||
MigrateWorkflowSendEmailAttachmentsCommand.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
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()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(
|
||||
`Workflow attachments migration already completed for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting workflow send email attachments migration for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const workflowVersions = await workflowVersionRepository.find({
|
||||
select: ['id', 'steps'],
|
||||
where: {
|
||||
status: In([
|
||||
WorkflowVersionStatus.DRAFT,
|
||||
WorkflowVersionStatus.ACTIVE,
|
||||
WorkflowVersionStatus.DEACTIVATED,
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const fileRepository = this.coreDataSource.getRepository(FileEntity);
|
||||
|
||||
for (const workflowVersion of workflowVersions) {
|
||||
const steps = workflowVersion.steps;
|
||||
|
||||
if (!isNonEmptyArray(steps)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const step of steps) {
|
||||
if (step.type !== WorkflowActionType.SEND_EMAIL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sendEmailStep = step as SendEmailStep;
|
||||
const files = sendEmailStep.settings?.input?.files;
|
||||
|
||||
if (!isNonEmptyArray(files)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const fileEntity = await fileRepository.findOne({
|
||||
where: {
|
||||
id: file.id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(fileEntity)) {
|
||||
this.logger.warn(
|
||||
`File ${file.id} not found for workflow version ${workflowVersion.id}, skipping`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fileEntity.path.startsWith(FileFolder.Workflow)) {
|
||||
this.logger.log(
|
||||
`File ${file.id} already in Workflow folder, skipping copy`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const newResourcePath = `${fileEntity.id}${isNonEmptyString(file.type) ? `.${file.type}` : ''}`;
|
||||
const newPath = `${FileFolder.Workflow}/${newResourcePath}`;
|
||||
|
||||
if (!isDryRun) {
|
||||
try {
|
||||
await this.fileStorageService.copyLegacy({
|
||||
from: {
|
||||
folderPath: `workspace-${workspaceId}`,
|
||||
filename: fileEntity.path,
|
||||
},
|
||||
to: {
|
||||
folderPath: `${workspaceId}/${workspaceCustomFlatApplication.universalIdentifier}`,
|
||||
filename: newPath,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to migrate file ${fileEntity.id} in workspace ${workspaceId}: ${error.message}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would migrate file ${fileEntity.id} from ${fileEntity.path} to ${newPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
await fileRepository.update(
|
||||
{ id: fileEntity.id },
|
||||
{
|
||||
path: newPath,
|
||||
applicationId: workspaceCustomFlatApplication.id,
|
||||
settings: {
|
||||
isTemporaryFile: true,
|
||||
toDelete: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
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 } from 'twenty-shared/types';
|
||||
import {
|
||||
extractFolderPathFilenameAndTypeOrThrow,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { And, DataSource, IsNull, Like, Not, Repository } from 'typeorm';
|
||||
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 { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
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';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-18:migrate-workspace-pictures',
|
||||
description:
|
||||
'Migrate workspace logos and workspace member avatars to file records',
|
||||
})
|
||||
export class MigrateWorkspacePicturesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
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,
|
||||
private readonly fileUrlService: FileUrlService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
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}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async migrateWorkspaceLogo({
|
||||
workspaceId,
|
||||
isDryRun,
|
||||
workspaceCustomFlatApplication,
|
||||
fileRepository,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
isDryRun: boolean;
|
||||
workspaceCustomFlatApplication: FlatApplication;
|
||||
fileRepository: Repository<FileEntity>;
|
||||
}): Promise<void> {
|
||||
const workspace = await this.workspaceRepository.findOne({
|
||||
where: {
|
||||
id: workspaceId,
|
||||
logo: Not(IsNull()),
|
||||
logoFileId: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!workspace || !isNonEmptyString(workspace.logo)) {
|
||||
this.logger.log(
|
||||
`No workspace logo to migrate for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Migrating workspace logo for workspace ${workspaceId}: ${workspace.logo}`,
|
||||
);
|
||||
|
||||
try {
|
||||
const { type: fileExtension } = extractFolderPathFilenameAndTypeOrThrow(
|
||||
workspace.logo,
|
||||
);
|
||||
|
||||
const fileId = v4();
|
||||
const newFilename = `${fileId}${isNonEmptyString(fileExtension) ? `.${fileExtension}` : ''}`;
|
||||
const newResourcePath = `${FileFolder.CorePicture}/${newFilename}`;
|
||||
|
||||
if (!isDryRun) {
|
||||
await this.fileStorageService.copyLegacy({
|
||||
from: {
|
||||
folderPath: `workspace-${workspaceId}`,
|
||||
filename: workspace.logo,
|
||||
},
|
||||
to: {
|
||||
folderPath: `${workspaceId}/${workspaceCustomFlatApplication.universalIdentifier}`,
|
||||
filename: newResourcePath,
|
||||
},
|
||||
});
|
||||
|
||||
const fileEntity = fileRepository.create({
|
||||
id: fileId,
|
||||
path: newResourcePath,
|
||||
workspaceId,
|
||||
applicationId: workspaceCustomFlatApplication.id,
|
||||
size: -1,
|
||||
settings: {
|
||||
isTemporaryFile: false,
|
||||
toDelete: false,
|
||||
},
|
||||
});
|
||||
|
||||
await fileRepository.save(fileEntity);
|
||||
|
||||
await this.workspaceRepository.update(
|
||||
{ id: workspaceId },
|
||||
{ logoFileId: fileId },
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Migrated workspace logo for workspace ${workspaceId} (${workspace.logo} -> ${newResourcePath})`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to migrate workspace logo for workspace ${workspaceId}: ${error.message}`,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async migrateWorkspaceMemberAvatars({
|
||||
workspaceId,
|
||||
isDryRun,
|
||||
workspaceCustomFlatApplication,
|
||||
fileRepository,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
isDryRun: boolean;
|
||||
workspaceCustomFlatApplication: FlatApplication;
|
||||
fileRepository: Repository<FileEntity>;
|
||||
}): Promise<void> {
|
||||
const { flatObjectMetadataMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
]);
|
||||
|
||||
const workspaceMemberObjectMetadata =
|
||||
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.workspaceMember.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!isDefined(workspaceMemberObjectMetadata)) {
|
||||
this.logger.warn(
|
||||
`Workspace member object metadata not found for workspace ${workspaceId}, skipping member avatar migration`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.twentyORMGlobalManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const workspaceMembers = await workspaceMemberRepository.find({
|
||||
where: {
|
||||
avatarUrl: And(Not(IsNull()), Not(Like(`%${FileFolder.CorePicture}%`))),
|
||||
},
|
||||
select: ['id', 'avatarUrl'],
|
||||
});
|
||||
|
||||
if (workspaceMembers.length === 0) {
|
||||
this.logger.log(
|
||||
`No workspace member avatars to migrate for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found ${workspaceMembers.length} workspace member avatar(s) to migrate in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
for (const workspaceMember of workspaceMembers) {
|
||||
if (!isNonEmptyString(workspaceMember.avatarUrl)) {
|
||||
this.logger.warn(
|
||||
`Skipping workspace member ${workspaceMember.id} - invalid avatarUrl`,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const { type: fileExtension } = extractFolderPathFilenameAndTypeOrThrow(
|
||||
workspaceMember.avatarUrl,
|
||||
);
|
||||
|
||||
const fileId = v4();
|
||||
const newFilename = `${fileId}${isNonEmptyString(fileExtension) ? `.${fileExtension}` : ''}`;
|
||||
const newResourcePath = `${FileFolder.CorePicture}/${newFilename}`;
|
||||
|
||||
if (!isDryRun) {
|
||||
await this.fileStorageService.copyLegacy({
|
||||
from: {
|
||||
folderPath: `workspace-${workspaceId}`,
|
||||
filename: workspaceMember.avatarUrl,
|
||||
},
|
||||
to: {
|
||||
folderPath: `${workspaceId}/${workspaceCustomFlatApplication.universalIdentifier}`,
|
||||
filename: newResourcePath,
|
||||
},
|
||||
});
|
||||
|
||||
const fileEntity = fileRepository.create({
|
||||
id: fileId,
|
||||
path: newResourcePath,
|
||||
workspaceId,
|
||||
applicationId: workspaceCustomFlatApplication.id,
|
||||
size: -1,
|
||||
settings: {
|
||||
isTemporaryFile: false,
|
||||
toDelete: false,
|
||||
},
|
||||
});
|
||||
|
||||
await fileRepository.save(fileEntity);
|
||||
|
||||
const signedUrl = this.fileUrlService.signFileByIdUrl({
|
||||
fileId,
|
||||
workspaceId,
|
||||
fileFolder: FileFolder.CorePicture,
|
||||
});
|
||||
|
||||
await workspaceMemberRepository.update(
|
||||
{ id: workspaceMember.id },
|
||||
{
|
||||
avatarUrl: signedUrl,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Migrated workspace member avatar ${workspaceMember.id}`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to migrate workspace member avatar ${workspaceMember.id} in workspace ${workspaceId}: ${error.message}`,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDryRun) {
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_CORE_PICTURE_MIGRATED],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-6
@@ -8,12 +8,13 @@ import { MigrateActivityRichTextAttachmentFileIdsCommand } from 'src/database/co
|
||||
import { MigrateAttachmentFilesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-attachment-files.command';
|
||||
import { MigrateFavoritesToNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-favorites-to-navigation-menu-items.command';
|
||||
import { MigratePersonAvatarFilesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-person-avatar-files.command';
|
||||
import { MigrateWorkflowSendEmailAttachmentsCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-workflow-send-email-attachments.command';
|
||||
import { MigrateWorkspacePicturesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-workspace-pictures.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';
|
||||
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { FilesFieldModule } from 'src/engine/core-modules/file/files-field/files-field.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
@@ -21,11 +22,12 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
|
||||
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -35,18 +37,18 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso
|
||||
PersonWorkspaceEntity,
|
||||
FileEntity,
|
||||
AttachmentWorkspaceEntity,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
ObjectMetadataEntity,
|
||||
FieldMetadataEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
FeatureFlagModule,
|
||||
FileStorageModule.forRoot(),
|
||||
WorkspaceCacheModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
WorkspaceMetadataVersionModule,
|
||||
FieldMetadataModule,
|
||||
ApplicationModule,
|
||||
FilesFieldModule,
|
||||
FileModule,
|
||||
UserWorkspaceModule,
|
||||
WorkspaceMigrationModule,
|
||||
],
|
||||
@@ -55,18 +57,22 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso
|
||||
MigrateFavoritesToNavigationMenuItemsCommand,
|
||||
MigrateAttachmentFilesCommand,
|
||||
BackfillFileSizeAndMimeTypeCommand,
|
||||
MigrateWorkspacePicturesCommand,
|
||||
MigrateActivityRichTextAttachmentFileIdsCommand,
|
||||
BackfillMessageChannelThrottleRetryAfterCommand,
|
||||
BackfillStandardViewsAndFieldMetadataCommand,
|
||||
MigrateWorkflowSendEmailAttachmentsCommand,
|
||||
],
|
||||
exports: [
|
||||
MigratePersonAvatarFilesCommand,
|
||||
MigrateFavoritesToNavigationMenuItemsCommand,
|
||||
MigrateAttachmentFilesCommand,
|
||||
BackfillFileSizeAndMimeTypeCommand,
|
||||
MigrateActivityRichTextAttachmentFileIdsCommand,
|
||||
BackfillMessageChannelThrottleRetryAfterCommand,
|
||||
BackfillStandardViewsAndFieldMetadataCommand,
|
||||
MigrateWorkspacePicturesCommand,
|
||||
BackfillFileSizeAndMimeTypeCommand,
|
||||
MigrateWorkflowSendEmailAttachmentsCommand,
|
||||
],
|
||||
})
|
||||
export class V1_18_UpgradeVersionCommandModule {}
|
||||
|
||||
+6
@@ -25,6 +25,8 @@ import { MigrateActivityRichTextAttachmentFileIdsCommand } from 'src/database/co
|
||||
import { MigrateAttachmentFilesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-attachment-files.command';
|
||||
import { MigrateFavoritesToNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-favorites-to-navigation-menu-items.command';
|
||||
import { MigratePersonAvatarFilesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-person-avatar-files.command';
|
||||
import { MigrateWorkflowSendEmailAttachmentsCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-workflow-send-email-attachments.command';
|
||||
import { MigrateWorkspacePicturesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-workspace-pictures.command';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
@@ -63,6 +65,8 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly migrateActivityRichTextAttachmentFileIdsCommand: MigrateActivityRichTextAttachmentFileIdsCommand,
|
||||
protected readonly backfillMessageChannelThrottleRetryAfterCommand: BackfillMessageChannelThrottleRetryAfterCommand,
|
||||
protected readonly backfillStandardViewsAndFieldMetadataCommand: BackfillStandardViewsAndFieldMetadataCommand,
|
||||
protected readonly migrateWorkspacePicturesCommand: MigrateWorkspacePicturesCommand,
|
||||
protected readonly migrateWorkflowSendEmailAttachmentsCommand: MigrateWorkflowSendEmailAttachmentsCommand,
|
||||
) {
|
||||
super(
|
||||
workspaceRepository,
|
||||
@@ -92,6 +96,8 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
this.migratePersonAvatarFilesCommand,
|
||||
this.migrateAttachmentFilesCommand,
|
||||
this.migrateActivityRichTextAttachmentFileIdsCommand,
|
||||
this.migrateWorkspacePicturesCommand,
|
||||
this.migrateWorkflowSendEmailAttachmentsCommand,
|
||||
this.backfillFileSizeAndMimeTypeCommand,
|
||||
this.backfillMessageChannelThrottleRetryAfterCommand,
|
||||
this.backfillStandardViewsAndFieldMetadataCommand,
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddLogoFileIdColumnOnWorkspaceTable1771323022170
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddLogoFileIdColumnOnWorkspaceTable1771323022170';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD "logoFileId" uuid`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD CONSTRAINT "UQ_282123b2f32e927b6003311e33a" UNIQUE ("logoFileId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD CONSTRAINT "FK_282123b2f32e927b6003311e33a" FOREIGN KEY ("logoFileId") REFERENCES "core"."file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP CONSTRAINT "FK_282123b2f32e927b6003311e33a"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP CONSTRAINT "UQ_282123b2f32e927b6003311e33a"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP COLUMN "logoFileId"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user