luisdralves 37725c4c7b
All checks were successful
Build and Deploy / build-deploy (push) Successful in 32s
Add build and deploy workflow
2026-05-03 23:18:50 +01:00
2026-05-03 17:07:05 +01:00
2026-05-03 17:07:05 +01:00
2026-05-03 23:18:50 +01:00
2026-05-03 17:07:05 +01:00
2026-04-04 01:18:06 +01:00
2026-04-04 01:18:06 +01:00
2026-04-04 01:18:06 +01:00
2026-05-03 18:39:27 +01:00
2026-04-03 17:05:41 +01:00
2026-05-03 23:18:50 +01:00
2026-04-04 01:18:06 +01:00
2026-05-03 23:18:50 +01:00
2026-05-03 18:39:27 +01:00
2026-05-03 18:39:27 +01:00

rate-my-shots

A photo-rating webapp that pairs assets from an Immich album for ELO-style head-to-head voting.

Local development

bun install
cp .env.example .env  # fill in IMMICH_URL, IMMICH_API_KEY, IMMICH_ALBUM_ID, ADMIN_PASSWORD_HASH
bun run scripts/migrate.ts
bun dev

Open http://localhost:3000.

Required env

Var Description
IMMICH_URL Base URL of the Immich instance
IMMICH_API_KEY Immich API key with read + rating-write scope
IMMICH_ALBUM_ID UUID of the album to pull assets from
ADMIN_PASSWORD_HASH scrypt hash, format <salt-hex>:<hash-hex> (see below)
DATABASE_PATH SQLite file path (default data/rate-my-shots.db)

Generate the admin password hash:

node -e "const c=require('crypto');const s=c.randomBytes(16);process.stdout.write(s.toString('hex')+':'+c.scryptSync(process.argv[1],s,64).toString('hex')+'\n')" 'your-password'

Deployment

The Dockerfile produces a standalone Next.js bundle that runs as a non-root bun user. Mount ./data on the host for SQLite persistence; the host directory must be writable by UID 1000.

The app trusts X-Forwarded-For for client IP (rate limiting, vote IP-binding). Run it behind a reverse proxy that strips/sets that header from untrusted clients — Caddy with reverse_proxy does this by default. Do not expose the container directly to untrusted networks; clients can otherwise spoof XFF and bypass rate limits or hijack pairings.

docker compose up -d --build

Security model

  • Admin auth is a hashed password (scrypt) verified at login; sessions are random opaque tokens stored in-memory with a 7-day TTL (server restart logs admins out).
  • Login attempts are rate-limited per IP (5 / 5 min, then exponential backoff).
  • The /img/[id] proxy only serves assets present in the local DB (album members or configured lens portraits). Other Immich assets are 404.
  • All /api/** POST/PUT/DELETE requests require Origin to match Host.
  • CSP, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, and Referrer-Policy: same-origin are set on all responses.
Description
No description provided
Readme 198 KiB
Languages
TypeScript 99.3%
Dockerfile 0.6%