# Media Request System Spec ## Goal Build a Silo-native request system that lets authenticated users discover and search TMDB movies and series, request missing media, and have approved requests fulfilled through Radarr and Sonarr. Silo owns request state, request limits, approval policy, visibility, and catalog availability detection. Radarr and Sonarr are external fulfillment adapters. ## V1 Scope - TMDB movie and series search. - Direct TMDB-backed discovery sections for requestable media. - One canonical request per media item. - Radarr fulfillment for movies. - Sonarr fulfillment for whole series. - Manual approval and configurable auto approval. - Global rolling request limits with per-user overrides. - User-facing request status badges on search and discovery results. - Admin queue for all requests. - Background reconciliation with Radarr, Sonarr, and the Silo catalog. ## Non-Goals For V1 - Season-specific or episode-specific requests. - Multiple Radarr or Sonarr instance routing. - User-selected quality tiers per request. - Public/global request lists for non-admin users. - Request comments or conversation threads. - Client app updates outside the server web UI. - Collection-backed request discovery. ## Request Visibility Requests are not treated as highly private, but Silo should avoid exposing a global request list to normal users. - Non-admin users can list only their own submitted requests. - Admin users can list all requests and see requester, profile, and audit details. - Search and discovery results include lightweight request state for the item. - Search and discovery results must not expose requester identity, notes, or user counts to non-admin users. If an item has already been requested, other users cannot request it. The item should show the existing request status instead. Example search/discovery item state: ```json { "request": { "status": "queued", "requestable": false } } ``` ## Status Model The primary request status tracks the normal fulfillment path: - `pending`: submitted and waiting for approval. - `approved`: approved in Silo, but not yet handed to Radarr or Sonarr. - `queued`: accepted by Radarr or Sonarr and waiting/searching. - `downloading`: Radarr or Sonarr reports active download/import activity. - `completed`: Silo catalog contains the requested media after a scan and provider-ID match. Exceptional outcomes are separate from the main status ladder: - `declined` - `cancelled` - `failed` V1 assumption: declined, cancelled, and failed requests no longer block a new request for the same item, but they still count against historical request limits. ## Request Limits Admins configure a global rolling quota: - `enabled` - `max_requests` - `window_days` Admins can define per-user override modes: - inherit global - custom `max_requests` and `window_days` - unlimited - blocked from requesting Quota rules: - A new request counts when a request row is created. - Every created request counts regardless of final status or outcome. - Pending, approved, queued, downloading, completed, declined, cancelled, and failed requests all count during the rolling window. - Duplicate attempts for an already-requested item do not count because no new request is created. - Limits are enforced per Silo user account, not per household profile. - The active profile is still recorded for audit and display. ## Auto Approval Auto approval supports a global default plus per-user override: - inherit global - always manual - auto approve - blocked Auto approval only applies when: - Requests are enabled. - The user is within quota. - The item is not already available in Silo. - The item does not already have an active request. - The relevant Radarr or Sonarr integration is configured. ## Discovery Request discovery is separate from Silo collections. It should call TMDB directly and enrich the results with Silo availability and request state. It must not create, sync, or depend on `library_collections`. V1 discovery sections: - `trending_movies`: `/trending/movie/week` - `trending_series`: `/trending/tv/week` - `popular_movies`: `/movie/popular` - `popular_series`: `/tv/popular` - `upcoming_movies`: `/movie/upcoming` - `on_air_series`: `/tv/on_the_air` Discovery responses should include: - media type - TMDB ID - title - year or first air year - overview - poster/backdrop URLs or paths - local availability - lightweight request state Example: ```json { "media_type": "movie", "tmdb_id": 550, "title": "Fight Club", "year": 1999, "poster_path": "/pB8BM7pdSp6B6Ih7QZ4DrQ3PmJK.jpg", "availability": "missing", "request": { "status": null, "requestable": true } } ``` Discovery implementation notes: - Add a dedicated request discovery service under the request system. - It may extend or reuse the low-level TMDB HTTP client. - It must not use collection templates, collection sync, or collection persistence. - Cache raw TMDB section responses briefly, for example 15-60 minutes. - Enrich request and availability state at request time so status remains current. ## Search Search should call TMDB directly for movie and TV results, then enrich each result with: - local Silo availability - existing request status - requestability under current settings and quota Search should not require the item to exist in the Silo catalog. ## Fulfillment ### Movies 1. User selects a TMDB movie search/discovery result. 2. Silo validates quota, availability, duplicate request state, and policy. 3. Silo creates a request. 4. If auto approved, or after manual admin approval, Silo looks up/adds the movie in Radarr. 5. Silo stores the Radarr movie ID and moves the request to `queued`. 6. Background reconciliation updates the request to `downloading` from Radarr activity/history. 7. Silo marks the request `completed` only after the Silo catalog sees the matching TMDB ID. ### Series 1. User selects a TMDB TV search/discovery result. 2. Silo resolves the TVDB ID needed by Sonarr. 3. Silo validates quota, availability, duplicate request state, and policy. 4. Silo creates a request. 5. If auto approved, or after manual admin approval, Silo adds the whole series in Sonarr using the default add behavior. 6. Silo stores the Sonarr series ID and moves the request to `queued`. 7. Background reconciliation updates the request to `downloading` from Sonarr activity/history. 8. Silo marks the request `completed` only after the Silo catalog sees the matching TMDB or TVDB ID. ## Radarr Integration The Radarr adapter should use Radarr v3 API endpoints: - `GET /api/v3/system/status` for connection checks. - `GET /api/v3/rootfolder` for root folder choices. - `GET /api/v3/qualityprofile` for quality profiles. - `GET /api/v3/tag` for tag choices. - `GET /api/v3/movie?tmdbId={id}` to detect existing Radarr items. - `GET /api/v3/movie/lookup/tmdb?tmdbId={id}` to hydrate add payloads. - `POST /api/v3/movie` to add approved movies. Configured movie options: - root folder - quality profile - tags - minimum availability - search on add ## Sonarr Integration The Sonarr adapter should use Sonarr v3 API endpoints: - `GET /api/v3/system/status` for connection checks. - `GET /api/v3/rootfolder` for root folder choices. - `GET /api/v3/qualityprofile` for quality profiles. - `GET /api/v3/tag` for tag choices. - `GET /api/v3/series?tvdbId={id}` to detect existing Sonarr items. - `GET /api/v3/series/lookup?term={term}` to hydrate add payloads. - `POST /api/v3/series` to add approved series. Configured series options: - root folder - quality profile - tags - series type - season folder - search for missing episodes on add ## API Surface Profile-scoped routes: - `GET /api/v1/requests/search?q=&media_type=movie|series` - `GET /api/v1/requests/discover` - `GET /api/v1/requests/discover/{section}?page=1` - `POST /api/v1/requests` - `GET /api/v1/requests/mine` - `GET /api/v1/requests/{id}` Admin routes: - `GET /api/v1/admin/requests` - `POST /api/v1/admin/requests/{id}/approve` - `POST /api/v1/admin/requests/{id}/decline` - `POST /api/v1/admin/requests/{id}/retry` - `GET /api/v1/admin/request-settings` - `PUT /api/v1/admin/request-settings` - `GET /api/v1/admin/request-users/{user_id}/limit` - `PUT /api/v1/admin/request-users/{user_id}/limit` - `GET /api/v1/admin/request-integrations` - `PUT /api/v1/admin/request-integrations` ## Data Model ### `media_requests` Canonical media request records. Suggested fields: - `id` - `media_type` - `provider` - `tmdb_id` - `tvdb_id` - `imdb_id` - `title` - `year` - `poster_path` - `overview` - `status` - `outcome` - `requested_by_user_id` - `requested_by_profile_id` - `integration_kind` - `external_id` - `external_status` - `last_error` - `created_at` - `updated_at` - `approved_at` - `completed_at` Add a uniqueness constraint for active requests by `(media_type, provider, tmdb_id)`. Declined, cancelled, failed, and completed requests should not block a future request. ### `media_request_events` Audit trail for request lifecycle changes. Suggested fields: - `id` - `request_id` - `event_type` - `actor_user_id` - `actor_profile_id` - `message` - `metadata` - `created_at` ### `request_user_limits` Per-user quota and approval overrides. Suggested fields: - `user_id` - `limit_mode` - `max_requests` - `window_days` - `approval_mode` - `updated_at` ### `request_settings` Global request settings. Suggested fields: - `requests_enabled` - `global_max_requests` - `global_window_days` - `global_auto_approval_enabled` - `updated_at` ### `request_integrations` Radarr and Sonarr configuration. API keys should use the existing sensitive settings pattern rather than being returned from API responses. The expected sensitive setting references are `requests.radarr.api_key` and `requests.sonarr.api_key`. Suggested fields: - `kind` - `enabled` - `base_url` - `api_key_ref` - `root_folder` - `quality_profile_id` - `tags` - `options` - `last_check_at` - `last_check_status` - `last_check_error` - `updated_at` ## UI Requirements User UI: - Request discovery page with TMDB sections. - Search page or search mode that includes TMDB results. - Clear badges for available, requested, and requestable states. - Own requests page. - Request button disabled when unavailable due to existing request, local availability, quota, or disabled system settings. Admin UI: - Request queue with filters by status and outcome. - Approve, decline, and retry actions. - Request settings page. - Radarr and Sonarr configuration with connection checks. - Per-user limit and approval override controls. ## Acceptance Criteria - A user can discover trending/popular/upcoming TMDB movies and series without touching Silo collections. - A user can search TMDB and request a missing movie or whole series. - Search and discovery items show whether they are available, already requested, or requestable. - A user cannot request an item that already has an active request. - Request limits count every created request within the rolling window, regardless of final status. - Admins can configure global limits and per-user overrides. - Admins can configure Radarr and Sonarr, approve/decline/retry requests, and see request audit history. - Auto-approved requests are submitted to Radarr/Sonarr without admin action. - Requests progress through `pending`, `approved`, `queued`, `downloading`, and `completed`. - `completed` means Silo has scanned and matched the media in its catalog, not merely that Radarr or Sonarr reports completion.