#!/usr/bin/env node // ============================================================ // PedScribe maintenance CLI // Usage: node scripts/maintenance.js // // Commands: // reindex Rebuild all indexes + refresh collation version // (run this after any Postgres image upgrade) // check Report collation/index health, no writes // help Show this message // ============================================================ require('dotenv').config(); var { Pool } = require('pg'); var pool = new Pool({ connectionString: process.env.DATABASE_URL || 'postgres://' + (process.env.DB_USER || 'pedscribe') + ':' + (process.env.DB_PASSWORD || process.env.POSTGRES_PASSWORD || '') + '@' + (process.env.DB_HOST || 'postgres') + ':' + (process.env.DB_PORT || 5432) + '/' + (process.env.DB_NAME || 'pedscribe') }); function log(msg) { process.stdout.write(msg + '\n'); } function warn(msg) { process.stderr.write('⚠️ ' + msg + '\n'); } function ok(msg) { process.stdout.write('✅ ' + msg + '\n'); } function heading(m) { process.stdout.write('\n── ' + m + ' ──\n'); } async function check() { var client = await pool.connect(); try { heading('Postgres version'); var v = await client.query('SELECT version()'); log(v.rows[0].version); heading('Collation version'); var d = await client.query( "SELECT datcollversion AS recorded, " + "pg_database_collation_actual_version(oid) AS actual " + "FROM pg_database WHERE datname = current_database()" ); var row = d.rows[0] || {}; log('Recorded: ' + row.recorded); log('Library: ' + row.actual); if (row.recorded && row.actual && row.recorded !== row.actual) { warn('Drift detected. Run `node scripts/maintenance.js reindex`'); } else { ok('Aligned — no drift'); } heading('Row counts'); var tables = ['users', 'user_sessions', 'audit_log', 'saved_encounters', 'audio_backups']; for (var i = 0; i < tables.length; i++) { try { var c = await client.query('SELECT COUNT(*)::int AS n FROM ' + tables[i]); log(tables[i].padEnd(20) + c.rows[0].n); } catch (e) { log(tables[i].padEnd(20) + '(missing)'); } } heading('Text indexes'); var idx = await client.query( "SELECT indexname, indexdef FROM pg_indexes " + "WHERE schemaname='public' AND (indexdef ILIKE '%email%' OR indexdef ILIKE '%token%' OR indexdef ILIKE '%slug%' OR indexdef ILIKE '%COLLATE%')" ); idx.rows.forEach(function(r) { var hasC = /COLLATE "C"/.test(r.indexdef); log((hasC ? '[C] ' : '[ ] ') + r.indexname); }); } finally { client.release(); } } async function reindex() { var client = await pool.connect(); try { heading('REINDEX DATABASE'); var start = Date.now(); log('Running REINDEX DATABASE pedscribe (this can take a few seconds on small DBs, longer on large)...'); await client.query('REINDEX DATABASE ' + (process.env.DB_NAME || 'pedscribe')); ok('REINDEX complete (' + ((Date.now() - start) / 1000).toFixed(1) + 's)'); heading('REFRESH COLLATION VERSION'); await client.query('ALTER DATABASE ' + (process.env.DB_NAME || 'pedscribe') + ' REFRESH COLLATION VERSION'); ok('Collation version refreshed'); heading('ANALYZE'); await client.query('ANALYZE'); ok('Statistics updated'); log('\nAll done. You can now continue using the app normally.'); } finally { client.release(); } } (async function main() { var cmd = process.argv[2] || 'help'; try { if (cmd === 'reindex') await reindex(); else if (cmd === 'check') await check(); else { log('PedScribe maintenance CLI'); log(''); log('Commands:'); log(' check Report health (no writes)'); log(' reindex REINDEX + REFRESH COLLATION + ANALYZE'); log(''); log('Run inside the container:'); log(' docker exec pediatric-ai-scribe node scripts/maintenance.js '); log(' docker exec pediatric-ai-scribe npm run maint:'); } } catch (e) { warn(e.message); process.exit(1); } finally { await pool.end(); } })();