mirror of
https://github.com/myronblair/kino-app
synced 2026-07-27 21:18:43 -05:00
Wire Requests approval into Radarr/Sonarr with live download status
Admin can now Approve a pending request as Movie or TV, which looks up the title via Radarr/Sonarr's own lookup API, adds the best match with search-on-add enabled, and tracks the resulting radarr_id/sonarr_id on the request. A live status line (searching/queued/downloading/downloaded, polled every 15s) shows next to each approved request.
This commit is contained in:
+66
-1
@@ -21,7 +21,7 @@ from models import (
|
||||
Movie, MovieCreate, MovieUpdate,
|
||||
Subtitle,
|
||||
WatchlistItem, ProgressUpsert,
|
||||
RequestCreate, RequestUpdate, MovieRequest,
|
||||
RequestCreate, RequestUpdate, RequestApprove, MovieRequest,
|
||||
AppSettings, AppSettingsPublic,
|
||||
TMDBSearchResult, RadarrMovieDTO,
|
||||
TranscodeJob, QueuePauseToggle,
|
||||
@@ -919,6 +919,71 @@ async def update_request(request_id: str, payload: RequestUpdate, user: dict = D
|
||||
return res
|
||||
|
||||
|
||||
@api.post("/requests/{request_id}/approve", response_model=MovieRequest)
|
||||
async def approve_request(request_id: str, payload: RequestApprove, user: dict = Depends(require_admin)):
|
||||
"""Looks up the request's title in Radarr/Sonarr, adds the best match with an immediate
|
||||
search enabled, and marks the request approved. Content actually lands via the normal
|
||||
Radarr/Sonarr download pipeline — this only kicks that off."""
|
||||
req = await db.requests.find_one({"id": request_id}, {"_id": 0})
|
||||
if not req: raise HTTPException(status_code=404, detail="Request not found")
|
||||
if payload.content_type not in ("movie", "tv"):
|
||||
raise HTTPException(status_code=400, detail="content_type must be movie or tv")
|
||||
s = await get_settings()
|
||||
|
||||
if payload.content_type == "movie":
|
||||
if not s.get("radarr_url") or not s.get("radarr_api_key"):
|
||||
raise HTTPException(status_code=400, detail="Radarr not configured")
|
||||
try:
|
||||
candidates = await asyncio.to_thread(radarr_client.search_movie, s["radarr_url"], s["radarr_api_key"], req["title"])
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=f"Radarr lookup error: {e}")
|
||||
match = next((c for c in candidates if req.get("year") and c.get("year") == req["year"]), candidates[0] if candidates else None)
|
||||
if not match:
|
||||
raise HTTPException(status_code=404, detail="No match found in Radarr")
|
||||
try:
|
||||
added = await asyncio.to_thread(radarr_client.add_movie, s["radarr_url"], s["radarr_api_key"], match["tmdb_id"], match["title"], match.get("year"))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=f"Radarr add error: {e}")
|
||||
updates = {"status": "approved", "content_type": "movie", "radarr_id": added["radarr_id"],
|
||||
"matched_title": added.get("title"), "matched_year": added.get("year")}
|
||||
else:
|
||||
if not s.get("sonarr_url") or not s.get("sonarr_api_key"):
|
||||
raise HTTPException(status_code=400, detail="Sonarr not configured")
|
||||
try:
|
||||
candidates = await asyncio.to_thread(sonarr_client.search_series, s["sonarr_url"], s["sonarr_api_key"], req["title"])
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=f"Sonarr lookup error: {e}")
|
||||
match = next((c for c in candidates if req.get("year") and c.get("year") == req["year"]), candidates[0] if candidates else None)
|
||||
if not match:
|
||||
raise HTTPException(status_code=404, detail="No match found in Sonarr")
|
||||
try:
|
||||
added = await asyncio.to_thread(sonarr_client.add_series, s["sonarr_url"], s["sonarr_api_key"], match["tvdb_id"], match["title"], match.get("year"))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=f"Sonarr add error: {e}")
|
||||
updates = {"status": "approved", "content_type": "tv", "sonarr_id": added["sonarr_id"],
|
||||
"matched_title": added.get("title"), "matched_year": added.get("year")}
|
||||
|
||||
res = await db.requests.find_one_and_update(
|
||||
{"id": request_id}, {"$set": updates}, projection={"_id": 0}, return_document=True,
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
@api.get("/requests/{request_id}/status")
|
||||
async def request_download_status(request_id: str, user: dict = Depends(require_admin)):
|
||||
req = await db.requests.find_one({"id": request_id}, {"_id": 0})
|
||||
if not req: raise HTTPException(status_code=404, detail="Request not found")
|
||||
s = await get_settings()
|
||||
try:
|
||||
if req.get("radarr_id") and s.get("radarr_url") and s.get("radarr_api_key"):
|
||||
return await asyncio.to_thread(radarr_client.movie_status, s["radarr_url"], s["radarr_api_key"], req["radarr_id"])
|
||||
if req.get("sonarr_id") and s.get("sonarr_url") and s.get("sonarr_api_key"):
|
||||
return await asyncio.to_thread(sonarr_client.series_status, s["sonarr_url"], s["sonarr_api_key"], req["sonarr_id"])
|
||||
except Exception as e:
|
||||
return {"state": "unknown", "error": str(e)}
|
||||
return {"state": req.get("status", "pending")}
|
||||
|
||||
|
||||
# ============ SETTINGS (admin) ============
|
||||
@api.get("/settings", response_model=AppSettingsPublic)
|
||||
async def read_settings(user: dict = Depends(require_admin)):
|
||||
|
||||
Reference in New Issue
Block a user