import { Router } from "express"; import { renderPdf } from "../services/browser.js"; import logger from "../services/logger.js"; import { templates, renderTemplate } from "../services/templates.js"; function sanitizeFilename(name) { return name.replace(/["\r\n\x00-\x1f]/g, "_").substring(0, 200); } export const templatesRouter = Router(); // GET /v1/templates — list available templates templatesRouter.get("/", (_req, res) => { const list = Object.entries(templates).map(([id, t]) => ({ id, name: t.name, description: t.description, fields: t.fields, })); res.json({ templates: list }); }); // POST /v1/templates/:id/render — render template to PDF templatesRouter.post("/:id/render", async (req, res) => { try { const id = req.params.id; const template = templates[id]; if (!template) { res.status(404).json({ error: `Template '${id}' not found` }); return; } const data = req.body.data || req.body; // Validate required fields const missingFields = template.fields .filter((f) => f.required && (data[f.name] === undefined || data[f.name] === null || data[f.name] === "")) .map((f) => f.name); if (missingFields.length > 0) { res.status(400).json({ error: "Missing required fields", missing: missingFields, hint: `Required fields for '${id}': ${template.fields.filter((f) => f.required).map((f) => f.name).join(", ")}`, }); return; } const html = renderTemplate(id, data); const pdf = await renderPdf(html, { format: data._format || "A4", margin: data._margin, }); const filename = sanitizeFilename(data._filename || `${id}.pdf`); res.setHeader("Content-Type", "application/pdf"); res.setHeader("Content-Disposition", `inline; filename="${filename}"`); res.send(pdf); } catch (err) { logger.error({ err }, "Template render error"); res.status(500).json({ error: "Template rendering failed", detail: err.message }); } });