# photo-api Standalone Go service for event photo galleries: admins upload photos from past events, group them into galleries, and share them with the community. Lives in this monorepo but is its own module, binary, and deployable unit. Design/decisions: [PLAN.md](./PLAN.md). ## What it does - Galleries with four visibility modes: `public` (listed on /photos), `private` (admins only), `link` (share-token URL), `ticket` (logged-in users with a confirmed ticket for the linked event — any confirmed ticket when the event is free). - Originals are stored byte-identical for download; a worker generates JPEG variants (thumb 512px q78, preview 2048px q85, EXIF stripped/orientation applied) queued in the DB with retries. - Storage: local disk (`STORAGE_PATH`) or S3/Garage/MinIO (set `S3_ENDPOINT` + `S3_BUCKET`), same selection convention as the backend. S3 downloads use short-lived presigned URLs; nothing in the bucket is public. - Auth: validates the backend's HS256 JWTs with the shared `JWT_SECRET` (issuer `spanglish`, audience `spanglish-app`) including the DB-backed tokenVersion/account-status revocation check. Admin surface is `admin`/`organizer` only. - Database: the same Postgres or SQLite database as the backend (`DB_TYPE`, `DATABASE_URL`). This service owns only the `photos_*` tables via its own embedded migrations (`photo-api migrate`); drizzle-kit never sees them. Reads of backend tables are confined to `internal/store/access.go` and sanity-checked at startup. ## Develop ```bash cp .env.example .env # set JWT_SECRET + DATABASE_URL to match backend/.env go run ./cmd/photo-api migrate go run ./cmd/photo-api # serves on :3003 (dev) go test ./... # SQLite; add PHOTO_TEST_PG= to also run on Postgres ``` From the repo root: `npm run dev:photos`, `npm run build:photos`, `npm run test:photos`. The Next dev server rewrites `/api/photos/*` to `PHOTO_API_URL` (default `http://localhost:3003`), so the frontend needs no extra config in dev. HEIC uploads require a converter CLI on the host: `apt install libvips-tools` (or `libheif-examples`). Without one, HEIC uploads are rejected with a clear message and a startup warning is logged. ## API Everything under `/api/photos`. Errors are `{"error": string}`. Admin (Bearer token, role admin/organizer): | Method | Path | Purpose | |---|---|---| | POST | `/api/photos/galleries` | create gallery | | GET | `/api/photos/galleries` | list all (`?eventId=`) | | GET | `/api/photos/galleries/:id` | gallery + photos (all statuses) | | PATCH | `/api/photos/galleries/:id` | update title/visibility/event/cover | | DELETE | `/api/photos/galleries/:id` | delete gallery + objects | | POST | `/api/photos/galleries/:id/photos` | multipart upload (`files`) | | PATCH | `/api/photos/galleries/:id/order` | reorder (`{photoIds}`) | | POST | `/api/photos/galleries/:id/share-token` | rotate share token | | DELETE | `/api/photos/photos/:photoId` | delete photo | | POST | `/api/photos/photos/:photoId/retry` | requeue failed photo | Viewer (anonymous or member token; `?token=` carries the share token): | Method | Path | Purpose | |---|---|---| | GET | `/api/photos/public/galleries` | public index | | GET | `/api/photos/public/galleries/:slug` | gallery view (access-checked) | | GET | `/api/photos/public/events/:eventSlug/gallery` | newest gallery of an event (access-checked) | | GET | `/api/photos/files/:photoId/:variant` | bytes; `thumb\|preview\|original` | | GET | `/api/photos/health` | liveness | ## Deploy (systemd, primary) ```bash cd photo-api && go build -o bin/photo-api ./cmd/photo-api sudo apt install libvips-tools sudo cp ../deploy/spanglish-photos.service /etc/systemd/system/ sudo systemctl daemon-reload && sudo systemctl enable --now spanglish-photos sudo nginx -t && sudo systemctl reload nginx # after installing the updated confs ``` nginx routes `location ^~ /api/photos/` → `127.0.0.1:3020` (see `deploy/front-end_nginx.conf`, `deploy/back-end_nginx.conf`, `deploy/spanglish_upstreams.conf`). The frontend's production `.env` should set `PHOTO_API_URL=http://127.0.0.1:3020` for server-side rendering of the public gallery pages. A `Dockerfile` is included as a secondary path for the compose-scale setup.