# Migrations And Seeders Backend ## Purpose Schema creation and reference-data seeding run through a small Umzug runner (it replaced sequelize-cli during the TypeScript/ESM migration). This doc covers the developer mechanism: the runner, file conventions, and how to add a migration or seeder. For VM/PM2 operational use (when migrate/seed run on deploy, `db:reset` recovery), see `../../docs/deployment-vm.md`. ## Files - Runner: `src/db/umzug.ts` — builds two `Umzug` instances (`migrator`, `seeder`) and a small CLI. - Reset: `src/db/reset.ts` — drops every table in the `public` schema, then re-runs `migrator.up()` + `seeder.up()`. - Schema snapshot: `src/db/initial-schema.ts` — DDL snapshot derived from the Sequelize models; the models remain the source of truth. - Migrations: `src/db/migrations/*.ts` — `20260610000000-initial-schema.ts` (creates the full schema from the snapshot) and `20260610010000-add-role-scope-and-user-campus.ts` (adds the NOT-NULL `roles.scope` enum and the nullable `users.campusId`). Phase 4 adds `20260611000000-policy-documents-and-acknowledgments.ts` (the unified policy store + per-version acknowledgments), `20260611010000-audio-files.ts` (the audio library) + `20260611060000-audio-files-kinds.ts` (the `kind` enum / nullable `url` / `recipe` JSONB), and `20260611040000-add-user-name-prefix.ts` (the `users.name_prefix` honorific enum), and `20260611070000-campuses-timezone.ts` (the required `campuses.timezone` IANA column — added nullable, backfilled, then set NOT NULL), and `20260612000000-frame-entries-week-label.ts` (the optional `frame_entries.week_label`; `week_of` is now the canonical Sunday-start ISO date). - Seeders: `src/db/seeders/[0-9]*.ts` — `admin-user` (the system users, the primary tenant's per-role users, and the secondary tenant's per-role users from `shared/constants/seed-fixtures.ts`), `user-roles` (the first-class roles, the permission catalog incl. product-feature permissions, the role->permission matrix, role assignment by user id), `product-campuses`, `content-catalog` (+ payloads under `seeders/content-catalog-data/`), `rbac-fixtures` (the two companies, school/campus ownership, per-user org/school/campus links, and user employment fields), `class-fixtures` (one class, enrollment, and guardian link per tenant), and `20260611050000-policy-documents-seed.ts` (3 safety protocols + 4 handbook policies). Shared fixture definitions live in `src/shared/constants/seed-fixtures.ts`. ## Mechanism - `migrator` globs `migrations/*.{ts,js}`; history is tracked in the default `SequelizeMeta` table via `SequelizeStorage`. - `seeder` globs `seeders/[0-9]*.{ts,js}`; history is tracked in a separate `SequelizeData` model/table. The runner sets both `modelName` and `tableName` to `SequelizeData`, which prevents Umzug from reusing the default `SequelizeMeta` model when seeders run after migrations in the same process. Only timestamped seeder files are loaded; colocated `*.test.ts` files are intentionally excluded from `db:seed`. - Each file is ESM TypeScript with a default export `{ up, down }`, each taking `(queryInterface, Sequelize)`. The runner accepts either a `default` export or top-level `up`/`down`. - Tracked names strip the `.ts`/`.js` extension, so history is stable whether the runner is executed via `tsx` (dev, `.ts`) or compiled (`prod`, `dist/.../*.js`). - The `glob` accepts both `.ts` and `.js`, so already-applied entries are not re-run after a build. - Seeders are append/idempotent: when a seeded row or relationship already exists, the seeder preserves it and inserts only missing rows/links. This lets `db:seed` recover from incomplete `SequelizeData` history without deleting tenant-edited content or failing on duplicate keys. ## CLI And Scripts `src/db/umzug.ts` exposes: `migrate:up`, `migrate:down`, `migrate:pending`, `seed:up`, `seed:down`. npm scripts wrap them: - Dev (via `tsx`): `db:migrate` (`migrate:up`), `db:migrate:undo` (`migrate:down`), `db:migrate:pending`, `db:seed` (`seed:up`), `db:seed:undo` (`seed:down`), `db:reset` (`tsx src/db/reset.ts`). - Prod (compiled, no `tsx`): `db:migrate:prod` (`node dist/db/umzug.js migrate:up`), `db:seed:prod` (`node dist/db/umzug.js seed:up`). ## Authoring A New Migration / Seeder 1. Add `src/db/migrations/-.ts` (or `seeders/-.ts`) exporting `export default { up, down }` with typed `(queryInterface, Sequelize)` signatures. 2. For seeders, use stable natural keys (`id`, `name`, `importHash`, or a junction pair) and insert only the missing rows. Do not delete/reinsert tenant-editable seed content in `up`. 3. Run `npm run db:migrate` (or `db:seed`) in dev; verify with `db:migrate:pending`. 4. Regenerate `database-schema.md` after any schema change (it is generated from the models). ## Tests - `src/shared/constants/seed-fixtures.test.ts` covers primary/secondary tenant user topology and credential uniqueness. - `src/db/umzug.test.ts` covers the dedicated `SequelizeData` storage contract for seeder history and the timestamp-only seeder glob. - `src/db/seeders/user-roles.test.ts` covers the seeded product-permission contract for parent communication and registrar report/audit grants. ## Related - `database-schema.md` (the generated schema reference), `backend-architecture.md` (DAL layer), `../../docs/deployment-vm.md` (operational migrate/seed on the VM).