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:
Etienne
2026-02-17 15:42:50 +00:00
committed by GitHub
parent 963f2de864
commit 163c1175cb
85 changed files with 2212 additions and 335 deletions
@@ -1,12 +1,23 @@
import { FileFolder } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { type FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { type 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 { type FileService } from 'src/engine/core-modules/file/services/file.service';
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
export class WorkspaceMemberQueryResultGetterHandler
implements QueryResultGetterHandlerInterface
{
constructor(private readonly fileService: FileService) {}
constructor(
private readonly fileService: FileService,
private readonly featureFlagService: FeatureFlagService,
private readonly fileUrlService: FileUrlService,
) {}
async handle(
workspaceMember: WorkspaceMemberWorkspaceEntity,
@@ -16,6 +27,33 @@ export class WorkspaceMemberQueryResultGetterHandler
return workspaceMember;
}
if (
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_CORE_PICTURE_MIGRATED,
workspaceId,
)
) {
const fileId = extractFileIdFromUrl(
workspaceMember.avatarUrl,
FileFolder.CorePicture,
);
if (!isDefined(fileId)) {
return workspaceMember;
}
const signedUrl = this.fileUrlService.signFileByIdUrl({
fileId,
workspaceId,
fileFolder: FileFolder.CorePicture,
});
return {
...workspaceMember,
avatarUrl: signedUrl,
};
}
const signedPath = this.fileService.signFileUrl({
url: workspaceMember.avatarUrl,
workspaceId,