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
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:
parent
1d5d9adf08
commit
6b1b3d584e
15 changed files with 399 additions and 290 deletions
29
dist/services/browser.js
vendored
29
dist/services/browser.js
vendored
|
|
@ -27,11 +27,14 @@ export function getPoolStats() {
|
|||
})),
|
||||
};
|
||||
}
|
||||
async function recyclePage(page) {
|
||||
export async function recyclePage(page) {
|
||||
try {
|
||||
const client = await page.createCDPSession();
|
||||
await client.send("Network.clearBrowserCache").catch(() => { });
|
||||
await client.detach().catch(() => { });
|
||||
// Clean up request interception (set by renderUrlPdf for SSRF protection)
|
||||
page.removeAllListeners("request");
|
||||
await page.setRequestInterception(false).catch(() => { });
|
||||
const cookies = await page.cookies();
|
||||
if (cookies.length > 0) {
|
||||
await page.deleteCookie(...cookies);
|
||||
|
|
@ -197,6 +200,8 @@ export async function renderPdf(html, options = {}) {
|
|||
const { page, instance } = await acquirePage();
|
||||
try {
|
||||
await page.setJavaScriptEnabled(false);
|
||||
const startTime = Date.now();
|
||||
let timeoutId;
|
||||
const result = await Promise.race([
|
||||
(async () => {
|
||||
await page.setContent(html, { waitUntil: "domcontentloaded", timeout: 15_000 });
|
||||
|
|
@ -217,9 +222,13 @@ export async function renderPdf(html, options = {}) {
|
|||
});
|
||||
return Buffer.from(pdf);
|
||||
})(),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("PDF_TIMEOUT")), 30_000)),
|
||||
]);
|
||||
return result;
|
||||
new Promise((_, reject) => {
|
||||
timeoutId = setTimeout(() => reject(new Error("PDF_TIMEOUT")), 30_000);
|
||||
}),
|
||||
]).finally(() => clearTimeout(timeoutId));
|
||||
const durationMs = Date.now() - startTime;
|
||||
logger.info(`PDF rendered in ${durationMs}ms (html, ${result.length} bytes)`);
|
||||
return { pdf: result, durationMs };
|
||||
}
|
||||
finally {
|
||||
releasePage(page, instance);
|
||||
|
|
@ -264,6 +273,8 @@ export async function renderUrlPdf(url, options = {}) {
|
|||
});
|
||||
}
|
||||
}
|
||||
const startTime = Date.now();
|
||||
let timeoutId;
|
||||
const result = await Promise.race([
|
||||
(async () => {
|
||||
await page.goto(url, {
|
||||
|
|
@ -286,9 +297,13 @@ export async function renderUrlPdf(url, options = {}) {
|
|||
});
|
||||
return Buffer.from(pdf);
|
||||
})(),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("PDF_TIMEOUT")), 30_000)),
|
||||
]);
|
||||
return result;
|
||||
new Promise((_, reject) => {
|
||||
timeoutId = setTimeout(() => reject(new Error("PDF_TIMEOUT")), 30_000);
|
||||
}),
|
||||
]).finally(() => clearTimeout(timeoutId));
|
||||
const durationMs = Date.now() - startTime;
|
||||
logger.info(`PDF rendered in ${durationMs}ms (url, ${result.length} bytes)`);
|
||||
return { pdf: result, durationMs };
|
||||
}
|
||||
finally {
|
||||
releasePage(page, instance);
|
||||
|
|
|
|||
21
dist/services/keys.js
vendored
21
dist/services/keys.js
vendored
|
|
@ -100,7 +100,26 @@ export async function downgradeByCustomer(stripeCustomerId) {
|
|||
await queryWithRetry("UPDATE api_keys SET tier = 'free' WHERE stripe_customer_id = $1", [stripeCustomerId]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// DB fallback: key may exist on another pod's cache or after a restart
|
||||
logger.info({ stripeCustomerId }, "downgradeByCustomer: cache miss, falling back to DB");
|
||||
const result = await queryWithRetry("SELECT key, tier, email, created_at, stripe_customer_id FROM api_keys WHERE stripe_customer_id = $1 LIMIT 1", [stripeCustomerId]);
|
||||
if (result.rows.length === 0) {
|
||||
logger.warn({ stripeCustomerId }, "downgradeByCustomer: customer not found in cache or DB");
|
||||
return false;
|
||||
}
|
||||
const row = result.rows[0];
|
||||
await queryWithRetry("UPDATE api_keys SET tier = 'free' WHERE stripe_customer_id = $1", [stripeCustomerId]);
|
||||
// Add to local cache so subsequent lookups on this pod work
|
||||
const cached = {
|
||||
key: row.key,
|
||||
tier: "free",
|
||||
email: row.email,
|
||||
createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,
|
||||
stripeCustomerId: row.stripe_customer_id || undefined,
|
||||
};
|
||||
keysCache.push(cached);
|
||||
logger.info({ stripeCustomerId, key: row.key }, "downgradeByCustomer: downgraded via DB fallback");
|
||||
return true;
|
||||
}
|
||||
export async function findKeyByCustomerId(stripeCustomerId) {
|
||||
// Check DB directly — survives pod restarts unlike in-memory cache
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue