import Database from "better-sqlite3"; import path from "path"; import { fileURLToPath } from "url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const DB_PATH = path.join(__dirname, "../../data/docfast.db"); class DatabaseService { db; constructor() { this.db = new Database(DB_PATH); this.initialize(); } initialize() { // Enable WAL mode for better performance this.db.pragma("journal_mode = WAL"); // Create tables this.db.exec(` CREATE TABLE IF NOT EXISTS keys ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL, api_key TEXT UNIQUE NOT NULL, tier TEXT NOT NULL CHECK (tier IN ('free', 'pro')), created_at TEXT NOT NULL, usage_count INTEGER DEFAULT 0, usage_month TEXT NOT NULL, stripe_customer_id TEXT ); CREATE INDEX IF NOT EXISTS idx_keys_api_key ON keys(api_key); CREATE INDEX IF NOT EXISTS idx_keys_email ON keys(email); CREATE INDEX IF NOT EXISTS idx_keys_stripe_customer_id ON keys(stripe_customer_id); CREATE TABLE IF NOT EXISTS usage ( id INTEGER PRIMARY KEY AUTOINCREMENT, api_key TEXT NOT NULL, endpoint TEXT NOT NULL, timestamp TEXT NOT NULL, FOREIGN KEY (api_key) REFERENCES keys(api_key) ); CREATE INDEX IF NOT EXISTS idx_usage_api_key ON usage(api_key); CREATE INDEX IF NOT EXISTS idx_usage_timestamp ON usage(timestamp); `); } // Key operations insertKey(key) { const stmt = this.db.prepare(` INSERT INTO keys (email, api_key, tier, created_at, usage_count, usage_month, stripe_customer_id) VALUES (?, ?, ?, ?, ?, ?, ?) `); const result = stmt.run(key.email, key.api_key, key.tier, key.created_at, key.usage_count, key.usage_month, key.stripe_customer_id || null); return { ...key, id: result.lastInsertRowid }; } getKeyByApiKey(apiKey) { const stmt = this.db.prepare("SELECT * FROM keys WHERE api_key = ?"); return stmt.get(apiKey); } getKeyByEmail(email, tier) { const stmt = this.db.prepare("SELECT * FROM keys WHERE email = ? AND tier = ?"); return stmt.get(email, tier); } getKeyByStripeCustomerId(stripeCustomerId) { const stmt = this.db.prepare("SELECT * FROM keys WHERE stripe_customer_id = ?"); return stmt.get(stripeCustomerId); } updateKeyTier(apiKey, tier) { const stmt = this.db.prepare("UPDATE keys SET tier = ? WHERE api_key = ?"); const result = stmt.run(tier, apiKey); return result.changes > 0; } deleteKeyByStripeCustomerId(stripeCustomerId) { const stmt = this.db.prepare("DELETE FROM keys WHERE stripe_customer_id = ?"); const result = stmt.run(stripeCustomerId); return result.changes > 0; } getAllKeys() { const stmt = this.db.prepare("SELECT * FROM keys"); return stmt.all(); } // Usage operations insertUsage(usage) { const stmt = this.db.prepare(` INSERT INTO usage (api_key, endpoint, timestamp) VALUES (?, ?, ?) `); const result = stmt.run(usage.api_key, usage.endpoint, usage.timestamp); return { ...usage, id: result.lastInsertRowid }; } getUsageForKey(apiKey, fromDate, toDate) { let query = "SELECT * FROM usage WHERE api_key = ?"; const params = [apiKey]; if (fromDate && toDate) { query += " AND timestamp >= ? AND timestamp <= ?"; params.push(fromDate, toDate); } else if (fromDate) { query += " AND timestamp >= ?"; params.push(fromDate); } query += " ORDER BY timestamp DESC"; const stmt = this.db.prepare(query); return stmt.all(...params); } // Utility method to migrate existing JSON data migrateFromJson(jsonKeys) { const insertStmt = this.db.prepare(` INSERT OR IGNORE INTO keys (email, api_key, tier, created_at, usage_count, usage_month, stripe_customer_id) VALUES (?, ?, ?, ?, ?, ?, ?) `); const transaction = this.db.transaction((keys) => { for (const key of keys) { const currentMonth = new Date().toISOString().slice(0, 7); // YYYY-MM insertStmt.run(key.email || "", key.key, key.tier, key.createdAt, 0, // reset usage count currentMonth, key.stripeCustomerId || null); } }); transaction(jsonKeys); } close() { this.db.close(); } } // Export singleton instance export const db = new DatabaseService();