feat: wire up swagger-jsdoc dynamic spec, delete static openapi.json
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
- Create src/swagger.ts config module for swagger-jsdoc
- Add GET /openapi.json dynamic route (generated from @openapi annotations)
- Delete static public/openapi.json (was drifting from code)
- Add @openapi annotation for deprecated /v1/signup/free in index.ts
- Import swaggerSpec into index.ts
- All 12 endpoints now code-driven: demo/html, demo/markdown, convert/html,
convert/markdown, convert/url, templates, templates/{id}/render,
recover, recover/verify, billing/checkout, signup/free, health
This commit is contained in:
parent
792e2d9142
commit
825c6562ba
11 changed files with 624 additions and 1070 deletions
50
dist/routes/billing.js
vendored
50
dist/routes/billing.js
vendored
|
|
@ -1,4 +1,5 @@
|
|||
import { Router } from "express";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import Stripe from "stripe";
|
||||
import { createProKey, downgradeByCustomer, updateEmailByCustomer } from "../services/keys.js";
|
||||
import logger from "../services/logger.js";
|
||||
|
|
@ -39,8 +40,51 @@ async function isDocFastSubscription(subscriptionId) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
// Create a Stripe Checkout session for Pro subscription
|
||||
router.post("/checkout", async (_req, res) => {
|
||||
// Rate limit checkout: max 3 requests per IP per hour
|
||||
const checkoutLimiter = rateLimit({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 3,
|
||||
keyGenerator: (req) => req.ip || req.socket.remoteAddress || "unknown",
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { error: "Too many checkout requests. Please try again later." },
|
||||
});
|
||||
/**
|
||||
* @openapi
|
||||
* /v1/billing/checkout:
|
||||
* post:
|
||||
* tags: [Billing]
|
||||
* summary: Create a Stripe checkout session
|
||||
* description: |
|
||||
* Creates a Stripe Checkout session for a Pro subscription (€9/month).
|
||||
* Returns a URL to redirect the user to Stripe's hosted payment page.
|
||||
* Rate limited to 3 requests per hour per IP.
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Checkout session created
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* url:
|
||||
* type: string
|
||||
* format: uri
|
||||
* description: Stripe Checkout URL to redirect the user to
|
||||
* 413:
|
||||
* description: Request body too large
|
||||
* 429:
|
||||
* description: Too many checkout requests
|
||||
* 500:
|
||||
* description: Failed to create checkout session
|
||||
*/
|
||||
router.post("/checkout", checkoutLimiter, async (req, res) => {
|
||||
// Reject suspiciously large request bodies (>1KB)
|
||||
const contentLength = parseInt(req.headers["content-length"] || "0", 10);
|
||||
if (contentLength > 1024) {
|
||||
res.status(413).json({ error: "Request body too large" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const priceId = await getOrCreateProPrice();
|
||||
const session = await getStripe().checkout.sessions.create({
|
||||
|
|
@ -50,6 +94,8 @@ router.post("/checkout", async (_req, res) => {
|
|||
success_url: `${process.env.BASE_URL || "https://docfast.dev"}/v1/billing/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${process.env.BASE_URL || "https://docfast.dev"}/#pricing`,
|
||||
});
|
||||
const clientIp = req.ip || req.socket.remoteAddress || "unknown";
|
||||
logger.info({ clientIp, sessionId: session.id }, "Checkout session created");
|
||||
res.json({ url: session.url });
|
||||
}
|
||||
catch (err) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue