docs: add missing OpenAPI annotations for signup/verify, billing/success, billing/webhook
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 16m15s
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 16m15s
This commit is contained in:
parent
427ec8e894
commit
8b31d11e74
15 changed files with 2167 additions and 128 deletions
13
dist/services/browser.js
vendored
13
dist/services/browser.js
vendored
|
|
@ -209,6 +209,11 @@ export async function renderPdf(html, options = {}) {
|
|||
headerTemplate: options.headerTemplate,
|
||||
footerTemplate: options.footerTemplate,
|
||||
displayHeaderFooter: options.displayHeaderFooter || false,
|
||||
...(options.scale !== undefined && { scale: options.scale }),
|
||||
...(options.pageRanges && { pageRanges: options.pageRanges }),
|
||||
...(options.preferCSSPageSize !== undefined && { preferCSSPageSize: options.preferCSSPageSize }),
|
||||
...(options.width && { width: options.width }),
|
||||
...(options.height && { height: options.height }),
|
||||
});
|
||||
return Buffer.from(pdf);
|
||||
})(),
|
||||
|
|
@ -270,6 +275,14 @@ export async function renderUrlPdf(url, options = {}) {
|
|||
landscape: options.landscape || false,
|
||||
printBackground: options.printBackground !== false,
|
||||
margin: options.margin || { top: "0", right: "0", bottom: "0", left: "0" },
|
||||
...(options.headerTemplate && { headerTemplate: options.headerTemplate }),
|
||||
...(options.footerTemplate && { footerTemplate: options.footerTemplate }),
|
||||
...(options.displayHeaderFooter !== undefined && { displayHeaderFooter: options.displayHeaderFooter }),
|
||||
...(options.scale !== undefined && { scale: options.scale }),
|
||||
...(options.pageRanges && { pageRanges: options.pageRanges }),
|
||||
...(options.preferCSSPageSize !== undefined && { preferCSSPageSize: options.preferCSSPageSize }),
|
||||
...(options.width && { width: options.width }),
|
||||
...(options.height && { height: options.height }),
|
||||
});
|
||||
return Buffer.from(pdf);
|
||||
})(),
|
||||
|
|
|
|||
69
dist/services/db.js
vendored
69
dist/services/db.js
vendored
|
|
@ -1,20 +1,7 @@
|
|||
import pg from "pg";
|
||||
import logger from "./logger.js";
|
||||
import { isTransientError } from "../utils/errors.js";
|
||||
const { Pool } = pg;
|
||||
// Transient error codes from PgBouncer / PostgreSQL that warrant retry
|
||||
const TRANSIENT_ERRORS = new Set([
|
||||
"ECONNRESET",
|
||||
"ECONNREFUSED",
|
||||
"EPIPE",
|
||||
"ETIMEDOUT",
|
||||
"CONNECTION_LOST",
|
||||
"57P01", // admin_shutdown
|
||||
"57P02", // crash_shutdown
|
||||
"57P03", // cannot_connect_now
|
||||
"08006", // connection_failure
|
||||
"08003", // connection_does_not_exist
|
||||
"08001", // sqlclient_unable_to_establish_sqlconnection
|
||||
]);
|
||||
const pool = new Pool({
|
||||
host: process.env.DATABASE_HOST || "172.17.0.1",
|
||||
port: parseInt(process.env.DATABASE_PORT || "5432", 10),
|
||||
|
|
@ -33,28 +20,7 @@ const pool = new Pool({
|
|||
pool.on("error", (err, client) => {
|
||||
logger.error({ err }, "Unexpected error on idle PostgreSQL client — evicted from pool");
|
||||
});
|
||||
/**
|
||||
* Determine if an error is transient (PgBouncer failover, network blip)
|
||||
*/
|
||||
export function isTransientError(err) {
|
||||
if (!err)
|
||||
return false;
|
||||
const code = err.code || "";
|
||||
const msg = (err.message || "").toLowerCase();
|
||||
if (TRANSIENT_ERRORS.has(code))
|
||||
return true;
|
||||
if (msg.includes("no available server"))
|
||||
return true; // PgBouncer specific
|
||||
if (msg.includes("connection terminated"))
|
||||
return true;
|
||||
if (msg.includes("connection refused"))
|
||||
return true;
|
||||
if (msg.includes("server closed the connection"))
|
||||
return true;
|
||||
if (msg.includes("timeout expired"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
export { isTransientError } from "../utils/errors.js";
|
||||
/**
|
||||
* Execute a query with automatic retry on transient errors.
|
||||
*
|
||||
|
|
@ -180,5 +146,36 @@ export async function initDatabase() {
|
|||
client.release();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clean up stale database entries:
|
||||
* - Expired pending verifications
|
||||
* - Unverified free-tier API keys (never completed verification)
|
||||
* - Orphaned usage rows (key no longer exists)
|
||||
*/
|
||||
export async function cleanupStaleData() {
|
||||
const results = { expiredVerifications: 0, staleKeys: 0, orphanedUsage: 0 };
|
||||
// 1. Delete expired pending verifications
|
||||
const pv = await queryWithRetry("DELETE FROM pending_verifications WHERE expires_at < NOW() RETURNING email");
|
||||
results.expiredVerifications = pv.rowCount || 0;
|
||||
// 2. Delete unverified free-tier keys (email not in verified verifications)
|
||||
const sk = await queryWithRetry(`
|
||||
DELETE FROM api_keys
|
||||
WHERE tier = 'free'
|
||||
AND email NOT IN (
|
||||
SELECT DISTINCT email FROM verifications WHERE verified_at IS NOT NULL
|
||||
)
|
||||
RETURNING key
|
||||
`);
|
||||
results.staleKeys = sk.rowCount || 0;
|
||||
// 3. Delete orphaned usage rows
|
||||
const ou = await queryWithRetry(`
|
||||
DELETE FROM usage
|
||||
WHERE key NOT IN (SELECT key FROM api_keys)
|
||||
RETURNING key
|
||||
`);
|
||||
results.orphanedUsage = ou.rowCount || 0;
|
||||
logger.info({ ...results }, `Database cleanup complete: ${results.expiredVerifications} expired verifications, ${results.staleKeys} stale keys, ${results.orphanedUsage} orphaned usage rows removed`);
|
||||
return results;
|
||||
}
|
||||
export { pool };
|
||||
export default pool;
|
||||
|
|
|
|||
29
dist/services/email.js
vendored
29
dist/services/email.js
vendored
|
|
@ -25,7 +25,34 @@ export async function sendVerificationEmail(email, code) {
|
|||
from: smtpFrom,
|
||||
to: email,
|
||||
subject: "DocFast - Verify your email",
|
||||
text: `Your DocFast verification code is: ${code}\n\nThis code expires in 15 minutes.\n\nIf you didn't request this, ignore this email.`,
|
||||
text: `Your DocFast verification code is: ${code}\n\nThis code expires in 15 minutes.\n\nIf you didn't request this, ignore this email.\n\n---\nDocFast — HTML to PDF API\nhttps://docfast.dev`,
|
||||
html: `<!DOCTYPE html>
|
||||
<html><body style="margin:0;padding:0;background:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background:#0a0a0a;padding:40px 0;">
|
||||
<tr><td align="center">
|
||||
<table width="480" cellpadding="0" cellspacing="0" style="background:#111;border-radius:12px;padding:40px;">
|
||||
<tr><td align="center" style="padding-bottom:24px;">
|
||||
<h1 style="margin:0;font-size:28px;font-weight:700;color:#44ff99;letter-spacing:-0.5px;">DocFast</h1>
|
||||
</td></tr>
|
||||
<tr><td align="center" style="padding-bottom:8px;">
|
||||
<p style="margin:0;font-size:16px;color:#e8e8e8;">Your verification code</p>
|
||||
</td></tr>
|
||||
<tr><td align="center" style="padding-bottom:24px;">
|
||||
<div style="display:inline-block;background:#0a0a0a;border:2px solid #44ff99;border-radius:8px;padding:16px 32px;font-family:monospace;font-size:32px;letter-spacing:8px;color:#44ff99;font-weight:700;">${code}</div>
|
||||
</td></tr>
|
||||
<tr><td align="center" style="padding-bottom:8px;">
|
||||
<p style="margin:0;font-size:14px;color:#999;">This code expires in 15 minutes.</p>
|
||||
</td></tr>
|
||||
<tr><td align="center" style="padding-bottom:24px;">
|
||||
<p style="margin:0;font-size:14px;color:#999;">If you didn't request this, ignore this email.</p>
|
||||
</td></tr>
|
||||
<tr><td align="center" style="border-top:1px solid #222;padding-top:20px;">
|
||||
<p style="margin:0;font-size:12px;color:#666;">DocFast — HTML to PDF API<br><a href="https://docfast.dev" style="color:#44ff99;text-decoration:none;">docfast.dev</a></p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
</body></html>`,
|
||||
});
|
||||
logger.info({ email, messageId: info.messageId }, "Verification email sent");
|
||||
return true;
|
||||
|
|
|
|||
3
dist/services/templates.js
vendored
3
dist/services/templates.js
vendored
|
|
@ -35,7 +35,8 @@ function esc(s) {
|
|||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
function renderInvoice(d) {
|
||||
const cur = esc(d.currency || "€");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue