# Chat attachments design ## Goal Add production-shaped attachments to the corporate AI chat. Users can attach files to a message, see them in history, and the configured AI Gateway receives temporary, authenticated access to them. ## Scope - Supported files: PNG, JPEG, WebP, PDF, TXT, Markdown, DOCX. - Limits: up to 10 files per message; up to 20 MiB per file. - The local Docker environment runs MinIO as the S3-compatible object store. - The application works with any S3-compatible provider in production through environment variables. - Browser uploads are sent to the authenticated application API. The API validates ownership, MIME type, file size and file signature where practical before storing the object. - The composer supports an attach button, drag-and-drop, selected-file cards, image previews and removing an item before sending. - Stored message history renders attachment cards and image previews. ## Storage and data model Create a `chat_attachments` table: - UUID primary key and `message_id` foreign key with cascading delete; - owner `user_id` foreign key, used for authorization checks; - original filename, normalized content type, byte size and S3 object key; - created timestamp. Objects are stored under a non-guessable key `users//attachments/`. The S3 bucket is private. Database rows are created only after a successful object write; an unsuccessful database write schedules/deletes the newly written object. Deleting a conversation cascades attachment metadata; application cleanup deletes corresponding objects. ## API contract `POST /api/attachments` accepts multipart `FormData` containing one `file`. It requires the existing Bearer JWT and returns attachment metadata for a temporary, not-yet-sent upload. The upload belongs to the authenticated user and expires after 24 hours if it is not linked to a message. `POST /api/chat` accepts JSON with the existing fields plus `attachmentIds: string[]`. It verifies every attachment belongs to the current user and is pending, creates the user message, atomically attaches the records to it, then starts the assistant stream. `GET /api/attachments/:id` verifies owner access and returns a short-lived redirect or stream from the private S3 object. User JWTs never reach object storage or the AI Gateway. The outbound AI Gateway request gains an `attachments` array alongside `model` and `messages`: ```json { "id": "attachment UUID", "name": "report.pdf", "contentType": "application/pdf", "size": 182736, "url": "short-lived signed URL" } ``` The present mock gateway reports which attachment names it received. A real AI Gateway decides which MIME types it can inspect and fetches the temporary URLs itself. ## Security and error handling - Validate file count, declared MIME type, extension, byte length and magic bytes for images/PDF. - Reject executable, archive and unknown content types. - No public bucket, directory listing or unauthenticated object URL. - Do not trust the browser-provided filename or content type. - Return normalized 400 validation errors, 401 authentication errors, 404 for inaccessible attachment IDs, and 503 for object-storage failures. - Explicitly use the Node.js route runtime. `Request.formData()` has no App Router size limit, so application-level checks are mandatory; the deployment proxy must enforce the same 20 MiB request limit. ## UI behavior The attach control is keyboard accessible and opens the native file picker. Dragging valid files over the composer shows a drop target. Selected files upload immediately, show per-file progress/error state and can be removed before the message is sent. The send button remains disabled while an upload is pending or failed. A text-only message continues to work unchanged. ## Verification - Unit tests cover validation, ownership, repository operations and Gateway payload creation. - Route tests cover multipart upload, unauthorized access, bad type/size and attachment-to-message linking. - Component tests cover selection, removal, pending upload state and sending attachment IDs. - Compose smoke test uploads a sample text file, sends a chat turn and confirms the attachment appears in history.