Progress Photos
Linear epic: FIT-72 Status: Shipped. Last reviewed: 2026-05-28
What
A private, chronological gallery of member-uploaded body photos. Each photo carries recorded_at (day granularity), optional notes, optional bodyweight_kg, dimensions, MIME, R2 storage key, and a generated thumbnail. Standalone — not linked to a body_metrics row (members can snap a photo without logging a measurement and vice versa).
Why
- Visual progress is a primary engagement driver for body-recomp members.
- Lighting / pose comparison is more meaningful than scale weight.
- Photos are private: visible only to the member and active staff (owner/admin/coach) in the same org.
Personas
| Persona | Surface | Capabilities |
|---|---|---|
| Member | Mobile app (primary) + web /members/me/progress-photos | Upload, list own timeline, soft-delete own photos |
| Staff (coach/admin/owner) | Member detail page | List a member’s photos, view full-res via presigned URL |
Capabilities
- Upload via the standard staged-PUT flow (
uploadsmodule) — member POSTs{ uploadId }after the binary lands in R2; service runssharpto generate a thumbnail and probe dimensions. - Thumbnail stored under a separate
thumbnail_r2_key. - List is paginated by cursor (id of the oldest row in the previous page), date-range filterable.
- Presigned reads — every list response presigns both full-size and thumbnail URLs (1h TTL, Redis-cached).
- Soft delete —
deleted_atis set; binary remains in R2 (no janitor today).
Capabilities (gaps)
| Gap | Tracking |
|---|---|
| R2 janitor for soft-deleted photo binaries | not tracked |
| Side-by-side compare UI | not tracked |
| Member-controlled per-photo “share with coach” toggle (today all photos are coach-visible) | not tracked |
| Push notification on upload (for coach attention) | FIT-170 follow-up |
| EXIF stripping on upload | not tracked — security concern: GPS coords could leak |
Related
../body-metrics/— independent timeline; sometimes paired in UI.../uploads-r2/— shared upload presign infrastructure.../members/— staff view sits on the member detail page.
Source code
- API:
apps/api/src/progress-photos/ - DB:
libs/db/src/lib/schema/progress-photos.ts - Web:
apps/web/src/components/member/(member self-view);apps/web/src/components/overview/members/(staff view) - R2:
apps/api/src/r2/r2.service.ts