import { unlink } from "node:fs/promises"; import { join } from "node:path"; import { zValidator } from "@hono/zod-validator"; import { Hono } from "hono"; import { createCandidateSchema, createThreadSchema, reorderCandidatesSchema, resolveThreadSchema, updateCandidateSchema, updateThreadSchema, } from "../../shared/schemas.ts"; import { parseId } from "../lib/params.ts"; import { createCandidate, createThread, deleteCandidate, deleteThread, getAllThreads, getThreadWithCandidates, reorderCandidates, resolveThread, updateCandidate, updateThread, } from "../services/thread.service.ts"; type Env = { Variables: { db?: any } }; const app = new Hono(); // Thread CRUD app.get("/", (c) => { const db = c.get("db"); const includeResolved = c.req.query("includeResolved") === "true"; const threads = getAllThreads(db, includeResolved); return c.json(threads); }); app.post("/", zValidator("json", createThreadSchema), (c) => { const db = c.get("db"); const data = c.req.valid("json"); const thread = createThread(db, data); return c.json(thread, 201); }); app.get("/:id", (c) => { const db = c.get("db"); const id = parseId(c.req.param("id")); if (!id) return c.json({ error: "Invalid thread ID" }, 400); const thread = getThreadWithCandidates(db, id); if (!thread) return c.json({ error: "Thread not found" }, 404); return c.json(thread); }); app.put("/:id", zValidator("json", updateThreadSchema), (c) => { const db = c.get("db"); const id = parseId(c.req.param("id")); if (!id) return c.json({ error: "Invalid thread ID" }, 400); const data = c.req.valid("json"); const thread = updateThread(db, id, data); if (!thread) return c.json({ error: "Thread not found" }, 404); return c.json(thread); }); app.delete("/:id", async (c) => { const db = c.get("db"); const id = parseId(c.req.param("id")); if (!id) return c.json({ error: "Invalid thread ID" }, 400); const deleted = deleteThread(db, id); if (!deleted) return c.json({ error: "Thread not found" }, 404); // Clean up candidate image files for (const filename of deleted.candidateImages) { try { await unlink(join("uploads", filename)); } catch { // File missing is not an error worth failing the delete over } } return c.json({ success: true }); }); // Candidate CRUD (nested under thread) app.post("/:id/candidates", zValidator("json", createCandidateSchema), (c) => { const db = c.get("db"); const threadId = parseId(c.req.param("id")); if (!threadId) return c.json({ error: "Invalid thread ID" }, 400); // Verify thread exists const thread = getThreadWithCandidates(db, threadId); if (!thread) return c.json({ error: "Thread not found" }, 404); const data = c.req.valid("json"); const candidate = createCandidate(db, threadId, data); return c.json(candidate, 201); }); app.put( "/:threadId/candidates/:candidateId", zValidator("json", updateCandidateSchema), (c) => { const db = c.get("db"); const candidateId = parseId(c.req.param("candidateId")); if (!candidateId) return c.json({ error: "Invalid candidate ID" }, 400); const data = c.req.valid("json"); const candidate = updateCandidate(db, candidateId, data); if (!candidate) return c.json({ error: "Candidate not found" }, 404); return c.json(candidate); }, ); app.delete("/:threadId/candidates/:candidateId", async (c) => { const db = c.get("db"); const candidateId = parseId(c.req.param("candidateId")); if (!candidateId) return c.json({ error: "Invalid candidate ID" }, 400); const deleted = deleteCandidate(db, candidateId); if (!deleted) return c.json({ error: "Candidate not found" }, 404); // Clean up image file if exists if (deleted.imageFilename) { try { await unlink(join("uploads", deleted.imageFilename)); } catch { // File missing is not an error } } return c.json({ success: true }); }); // Candidate reorder app.patch( "/:id/candidates/reorder", zValidator("json", reorderCandidatesSchema), (c) => { const db = c.get("db"); const threadId = parseId(c.req.param("id")); if (!threadId) return c.json({ error: "Invalid thread ID" }, 400); const { orderedIds } = c.req.valid("json"); const result = reorderCandidates(db, threadId, orderedIds); if (!result.success) return c.json({ error: result.error }, 400); return c.json({ success: true }); }, ); // Resolution app.post("/:id/resolve", zValidator("json", resolveThreadSchema), (c) => { const db = c.get("db"); const threadId = parseId(c.req.param("id")); if (!threadId) return c.json({ error: "Invalid thread ID" }, 400); const { candidateId } = c.req.valid("json"); const result = resolveThread(db, threadId, candidateId); if (!result.success) { return c.json({ error: result.error }, 400); } return c.json({ success: true, item: result.item }); }); export { app as threadRoutes };