"use server"; // Manual search, the Sonarr-style "show me what is out there" button. // // Both actions re-check the session. Server Actions are reachable by direct // POST, not only through the buttons on the page, so the layout's admin guard // governs rendering and nothing else -- and the second of these adds torrents. import { auth } from "@/auth"; import { revalidatePath } from "next/cache"; import { grabRelease, searchReleases, IndexerUnavailableError, type SearchResult, } from "@/lib/indexer"; async function requireAdmin() { const session = await auth(); if (!session?.user?.isAdmin) throw new Error("Unauthorized"); } // Errors are returned rather than thrown: an indexer being down, or the fetch // stack being stopped, is an ordinary condition that belongs in the panel next // to the results, not an error boundary that blanks the inventory page. export type SearchState = | { ok: true; result: SearchResult } | { ok: false; error: string }; export async function runManualSearch(target: { mediaItemId: string; seasonNumber?: number | null; episodeNumber?: number | null; }): Promise { await requireAdmin(); try { return { ok: true, result: await searchReleases(target) }; } catch (error) { if (error instanceof IndexerUnavailableError) return { ok: false, error: error.message }; return { ok: false, error: error instanceof Error ? error.message : "Search failed" }; } } export type GrabState = { ok: true; message: string } | { ok: false; error: string }; export async function grabCandidate(choice: { searchId: string; infoHash: string; }): Promise { await requireAdmin(); try { const result = await grabRelease(choice); // Sending it to qBittorrent changes what the Downloads page shows, and it // changes the inventory row's "in flight" state. revalidatePath("/admin/downloads"); revalidatePath("/admin/inventory"); revalidatePath("/admin/calendar"); return result.grabbed ? { ok: true, message: `Sent to qBittorrent: ${result.title}` } : { ok: false, error: result.reason ?? "Not grabbed" }; } catch (error) { return { ok: false, error: error instanceof Error ? error.message : "Grab failed" }; } }