import { Hono } from "hono"; import { zValidator } from "@hono/zod-validator"; import { createThreadSchema, updateThreadSchema, createCandidateSchema, updateCandidateSchema, resolveThreadSchema, } from "../../shared/schemas.ts"; import { getAllThreads, getThreadWithCandidates, createThread, updateThread, deleteThread, createCandidate, updateCandidate, deleteCandidate, resolveThread, } from "../services/thread.service.ts"; import { unlink } from "node:fs/promises"; import { join } from "node:path"; 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 = Number(c.req.param("id")); 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 = Number(c.req.param("id")); 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 = Number(c.req.param("id")); 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 = Number(c.req.param("id")); // 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 = Number(c.req.param("candidateId")); 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 = Number(c.req.param("candidateId")); 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 }); }); // Resolution app.post("/:id/resolve", zValidator("json", resolveThreadSchema), (c) => { const db = c.get("db"); const threadId = Number(c.req.param("id")); 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 };