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
@@ -16,7 +16,7 @@ import { AttachmentQueryResultGetterHandler } from 'src/engine/api/graphql/works
|
||||
import { PersonQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/person-query-result-getter.handler';
|
||||
import { WorkspaceMemberQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
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 { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
@@ -42,7 +42,7 @@ export class CommonResultGettersService {
|
||||
|
||||
constructor(
|
||||
private readonly fileService: FileService,
|
||||
private readonly filesFieldService: FilesFieldService,
|
||||
private readonly fileUrlService: FileUrlService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {
|
||||
this.initializeObjectHandlers();
|
||||
@@ -55,7 +55,11 @@ export class CommonResultGettersService {
|
||||
['person', new PersonQueryResultGetterHandler(this.fileService)],
|
||||
[
|
||||
'workspaceMember',
|
||||
new WorkspaceMemberQueryResultGetterHandler(this.fileService),
|
||||
new WorkspaceMemberQueryResultGetterHandler(
|
||||
this.fileService,
|
||||
this.featureFlagService,
|
||||
this.fileUrlService,
|
||||
),
|
||||
],
|
||||
]);
|
||||
}
|
||||
@@ -67,13 +71,13 @@ export class CommonResultGettersService {
|
||||
>([
|
||||
[
|
||||
FieldMetadataType.FILES,
|
||||
new FilesFieldQueryResultGetterHandler(this.filesFieldService),
|
||||
new FilesFieldQueryResultGetterHandler(this.fileUrlService),
|
||||
],
|
||||
[
|
||||
FieldMetadataType.RICH_TEXT_V2,
|
||||
new RichTextV2FieldQueryResultGetterHandler(
|
||||
this.fileService,
|
||||
this.filesFieldService,
|
||||
this.fileUrlService,
|
||||
this.featureFlagService,
|
||||
),
|
||||
],
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@ import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
|
||||
|
||||
import { RichTextV2FieldQueryResultGetterHandler } from 'src/engine/api/common/common-result-getters/handlers/field-handlers/rich-text-v2-field-query-result-getter.handler';
|
||||
import { type FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { type FilesFieldService } from 'src/engine/core-modules/file/files-field/files-field.service';
|
||||
import { type FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
|
||||
import { type FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
@@ -24,9 +24,9 @@ const mockFileService = {
|
||||
signFileUrl: jest.fn().mockReturnValue('signed-path'),
|
||||
} as unknown as FileService;
|
||||
|
||||
const mockFilesFieldService = {
|
||||
const mockFileUrlService = {
|
||||
signFileUrl: jest.fn().mockReturnValue('signed-path'),
|
||||
} as unknown as FilesFieldService;
|
||||
} as unknown as FileUrlService;
|
||||
|
||||
const mockFeatureFlagService = {
|
||||
isFeatureEnabled: jest.fn().mockReturnValue(true),
|
||||
@@ -39,7 +39,7 @@ describe('RichTextV2FieldQueryResultGetterHandler', () => {
|
||||
process.env.SERVER_URL = 'https://my-domain.twenty.com';
|
||||
handler = new RichTextV2FieldQueryResultGetterHandler(
|
||||
mockFileService,
|
||||
mockFilesFieldService,
|
||||
mockFileUrlService,
|
||||
mockFeatureFlagService,
|
||||
);
|
||||
});
|
||||
|
||||
+9
-4
@@ -1,16 +1,20 @@
|
||||
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
FileFolder,
|
||||
type ObjectRecord,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface';
|
||||
|
||||
import { isFileOutputArray } from 'src/engine/api/common/common-args-processors/data-arg-processor/types/file-item.guard';
|
||||
import type { SignedFileOutput } from 'src/engine/api/common/common-args-processors/data-arg-processor/types/file-item.type';
|
||||
import { type FilesFieldService } from 'src/engine/core-modules/file/files-field/files-field.service';
|
||||
import { type FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
export class FilesFieldQueryResultGetterHandler
|
||||
implements QueryResultGetterHandlerInterface
|
||||
{
|
||||
constructor(private readonly filesFieldService: FilesFieldService) {}
|
||||
constructor(private readonly fileUrlService: FileUrlService) {}
|
||||
|
||||
async handle(
|
||||
record: ObjectRecord,
|
||||
@@ -35,9 +39,10 @@ export class FilesFieldQueryResultGetterHandler
|
||||
const signedFilesFieldValue: SignedFileOutput[] = [];
|
||||
|
||||
for (const file of filesFieldValue) {
|
||||
const url = this.filesFieldService.signFileUrl({
|
||||
const url = this.fileUrlService.signFileByIdUrl({
|
||||
fileId: file.fileId,
|
||||
workspaceId,
|
||||
fileFolder: FileFolder.FilesField,
|
||||
});
|
||||
|
||||
signedFilesFieldValue.push({
|
||||
|
||||
+13
-5
@@ -1,11 +1,15 @@
|
||||
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
FileFolder,
|
||||
type ObjectRecord,
|
||||
} 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 FilesFieldService } from 'src/engine/core-modules/file/files-field/files-field.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 FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
@@ -34,7 +38,7 @@ export class RichTextV2FieldQueryResultGetterHandler
|
||||
{
|
||||
constructor(
|
||||
private readonly fileService: FileService,
|
||||
private readonly filesFieldService: FilesFieldService,
|
||||
private readonly fileUrlService: FileUrlService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
@@ -92,15 +96,19 @@ export class RichTextV2FieldQueryResultGetterHandler
|
||||
): RichTextBlock[] => {
|
||||
return blocknoteBlocks.map((block: RichTextBlock) => {
|
||||
if (isFilesFieldMigrated && isDefined(block.props?.url)) {
|
||||
const fileIdFromUrl = extractFileIdFromUrl(block.props.url);
|
||||
const fileIdFromUrl = extractFileIdFromUrl(
|
||||
block.props.url,
|
||||
FileFolder.FilesField,
|
||||
);
|
||||
|
||||
if (!isDefined(fileIdFromUrl)) {
|
||||
return block;
|
||||
}
|
||||
|
||||
const url = this.filesFieldService.signFileUrl({
|
||||
const url = this.fileUrlService.signFileByIdUrl({
|
||||
fileId: fileIdFromUrl,
|
||||
workspaceId,
|
||||
fileFolder: FileFolder.FilesField,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -13,7 +13,6 @@ import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-que
|
||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { FilesFieldModule } from 'src/engine/core-modules/file/files-field/files-field.module';
|
||||
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
|
||||
import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module';
|
||||
import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module';
|
||||
@@ -35,7 +34,6 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
|
||||
UserRoleModule,
|
||||
ApiKeyModule,
|
||||
FileModule,
|
||||
FilesFieldModule,
|
||||
ViewModule,
|
||||
ViewFilterModule,
|
||||
ViewFilterGroupModule,
|
||||
|
||||
+39
-1
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user