diff --git a/README.md b/README.md index 5fb5c37..eb53a72 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,104 @@ # Cinemate 🎬 -**Tinder for movies & TV shows.** Two users join a room with an invite code, get a -shared deck of titles, swipe, and when both like the same title β†’ **match** = the -winning title (dedicated screen, live over WebSocket, no refresh). Works **solo** -too (any like = personal watchlist). - -Runs entirely on **Cloudflare free tier**. +**Tinder for movies & TV shows.** Two people join a room with an invite code, get a +shared-but-personalised deck of titles, swipe, and when both like the same title β†’ +**match** (dedicated screen, live over WebSocket, no refresh). Works **solo** too +(any like = personal watchlist). + +Live: **https://cinemate.valegboth.win** Β· runs entirely on **Cloudflare free tier**. + +## Features +- **Quiz profile** β€” liked genres (tap = like, double-tap = love), genres to avoid, + and **seed titles** ("movies you loved", with live autocomplete). Editable anytime + (Profile β†’ Edit preferences); nothing is required (empty profile = popular titles). +- **Per-user decks** β€” one shared pool (both users' tastes + seed recommendations + + common ground) served in a **weighted-random order per user**, so each sees their + own taste first while both can still match. +- **Swipe UX** β€” drag with rotation + LIKE/NOPE stamps, buttons, **undo**, **trailer** + (YouTube), movie/TV toggle, "New session" for a fresh deck. +- **Ratings** β€” IMDb / Rotten Tomatoes / Metacritic (via OMDb) on the card and match. +- **Live match** β€” instant match screen over WebSocket (Durable Object, hibernation). +- **Invite** β€” 6-char code + one-tap shareable link (`/join?code=…`). +- **Solo mode** + persistent watchlist. +- **Overseerr** (optional) β€” excludes already requested/available titles from the deck + and adds an **"Add to Overseerr"** button on match (auto-request β†’ Radarr/Sonarr). +- Watch providers (where to watch, RO) on the match screen. TMDb attribution in the footer. ## Stack - -| Piece | Service | Role | -| ---------- | ------------------------------ | ------------------------------------------ | -| Frontend | Workers Static Assets (`public/`) | swipe UI (vanilla HTML/CSS/JS) | -| API | Cloudflare Workers (Hono/TS) | routing, logic, simple auth | -| Database | D1 (SQLite) | users, profiles, rooms, swipes, matches | -| Live state | Durable Objects + WebSocket | swipe session, instant match broadcast | -| Cache | Workers KV | TMDb responses (avoids rate limiting) | -| Movie data | TMDb API | posters, titles, genres, overview, providers | - -The Worker serves both the static frontend and `/api/*` on the **same origin**, so -there is no CORS and a single `wrangler deploy` ships everything. +| Piece | Service | Role | +| ---------- | -------------------------------- | --------------------------------------- | +| Frontend | Workers Static Assets (`public/`)| swipe UI (vanilla HTML/CSS/JS) | +| API | Cloudflare Workers (Hono/TS) | routing, logic, simple auth | +| Database | D1 (SQLite) | users, profiles, rooms, swipes, matches | +| Live state | Durable Objects + WebSocket | swipe session, instant match broadcast | +| Cache | Workers KV | TMDb / OMDb / Overseerr responses | +| Movie data | TMDb (primary) + OMDb (ratings) | posters, genres, recommendations, ratings | +| Requests | Overseerr (optional) | exclude requested + request on match | + +The Worker serves both the static frontend and `/api/*` on the **same origin** (no CORS, +one `wrangler deploy`). Overseerr is reached over a **Cloudflare Tunnel + Access** service +token, so it stays private (never publicly exposed). ## Project layout - ``` cinemate/ -β”œβ”€β”€ PLAN.md # roadmap -β”œβ”€β”€ wrangler.toml # Workers + D1 + DO + KV + Static Assets config -β”œβ”€β”€ schema.sql # D1 schema +β”œβ”€β”€ PLAN.md Β· schema.sql Β· wrangler.toml β”œβ”€β”€ src/ -β”‚ β”œβ”€β”€ index.ts # Worker entry, mounts Hono routes -β”‚ β”œβ”€β”€ types.ts # shared types (Env, Profile, Room, DeckCard…) -β”‚ β”œβ”€β”€ routes/ # users, profile, rooms -β”‚ β”œβ”€β”€ services/tmdb.ts # TMDb integration + KV cache -β”‚ β”œβ”€β”€ lib/ # ids, db helpers, mappers, deck -β”‚ └── durable-objects/room.ts # live room state (WebSocket, broadcast) -└── public/ # static frontend (index.html, style.css, app.js) +β”‚ β”œβ”€β”€ index.ts # Worker entry, mounts routes +β”‚ β”œβ”€β”€ types.ts # shared types +β”‚ β”œβ”€β”€ routes/ # users, profile, rooms, search +β”‚ β”œβ”€β”€ services/ # tmdb, omdb, overseerr +β”‚ β”œβ”€β”€ lib/ # ids, db, mappers, deck +β”‚ └── durable-objects/room.ts # live room state (WebSocket) +└── public/ # index.html, style.css, app.js ``` ## Local setup - -### 1. Prerequisites -`node` (18+), `npm`, `git`. `wrangler` comes as a dev dependency. - -### 2. Install ```bash npm install +cp .dev.vars.example .dev.vars # fill in TMDB_API_KEY (see below) +npx wrangler d1 create cinemate-db # copy database_id into wrangler.toml +npx wrangler kv:namespace create TMDB_CACHE # copy id into wrangler.toml +npm run db:migrate:local # apply schema.sql to the local D1 +npx wrangler dev # http://localhost:8787 (UI + API, same origin) ``` -### 3. TMDb key (local) -Get a free key at , then: -```bash -cp .dev.vars.example .dev.vars # then put your key in TMDB_API_KEY -``` -`.dev.vars` is gitignored β€” **the key never lands in the repo or the frontend**. - -### 4. Cloudflare resources (create once) -```bash -npx wrangler d1 create cinemate-db # copy database_id into wrangler.toml -npx wrangler kv:namespace create TMDB_CACHE # copy id into wrangler.toml -npm run db:migrate:remote # apply schema.sql to remote D1 -npx wrangler secret put TMDB_API_KEY # production secret -``` +### Secrets / env (`.dev.vars` locally, `wrangler secret put` in prod) +| Var | Required | Purpose | +| --- | --- | --- | +| `TMDB_API_KEY` | βœ… | TMDb (v3 key or v4 token β€” auto-detected) | +| `OMDB_API_KEY` | optional | IMDb/RT/Metacritic ratings (best-effort) | +| `OVERSEERR_URL` / `OVERSEERR_API_KEY` | optional | Overseerr integration | +| `CF_ACCESS_CLIENT_ID` / `CF_ACCESS_CLIENT_SECRET` | optional | if Overseerr is behind Cloudflare Access | -### 5. Run locally -One server serves both the frontend and the API (same origin): -```bash -npm run db:migrate:local # apply schema to the local D1 (once) -npx wrangler dev # open http://localhost:8787 -``` +The keys never reach the frontend β€” the browser calls the Worker, the Worker calls the APIs. ## npm scripts - -| Script | What it does | -| -------------------------- | ------------------------------------- | -| `npm run dev` | run the Worker + frontend locally | -| `npm run typecheck` | `tsc --noEmit` | -| `npm run lint` | ESLint | -| `npm run build` | bundling dry-run (build validation) | -| `npm run deploy` | deploy Worker + static assets | -| `npm run db:migrate:local` | apply `schema.sql` to the local D1 | -| `npm run db:migrate:remote`| apply `schema.sql` to the remote D1 | +| Script | Does | +| --- | --- | +| `npm run dev` | run the Worker + frontend locally | +| `npm run lint` / `typecheck` / `build` | ESLint / `tsc --noEmit` / bundling dry-run | +| `npm run deploy` | deploy Worker + static assets | +| `npm run db:migrate:local` / `:remote` | apply `schema.sql` to local / remote D1 | + +## API routes (all under `/api`) +`POST /users` Β· `GET /users/:id/watchlist` Β· `POST /profile/quiz` Β· `GET /profile/:id` +Β· `GET /search` Β· `POST /rooms` Β· `POST /rooms/join` Β· `GET /rooms/:id` +Β· `PATCH /rooms/:id` (movie/TV toggle) Β· `POST /rooms/:id/new-session` +Β· `GET /rooms/:id/deck` Β· `POST /rooms/:id/swipe` Β· `DELETE /rooms/:id/swipe` (undo) +Β· `GET /rooms/:id/matches` Β· `GET /rooms/:id/ws` Β· `GET /rooms/:id/providers/:tmdbId` +Β· `GET /rooms/:id/rating/:tmdbId` Β· `GET /rooms/:id/trailer/:tmdbId` +Β· `POST /rooms/:id/request` (Add to Overseerr) ## CI/CD -- **PR β†’ main**: lint + typecheck + build (`.github/workflows/ci.yml`). -- **push β†’ main**: a single `wrangler deploy` (Worker + static frontend) - (`.github/workflows/deploy.yml`). Needs repo secrets `CLOUDFLARE_API_TOKEN` and - `CLOUDFLARE_ACCOUNT_ID`. +- **PR β†’ main**: lint + typecheck + build. +- **push β†’ main**: a single `wrangler deploy` (Worker + static frontend). Needs repo + secrets `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID`. ## Status -See [PLAN.md](./PLAN.md). V1 and V2.1–V2.2 are done; more on the roadmap. +V1 + V2 shipped (quiz, per-user decks, ratings, invite link, gestures/trailer/undo, +solo watchlist, edit preferences, Overseerr). See [PLAN.md](./PLAN.md) for the roadmap +(Letterboxd import, group mode). ## Attribution This product uses the TMDB API but is not endorsed or certified by TMDB.