Initial MVP: DocFast PDF API
- HTML/Markdown to PDF conversion via Puppeteer - Invoice and receipt templates - API key auth + rate limiting - Dockerfile for deployment
This commit is contained in:
commit
feee0317ae
14 changed files with 4529 additions and 0 deletions
78
src/routes/convert.ts
Normal file
78
src/routes/convert.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { Router, Request, Response } from "express";
|
||||
import { renderPdf } from "../services/browser.js";
|
||||
import { markdownToHtml, wrapHtml } from "../services/markdown.js";
|
||||
|
||||
export const convertRouter = Router();
|
||||
|
||||
interface ConvertBody {
|
||||
html?: string;
|
||||
markdown?: string;
|
||||
css?: string;
|
||||
format?: string;
|
||||
landscape?: boolean;
|
||||
margin?: { top?: string; right?: string; bottom?: string; left?: string };
|
||||
printBackground?: boolean;
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
// POST /v1/convert/html
|
||||
convertRouter.post("/html", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const body: ConvertBody =
|
||||
typeof req.body === "string" ? { html: req.body } : req.body;
|
||||
|
||||
if (!body.html) {
|
||||
res.status(400).json({ error: "Missing 'html' field" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap bare HTML fragments
|
||||
const fullHtml = body.html.includes("<html")
|
||||
? body.html
|
||||
: wrapHtml(body.html, body.css);
|
||||
|
||||
const pdf = await renderPdf(fullHtml, {
|
||||
format: body.format,
|
||||
landscape: body.landscape,
|
||||
margin: body.margin,
|
||||
printBackground: body.printBackground,
|
||||
});
|
||||
|
||||
const filename = body.filename || "document.pdf";
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
res.setHeader("Content-Disposition", `inline; filename="${filename}"`);
|
||||
res.send(pdf);
|
||||
} catch (err: any) {
|
||||
console.error("Convert HTML error:", err);
|
||||
res.status(500).json({ error: "PDF generation failed", detail: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /v1/convert/markdown
|
||||
convertRouter.post("/markdown", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const body: ConvertBody =
|
||||
typeof req.body === "string" ? { markdown: req.body } : req.body;
|
||||
|
||||
if (!body.markdown) {
|
||||
res.status(400).json({ error: "Missing 'markdown' field" });
|
||||
return;
|
||||
}
|
||||
|
||||
const html = markdownToHtml(body.markdown, body.css);
|
||||
const pdf = await renderPdf(html, {
|
||||
format: body.format,
|
||||
landscape: body.landscape,
|
||||
margin: body.margin,
|
||||
printBackground: body.printBackground,
|
||||
});
|
||||
|
||||
const filename = body.filename || "document.pdf";
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
res.setHeader("Content-Disposition", `inline; filename="${filename}"`);
|
||||
res.send(pdf);
|
||||
} catch (err: any) {
|
||||
console.error("Convert MD error:", err);
|
||||
res.status(500).json({ error: "PDF generation failed", detail: err.message });
|
||||
}
|
||||
});
|
||||
7
src/routes/health.ts
Normal file
7
src/routes/health.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { Router } from "express";
|
||||
|
||||
export const healthRouter = Router();
|
||||
|
||||
healthRouter.get("/", (_req, res) => {
|
||||
res.json({ status: "ok", version: "0.1.0" });
|
||||
});
|
||||
43
src/routes/templates.ts
Normal file
43
src/routes/templates.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { Router, Request, Response } from "express";
|
||||
import { renderPdf } from "../services/browser.js";
|
||||
import { templates, renderTemplate } from "../services/templates.js";
|
||||
|
||||
export const templatesRouter = Router();
|
||||
|
||||
// GET /v1/templates — list available templates
|
||||
templatesRouter.get("/", (_req: Request, res: Response) => {
|
||||
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: Request, res: Response) => {
|
||||
try {
|
||||
const id = req.params.id as string;
|
||||
const template = templates[id];
|
||||
if (!template) {
|
||||
res.status(404).json({ error: `Template '${id}' not found` });
|
||||
return;
|
||||
}
|
||||
|
||||
const data = req.body;
|
||||
const html = renderTemplate(id, data);
|
||||
const pdf = await renderPdf(html, {
|
||||
format: data._format || "A4",
|
||||
margin: data._margin,
|
||||
});
|
||||
|
||||
const filename = data._filename || `${id}.pdf`;
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
res.setHeader("Content-Disposition", `inline; filename="${filename}"`);
|
||||
res.send(pdf);
|
||||
} catch (err: any) {
|
||||
console.error("Template render error:", err);
|
||||
res.status(500).json({ error: "Template rendering failed", detail: err.message });
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue