Files
MichilisandClaude Opus 5 733d2459df Migrate authentication to Better Auth
Replace the hand-rolled JWT auth with Better Auth 1.6.25 httpOnly cookie
sessions, validated against the database on every request so revocation,
bans and role changes take effect immediately.

Backend:
- betterAuth.ts wires the Drizzle adapter, magic links, Google sign-in and
  the admin plugin; auth-schema.ts maps Better Auth's models onto the
  existing `users` table so user IDs and their foreign keys survive intact.
- routes/auth.ts is gone; Better Auth serves the standard endpoints and
  authExt.ts carries the flows it doesn't cover.
- auth.ts shrinks to session resolution and helpers; sessions/revocation in
  dashboard.ts now read and delete `auth_sessions` rows directly.
- Schema adds the Better Auth core + admin columns (email_verified, image,
  banned, ban_reason, ban_expires), with migrations and tests.
- rateLimit.ts resolves client IPs spoof-resistantly: proxy headers are only
  honoured from loopback/RFC1918 peers plus TRUSTED_PROXIES.
- passwordPolicy.ts centralises password validation.
- Bump drizzle-orm, drizzle-kit and better-sqlite3 to versions compatible
  with Better Auth.

Frontend:
- auth-client.ts plus a reworked AuthContext and api/client.ts move to
  cookie-based sessions; no more bearer tokens in requests or middleware.

photo-api:
- Validate Better Auth session cookies against the shared auth_sessions
  table instead of verifying JWTs; JWT_SECRET is no longer needed for user
  auth, and PHOTO_VIEW_SECRET now signs gallery view tokens.

BETTER_AUTH_SECRET and BETTER_AUTH_URL are required in production; the
deprecated JWT_SECRET stays only as the photo-api view-token fallback.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:07:04 +00:00
..
2026-07-29 19:07:04 +00:00
2026-07-29 19:07:04 +00:00

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.

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

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)

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.