#!/usr/bin/env node /** * Migration script: JSON files → PostgreSQL * Run on the server where JSON data files exist. * Usage: DATABASE_PASSWORD=docfast node scripts/migrate-to-postgres.mjs */ import pg from "pg"; import { readFileSync, existsSync } from "fs"; const { Pool } = pg; const pool = new Pool({ host: process.env.DATABASE_HOST || "127.0.0.1", port: parseInt(process.env.DATABASE_PORT || "5432", 10), database: process.env.DATABASE_NAME || "docfast", user: process.env.DATABASE_USER || "docfast", password: process.env.DATABASE_PASSWORD || "docfast", }); async function migrate() { const client = await pool.connect(); try { // Create tables await client.query(` CREATE TABLE IF NOT EXISTS api_keys ( key TEXT PRIMARY KEY, tier TEXT NOT NULL DEFAULT 'free', email TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), stripe_customer_id TEXT ); CREATE INDEX IF NOT EXISTS idx_api_keys_email ON api_keys(email); CREATE INDEX IF NOT EXISTS idx_api_keys_stripe ON api_keys(stripe_customer_id); CREATE TABLE IF NOT EXISTS verifications ( id SERIAL PRIMARY KEY, email TEXT NOT NULL, token TEXT NOT NULL UNIQUE, api_key TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), verified_at TIMESTAMPTZ ); CREATE INDEX IF NOT EXISTS idx_verifications_email ON verifications(email); CREATE INDEX IF NOT EXISTS idx_verifications_token ON verifications(token); CREATE TABLE IF NOT EXISTS pending_verifications ( email TEXT PRIMARY KEY, code TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), expires_at TIMESTAMPTZ NOT NULL, attempts INT NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS usage ( key TEXT PRIMARY KEY, count INT NOT NULL DEFAULT 0, month_key TEXT NOT NULL ); `); console.log("✅ Tables created"); // Migrate keys.json const keysPath = "/opt/docfast/data/keys.json"; if (existsSync(keysPath)) { const keysData = JSON.parse(readFileSync(keysPath, "utf-8")); const keys = keysData.keys || []; let keyCount = 0; for (const k of keys) { await client.query( `INSERT INTO api_keys (key, tier, email, created_at, stripe_customer_id) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (key) DO NOTHING`, [k.key, k.tier, k.email || "", k.createdAt, k.stripeCustomerId || null] ); keyCount++; } console.log(`✅ Migrated ${keyCount} API keys`); } else { // Try docker volume path console.log("⚠️ keys.json not found at", keysPath); } // Migrate verifications.json const verifPath = "/opt/docfast/data/verifications.json"; if (existsSync(verifPath)) { const data = JSON.parse(readFileSync(verifPath, "utf-8")); const verifications = Array.isArray(data) ? data : (data.verifications || []); const pending = data.pendingVerifications || []; let vCount = 0; for (const v of verifications) { await client.query( `INSERT INTO verifications (email, token, api_key, created_at, verified_at) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (token) DO NOTHING`, [v.email, v.token, v.apiKey, v.createdAt, v.verifiedAt || null] ); vCount++; } console.log(`✅ Migrated ${vCount} verifications`); let pCount = 0; for (const p of pending) { await client.query( `INSERT INTO pending_verifications (email, code, created_at, expires_at, attempts) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (email) DO NOTHING`, [p.email, p.code, p.createdAt, p.expiresAt, p.attempts] ); pCount++; } console.log(`✅ Migrated ${pCount} pending verifications`); } else { console.log("⚠️ verifications.json not found at", verifPath); } // Migrate usage.json const usagePath = "/opt/docfast/data/usage.json"; if (existsSync(usagePath)) { const usageData = JSON.parse(readFileSync(usagePath, "utf-8")); let uCount = 0; for (const [key, record] of Object.entries(usageData)) { const r = record as any; await client.query( `INSERT INTO usage (key, count, month_key) VALUES ($1, $2, $3) ON CONFLICT (key) DO UPDATE SET count = $2, month_key = $3`, [key, r.count, r.monthKey] ); uCount++; } console.log(`✅ Migrated ${uCount} usage records`); } else { console.log("⚠️ usage.json not found at", usagePath); } console.log("\n🎉 Migration complete!"); } finally { client.release(); await pool.end(); } } migrate().catch((err) => { console.error("Migration failed:", err); process.exit(1); });