diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/useUploadEmailAttachment.ts b/packages/twenty-front/src/modules/activities/emails/hooks/useUploadEmailAttachment.ts index ce9b1efda7..194e71f901 100644 --- a/packages/twenty-front/src/modules/activities/emails/hooks/useUploadEmailAttachment.ts +++ b/packages/twenty-front/src/modules/activities/emails/hooks/useUploadEmailAttachment.ts @@ -1,23 +1,15 @@ -import { useMutation } from '@apollo/client/react'; import { useLingui } from '@lingui/react/macro'; import { type EmailAttachment } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; import { MAX_ATTACHMENT_SIZE } from '@/advanced-text-editor/utils/maxAttachmentSize'; -import { UPLOAD_EMAIL_ATTACHMENT_FILE } from '@/file/graphql/mutations/uploadEmailAttachmentFile'; +import { useDirectFileUpload } from '@/file/hooks/useDirectFileUpload'; import { formatFileSize } from '@/file/utils/formatFileSize'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { - type UploadEmailAttachmentFileMutation, - type UploadEmailAttachmentFileMutationVariables, -} from '~/generated-metadata/graphql'; +import { FileFolder } from '~/generated-metadata/graphql'; import { logError } from '~/utils/logError'; export const useUploadEmailAttachment = () => { - const [uploadEmailAttachmentFileMutation] = useMutation< - UploadEmailAttachmentFileMutation, - UploadEmailAttachmentFileMutationVariables - >(UPLOAD_EMAIL_ATTACHMENT_FILE); + const { uploadFile: directUploadFile } = useDirectFileUpload(); const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); const { t } = useLingui(); @@ -36,16 +28,10 @@ export const useUploadEmailAttachment = () => { return null; } - const result = await uploadEmailAttachmentFileMutation({ - variables: { file }, + const uploadedFile = await directUploadFile(file, { + fileFolder: FileFolder.EmailAttachment, }); - const uploadedFile = result?.data?.uploadEmailAttachmentFile; - - if (!isDefined(uploadedFile)) { - throw new Error('File upload failed'); - } - const attachment: EmailAttachment = { id: uploadedFile.id, name: file.name, diff --git a/packages/twenty-front/src/modules/ai/hooks/useAiChatFileUpload.ts b/packages/twenty-front/src/modules/ai/hooks/useAiChatFileUpload.ts index 63084ff68f..6b47621bf4 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAiChatFileUpload.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAiChatFileUpload.ts @@ -1,19 +1,16 @@ import { agentChatSelectedFilesState } from '@/ai/states/agentChatSelectedFilesState'; import { agentChatUploadedFilesState } from '@/ai/states/agentChatUploadedFilesState'; +import { useDirectFileUpload } from '@/file/hooks/useDirectFileUpload'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; -import { useApolloClient, useMutation } from '@apollo/client/react'; import { useLingui } from '@lingui/react/macro'; import { isDefined } from 'twenty-shared/utils'; import { type AgentChatFileUIPart } from '@/ai/types/agent-chat-file-ui-part.type'; -import { UploadAiChatFileDocument } from '~/generated-metadata/graphql'; +import { FileFolder } from '~/generated-metadata/graphql'; export const useAiChatFileUpload = () => { - const apolloClient = useApolloClient(); - const [uploadAiChatFile] = useMutation(UploadAiChatFileDocument, { - client: apolloClient, - }); + const { uploadFile: directUploadFile } = useDirectFileUpload(); const { t } = useLingui(); const { enqueueErrorSnackBar } = useSnackBar(); const [agentChatSelectedFiles, setAgentChatSelectedFiles] = useAtomState( @@ -25,26 +22,18 @@ export const useAiChatFileUpload = () => { const sendFile = async (file: File): Promise => { try { - const result = await uploadAiChatFile({ - variables: { - file, - }, + const uploadedFile = await directUploadFile(file, { + fileFolder: FileFolder.AgentChat, }); - const response = result?.data?.uploadAiChatFile; - - if (!isDefined(response)) { - throw new Error(t`Couldn't upload the file.`); - } - setAgentChatSelectedFiles( agentChatSelectedFiles.filter((f) => f.name !== file.name), ); return { filename: file.name, mediaType: file.type, - url: response.url, - fileId: response.id, + url: uploadedFile.url, + fileId: uploadedFile.id, type: 'file', }; } catch { diff --git a/packages/twenty-server/src/engine/core-modules/file/file-upload/services/file-upload.service.spec.ts b/packages/twenty-server/src/engine/core-modules/file/file-upload/services/file-upload.service.spec.ts index 1ea5ae91fc..1d4ec287dd 100644 --- a/packages/twenty-server/src/engine/core-modules/file/file-upload/services/file-upload.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/file/file-upload/services/file-upload.service.spec.ts @@ -189,6 +189,30 @@ describe('FileUploadService', () => { ); expect(result.contentType).toBe('application/octet-stream'); }); + + it.each([FileFolder.EmailAttachment, FileFolder.AgentChat])( + 'should support direct upload for the %s folder', + async (fileFolder) => { + fileStorageService.getPresignedUploadUrl.mockResolvedValueOnce( + 'https://bucket/presigned-put', + ); + + const result = await service.createFileUpload({ + workspaceId: 'workspace-id', + filename: 'document.pdf', + size: 1024, + fileFolder, + }); + + expect(fileStorageService.createPendingFile).toHaveBeenCalledWith( + expect.objectContaining({ + fileFolder, + resourcePath: 'mocked-file-id.pdf', + }), + ); + expect(result.uploadUrl).toBe('https://bucket/presigned-put'); + }, + ); }); describe('completeFileUpload', () => { 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 9385c9d5da..3af4dd43aa 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 @@ -41,6 +41,8 @@ import { readReadablePrefix } from 'src/utils/read-readable-prefix'; export const DIRECT_UPLOAD_FILE_FOLDERS = [ FileFolder.FilesField, FileFolder.Workflow, + FileFolder.EmailAttachment, + FileFolder.AgentChat, ] as const; @Injectable()