pediatric-ai-scribe-v3/docs/migrations.md
Daniel 17fa3d56f4 Add node-pg-migrate for versioned schema changes + better mobile UA labels
Infrastructure only — no existing data or tables modified.

  src/db/migrate.js           — programmatic runner, fires at boot after
                                 the existing idempotent initDatabase()
  migrations/1744600000000...  — intentionally empty example, documents
                                 the file shape. Registered in the new
                                 pgmigrations tracking table so it won't
                                 rerun.
  .node-pg-migraterc.json     — CLI config (migrations-dir, utc naming)
  docs/migrations.md          — workflow + conventions
  package.json                — migrate:up/down/new/status npm scripts
                                 (status is a direct pgmigrations query
                                 since node-pg-migrate v7 lacks a status
                                 subcommand)

src/utils/sessions.js:
  - parseUserAgent now recognizes the Capacitor wrapper (UA suffix
    "PedScribe-Android" / "PedScribe-iOS") and labels sessions
    "PedScribe (Android)" instead of "Chrome on Android".

Going forward: schema changes go in /migrations as versioned files
with up() + down(); the inline init in database.js is the implicit
baseline for everything already in production.
2026-04-14 05:06:19 +02:00

115 lines
3.2 KiB
Markdown

# Database Migrations
The app uses [node-pg-migrate](https://github.com/salsita/node-pg-migrate)
for versioned, reversible schema changes.
## How it works
Boot sequence:
1. **Baseline init**`src/db/database.js` runs `CREATE TABLE IF NOT EXISTS`
and `ALTER TABLE ADD COLUMN IF NOT EXISTS` for the existing schema. This
is the implicit baseline — everything that was in place before
migrations were introduced. Idempotent on every boot.
2. **Migrations**`src/db/migrate.js` runs every file in `/app/migrations/`
that hasn't already been recorded in the `pgmigrations` table, in
filename order. Each applied migration is inserted into `pgmigrations`
so it only runs once.
New schema changes should go in versioned migration files, not in the
inline `database.js` init.
## Creating a migration
```bash
docker exec -w /app pediatric-ai-scribe npm run migrate:new -- add_avatar_url
```
Creates a file like `migrations/1744601234567_add_avatar_url.js` with
empty `up()` and `down()` functions. Edit it:
```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/
## Running migrations
Migrations apply automatically on app boot. To run them manually (e.g.
before a restart):
```bash
docker exec -w /app pediatric-ai-scribe npm run migrate:up
```
## Rolling back
Roll back the most recent migration:
```bash
docker exec -w /app pediatric-ai-scribe npm run migrate:down
```
This calls the file's `down()`. If `down()` is empty or missing, the
rollback is a no-op but the migration is removed from `pgmigrations`
— meaning the next `up` will reapply it.
## Viewing state
Which migrations have been applied:
```bash
docker exec -w /app pediatric-ai-scribe npm run migrate:status
```
Or directly:
```bash
docker exec pedscribe-db psql -U pedscribe -d pedscribe \
-c "SELECT id, name, run_on FROM pgmigrations ORDER BY id;"
```
## Raw SQL migrations
If pgm's JS helpers are limiting, drop to SQL:
```js
exports.up = (pgm) => {
pgm.sql(`
CREATE INDEX CONCURRENTLY idx_audit_log_action
ON audit_log (action)
WHERE action IN ('login', 'login_failed', 'session_idle_timeout');
`);
};
```
Note: `CREATE INDEX CONCURRENTLY` cannot run inside a transaction. For
that you need `exports.disableTransaction = true;` in the migration file.
## Conventions
- One logical change per file. Don't bundle unrelated alters.
- Always write `down()` unless rollback is fundamentally impossible
(e.g., dropping a column that had unique data).
- Name files by what the change does (`add_foo`, `backfill_bar`), not
the ticket number.
- Migrations run in filename order — the timestamp prefix ensures order
across checkouts from different devs.
- Never edit an already-applied migration. Write a new one to fix it.
## Relationship to the inline init
`src/db/database.js` still runs on every boot and handles the pre-migration
baseline. Do not add new schema changes there — use migrations. The inline
init will gradually shrink as old CREATE TABLE statements age out.