From 02a181a49fcf5a5e2028397ae9b1173bed765f2b Mon Sep 17 00:00:00 2001 From: martmull Date: Tue, 20 Jan 2026 15:10:34 +0100 Subject: [PATCH] Add endpoint to upload application file (#17270) As title --- .../src/generated-metadata/graphql.ts | 13 ++ .../twenty-front/src/generated/graphql.ts | 13 ++ .../cli/utilities/api/services/api.service.ts | 132 ++++++++++++++++++ .../settings/interfaces/settings.interface.ts | 2 +- .../application/application-sync.module.ts | 5 + .../application/application.resolver.ts | 77 ++++++++++ .../dtos/uploadApplicationFileInput.ts | 15 ++ .../resolvers/file-upload.resolver.ts | 3 +- .../services/file-upload.service.ts | 3 +- .../file/interfaces/file-folder.interface.ts | 24 ++-- .../file/services/file-metadata.service.ts | 3 +- .../__tests__/check-file-folder.utils.spec.ts | 2 +- .../__tests__/check-file-name.utils.spec.ts | 2 +- .../__tests__/check-file-path.utils.spec.ts | 2 +- .../file/utils/check-file-folder.utils.ts | 7 +- .../file/utils/check-file-path.utils.ts | 7 +- .../utils/serverless-get-folder.utils.ts | 3 +- .../code-interpreter-tool.ts | 2 +- .../user-workspace/user-workspace.resolver.ts | 3 +- .../user-workspace.service.spec.ts | 3 +- .../user-workspace/user-workspace.service.ts | 2 +- .../workspace/workspace.resolver.ts | 3 +- .../twenty-shared/src/types/FileFolder.ts | 14 ++ packages/twenty-shared/src/types/index.ts | 1 + 24 files changed, 302 insertions(+), 39 deletions(-) create mode 100644 packages/twenty-server/src/engine/core-modules/application/dtos/uploadApplicationFileInput.ts create mode 100644 packages/twenty-shared/src/types/FileFolder.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 9f42abbfd6f..6f9815e6957 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1567,12 +1567,16 @@ export type File = { export enum FileFolder { AgentChat = 'AgentChat', + Assets = 'Assets', Attachment = 'Attachment', File = 'File', + FrontComponents = 'FrontComponents', + Functions = 'Functions', PersonPicture = 'PersonPicture', ProfilePicture = 'ProfilePicture', ServerlessFunction = 'ServerlessFunction', ServerlessFunctionToDelete = 'ServerlessFunctionToDelete', + SourceCode = 'SourceCode', WorkspaceLogo = 'WorkspaceLogo' } @@ -2130,6 +2134,7 @@ export type Mutation = { updateWorkspace: Workspace; updateWorkspaceFeatureFlag: Scalars['Boolean']; updateWorkspaceMemberRole: WorkspaceMember; + uploadApplicationFile: File; uploadFile: SignedFile; uploadImage: SignedFile; uploadWorkspaceLogo: SignedFile; @@ -3045,6 +3050,14 @@ export type MutationUpdateWorkspaceMemberRoleArgs = { }; +export type MutationUploadApplicationFileArgs = { + applicationUniversalIdentifier: Scalars['String']; + file: Scalars['Upload']; + fileFolder: FileFolder; + filePath: Scalars['String']; +}; + + export type MutationUploadFileArgs = { file: Scalars['Upload']; fileFolder?: InputMaybe; diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 381c1ffb17d..60e136d8323 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -1544,12 +1544,16 @@ export type File = { export enum FileFolder { AgentChat = 'AgentChat', + Assets = 'Assets', Attachment = 'Attachment', File = 'File', + FrontComponents = 'FrontComponents', + Functions = 'Functions', PersonPicture = 'PersonPicture', ProfilePicture = 'ProfilePicture', ServerlessFunction = 'ServerlessFunction', ServerlessFunctionToDelete = 'ServerlessFunctionToDelete', + SourceCode = 'SourceCode', WorkspaceLogo = 'WorkspaceLogo' } @@ -2096,6 +2100,7 @@ export type Mutation = { updateWorkspace: Workspace; updateWorkspaceFeatureFlag: Scalars['Boolean']; updateWorkspaceMemberRole: WorkspaceMember; + uploadApplicationFile: File; uploadFile: SignedFile; uploadImage: SignedFile; uploadWorkspaceLogo: SignedFile; @@ -2955,6 +2960,14 @@ export type MutationUpdateWorkspaceMemberRoleArgs = { }; +export type MutationUploadApplicationFileArgs = { + applicationUniversalIdentifier: Scalars['String']; + file: Scalars['Upload']; + fileFolder: FileFolder; + filePath: Scalars['String']; +}; + + export type MutationUploadFileArgs = { file: Scalars['Upload']; fileFolder?: InputMaybe; diff --git a/packages/twenty-sdk/src/cli/utilities/api/services/api.service.ts b/packages/twenty-sdk/src/cli/utilities/api/services/api.service.ts index a8fa4db9702..be36aac61d1 100644 --- a/packages/twenty-sdk/src/cli/utilities/api/services/api.service.ts +++ b/packages/twenty-sdk/src/cli/utilities/api/services/api.service.ts @@ -6,12 +6,15 @@ import { printSchema, } from 'graphql/index'; import { createClient } from 'graphql-sse'; +import * as fs from 'fs'; +import * as path from 'path'; import { type ApiResponse } from '../types/api-response.types'; import { ConfigService } from '@/cli/utilities/config/services/config.service'; import { type PackageJson, type ApplicationManifest, } from 'twenty-shared/application'; +import { type FileFolder } from 'twenty-shared/types'; export class ApiService { private client: AxiosInstance; @@ -414,4 +417,133 @@ export class ApiService { }, ); } + + async uploadFile({ + filePath, + fileFolder, + applicationUniversalIdentifier, + }: { + filePath: string; + fileFolder: FileFolder; + applicationUniversalIdentifier: string; + }): Promise> { + try { + const absolutePath = path.resolve(filePath); + + if (!fs.existsSync(absolutePath)) { + return { + success: false, + error: `File not found: ${absolutePath}`, + }; + } + + const filename = path.basename(absolutePath); + const buffer = fs.readFileSync(absolutePath); + const mimeType = this.getMimeType(filename); + + const mutation = ` + mutation UploadApplicationFile($file: Upload!, $applicationUniversalIdentifier: String!, $fileFolder: FileFolder!, $filePath: String!) { + uploadApplicationFile(file: $file, applicationUniversalIdentifier: $applicationUniversalIdentifier, fileFolder: $fileFolder, filePath: $filePath) + { path } + } + `; + + const operations = JSON.stringify({ + query: mutation, + variables: { + file: null, + applicationUniversalIdentifier, + filePath, + fileFolder, + }, + }); + + const map = JSON.stringify({ + '0': ['variables.file'], + }); + + const formData = new FormData(); + + formData.append('operations', operations); + formData.append('map', map); + formData.append( + '0', + new Blob([new Uint8Array(buffer)], { type: mimeType }), + filename, + ); + + const response: AxiosResponse = await this.client.post( + '/graphql', + formData, + ); + + if (response.data.errors) { + return { + success: false, + error: response.data.errors[0]?.message || 'Failed to upload file', + }; + } + + return { + success: true, + data: response.data.data.uploadApplicationFile, + message: `Successfully uploaded ${filename}`, + }; + } catch (error) { + if (axios.isAxiosError(error) && error.response) { + return { + success: false, + error: error.response.data?.errors?.[0]?.message || error.message, + }; + } + + return { + success: false, + error, + }; + } + } + + private getMimeType(filename: string): string { + const ext = path.extname(filename).toLowerCase(); + const mimeTypes: Record = { + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.webp': 'image/webp', + '.svg': 'image/svg+xml', + '.bmp': 'image/bmp', + '.ico': 'image/x-icon', + '.pdf': 'application/pdf', + '.doc': 'application/msword', + '.docx': + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.xls': 'application/vnd.ms-excel', + '.xlsx': + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.ppt': 'application/vnd.ms-powerpoint', + '.pptx': + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + '.txt': 'text/plain', + '.csv': 'text/csv', + '.json': 'application/json', + '.xml': 'application/xml', + '.zip': 'application/zip', + '.tar': 'application/x-tar', + '.gz': 'application/gzip', + '.mp3': 'audio/mpeg', + '.mp4': 'video/mp4', + '.avi': 'video/x-msvideo', + '.mov': 'video/quicktime', + '.js': 'application/javascript', + '.ts': 'application/typescript', + '.jsx': 'application/javascript', + '.tsx': 'application/typescript', + '.html': 'text/html', + '.css': 'text/css', + }; + + return mimeTypes[ext] || 'application/octet-stream'; + } } diff --git a/packages/twenty-server/src/engine/constants/settings/interfaces/settings.interface.ts b/packages/twenty-server/src/engine/constants/settings/interfaces/settings.interface.ts index 806f2bf30a2..17dcb34ac8e 100644 --- a/packages/twenty-server/src/engine/constants/settings/interfaces/settings.interface.ts +++ b/packages/twenty-server/src/engine/constants/settings/interfaces/settings.interface.ts @@ -1,4 +1,4 @@ -import { type FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { type FileFolder } from 'twenty-shared/types'; import { type ShortCropSize } from 'src/utils/image'; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-sync.module.ts b/packages/twenty-server/src/engine/core-modules/application/application-sync.module.ts index 70a25a4bd12..4759c5b08da 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-sync.module.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-sync.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service'; import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; @@ -20,9 +21,12 @@ import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-builder-graphql-api-exception.interceptor'; import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module'; import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; +import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module'; +import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity'; @Module({ imports: [ + TypeOrmModule.forFeature([FileEntity]), ApplicationModule, ApplicationVariableEntityModule, WorkspaceManyOrAllFlatEntityMapsCacheModule, @@ -40,6 +44,7 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo ObjectPermissionModule, PermissionFlagModule, WorkflowCommonModule, + FileStorageModule, ], providers: [ ApplicationResolver, diff --git a/packages/twenty-server/src/engine/core-modules/application/application.resolver.ts b/packages/twenty-server/src/engine/core-modules/application/application.resolver.ts index 4fe6c0f0610..622b7a90ce9 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application.resolver.ts @@ -1,7 +1,15 @@ import { UseFilters, UseGuards, UseInterceptors } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { InjectRepository } from '@nestjs/typeorm'; +import path, { join } from 'path'; + +import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; import { PermissionFlagType } from 'twenty-shared/constants'; +import { type Repository } from 'typeorm'; +import { FileFolder } from 'twenty-shared/types'; + +import type { FileUpload } from 'graphql-upload/processRequest.mjs'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter'; @@ -11,12 +19,21 @@ import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/applica import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input'; import { UninstallApplicationInput } from 'src/engine/core-modules/application/dtos/uninstallApplicationInput'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { RequireFeatureFlag } from 'src/engine/guards/feature-flag.guard'; import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-builder-graphql-api-exception.interceptor'; +import { streamToBuffer } from 'src/utils/stream-to-buffer'; +import { UploadApplicationFileInput } from 'src/engine/core-modules/application/dtos/uploadApplicationFileInput'; +import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity'; +import { + ApplicationException, + ApplicationExceptionCode, +} from 'src/engine/core-modules/application/application.exception'; +import { FileDTO } from 'src/engine/core-modules/file/dtos/file.dto'; @UseGuards( WorkspaceAuthGuard, @@ -29,6 +46,9 @@ export class ApplicationResolver { constructor( private readonly applicationSyncService: ApplicationSyncService, private readonly applicationService: ApplicationService, + private readonly fileStorageService: FileStorageService, + @InjectRepository(FileEntity) + private readonly fileRepository: Repository, ) {} @Query(() => [ApplicationDTO]) @@ -75,4 +95,61 @@ export class ApplicationResolver { return true; } + + @Mutation(() => FileDTO) + @UseGuards(SettingsPermissionGuard(PermissionFlagType.UPLOAD_FILE)) + async uploadApplicationFile( + @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, + @Args({ name: 'file', type: () => GraphQLUpload }) + { createReadStream, mimetype }: FileUpload, + @Args() + { + applicationUniversalIdentifier, + fileFolder, + filePath, + }: UploadApplicationFileInput, + ): Promise { + const allowedApplicationFileFolders: FileFolder[] = [ + FileFolder.Functions, + FileFolder.FrontComponents, + FileFolder.Assets, + FileFolder.SourceCode, + ]; + + if (!allowedApplicationFileFolders.includes(fileFolder)) { + throw new ApplicationException( + `Invalid fileFolder for application file upload. Allowed values: ${allowedApplicationFileFolders.join(', ')}`, + ApplicationExceptionCode.INVALID_INPUT, + ); + } + + const stream = createReadStream(); + const buffer = await streamToBuffer(stream); + + const dirname = path.dirname(filePath); + + const filename = path.basename(filePath); + + const folderPath = join( + `workspace-${workspaceId}`, + applicationUniversalIdentifier, + fileFolder, + dirname, + ); + + await this.fileStorageService.write({ + file: buffer, + name: filename, + folder: folderPath, + mimeType: mimetype, + }); + + const createdFile = this.fileRepository.create({ + path: join(folderPath, filename), + size: buffer.length, + workspaceId, + }); + + return await this.fileRepository.save(createdFile); + } } diff --git a/packages/twenty-server/src/engine/core-modules/application/dtos/uploadApplicationFileInput.ts b/packages/twenty-server/src/engine/core-modules/application/dtos/uploadApplicationFileInput.ts new file mode 100644 index 00000000000..d21069b00df --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/dtos/uploadApplicationFileInput.ts @@ -0,0 +1,15 @@ +import { ArgsType, Field } from '@nestjs/graphql'; + +import { FileFolder } from 'twenty-shared/types'; + +@ArgsType() +export class UploadApplicationFileInput { + @Field(() => String) + applicationUniversalIdentifier: string; + + @Field(() => FileFolder) + fileFolder: FileFolder; + + @Field(() => String) + filePath: string; +} diff --git a/packages/twenty-server/src/engine/core-modules/file/file-upload/resolvers/file-upload.resolver.ts b/packages/twenty-server/src/engine/core-modules/file/file-upload/resolvers/file-upload.resolver.ts index 659a3800ea2..8b42afde8bc 100644 --- a/packages/twenty-server/src/engine/core-modules/file/file-upload/resolvers/file-upload.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/file/file-upload/resolvers/file-upload.resolver.ts @@ -3,8 +3,7 @@ import { Args, Mutation, Resolver } from '@nestjs/graphql'; import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; import { PermissionFlagType } from 'twenty-shared/constants'; - -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; import type { FileUpload } from 'graphql-upload/processRequest.mjs'; diff --git a/packages/twenty-server/src/engine/core-modules/file/file-upload/services/file-upload.service.ts b/packages/twenty-server/src/engine/core-modules/file/file-upload/services/file-upload.service.ts index 5b3e7df01a6..a166389bd45 100644 --- a/packages/twenty-server/src/engine/core-modules/file/file-upload/services/file-upload.service.ts +++ b/packages/twenty-server/src/engine/core-modules/file/file-upload/services/file-upload.service.ts @@ -5,8 +5,7 @@ import DOMPurify from 'dompurify'; import FileType from 'file-type'; import sharp from 'sharp'; import { v4 } from 'uuid'; - -import { type FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { type FileFolder } from 'twenty-shared/types'; import { settings } from 'src/engine/constants/settings'; import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; diff --git a/packages/twenty-server/src/engine/core-modules/file/interfaces/file-folder.interface.ts b/packages/twenty-server/src/engine/core-modules/file/interfaces/file-folder.interface.ts index 99100503227..0d3be6b4fc0 100644 --- a/packages/twenty-server/src/engine/core-modules/file/interfaces/file-folder.interface.ts +++ b/packages/twenty-server/src/engine/core-modules/file/interfaces/file-folder.interface.ts @@ -1,17 +1,7 @@ import { registerEnumType } from '@nestjs/graphql'; import { type KebabCase } from 'type-fest'; - -export enum FileFolder { - ProfilePicture = 'profile-picture', - WorkspaceLogo = 'workspace-logo', - Attachment = 'attachment', - PersonPicture = 'person-picture', - ServerlessFunction = 'serverless-function', - ServerlessFunctionToDelete = 'serverless-function-to-delete', - File = 'file', - AgentChat = 'agent-chat', -} +import { FileFolder } from 'twenty-shared/types'; registerEnumType(FileFolder, { name: 'FileFolder', @@ -46,6 +36,18 @@ export const fileFolderConfigs: Record = { [FileFolder.AgentChat]: { ignoreExpirationToken: false, }, + [FileFolder.Functions]: { + ignoreExpirationToken: false, + }, + [FileFolder.FrontComponents]: { + ignoreExpirationToken: false, + }, + [FileFolder.Assets]: { + ignoreExpirationToken: true, + }, + [FileFolder.SourceCode]: { + ignoreExpirationToken: false, + }, }; export type AllowedFolders = KebabCase; diff --git a/packages/twenty-server/src/engine/core-modules/file/services/file-metadata.service.ts b/packages/twenty-server/src/engine/core-modules/file/services/file-metadata.service.ts index 55440162e96..194a0f36d38 100644 --- a/packages/twenty-server/src/engine/core-modules/file/services/file-metadata.service.ts +++ b/packages/twenty-server/src/engine/core-modules/file/services/file-metadata.service.ts @@ -3,8 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { extractFolderPathFilenameAndTypeOrThrow } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; - -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; import { type FileDTO } from 'src/engine/core-modules/file/dtos/file.dto'; import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity'; diff --git a/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-folder.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-folder.utils.spec.ts index b57943bbcb2..ebfd582b31a 100644 --- a/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-folder.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-folder.utils.spec.ts @@ -1,6 +1,6 @@ import { BadRequestException } from '@nestjs/common'; -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; import { checkFileFolder } from 'src/engine/core-modules/file/utils/check-file-folder.utils'; diff --git a/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-name.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-name.utils.spec.ts index fe228dfc552..2ee053c8a0a 100644 --- a/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-name.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-name.utils.spec.ts @@ -1,4 +1,4 @@ -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; import { checkFilename } from 'src/engine/core-modules/file/utils/check-file-name.utils'; diff --git a/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-path.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-path.utils.spec.ts index 4d4aac28bc1..d698fc51585 100644 --- a/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-path.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/file/utils/__tests__/check-file-path.utils.spec.ts @@ -1,4 +1,4 @@ -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; import { checkFilePath } from 'src/engine/core-modules/file/utils/check-file-path.utils'; diff --git a/packages/twenty-server/src/engine/core-modules/file/utils/check-file-folder.utils.ts b/packages/twenty-server/src/engine/core-modules/file/utils/check-file-folder.utils.ts index a9cf8638422..5218e53da9e 100644 --- a/packages/twenty-server/src/engine/core-modules/file/utils/check-file-folder.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/file/utils/check-file-folder.utils.ts @@ -1,9 +1,8 @@ import { BadRequestException } from '@nestjs/common'; -import { - type AllowedFolders, - FileFolder, -} from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; + +import { type AllowedFolders } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; import { kebabCase } from 'src/utils/kebab-case'; diff --git a/packages/twenty-server/src/engine/core-modules/file/utils/check-file-path.utils.ts b/packages/twenty-server/src/engine/core-modules/file/utils/check-file-path.utils.ts index 8208aa7f39c..6238f5b449a 100644 --- a/packages/twenty-server/src/engine/core-modules/file/utils/check-file-path.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/file/utils/check-file-path.utils.ts @@ -1,9 +1,8 @@ import { BadRequestException } from '@nestjs/common'; -import { - type AllowedFolders, - FileFolder, -} from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; + +import { type AllowedFolders } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; import { kebabCase } from 'src/utils/kebab-case'; import { settings } from 'src/engine/constants/settings'; diff --git a/packages/twenty-server/src/engine/core-modules/serverless/utils/serverless-get-folder.utils.ts b/packages/twenty-server/src/engine/core-modules/serverless/utils/serverless-get-folder.utils.ts index 9e42a2cff33..6e78863d171 100644 --- a/packages/twenty-server/src/engine/core-modules/serverless/utils/serverless-get-folder.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/serverless/utils/serverless-get-folder.utils.ts @@ -1,8 +1,7 @@ import { join } from 'path'; import { isDefined } from 'twenty-shared/utils'; - -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; import { diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool.ts index d5f6bb3cdc0..6af3cda26d9 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool.ts @@ -9,12 +9,12 @@ import { type CodeExecutionState, } from 'twenty-shared/ai'; import { v4 } from 'uuid'; +import { FileFolder } from 'twenty-shared/types'; import { type InputFile, type OutputFile, } from 'src/engine/core-modules/code-interpreter/drivers/interfaces/code-interpreter-driver.interface'; -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; import { type AccessTokenJwtPayload, diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.resolver.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.resolver.ts index 07383f97069..0baceca6b36 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.resolver.ts @@ -2,8 +2,7 @@ import { UseGuards } from '@nestjs/common'; import { Args, Mutation, Resolver } from '@nestjs/graphql'; import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; - -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; import type { FileUpload } from 'graphql-upload/processRequest.mjs'; diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts index 83d5f4e8244..90cb9602756 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts @@ -2,8 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { type DataSource, type Repository } from 'typeorm'; - -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; import { type ApprovedAccessDomainEntity } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity'; import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service'; diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts index 8db35369c4d..a9e5f7f352f 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts @@ -4,9 +4,9 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations'; import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; import { type QueryRunner, IsNull, Not, type Repository } from 'typeorm'; +import { FileFolder } from 'twenty-shared/types'; import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception'; -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; import { type AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity'; import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service'; diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts index 616c057be43..c46a4c5c3d1 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts @@ -19,8 +19,7 @@ import assert from 'assert'; import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; import { PermissionFlagType } from 'twenty-shared/constants'; import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; - -import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; +import { FileFolder } from 'twenty-shared/types'; import type { FileUpload } from 'graphql-upload/processRequest.mjs'; diff --git a/packages/twenty-shared/src/types/FileFolder.ts b/packages/twenty-shared/src/types/FileFolder.ts new file mode 100644 index 00000000000..b204386ccf0 --- /dev/null +++ b/packages/twenty-shared/src/types/FileFolder.ts @@ -0,0 +1,14 @@ +export enum FileFolder { + ProfilePicture = 'profile-picture', + WorkspaceLogo = 'workspace-logo', + Attachment = 'attachment', + PersonPicture = 'person-picture', + ServerlessFunction = 'serverless-function', + ServerlessFunctionToDelete = 'serverless-function-to-delete', + File = 'file', + AgentChat = 'agent-chat', + Functions = 'functions', + FrontComponents = 'front-components', + Assets = 'assets', + SourceCode = 'source-code', +} diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index 9688509daa4..8cc5851ded5 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -108,6 +108,7 @@ export type { export { NumberDataType, DateDisplayFormat } from './FieldMetadataSettings'; export { FieldMetadataType } from './FieldMetadataType'; export type { FieldRatingValue } from './FieldRatingValue'; +export { FileFolder } from './FileFolder'; export type { FilterableFieldType, FilterableAndTSVectorFieldType,