All checks were successful
Deploy to Production / Deploy to Server (push) Successful in 2m20s
- Added support@docfast.dev to footer, impressum, terms, landing page, openapi.json - Fixed audit #6: Template render validates required fields (400 on missing) - Fixed audit #7: Content-Type check on markdown/URL routes (415) - Fixed audit #11: /v1/usage and /v1/concurrency now require ADMIN_API_KEY - Fixed audit Critical #3: URL convert uses domcontentloaded instead of networkidle0
55 lines
2.1 KiB
JavaScript
55 lines
2.1 KiB
JavaScript
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 });
|
|
}
|
|
});
|