diff --git a/dist/index.js b/dist/index.js index daba546..cf924ba 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,11 +1,9 @@ import express from "express"; import { randomUUID } from "crypto"; -import { createRequire } from "module"; +import "./types.js"; // Augments Express.Request with requestId, acquirePdfSlot, releasePdfSlot import { compressionMiddleware } from "./middleware/compression.js"; import logger from "./services/logger.js"; import helmet from "helmet"; -const _require = createRequire(import.meta.url); -const APP_VERSION = _require("../package.json").version; import path from "path"; import { fileURLToPath } from "url"; import rateLimit from "express-rate-limit"; @@ -18,13 +16,13 @@ import { emailChangeRouter } from "./routes/email-change.js"; import { billingRouter } from "./routes/billing.js"; import { authMiddleware } from "./middleware/auth.js"; import { usageMiddleware, loadUsageData, flushDirtyEntries } from "./middleware/usage.js"; -import { getUsageStats, getUsageForKey } from "./middleware/usage.js"; -import { pdfRateLimitMiddleware, getConcurrencyStats } from "./middleware/pdfRateLimit.js"; +import { pdfRateLimitMiddleware } from "./middleware/pdfRateLimit.js"; +import { adminRouter } from "./routes/admin.js"; import { initBrowser, closeBrowser } from "./services/browser.js"; -import { loadKeys, getAllKeys, isProKey } from "./services/keys.js"; -import { verifyToken, loadVerifications } from "./services/verification.js"; +import { loadKeys, getAllKeys } from "./services/keys.js"; +import { pagesRouter } from "./routes/pages.js"; import { initDatabase, pool, cleanupStaleData } from "./services/db.js"; -import { swaggerSpec } from "./swagger.js"; +import { startPeriodicCleanup, stopPeriodicCleanup } from "./utils/periodic-cleanup.js"; const app = express(); const PORT = parseInt(process.env.PORT || "3100", 10); app.use(helmet({ crossOriginResourcePolicy: { policy: "cross-origin" } })); @@ -49,7 +47,15 @@ app.use((_req, res, next) => { }); // Compression app.use(compressionMiddleware); +// Block search engine indexing on staging +app.use((req, res, next) => { + if (req.hostname.includes("staging")) { + res.setHeader("X-Robots-Tag", "noindex, nofollow"); + } + next(); +}); // Differentiated CORS middleware +const ALLOWED_ORIGINS = new Set(["https://docfast.dev", "https://staging.docfast.dev"]); app.use((req, res, next) => { const isAuthBillingRoute = req.path.startsWith('/v1/signup') || req.path.startsWith('/v1/recover') || @@ -57,7 +63,14 @@ app.use((req, res, next) => { req.path.startsWith('/v1/demo') || req.path.startsWith('/v1/email-change'); if (isAuthBillingRoute) { - res.setHeader("Access-Control-Allow-Origin", "https://docfast.dev"); + const origin = req.headers.origin; + if (origin && ALLOWED_ORIGINS.has(origin)) { + res.setHeader("Access-Control-Allow-Origin", origin); + res.setHeader("Vary", "Origin"); + } + else { + res.setHeader("Access-Control-Allow-Origin", "https://docfast.dev"); + } } else { res.setHeader("Access-Control-Allow-Origin", "*"); @@ -128,155 +141,11 @@ app.use("/v1/billing", defaultJsonParser, billingRouter); const convertBodyLimit = express.json({ limit: "500kb" }); app.use("/v1/convert", convertBodyLimit, authMiddleware, usageMiddleware, pdfRateLimitMiddleware, convertRouter); app.use("/v1/templates", defaultJsonParser, authMiddleware, usageMiddleware, templatesRouter); -/** - * @openapi - * /v1/usage/me: - * get: - * summary: Get your current month's usage - * description: Returns the authenticated user's PDF generation usage for the current billing month. - * security: - * - ApiKeyAuth: [] - * responses: - * 200: - * description: Current usage statistics - * content: - * application/json: - * schema: - * type: object - * properties: - * used: - * type: integer - * description: Number of PDFs generated this month - * limit: - * type: integer - * description: Monthly PDF limit for your plan - * plan: - * type: string - * enum: [pro, demo] - * description: Your current plan - * month: - * type: string - * description: Current billing month (YYYY-MM) - * 401: - * description: Missing or invalid API key - */ -app.get("/v1/usage/me", authMiddleware, (req, res) => { - const key = req.apiKeyInfo.key; - const { count, monthKey } = getUsageForKey(key); - const pro = isProKey(key); - res.json({ - used: count, - limit: pro ? 5000 : 100, - plan: pro ? "pro" : "demo", - month: monthKey, - }); -}); -// Admin: usage stats (admin key required) -const adminAuth = (req, res, next) => { - const adminKey = process.env.ADMIN_API_KEY; - if (!adminKey) { - res.status(503).json({ error: "Admin access not configured" }); - return; - } - if (req.apiKeyInfo?.key !== adminKey) { - res.status(403).json({ error: "Admin access required" }); - return; - } - next(); -}; -app.get("/v1/usage", authMiddleware, adminAuth, (req, res) => { - res.json(getUsageStats(req.apiKeyInfo?.key)); -}); -// Admin: concurrency stats (admin key required) -app.get("/v1/concurrency", authMiddleware, adminAuth, (_req, res) => { - res.json(getConcurrencyStats()); -}); -// Admin: database cleanup (admin key required) -app.post("/admin/cleanup", authMiddleware, adminAuth, async (_req, res) => { - try { - const results = await cleanupStaleData(); - res.json({ status: "ok", cleaned: results }); - } - catch (err) { - logger.error({ err }, "Admin cleanup failed"); - res.status(500).json({ error: "Cleanup failed", message: err.message }); - } -}); -// Email verification endpoint -app.get("/verify", (req, res) => { - const token = req.query.token; - if (!token) { - res.status(400).send(verifyPage("Invalid Link", "No verification token provided.", null)); - return; - } - const result = verifyToken(token); - switch (result.status) { - case "ok": - res.send(verifyPage("Email Verified! 🚀", "Your DocFast API key is ready:", result.verification.apiKey)); - break; - case "already_verified": - res.send(verifyPage("Already Verified", "This email was already verified. Here's your API key:", result.verification.apiKey)); - break; - case "expired": - res.status(410).send(verifyPage("Link Expired", "This verification link has expired (24h). Please sign up again.", null)); - break; - case "invalid": - res.status(404).send(verifyPage("Invalid Link", "This verification link is not valid.", null)); - break; - } -}); -function verifyPage(title, message, apiKey) { - return ` -
-${message}
-${apiKey ? ` -