v0.2.1: request logging, 404 handler, permissions-policy, SEO improvements, typo fix
Some checks failed
Deploy to Production / Deploy to Server (push) Failing after 20s
Some checks failed
Deploy to Production / Deploy to Server (push) Failing after 20s
This commit is contained in:
parent
210e71e3d8
commit
86f8da62ec
5 changed files with 45 additions and 8 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "docfast-api",
|
"name": "docfast-api",
|
||||||
"version": "0.1.0",
|
"version": "0.2.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "docfast-api",
|
"name": "docfast-api",
|
||||||
"version": "0.1.0",
|
"version": "0.2.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"compression": "^1.8.1",
|
"compression": "^1.8.1",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "docfast-api",
|
"name": "docfast-api",
|
||||||
"version": "0.1.0",
|
"version": "0.2.1",
|
||||||
"description": "Markdown/HTML to PDF API with built-in invoice templates",
|
"description": "Markdown/HTML to PDF API with built-in invoice templates",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,11 @@
|
||||||
<meta name="twitter:title" content="DocFast — HTML & Markdown to PDF API">
|
<meta name="twitter:title" content="DocFast — HTML & Markdown to PDF API">
|
||||||
<meta name="twitter:description" content="Convert HTML and Markdown to beautiful PDFs with a simple API call.">
|
<meta name="twitter:description" content="Convert HTML and Markdown to beautiful PDFs with a simple API call.">
|
||||||
<link rel="canonical" href="https://docfast.dev">
|
<link rel="canonical" href="https://docfast.dev">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{"@context":"https://schema.org","@type":"SoftwareApplication","name":"DocFast","url":"https://docfast.dev","applicationCategory":"DeveloperApplication","operatingSystem":"Web","description":"Convert HTML and Markdown to beautiful PDFs with a simple API call. Fast, reliable, developer-friendly.","offers":[{"@type":"Offer","price":"0","priceCurrency":"USD","name":"Free","description":"100 PDFs/month"},{"@type":"Offer","price":"9","priceCurrency":"USD","name":"Pro","description":"10,000 PDFs/month","billingIncrement":"P1M"}]}
|
||||||
|
</script>
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||||
<style>
|
<style>
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemapns.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemapns.org/schemas/sitemap/0.9">
|
||||||
<url><loc>https://docfast.dev/</loc><changefreq>weekly</changefreq><priority>1.0</priority></url>
|
<url><loc>https://docfast.dev/</loc><lastmod>2026-02-16</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url>
|
||||||
<url><loc>https://docfast.dev/docs</loc><changefreq>weekly</changefreq><priority>0.8</priority></url>
|
<url><loc>https://docfast.dev/docs</loc><lastmod>2026-02-16</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>
|
||||||
<url><loc>https://docfast.dev/v1/health</loc><changefreq>daily</changefreq><priority>0.3</priority></url>
|
|
||||||
</urlset>
|
</urlset>
|
||||||
|
|
|
||||||
37
src/index.ts
37
src/index.ts
|
|
@ -27,11 +27,24 @@ const PORT = parseInt(process.env.PORT || "3100", 10);
|
||||||
|
|
||||||
app.use(helmet({ crossOriginResourcePolicy: { policy: "cross-origin" } }));
|
app.use(helmet({ crossOriginResourcePolicy: { policy: "cross-origin" } }));
|
||||||
|
|
||||||
// Request ID middleware
|
// Request ID + request logging middleware
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
const requestId = (req.headers["x-request-id"] as string) || randomUUID();
|
const requestId = (req.headers["x-request-id"] as string) || randomUUID();
|
||||||
(req as any).requestId = requestId;
|
(req as any).requestId = requestId;
|
||||||
res.setHeader("X-Request-Id", requestId);
|
res.setHeader("X-Request-Id", requestId);
|
||||||
|
const start = Date.now();
|
||||||
|
res.on("finish", () => {
|
||||||
|
const ms = Date.now() - start;
|
||||||
|
if (req.path !== "/health") {
|
||||||
|
logger.info({ method: req.method, path: req.path, status: res.statusCode, ms, requestId }, "request");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Permissions-Policy header
|
||||||
|
app.use((_req, res, next) => {
|
||||||
|
res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=(self)");
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -150,7 +163,7 @@ p{color:#7a8194;margin-bottom:24px;line-height:1.6}
|
||||||
<h1>${title}</h1>
|
<h1>${title}</h1>
|
||||||
<p>${message}</p>
|
<p>${message}</p>
|
||||||
${apiKey ? `
|
${apiKey ? `
|
||||||
<div class="warning">⚠️ Save your API key securely. You can recover it via email if needed..</div>
|
<div class="warning">⚠️ Save your API key securely. You can recover it via email if needed.</div>
|
||||||
<div class="key-box" onclick="navigator.clipboard.writeText('${apiKey}');this.style.borderColor='#5eead4';setTimeout(()=>this.style.borderColor='#34d399',1500)">${apiKey}</div>
|
<div class="key-box" onclick="navigator.clipboard.writeText('${apiKey}');this.style.borderColor='#5eead4';setTimeout(()=>this.style.borderColor='#34d399',1500)">${apiKey}</div>
|
||||||
<div class="links">100 free PDFs/month · <a href="/docs">Read the docs →</a></div>
|
<div class="links">100 free PDFs/month · <a href="/docs">Read the docs →</a></div>
|
||||||
` : `<div class="links"><a href="/">← Back to DocFast</a></div>`}
|
` : `<div class="links"><a href="/">← Back to DocFast</a></div>`}
|
||||||
|
|
@ -231,6 +244,26 @@ app.use((req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 404 handler — must be after all routes
|
||||||
|
app.use((req, res) => {
|
||||||
|
if (req.path.startsWith("/v1/")) {
|
||||||
|
res.status(404).json({ error: "Not found" });
|
||||||
|
} else {
|
||||||
|
const accepts = req.headers.accept || "";
|
||||||
|
if (accepts.includes("text/html")) {
|
||||||
|
res.status(404).send(`<!DOCTYPE html>
|
||||||
|
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>404 — DocFast</title>
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||||
|
<style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Inter',-apple-system,sans-serif;background:#0b0d11;color:#e4e7ed;min-height:100vh;display:flex;align-items:center;justify-content:center}
|
||||||
|
.c{text-align:center}.c h1{font-size:4rem;font-weight:800;color:#34d399;margin-bottom:12px}.c p{color:#7a8194;margin-bottom:24px}.c a{color:#34d399;text-decoration:none}.c a:hover{color:#5eead4}</style>
|
||||||
|
</head><body><div class="c"><h1>404</h1><p>Page not found.</p><p><a href="/">← Back to DocFast</a> · <a href="/docs">API Docs</a></p></div></body></html>`);
|
||||||
|
} else {
|
||||||
|
res.status(404).json({ error: "Not found" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
// Initialize PostgreSQL
|
// Initialize PostgreSQL
|
||||||
await initDatabase();
|
await initDatabase();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue