pediatric-ai-scribe-v3/docs/migrations.md
Daniel 857ed341f5 docs: rewrite architecture, authentication, configuration, deployment, ai-providers, speech, database, learning-hub, migrations, developer-guide for public audience
- 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)
2026-04-15 00:26:38 +02:00

93 lines
2.9 KiB
Markdown

# Database migrations
The project uses [node-pg-migrate](https://github.com/salsita/node-pg-migrate)
for versioned, reversible schema changes layered on top of the idempotent
baseline init in `src/db/database.js`.
## Boot sequence
1. `initDatabase()` in `src/db/database.js` — the baseline.
- `CREATE TABLE IF NOT EXISTS` for every legacy table.
- `ALTER TABLE ADD COLUMN IF NOT EXISTS` for every column added before the
migration tool existed.
- Collation-drift check + auto `REINDEX DATABASE` on mismatch.
- `COLLATE "C"` conversion for lookup-critical indexes (gated by
`migration.text_indexes_c` flag in `app_settings`).
2. `src/db/migrate.js` — runs every file in `/app/migrations/` that isn't
already recorded in the `pgmigrations` table, in filename order. Each
applied file is inserted into `pgmigrations` so it runs exactly once.
All new schema changes go in versioned migration files, not in the inline
baseline.
## Creating a migration
```bash
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:
```js
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:
```bash
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
```js
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.