/** * One-time migration: PostgreSQL auth tables → JSON files * * Usage: * POSTGRES_URL="postgres://..." bun run scripts/migrate-pg-to-files.ts * * Reads users and passkeys from Postgres, writes JSON files to {DATA_PATH}/auth/. * Safe to run multiple times (overwrites files). */ import { join } from 'node:path'; import { mkdir } from 'node:fs/promises'; import postgres from 'postgres'; const POSTGRES_URL = process.env.POSTGRES_URL; if (!POSTGRES_URL) { console.error('POSTGRES_URL env var is required'); process.exit(1); } const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); const AUTH_DIR = join(DATA_PATH, 'auth'); const sql = postgres(POSTGRES_URL); try { await mkdir(AUTH_DIR, { recursive: true }); const users = await sql`SELECT id, email, password, role, status, name, username, avatar, password_changed_at FROM users ORDER BY id`; const passkeys = await sql`SELECT id, email, origin, credential_id, public_key, counter FROM passkeys ORDER BY id`; const mappedUsers = users.map((u) => ({ id: Number(u.id), email: u.email, password: u.password ?? null, role: u.role ?? 'Member', status: u.status ?? 'Unverified', name: u.name ?? null, username: u.username ?? null, avatar: u.avatar ?? null, passwordChangedAt: u.password_changed_at ? Number(u.password_changed_at) : null, })); const mappedPasskeys = passkeys.map((p) => ({ id: Number(p.id), email: p.email, origin: p.origin ?? null, credentialId: p.credential_id ?? null, publicKey: p.public_key ?? null, counter: Number(p.counter ?? 0), })); const maxUserId = mappedUsers.reduce((max, u) => Math.max(max, u.id), 0); const maxPasskeyId = mappedPasskeys.reduce((max, p) => Math.max(max, p.id), 0); const meta = { nextUserId: maxUserId + 1, nextPasskeyId: maxPasskeyId + 1, }; const write = (file: string, data: unknown) => Bun.write(join(AUTH_DIR, file), JSON.stringify(data, null, 2)); await Promise.all([ write('users.json', mappedUsers), write('passkeys.json', mappedPasskeys), write('passkey-challenges.json', []), write('token-blacklist.json', []), write('meta.json', meta), ]); console.log(`Migrated ${mappedUsers.length} users, ${mappedPasskeys.length} passkeys`); console.log(`Files written to ${AUTH_DIR}`); console.log(`meta: nextUserId=${meta.nextUserId}, nextPasskeyId=${meta.nextPasskeyId}`); } catch (err) { console.error('Migration failed:', err); process.exit(1); } finally { await sql.end(); }