/** * Migration script: per-user settings.json and state.json → PostgreSQL * * Reads from $DATA_PATH/{email}/settings/settings.json and state/state.json * Writes to user_settings and user_state tables * * Usage: bun run scripts/migrate-user-settings-to-pg.ts */ import { join } from 'node:path'; import { readdirSync } from 'node:fs'; import { getUserByEmail, setUserSettings, patchUserState } from 'officerdb'; const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); async function readJson(path: string): Promise { try { const file = Bun.file(path); if (!(await file.exists())) return null; return (await file.json()) as T; } catch { return null; } } async function migrate() { console.log(`[migrate] Scanning ${DATA_PATH} for user data dirs`); // User data dirs are named by email (contain @) const entries = readdirSync(DATA_PATH, { withFileTypes: true }); const userDirs = entries.filter((e) => e.isDirectory() && e.name.includes('@')); console.log(`[migrate] Found ${userDirs.length} user dirs: ${userDirs.map((d) => d.name).join(', ')}`); for (const dir of userDirs) { const email = dir.name; const dbUser = await getUserByEmail(email); if (!dbUser) { console.warn(` [skip] ${email} — no matching user in DB`); continue; } // Settings const settingsPath = join(DATA_PATH, email, 'settings', 'settings.json'); const settings = await readJson>(settingsPath); if (settings && Object.keys(settings).length > 0) { await setUserSettings(dbUser.id, settings); console.log(` [settings] ${email} — ${Object.keys(settings).length} keys`); } // State const statePath = join(DATA_PATH, email, 'state', 'state.json'); const state = await readJson>(statePath); if (state && Object.keys(state).length > 0) { await patchUserState(dbUser.id, state); console.log(` [state] ${email} — ${Object.keys(state).length} keys`); } } console.log('[migrate] Done!'); process.exit(0); } migrate().catch((err) => { console.error('[migrate] Failed:', err); process.exit(1); });