feat: add database cleanup function and admin endpoint
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Failing after 13m30s
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Failing after 13m30s
- Add cleanupStaleData() in db.ts: purges expired verifications, unverified free-tier keys, and orphaned usage rows - Add POST /admin/cleanup endpoint (admin auth required) - Run cleanup 30s after startup (non-blocking) - Fix missing import from broken previous commit
This commit is contained in:
parent
978c3dc2d4
commit
2fcfa1722c
2 changed files with 54 additions and 1 deletions
13
src/index.ts
13
src/index.ts
|
|
@ -23,7 +23,7 @@ import { pdfRateLimitMiddleware, getConcurrencyStats } from "./middleware/pdfRat
|
||||||
import { initBrowser, closeBrowser } from "./services/browser.js";
|
import { initBrowser, closeBrowser } from "./services/browser.js";
|
||||||
import { loadKeys, getAllKeys } from "./services/keys.js";
|
import { loadKeys, getAllKeys } from "./services/keys.js";
|
||||||
import { verifyToken, loadVerifications } from "./services/verification.js";
|
import { verifyToken, loadVerifications } from "./services/verification.js";
|
||||||
import { initDatabase, pool } from "./services/db.js";
|
import { initDatabase, pool, cleanupStaleData } from "./services/db.js";
|
||||||
import { swaggerSpec } from "./swagger.js";
|
import { swaggerSpec } from "./swagger.js";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
@ -153,6 +153,17 @@ app.get("/v1/concurrency", authMiddleware, adminAuth, (_req: any, res: any) => {
|
||||||
res.json(getConcurrencyStats());
|
res.json(getConcurrencyStats());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Admin: database cleanup (admin key required)
|
||||||
|
app.post("/admin/cleanup", authMiddleware, adminAuth, async (_req: any, res: any) => {
|
||||||
|
try {
|
||||||
|
const results = await cleanupStaleData();
|
||||||
|
res.json({ status: "ok", cleaned: results });
|
||||||
|
} catch (err: any) {
|
||||||
|
logger.error({ err }, "Admin cleanup failed");
|
||||||
|
res.status(500).json({ error: "Cleanup failed", message: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Email verification endpoint
|
// Email verification endpoint
|
||||||
app.get("/verify", (req, res) => {
|
app.get("/verify", (req, res) => {
|
||||||
const token = req.query.token as string;
|
const token = req.query.token as string;
|
||||||
|
|
|
||||||
|
|
@ -196,5 +196,47 @@ export async function initDatabase(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(): Promise<{ expiredVerifications: number; staleKeys: number; orphanedUsage: number }> {
|
||||||
|
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 { pool };
|
||||||
export default pool;
|
export default pool;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue