117 lines
3.4 KiB
TypeScript
117 lines
3.4 KiB
TypeScript
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;
|