feat(files): use direct-to-storage upload for email and AI-chat attachments (#22610)
## Context Follow-up to the direct-to-storage upload work (#22449 / #22531 / #22533 / #22576). That migrated files-field, attachments and workflow uploads off the buffered path. This PR does the same for the **last two user-facing upload surfaces**: email attachments and AI-chat files. ## What this does - Adds `EmailAttachment` and `AgentChat` to the server's `DIRECT_UPLOAD_FILE_FOLDERS` allowlist. Both folders already resolve through the workspace-custom-application path in `resolveUploadLocation`, so no other server change is needed. - Routes the two frontend hooks through the existing `useDirectFileUpload` handshake (`createFileUpload` → `PUT` → `completeFileUpload`): - `useUploadEmailAttachment` → `FileFolder.EmailAttachment` (keeps its existing `MAX_ATTACHMENT_SIZE` client check — email has a real send-size limit). - `useAiChatFileUpload` → `FileFolder.AgentChat`. Each hook keeps its public signature and return shape, so call sites are unchanged. No schema change and no codegen needed — the `CreateFileUpload`/`CompleteFileUpload` documents and the `FileFolder` enum values already exist in `generated-metadata` from #22576. ## Why these are safe to migrate Both server services (`file-ai-chat`, `file-email-attachment`) just `writeFile` (store) and return a signed URL — no synchronous processing of the bytes at upload time — so the store-and-reference direct-upload flow fits exactly, same as files-field/workflow. ## Out of scope `CorePicture` (avatars, member/workspace pictures, logos) stays on the buffered path on purpose: small images that go through server-side image handling and are served inline, where the 10 MB body limit is already appropriate. ## Tests Extends the `FileUploadService` unit spec with an `it.each` asserting `createFileUpload` supports the `EmailAttachment` and `AgentChat` folders. ## Verification `typecheck` and `lint:diff-with-main` green on both `twenty-front` and `twenty-server`. (The server jest suite couldn't run in my local sandbox due to an unrelated config-import quirk present on a clean `main` checkout too — CI runs it normally.) https://claude.ai/code/session_015UH8KWmsB9zdYaog8MFG1d --- _Generated by [Claude Code](https://claude.ai/code/session_015UH8KWmsB9zdYaog8MFG1d)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22610?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+24
@@ -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', () => {
|
||||
|
||||
+2
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user