1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What This Is
Lumière is a personal cinema diary — a server-rendered FastAPI web app backed by SQLite. A single owner logs in with a password; the `/tyler` public profile is read-only for guests. Films live on three shelves: `diary`, `queue`, and `abandoned`. TMDB enriches metadata (posters, directors, runtime, countries, cast, overview); OMDb supplies third-party ratings (IMDb, Rotten Tomatoes, Metacritic) on the detail page.
## Commands
```bash
# Setup
python -m venv .venv
.venv/bin/pip install -r requirements.txt
# Run dev server
.venv/bin/uvicorn main:app --reload
# Syntax check (run before any PR)
.venv/bin/python -m py_compile main.py database.py models.py routers/*.py services/*.py
# Tests (no suite yet — use pytest when adding tests)
.venv/bin/pytest
```
Requires a `.env` file with:
```
TMDB_API_KEY=...
OMDB_API_KEY=... # for IMDb/RT/Metacritic ratings
OWNER_PASSWORD_HASH=... # argon2 hash; generate with: python -c "from argon2 import PasswordHasher; print(PasswordHasher().hash('your-password'))"
SESSION_SECRET=... # random secret for cookie signing
```
App runs at `http://127.0.0.1:8000`.
## Architecture
**Request flow:** Browser → FastAPI router → SQLAlchemy (SQLite) → Jinja2 template → HTML response. `static/app.js` handles star ratings (JSON PATCH) and TMDB search autocomplete without a build step.
**Key files:**
- `main.py` — app entry, router registration, `AuthMiddleware` (session-based, password protected)
- `database.py` — SQLite engine, sessions, self-healing schema migration (no Alembic; renames old table on column mismatch and migrates data)
- `models.py` — single `Film` ORM model with a `shelf` column
- `routers/films.py` — shelf listing, CRUD, director page, queue random pick, star rating API, infinite-scroll partial
- `routers/imports.py` — Letterboxd and watchlist CSV import, deduplication, enrichment
- `routers/stats.py` — all-time stats and year-in-review (pure Python aggregation, no SQL GROUP BY)
- `routers/auth.py` — `/login` / `/logout` using argon2 password hashing
- `routers/profile.py` — public `/tyler` profile page (no auth required)
- `routers/about.py` — static about page
- `routers/tmdb.py` — `/tmdb/search?q=` proxy for JS autocomplete
- `services/tmdb.py` — TMDB API client: search, detail fetch, metadata application, director bio/image
- `services/omdb.py` — OMDb API client: fetches IMDb/Rotten Tomatoes/Metacritic ratings for detail page
- `services/film_people.py` — director name normalization and URL slug helpers
- `services/countries.py` — ISO numeric country code mapping for stats world map
**Patterns:**
- Route logic stays in `routers/`, shared service logic in `services/`
- Stats computed in-memory from full query results (appropriate for personal-scale SQLite)
- TMDB fields are additive/optional; all core film fields work without enrichment
- Import pipeline dedupes by TMDB ID first, then normalized title+year+date
## Coding Conventions
- 4-space indentation; `snake_case` for functions/variables, `PascalCase` for ORM models, uppercase for shelf/status constants
- Jinja templates stay minimal; defer behavior to `static/app.js`
- `lumiere.db` and `*.db.bak-*` files are local artifacts — do not drive code changes from them
|