Radarr v3 shim for Jellyseerr #62

Merged
naps62-yolo merged 2 commits from issue/42-radarr-shim into main 2026-08-22 21:00:40 +01:00
Owner

Adds the arr-compat crate: the Radarr v3 slice Jellyseerr calls (DESIGN.md §9.4), merged into the app under /api/v3.

system/status, rootfolder, qualityprofile, tag, movie GET and POST, movie/lookup. Nothing else, and no arr-core dependency in either direction.

Two of Radarr's ideas are absorbed here rather than passed on:

  • Quality profile. Policy attaches to a root (§5.1), so one fake profile is exposed per movie root, its id is the root's id, and whatever profile an add carries is read and dropped. rootFolderPath decides which policy a request lands under — with no default and no fallback, because guessing files a film under the wrong policy and for kids that means the wrong audio in front of a child.
  • searchForMovie. The reconcile loop searches whatever is wanted (§8), so obeying the flag would be a second search path to keep correct.

Radarr's tags map onto owners (§4.3). monitored maps onto wanted (§4.1). An add fills original_language from TMDB best-effort, since §5.2 is expressed against it and nothing else populates it yet; TMDB being down still creates the movie.

Both spellings of qualityProfile/qualityprofile are routed: Radarr runs on case-insensitive ASP.NET routing and Jellyseerr asks for the camelCase one.

Verification

just ci passes locally: fmt, clippy -D warnings, cargo machete, 102 tests (11 new).

Smoke-tested against the built binary rather than the live Jellyseerr:

GET  /api/v3/system/status   -> {"appName":"Radarr","version":"5.14.0.9383",...}
GET  /api/v3/rootfolder      -> the two real movie roots, ids 1 and 2
GET  /api/v3/qualityProfile  -> "Movies — kids" / "Movies — main", same ids
POST /api/v3/movie           -> 201, wanted movie in /mnt/media/movies/main
POST /api/v3/movie (bad root)-> 400 [{"propertyName":"RootFolderPath",...}]
GET  /api/v3/movie?tmdbId=   -> the row, Radarr-shaped
GET  /api/v3/movie/lookup    -> 502 with no TMDB key, rather than an empty
                                list that reads as "no such film"

The live-Jellyseerr connection in the issue's acceptance criteria has not been run — no Jellyseerr address is recorded in the repo.

Closes #42

Adds the `arr-compat` crate: the Radarr v3 slice Jellyseerr calls (`DESIGN.md` §9.4), merged into the app under `/api/v3`. `system/status`, `rootfolder`, `qualityprofile`, `tag`, `movie` GET and POST, `movie/lookup`. Nothing else, and no `arr-core` dependency in either direction. Two of Radarr's ideas are absorbed here rather than passed on: - **Quality profile.** Policy attaches to a root (§5.1), so one fake profile is exposed per movie root, its id is the root's id, and whatever profile an add carries is read and dropped. `rootFolderPath` decides which policy a request lands under — with no default and no fallback, because guessing files a film under the wrong policy and for `kids` that means the wrong audio in front of a child. - **`searchForMovie`.** The reconcile loop searches whatever is wanted (§8), so obeying the flag would be a second search path to keep correct. Radarr's tags map onto owners (§4.3). `monitored` maps onto `wanted` (§4.1). An add fills `original_language` from TMDB best-effort, since §5.2 is expressed against it and nothing else populates it yet; TMDB being down still creates the movie. Both spellings of `qualityProfile`/`qualityprofile` are routed: Radarr runs on case-insensitive ASP.NET routing and Jellyseerr asks for the camelCase one. <details> <summary>Verification</summary> `just ci` passes locally: fmt, clippy `-D warnings`, `cargo machete`, 102 tests (11 new). Smoke-tested against the built binary rather than the live Jellyseerr: ``` GET /api/v3/system/status -> {"appName":"Radarr","version":"5.14.0.9383",...} GET /api/v3/rootfolder -> the two real movie roots, ids 1 and 2 GET /api/v3/qualityProfile -> "Movies — kids" / "Movies — main", same ids POST /api/v3/movie -> 201, wanted movie in /mnt/media/movies/main POST /api/v3/movie (bad root)-> 400 [{"propertyName":"RootFolderPath",...}] GET /api/v3/movie?tmdbId= -> the row, Radarr-shaped GET /api/v3/movie/lookup -> 502 with no TMDB key, rather than an empty list that reads as "no such film" ``` The live-Jellyseerr connection in the issue's acceptance criteria has not been run — no Jellyseerr address is recorded in the repo. </details> Closes #42
naps62-yolo added 1 commit 2026-08-22 20:51:40 +01:00
feat(compat): Radarr v3 shim for Jellyseerr
ci / web (pull_request) Successful in 6s
ci / rust (pull_request) Successful in 1m14s
e2e / e2e (pull_request) Successful in 1m24s
e03b9695a0
Jellyseerr stays and keeps talking to what it thinks is a Radarr, so the
crate is a translation layer over the real domain and holds no state.

Two of Radarr's ideas are absorbed rather than passed on: policy attaches
to a root (DESIGN.md §5.1), so `qualityProfileId` on an add is read and
dropped and `rootFolderPath` decides which policy a request lands under;
and `searchForMovie` is dropped because the reconcile loop already
searches whatever is wanted (§8).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
naps62-yolo added 1 commit 2026-08-22 20:52:32 +01:00
Merge remote-tracking branch 'origin/main' into issue/42-radarr-shim
ci / web (pull_request) Successful in 10s
ci / rust (pull_request) Successful in 1m51s
e2e / e2e (pull_request) Successful in 1m25s
7f8a4b1a80
naps62-yolo reviewed 2026-08-22 20:54:17 +01:00
naps62-yolo left a comment
Author
Owner

Reviewed e03b9695. No findings.

Reviewed `e03b9695`. No findings. <!-- agent-meta: {"model":"gpt-5.6","session":"127e127b"} -->
naps62-yolo merged commit 94c334ffe9 into main 2026-08-22 21:00:40 +01:00
naps62-yolo deleted branch issue/42-radarr-shim 2026-08-22 21:00:42 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yolo/arr#62