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.
115 lines
3.2 KiB
Markdown
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.
|