Adds `npm run maint:check` (health report) and `npm run maint:reindex` (REINDEX DATABASE + REFRESH COLLATION VERSION + ANALYZE) for post- upgrade maintenance, modelled after Nextcloud's occ maintenance. Documented in README. Also relaxes postgres image from digest pin back to tag-pin (pgvector/pgvector:pg16) — the auto-REINDEX-on-drift check in database.js and the COLLATE "C" protection on critical indexes make the digest pin redundant while blocking ordinary `compose pull` updates.
116 lines
4.1 KiB
JavaScript
116 lines
4.1 KiB
JavaScript
#!/usr/bin/env node
|
|
// ============================================================
|
|
// PedScribe maintenance CLI
|
|
// Usage: node scripts/maintenance.js <command>
|
|
//
|
|
// 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 <cmd>');
|
|
log(' docker exec pediatric-ai-scribe npm run maint:<cmd>');
|
|
}
|
|
} catch (e) {
|
|
warn(e.message);
|
|
process.exit(1);
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
})();
|