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 { loadKeys, getAllKeys } from "./services/keys.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";
|
||||
|
||||
const app = express();
|
||||
|
|
@ -153,6 +153,17 @@ app.get("/v1/concurrency", authMiddleware, adminAuth, (_req: any, res: any) => {
|
|||
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
|
||||
app.get("/verify", (req, res) => {
|
||||
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 default pool;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue