Password resets, bot walls, legal pages, and a second way to add studios
Today was the day of "things you need before you can tell anyone about the site." A proper password reset flow with transactional email via Resend, including
Today was the day of "things you need before you can tell anyone about the site." A proper password reset flow with transactional email via Resend, including the subtle part where resetting your password invalidates all your existing JWTs, so an attacker who snatched an old token can't keep using it after the real user rotates. Cloudflare Turnstile sitting on register, login, forgot-password, and review submission, with lazy per-component loading so the widget only loads the script when a real user arrives at a form, not on every page view. Reviews got an anti-spam pass: the API layer now blocks links in review bodies (the single highest-signal spam vector), but we also added structured Review References, up to three on-site video links that are allowed, threaded through a picker UI and rendered as poster thumbnails under the review. And a denormalised reviewer_review_count column on users so the byline on every review can show "wrote 12 other reviews" without a subquery per card.
Meta descriptions went from generic to unique, every video page gets its own sentence composed from the video's title, cast, tags, and runtime; every actor gets one composed from their filmography; same for studios and tags. Google sees materially different HTML per URL instead of near-duplicates of the site's default description. The admin grew proper studio CRUD (add, edit, per-studio scrape scheduling) so adding a second studio no longer requires SQL. A separate column, nats_site_id, unlocks deterministic postback attribution when multiple studios share an affiliate account. Plausible and Cloudflare Web Analytics got wired up, not for us, but as a credibility layer for future studio pitches.
The unglamorous but load-bearing block: six legal and informational pages (Privacy, Terms, DMCA, 2257, About, Contact), all plain JSX with a shared .prose class. And a quiet but durable refactor: user profiles moved from /:username to /u/:username (Reddit-style). This lets any username be legal, no more "reserved usernames" list to maintain, because the /u/ prefix prevents collisions with any current or future top-level route. Day closed with pluggable import sources: studios can now source from either an Apify scraper or a public JSON feed, enforced by a mutually-exclusive CHECK constraint. The existing Apify path is unchanged; new JSON-feed studios drop in without writing a scraper.