The first authenticated-user concept in Aurora Core -- everything else in
this codebase is public, read-only catalog data. Foundation for ratings
and a watchlist (not yet built): POST /auth/register, POST /auth/login,
GET /auth/me.
Security specifics worth knowing:
- Passwords hashed with argon2 (via argon2-cffi), never stored plain or
logged.
- Login returns an identical 401 for a nonexistent email and a wrong
password on a real one, including matching response timing
(burn_time_like_a_password_check) -- otherwise either the status code
or the timing alone lets a client enumerate registered emails.
- AURORA_SECRET_KEY now actually signs JWTs (previously defined but
unused anywhere) -- .env.example's existing token_urlsafe(48) guidance
already produces a long enough key, just flagged its new stakes.
- Email matched case-insensitively via explicit normalization in the
service layer, not relied on from Pydantic's EmailStr (which does not
lowercase the local part).
12 new integration tests (register/login/me over real HTTP via the
`client` fixture) plus 10 unit tests for the hashing/JWT logic itself,
following the same-day lesson that only unit tests actually execute
locally without Postgres -- the auth core is tested for real here rather
than only through integration tests that might not run before a deploy.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Mirrors TMDb's own /trending/{movie|tv}/{day|week} ranking rather than
Aurora computing trending itself from popularity deltas -- that would
require re-syncing the entire catalog on a schedule, infeasible at any
real scale, whereas TMDb already computes this well from signals (real
view/search velocity) Aurora has no access to.
New: TrendingEntry model + migration 0003, MetadataProvider.get_trending
(concrete/optional, like aclose(), so existing test fakes don't all need
updating), refresh_trending() which actually ingests each trending item
(only ~20 per window, cheap enough to fully ingest rather than apply the
never-fabricate-a-stub rule recommendations uses), a twice-daily arq cron
job (WorkerSettings.cron_jobs, new to this codebase), a manual admin
trigger for right after a deploy, and GET /movies/trending + GET
/series/trending.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
New TMDb integration end to end: append_to_response "watch/providers",
normalizer parsing grouped by country/monetization type, a WatchProvider
catalog entry (external-id keyed, same get_or_create pattern as Company/
Collection/Person) + polymorphic WatchProviderOffering table (new
migration 0002), ingestion write (including provider logos via the
existing Artwork table), and API exposure -- embedded on movie/series
detail plus a dedicated /watch-providers sub-route, shaped to mirror
TMDb's own response for easy porting.
Note: this data comes from JustWatch via TMDb and carries its own
attribution requirement separate from TMDb's general one -- flagged in
docs/api.md, must be satisfied before showing this in the frontend.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Postgres ENUM columns auto-create their type as a side effect of the
first CREATE TABLE that uses them; since the migration also explicitly
pre-creates all 7 enum types up front (to group and control that DDL
alongside their explicit drop in downgrade()), the two collided with
"type already exists" on the first table past the first enum's owner.
create_type=False on each ENUM tells SQLAlchemy the migration manages
that DDL itself.
Found by running the migration against a real PostgreSQL for the first
time (docker compose up on a devbox) — this repo had no Docker/Postgres
available to test against when the migration was originally written.
Co-Authored-By: Claude Sonnet 5 <[email protected]>