feat(requests): multi-instance Sonarr/Radarr routing with HD/4K defaults and anime overrides (#39)

* docs: design spec for multi-instance Sonarr/Radarr request routing

Seerr-style multi-instance arr management inside Silo's request system:
many instances per kind, HD/4K default routing, entitlement-driven
dual-quality fan-out, per-instance anime overrides (keyword 210024),
and a one-to-many media_request_targets model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: implementation plan for multi-instance arr request routing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(requests): migration for multi-instance arr routing

Adds migration 169 to convert request_integrations from a one-row-per-kind
table keyed on `kind` to a multi-instance table keyed on `id`, with HD/4K
defaults, anime overrides, and a new one-to-many media_request_targets table
for per-quality fulfillment tracking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(requests): instance, target, and dual-quality types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(requests): id-based integration CRUD

Replace upsert-by-kind (UpsertIntegration/UpsertIntegrations) with
GetIntegration, CreateIntegration, UpdateIntegration, DeleteIntegration,
and ClearDefault. Rewrites scanIntegration and integrationColumns to cover
all new multi-instance columns (id, name, is_4k, is_default, is_default_4k,
anime_* fields). Updates the Store interface accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(requests): target persistence and aggregate status

* feat(tmdb): expose keyword ids and original language on detail

* feat(requests): Seerr-exact anime detection (keyword 210024)

* feat(requests): quality/anime routing engine

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(requests): force_dual_quality setting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(requests): multi-target fulfillment, reconcile, retry, and instance CRUD

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(api): request integration CRUD endpoints, targets in responses, entitlement wiring

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): multi-instance request integration types and CRUD hooks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): multi-instance arr manager, dual-quality toggle, per-target queue

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(web): UX review fixes for arr manager (delete confirm, switch hints, test feedback, dirty + target status)

* fix(requests): address code-review findings (test-connection by id, HD-only default ceiling, retryable partial failure, idempotent submit, transactional defaults, presence/target reconcile, auto-approve gate)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review (anime override fallback, non-null slices, save gate, a11y, DeleteTarget not-found)

- routing: anime fields only override standard root/profile/tags when set,
  so enabling anime with blank fields reuses standard values instead of
  clearing them into an invalid submission
- api: normalize nil Tags/AnimeTags to [] so they serialize as arrays not null
- web: require an API key before saving a NEW instance; add aria-expanded/
  aria-controls to the anime-overrides disclosure toggle
- repo: DeleteTarget returns ErrNotFound when no row was deleted

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(requests): address PR review findings

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
This commit is contained in:
fluxis
2026-06-02 11:25:18 -04:00
committed by GitHub
co-authored by Claude Opus 4.8 Quick
parent a6b510c7c5
commit ea3b5a2e29
27 changed files with 4257 additions and 514 deletions
+29
View File
@@ -1531,6 +1531,22 @@ export interface CreateMediaRequestInput {
backdrop_path?: string;
}
export interface RequestTarget {
id: number;
request_id: string;
integration_id?: string;
integration_kind?: string;
instance_name?: string;
quality: "1080p" | "2160p";
is_anime: boolean;
external_id?: string;
external_status?: string;
status: MediaRequestStatus | "failed";
last_error?: string;
created_at: string;
updated_at: string;
}
export interface MediaRequest {
id: string;
provider: string;
@@ -1547,6 +1563,8 @@ export interface MediaRequest {
outcome: MediaRequestOutcome;
requested_by_user_id?: number;
requested_by_profile_id?: string;
is_anime?: boolean;
targets?: RequestTarget[];
integration_kind?: string;
external_id?: string;
external_status?: string;
@@ -1570,6 +1588,7 @@ export interface RequestSettings {
global_max_requests: number;
global_window_days: number;
global_auto_approval_enabled: boolean;
force_dual_quality: boolean;
updated_at: string;
}
@@ -1583,14 +1602,23 @@ export interface RequestUserLimit {
}
export interface RequestIntegration {
id: string;
name: string;
kind: string;
enabled: boolean;
is_4k: boolean;
is_default: boolean;
is_default_4k: boolean;
base_url: string;
api_key_ref?: string;
has_api_key?: boolean;
root_folder: string;
quality_profile_id?: number | null;
tags: number[];
anime_enabled: boolean;
anime_quality_profile_id?: number | null;
anime_root_folder?: string;
anime_tags: number[];
options: Record<string, unknown>;
last_check_at?: string | null;
last_check_status?: string;
@@ -1623,6 +1651,7 @@ export interface RequestIntegrationOptions {
}
export interface LoadRequestIntegrationOptionsRequest {
kind: "radarr" | "sonarr";
base_url: string;
api_key_ref?: string;
}