clickclack/docs/features/uploads.md
2026-05-08 12:05:27 +01:00

65 lines
2.0 KiB
Markdown

---
read_when:
- changing upload storage, attachment endpoints, or upload limits
- moving uploads off local disk
---
# Uploads
Uploads are local files keyed by an `upl_...` ID and attached to messages
through a join table. The server streams them back to authenticated workspace
members.
## Endpoints
```http
POST /api/uploads # multipart: file, workspace_id
GET /api/uploads/{upload_id} # streams the file
POST /api/messages/{message_id}/attachments # { upload_id }
```
- Upload size cap: `32 MiB` per request (`ParseMultipartForm(32 << 20)`).
Anything larger should use a future direct-to-storage flow.
- The file is written to the upload directory (`<data>/uploads`) under a
random `upload-*` name. The original filename is recorded in the `uploads`
row but not used on disk.
- `Content-Type` falls back to `application/octet-stream` when the client
doesn't send one.
## Attaching to a message
`POST /api/messages/{message_id}/attachments` records a row in
`message_attachments`. The store hydrates attachments on
`ListMessages`/`GetThread`, so subsequent reads include the attachment list
without an extra round-trip.
The handler checks that the requesting user is a member of the message's
workspace before linking. There is no "upload owner must equal message
author" rule today.
The web client renders `image/*` uploads inline in timelines and threads, and
renders `video/*` uploads as inline native players with controls. Other content
types appear as authenticated download cards that link to
`/api/uploads/{upload_id}`.
## Storage layout
```
<data>/
clickclack.db
uploads/
upload-XXXXXXXX
...
logs/
```
Configure `<data>` with `--data` or `CLICKCLACK_DATA`. The server creates
`uploads/` on demand when the first request arrives.
## What is intentionally missing
- Object storage (S3/GCS) — keep the local path single-node-only for V1.
- Image thumbnailing/transcoding.
- Virus scanning.
- Per-workspace quotas.