import { Router } from "express"; import { z } from "zod"; import { createWatcher, deleteWatcher, fetchWatcherImage, getQbitCategories, getWatcherItems, getWatcherSummary, listTrackers, listWatchers, runWatcherById, updateWatcher, } from "./watcher.service"; const router = Router(); const createWatcherSchema = z.object({ tracker: z.string().min(1), cookie: z.string().min(1), intervalMinutes: z.number().int().min(1).max(24 * 60), category: z.string().optional(), enabled: z.boolean().optional(), }); const updateWatcherSchema = z.object({ cookie: z.string().min(1).optional(), intervalMinutes: z.number().int().min(1).max(24 * 60).optional(), category: z.string().optional(), enabled: z.boolean().optional(), }); router.get("/trackers", (_req, res) => { return res.json(listTrackers()); }); router.get("/categories", async (_req, res) => { try { return res.json(await getQbitCategories()); } catch (error) { return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to load qBittorrent categories" }); } }); router.get("/", async (_req, res) => { return res.json(await listWatchers()); }); router.post("/", async (req, res) => { const parsed = createWatcherSchema.safeParse(req.body ?? {}); if (!parsed.success) { return res.status(400).json({ error: "Invalid watcher payload" }); } try { const watcher = await createWatcher(parsed.data); return res.status(201).json(watcher); } catch (error) { return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to create watcher" }); } }); router.patch("/:id", async (req, res) => { const parsed = updateWatcherSchema.safeParse(req.body ?? {}); if (!parsed.success) { return res.status(400).json({ error: "Invalid watcher payload" }); } try { const watcher = await updateWatcher(req.params.id, parsed.data); return res.json(watcher); } catch (error) { return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to update watcher" }); } }); router.delete("/:id", async (req, res) => { try { return res.json(await deleteWatcher(req.params.id)); } catch (error) { return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to delete watcher" }); } }); router.post("/:id/run", async (req, res) => { try { await runWatcherById(req.params.id); return res.json({ ok: true }); } catch (error) { return res.status(400).json({ error: error instanceof Error ? error.message : "Watcher run failed" }); } }); router.get("/summary", async (_req, res) => { return res.json(await getWatcherSummary()); }); router.get("/items", async (_req, res) => { return res.json(await getWatcherItems()); }); router.get("/image", async (req, res) => { const watcherId = String(req.query.watcherId ?? ""); const imageUrl = String(req.query.url ?? ""); if (!watcherId || !imageUrl) { return res.status(400).json({ error: "Missing watcherId or url" }); } try { const image = await fetchWatcherImage(watcherId, imageUrl); res.setHeader("Content-Type", image.contentType); res.setHeader("Cache-Control", "private, max-age=300"); return res.send(image.data); } catch (error) { return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to fetch image" }); } }); export default router;