fix: OpenAPI spec accuracy — hide internal endpoints, mark signup/verify deprecated
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 13m9s

- Remove @openapi annotations from /v1/billing/webhook (Stripe-internal)
- Remove @openapi annotations from /v1/billing/success (browser redirect)
- Mark /v1/signup/verify as deprecated (returns 410)
- Add 3 TDD tests in openapi-spec.test.ts
- Update 2 existing tests in app-routes.test.ts
- 530 tests passing (was 527)
This commit is contained in:
Hoid 2026-03-07 14:06:12 +01:00
parent 1d5d9adf08
commit 6b1b3d584e
15 changed files with 399 additions and 290 deletions

73
dist/index.js vendored
View file

@ -14,13 +14,14 @@ import { templatesRouter } from "./routes/templates.js";
import { healthRouter } from "./routes/health.js";
import { demoRouter } from "./routes/demo.js";
import { recoverRouter } from "./routes/recover.js";
import { emailChangeRouter } from "./routes/email-change.js";
import { billingRouter } from "./routes/billing.js";
import { authMiddleware } from "./middleware/auth.js";
import { usageMiddleware, loadUsageData } from "./middleware/usage.js";
import { getUsageStats } from "./middleware/usage.js";
import { usageMiddleware, loadUsageData, flushDirtyEntries } from "./middleware/usage.js";
import { getUsageStats, getUsageForKey } from "./middleware/usage.js";
import { pdfRateLimitMiddleware, getConcurrencyStats } from "./middleware/pdfRateLimit.js";
import { initBrowser, closeBrowser } from "./services/browser.js";
import { loadKeys, getAllKeys } from "./services/keys.js";
import { loadKeys, getAllKeys, isProKey } from "./services/keys.js";
import { verifyToken, loadVerifications } from "./services/verification.js";
import { initDatabase, pool, cleanupStaleData } from "./services/db.js";
import { swaggerSpec } from "./swagger.js";
@ -53,7 +54,8 @@ app.use((req, res, next) => {
const isAuthBillingRoute = req.path.startsWith('/v1/signup') ||
req.path.startsWith('/v1/recover') ||
req.path.startsWith('/v1/billing') ||
req.path.startsWith('/v1/demo');
req.path.startsWith('/v1/demo') ||
req.path.startsWith('/v1/email-change');
if (isAuthBillingRoute) {
res.setHeader("Access-Control-Allow-Origin", "https://docfast.dev");
}
@ -71,7 +73,8 @@ app.use((req, res, next) => {
});
// Raw body for Stripe webhook signature verification
app.use("/v1/billing/webhook", express.raw({ type: "application/json" }));
app.use(express.json({ limit: "2mb" }));
// NOTE: No global express.json() here — route-specific parsers are applied
// per-route below to enforce correct body size limits (BUG-101 fix).
app.use(express.text({ limit: "2mb", type: "text/*" }));
// Trust nginx proxy
app.set("trust proxy", 1);
@ -116,12 +119,58 @@ app.use("/v1/signup", (_req, res) => {
pro_url: "https://docfast.dev/#pricing"
});
});
app.use("/v1/recover", recoverRouter);
app.use("/v1/billing", billingRouter);
// Default 2MB JSON parser for standard routes
const defaultJsonParser = express.json({ limit: "2mb" });
app.use("/v1/recover", defaultJsonParser, recoverRouter);
app.use("/v1/email-change", defaultJsonParser, emailChangeRouter);
app.use("/v1/billing", defaultJsonParser, billingRouter);
// Authenticated routes — conversion routes get tighter body limits (500KB)
const convertBodyLimit = express.json({ limit: "500kb" });
app.use("/v1/convert", convertBodyLimit, authMiddleware, usageMiddleware, pdfRateLimitMiddleware, convertRouter);
app.use("/v1/templates", authMiddleware, usageMiddleware, templatesRouter);
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;
@ -362,6 +411,14 @@ async function start() {
resolve();
});
});
// 1.5. Flush dirty usage entries while DB pool is still alive
try {
await flushDirtyEntries();
logger.info("Usage data flushed");
}
catch (err) {
logger.error({ err }, "Error flushing usage data during shutdown");
}
// 2. Close Puppeteer browser pool
try {
await closeBrowser();