2.0 KiB
| read_when | ||
|---|---|---|
|
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
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 MiBper 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 randomupload-*name. The original filename is recorded in theuploadsrow but not used on disk. Content-Typefalls back toapplication/octet-streamwhen 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.