Add photo galleries API and frontend for public and admin viewing.
Introduces the Go photo-api service, nginx/systemd deploy wiring, and Next.js gallery/lightbox pages so event photos can be managed and browsed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# 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=<url> 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.
|
||||
Reference in New Issue
Block a user