Image upload endpoint + posta.link/v1 wire wrapping #22

Merged
arne merged 2 commits from wire-link-v1 into main 2026-05-13 21:37:55 +02:00
Owner

Summary

  • Adds POST /api/v1/uploads (10 MiB cap, image/jpeg + image/png, magic-byte sniffed) backed by a new internal/uploadstore that stores files content-addressed at /upload-<sha>.<ext>.
  • Public GET mirrors the avatar handler: immutable cache, ETag, no auth.
  • Client API ergonomic shape {\"image\":{\"url\":…, \"mediaType\":…, \"name\":…, \"alt\":…, \"size\":…}} wraps to posta.link/v1 on the wire (SPEC §13.3) before signing; inbound posta.link/v1 unwraps back in the DTO.

⚠️ Stacked on #21 (wire-text-v1). Once #21 lands this PR retargets to main.

Out of scope (deferred):

  • GC of unreferenced uploads
  • Server-side resize/compress pipeline

Test plan

  • go test ./...
  • uploadstore unit tests (write, locate, read, append-only, traversal-rejection, missing file, invalid extension).
  • Upload endpoint integration tests: success, 413 oversize, unsupported media type, content-type mismatch, public GET with cache headers, 404 on unknown hash.
  • Wire-shape contract: POST /messages with {\"image\":{…}} produces a queued payload of {\"kind\":\"posta.link/v1\",\"url\":…,…}.
  • Inbound posta.link/v1 envelope surfaces as {\"image\":{…}} in GET /messages DTO.
  • Round-trip + passthrough unit tests in internal/payload.

🤖 Generated with Claude Code

## Summary - Adds `POST /api/v1/uploads` (10 MiB cap, `image/jpeg` + `image/png`, magic-byte sniffed) backed by a new `internal/uploadstore` that stores files content-addressed at `/upload-<sha>.<ext>`. - Public GET mirrors the avatar handler: immutable cache, ETag, no auth. - Client API ergonomic shape `{\"image\":{\"url\":…, \"mediaType\":…, \"name\":…, \"alt\":…, \"size\":…}}` wraps to `posta.link/v1` on the wire (SPEC §13.3) before signing; inbound `posta.link/v1` unwraps back in the DTO. > ⚠️ Stacked on #21 (wire-text-v1). Once #21 lands this PR retargets to main. Out of scope (deferred): - GC of unreferenced uploads - Server-side resize/compress pipeline ## Test plan - [x] `go test ./...` - [x] uploadstore unit tests (write, locate, read, append-only, traversal-rejection, missing file, invalid extension). - [x] Upload endpoint integration tests: success, 413 oversize, unsupported media type, content-type mismatch, public GET with cache headers, 404 on unknown hash. - [x] Wire-shape contract: `POST /messages` with `{\"image\":{…}}` produces a queued payload of `{\"kind\":\"posta.link/v1\",\"url\":…,…}`. - [x] Inbound `posta.link/v1` envelope surfaces as `{\"image\":{…}}` in `GET /messages` DTO. - [x] Round-trip + passthrough unit tests in `internal/payload`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds POST /api/v1/uploads (10 MiB cap, image/jpeg + image/png,
magic-byte sniffed) backed by a new internal/uploadstore that
stores files content-addressed at /upload-<sha>.<ext>. Public GET
mirrors the avatar handler: immutable cache, ETag, no auth.

The client API ergonomic shape is {"image":{"url":…, …}}; the
server wraps to posta.link/v1 on the wire (SPEC §13.3) and unwraps
in the DTO. Required field is url; optional advisory fields
(mediaType, name, alt, size) are echoed through verbatim when
present and omitted from the signed bytes when absent.

GC of unreferenced uploads and the future resize/compress pipeline
are explicitly out of scope here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
arne changed target branch from wire-text-v1 to main 2026-05-13 21:34:44 +02:00
arne force-pushed wire-link-v1 from aeb52c8127 to 486f416eeb 2026-05-13 21:37:49 +02:00 Compare
arne merged commit 192215c2a2 into main 2026-05-13 21:37:55 +02:00
arne deleted branch wire-link-v1 2026-05-13 21:37:55 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
posta/server!22
No description provided.