import { randomBytes } from "crypto"; import logger from "./logger.js"; import pool from "./db.js"; // In-memory cache for fast lookups, synced with PostgreSQL let keysCache = []; export async function loadKeys() { try { const result = await pool.query("SELECT key, tier, email, created_at, stripe_customer_id FROM api_keys"); keysCache = result.rows.map((r) => ({ key: r.key, tier: r.tier, email: r.email, createdAt: r.created_at instanceof Date ? r.created_at.toISOString() : r.created_at, stripeCustomerId: r.stripe_customer_id || undefined, })); } catch (err) { logger.error({ err }, "Failed to load keys from PostgreSQL"); keysCache = []; } // Also load seed keys from env const envKeys = process.env.API_KEYS?.split(",").map((k) => k.trim()).filter(Boolean) || []; for (const k of envKeys) { if (!keysCache.find((e) => e.key === k)) { const entry = { key: k, tier: "pro", email: "seed@docfast.dev", createdAt: new Date().toISOString() }; keysCache.push(entry); // Upsert into DB await pool.query(`INSERT INTO api_keys (key, tier, email, created_at) VALUES ($1, $2, $3, $4) ON CONFLICT (key) DO NOTHING`, [k, "pro", "seed@docfast.dev", new Date().toISOString()]).catch(() => { }); } } } export function isValidKey(key) { return keysCache.some((k) => k.key === key); } export function getKeyInfo(key) { return keysCache.find((k) => k.key === key); } export function isProKey(key) { const info = getKeyInfo(key); return info?.tier === "pro"; } function generateKey(prefix) { return `${prefix}_${randomBytes(24).toString("hex")}`; } export async function createFreeKey(email) { if (email) { const existing = keysCache.find((k) => k.email === email && k.tier === "free"); if (existing) return existing; } const entry = { key: generateKey("df_free"), tier: "free", email: email || "", createdAt: new Date().toISOString(), }; await pool.query("INSERT INTO api_keys (key, tier, email, created_at) VALUES ($1, $2, $3, $4)", [entry.key, entry.tier, entry.email, entry.createdAt]); keysCache.push(entry); return entry; } export async function createProKey(email, stripeCustomerId) { const existing = keysCache.find((k) => k.stripeCustomerId === stripeCustomerId); if (existing) { existing.tier = "pro"; await pool.query("UPDATE api_keys SET tier = 'pro' WHERE key = $1", [existing.key]); return existing; } const entry = { key: generateKey("df_pro"), tier: "pro", email, createdAt: new Date().toISOString(), stripeCustomerId, }; await pool.query("INSERT INTO api_keys (key, tier, email, created_at, stripe_customer_id) VALUES ($1, $2, $3, $4, $5)", [entry.key, entry.tier, entry.email, entry.createdAt, entry.stripeCustomerId]); keysCache.push(entry); return entry; } export async function revokeByCustomer(stripeCustomerId) { const idx = keysCache.findIndex((k) => k.stripeCustomerId === stripeCustomerId); if (idx >= 0) { const key = keysCache[idx].key; keysCache.splice(idx, 1); await pool.query("DELETE FROM api_keys WHERE key = $1", [key]); return true; } return false; } export function getAllKeys() { return [...keysCache]; } export async function updateKeyEmail(apiKey, newEmail) { const entry = keysCache.find((k) => k.key === apiKey); if (!entry) return false; entry.email = newEmail; await pool.query("UPDATE api_keys SET email = $1 WHERE key = $2", [newEmail, apiKey]); return true; }