- Drop first/second-person voice; reference-style prose throughout - Remove stale information; align with current code (argon2id primary, hybrid cookie/Bearer auth, sliding 24h idle, AES-256-GCM PHI at rest, backup codes, node-pg-migrate, collation-drift guard, multi-arch Docker, auto-version pipeline) - Preserve all technical accuracy and code examples - Remove any remaining references to separate PedsHub Quiz app - Keep consistent tone across files (tables + code blocks, imperatives where needed) - api-reference.md and developer-guide.md route tables expanded to reflect current routes (billing, sessions)
2.9 KiB
2.9 KiB
Database migrations
The project uses node-pg-migrate
for versioned, reversible schema changes layered on top of the idempotent
baseline init in src/db/database.js.
Boot sequence
initDatabase()insrc/db/database.js— the baseline.CREATE TABLE IF NOT EXISTSfor every legacy table.ALTER TABLE ADD COLUMN IF NOT EXISTSfor every column added before the migration tool existed.- Collation-drift check + auto
REINDEX DATABASEon mismatch. COLLATE "C"conversion for lookup-critical indexes (gated bymigration.text_indexes_cflag inapp_settings).
src/db/migrate.js— runs every file in/app/migrations/that isn't already recorded in thepgmigrationstable, in filename order. Each applied file is inserted intopgmigrationsso it runs exactly once.
All new schema changes go in versioned migration files, not in the inline baseline.
Creating a migration
docker exec -w /app pediatric-ai-scribe npm run migrate:new -- add_avatar_url
Produces migrations/<utc-ms>_add_avatar_url.js with empty up() and
down(). Edit:
exports.up = (pgm) => {
pgm.addColumn('users', {
avatar_url: { type: 'text', notNull: false }
});
pgm.createIndex('users', 'avatar_url');
};
exports.down = (pgm) => {
pgm.dropIndex('users', 'avatar_url');
pgm.dropColumn('users', 'avatar_url');
};
Full API: https://salsita.github.io/node-pg-migrate/
Commands
| Command | Effect |
|---|---|
npm run migrate:up |
Apply all pending |
npm run migrate:down |
Roll back the most recent one |
npm run migrate:new -- <name> |
Scaffold a new file |
npm run migrate:status |
Dump pgmigrations as a table |
Or SQL directly:
docker exec pedscribe-db psql -U pedscribe -d pedscribe \
-c "SELECT id, name, run_on FROM pgmigrations ORDER BY id;"
Raw SQL inside a migration
exports.up = (pgm) => {
pgm.sql(`
CREATE INDEX CONCURRENTLY idx_audit_action
ON audit_log (action)
WHERE action IN ('login', 'login_failed', 'session_idle_timeout');
`);
};
CREATE INDEX CONCURRENTLY cannot run in a transaction. Add
exports.disableTransaction = true; to the file when using it.
Conventions
- One logical change per file. No bundling unrelated alters.
- Always write
down()unless rollback is impossible (e.g., dropping a column that had unique data). - Name files by what they do (
add_foo,backfill_bar), not ticket numbers. - Filename UTC-ms prefix drives ordering across forks / PRs.
- Never edit an already-applied migration. Fix forward with a new file.
Rollback semantics
migrate:down runs the file's down() and removes its row from
pgmigrations. An empty / missing down() still clears the row — the next
up reapplies the migration. Treat missing down as "no-op rollback" and
document it in the file header.