Files
q-buffer/apps/server/src/watcher/watcher.routes.ts

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;