- /assets/*: immutable one-year cache on hits, plain 404 on misses
(previously a missing asset fell back to index.html served as its
content type, breaking CSS/JS after every deploy for cached clients)
- index.html and SPA routes: no-cache, so deploys are visible immediately
- sha256+crc32 hashed during upload streaming; unique index per album
- duplicate content returns the existing photo (race-safe via 23505)
- client hashes locally (WebCrypto) and skips the transfer entirely for
content the album already has
- zip downloads stream S3->response directly using the stored crc32;
pre-hash photos spool once and self-heal (crc via zip, sha via reprocess)
- upload UI: overall progress bar, bytes, live speed, ETA
Rust (axum + sqlx) API and worker sharing a Postgres-backed job queue
(SKIP LOCKED, heartbeat, reaper, typed statuses), S3 storage with derived
keys and a fully private bucket, OIDC photographer login with per-request
allowlist checks, client share links with argon2 passwords and lockout,
cookie-based image authorization with sliding expiry, hand-rolled
spec-compliant streaming ZIP downloads with exact Content-Length,
React + Vite gallery frontend, single Docker image, Helm chart for
external S3 + Postgres, and Gitea CI.
Co-Authored-By: Claude <noreply@anthropic.com>