* feat(requests): enforce per-profile rating limits in discovery
- Resolve each profile's max content rating and filter discovery, detail, and browse results against it, failing closed on missing ratings
- Reject request submissions for titles above the viewer's ceiling
- Add TMDB GetCertification backed by release_dates/content_ratings with a long-lived cache and singleflight
- Push certification.lte to TMDB for studio/network/genre browse as a cost pre-filter
- Backfill restricted section pages from a fixed window of TMDB pages to keep carousels populated and pagination stable
* fix(requests): address discovery rating review findings
- Preserve backfill overflow: sections use plain TMDB cursor semantics
plus an additive next_page field instead of fixed windows, so an early
stop never drops allowed titles from unconsumed pages (bit hardest at
permissive R/TV-MA ceilings).
- Bound cold-path cost: DiscoverAll backfills at most 2 TMDB pages per
section (vs 5 for a direct section request), capping worst-case cold
certification hydration at 240 lookups instead of 600.
- Keep the TMDB prefilter a superset: rank-3 ceilings now push down
certification.lte=NC-17/TV-MA rather than R, so titles the local
ladder allows can't vanish upstream unrecoverably.
- Fail closed on foreign certifications: enforcement-path lookups use
new US-only pickers (a Canadian PG no longer reads as US PG), while
the display path keeps its any-country fallback. US multi-entry
disagreements prefer the theatrical/real rating over festival NR.
- Detach shared certification fetches from the first caller's context
(WithoutCancel + 30s bound) so one disconnecting client can't fail
the singleflight result for concurrent waiters.
- Advertise enforcement via rating_restrictions_enforced on
/requests/status so clients can feature-detect instead of
version-sniffing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(requests): harden rating enforcement per second review pass
- GetDetail gates on the US-only enforcement certification (cached
GetCertification) instead of the display rating, whose any-country
fallback let a foreign "PG" pass the US ladder.
- pickUSMovieCertification takes the strictest recognized US rating when
multiple release entries disagree ([PG, R] -> R); entry order is not
meaningful and enforcement must not admit a title on its most lenient
certificate.
- Certification singleflight uses DoChan so a canceled caller returns
ctx.Err() immediately instead of blocking up to 30s on the detached
shared fetch (which still completes for surviving waiters).
- Viewer rating ceiling resolves once per request and threads through
discover/browse/detail enrichment (enrichPageWithCeiling); DiscoverAll
drops from 12 scope resolutions per load to 1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Add TTL caches with singleflight for DiscoverSection, DiscoverPage, and GetExternalIDs to reduce upstream TMDB calls
- Reuse CastCarousel with a fullBleed variant on RequestDetail and drop the bespoke RequestCastRow
- GetExternalIDs now uses the dedicated /movie/{id}/external_ids and
/tv/{id}/external_ids endpoints instead of fetching the full detail
with append_to_response=external_ids. The dedicated payload is
one or two orders of magnitude smaller for the same fields.
- Document PosterPath/BackdropPath on MediaResult as raw TMDB path
fragments that callers must prefix with the image base URL.
- normalizeCast switches from inline insertion sort to sort.SliceStable.
The output is identical; the new form is one line and O(n log n).
- normalizeIntegration no longer reuses integration.Tags' backing
array via Tags[:0]; the slice is callable code, so reusing the
array would silently corrupt the caller's slice if it kept a
reference. Allocate a fresh slice instead.
- HandleGet now requires a profile, matching the rest of the
/requests user-group handlers. Router middleware enforces this
already, but the inline check is defense-in-depth for any future
remount.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The client previously fell back to a hardcoded project-level API key
when the caller passed an empty string. Once committed the key cannot
be rotated without a rebuild, and the silent fallback meant that
misconfiguration produced unattributed traffic against a shared key.
Remove the default. Clients constructed with an empty key now return
ErrMissingAPIKey from every API call so operators see the
configuration problem immediately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add `media_type=all` to request search, backed by TMDB `/search/multi` filtered to movies and series
- Default the Requests page filter to All and refresh search results grid styling
- Refine RequestPosterCard with status accent bar, richer fallback poster, and fluid grid layout
Wire curated TMDB-backed studios/networks/genres discovery into the requests service and UI, replacing on-demand logo fetches with fixed duotone logos and adding browse routes plus tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add request domain, repository, service, and reconcile task
- Add Radarr/Sonarr fulfillment adapters and TMDB discovery
- Expose user and admin request APIs with quota and approval rules
- Add web UI for browsing, requesting, and admin queue management
- Migration 139 introduces media_requests and related tables