Files
baya-monorepo/dev/post-phase/refinement/refinement-phase-1-database-and-seed.md
T
2026-07-10 20:59:47 +03:30

10 KiB
Raw Blame History

Refinement Phase 1 — Database: local-dev story, demo seed & migration hygiene

Mission: turn a fresh database into a populated marketplace instead of an empty shell, and give the project a clean local-development database story. The 17 EF migrations are current and apply cleanly, but a fresh DB seeds only lookup tables + one admin + one gateway — no nurses, no variants, no search rows, no bookings — so every discovery/search/booking screen is empty on the real path. This phase adds a Development-gated demo seed and a repeatable local DB setup.

Track: backend (+ DB) · Depends on: Phase 0 · Unlocks: real search/discovery/booking data for Phase 4 Before you start, read ../../phases/_shared/agent-operating-rules.md.

1. Context — where this sits

Myth to correct up front: the migrations are not "behind." There are 17 migrations (20260628…InitialBaseline20260709…MessagingAndPartnerCenters) tracking every backend phase b0b15; the model snapshot is in sync and the working tree is clean. If a developer thinks migrations are behind, it's because the target database was never migrated (they were running the frontend entirely on mocks) — not because migration files are missing. Program.cs calls MigrateAsync() on every non-Testing boot, so simply booting against a fresh DB brings it fully up to date.

The real database problems are two:

  1. No local-dev database story. Both appsettings point at the committed remote SQL Server; there is no localhost default and no SQLite fallback for dotnet run. (Phase 0 introduced the local instance; this phase makes it the clean, documented default and adds demo data.)
  2. No transactional/domain seed data. The only seed is reference/lookup tables (31 provinces + cities + Tehran's 22 districts, the 5-category catalog skeleton, verification step types, review tags, holidays, cancellation policies, platform config, the invoice-number counter) plus one admin/qw123321 user and one sandbox ZarinPal gateway. There are no nurses, customers, profiles, variants, service areas, or nurse_search_index rows, so GET /api/v1/search/nurses returns an empty list even after Phase 3/4 make the frontend call it for real.

What already exists (do not rebuild): all 17 migrations; the HasData reference seeds; the boot-time MigrateAsync + SeedDefaultUsersAsync + SeedPaymentGatewaysAsync. This phase adds an additional, environment-gated demo seeder — it does not touch the reference seeds or the migration set.

2. Required reading (do this first)

  • server/src/API/Baya.Web.Api/Program.cs:99-104 — the boot migrate + seed calls.
  • server/src/Infrastructure/Baya.Infrastructure.Persistence/ServiceConfiguration/ServiceCollectionExtensions.csApplyMigrationsAsync, SeedPaymentGatewaysAsync, and where a new demo seeder would register.
  • server/src/Infrastructure/Baya.Infrastructure.Identity/Identity/SeedDatabaseService/SeedDataBase.cs — the existing role + admin seed pattern to mirror.
  • The catalog seed (Persistence/Configuration/CatalogConfig/) and the frontend-backend-gaps note that a fresh catalog has categories but no option groups — the variant builder's required-option step has nothing to render until an admin authors groups. Decide: seed a representative option-group set, or accept the empty state until the f15 admin catalog UI is used.
  • product/data-model/ for the entities you'll seed (nurse_profiles, nurse_service_variants, nurse_service_areas, nurse_search_index, patients, customer_addresses) so the demo world is valid per the business rules (verified nurse → is_searchable requires is_verified=1 AND accepting AND active variant).
  • The b7 search invariant in server/CLAUDE.md ("Search & matching") — a demo nurse only appears in search if the maintainer wrote a nurse_search_index row with is_searchable=1.

3. Scope — build this

3.1 A clean local database default (finish what Phase 0 started)

  • Ensure server/docker-compose.yml (or a documented LocalDB path) gives a one-command local SQL Server.
  • The Development connection string resolves from user-secrets/env (not a committed file) to that local instance; appsettings*.json carry only a placeholder.
  • Verify dotnet ef database update --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api works against the local DB, and that booting also self-migrates. Document both paths (boot-migrate for dev convenience; explicit ef database update for the deploy/migrations-off-boot direction that Phase 7 will formalise).

3.2 A Development-gated demo seeder (SeedDemoWorldAsync)

A new seeder, invoked from Program.cs only when app.Environment.IsDevelopment() (never in Production/Staging), that idempotently creates a coherent demo marketplace. It must produce data that satisfies every downstream invariant so the real-path screens actually populate:

  • 23 nurse accounts (phone-based users with the nurse role) each with a nurse_profiles row — at least one fully verified (is_verified=1, verification approved, a verified primary bank account with matched_national_id=1) and one unverified (so the trust badge / publish-gate states demo).
  • Each verified nurse: 24 nurse_service_variants across price units (with valid option answers for the seeded categories) + nurse_service_areas in Tehran (some whole-city district_id=NULL, some specific districts) → and therefore nurse_search_index rows with is_searchable=1 (drive this through the real ISearchIndexMaintainer.RebuildAsync / the reindex hooks, not by hand-inserting index rows, so the projection stays truthful).
  • 12 customer accounts each with a customer_profiles row, 12 patients (with relation/conditions once Phase 3 REQ-005 lands — until then, the fields the DTO supports), and 12 customer_addresses with coordinates (so booking + EVV have a real door location).
  • A representative service-option-group set per category (or a decision to defer to the f15 admin UI) so the variant builder's required-option step renders on the real path (the frontend-backend-gaps "data gap").
  • (Optional, if you want the post-payment screens to demo without running the full funnel) a couple of confirmed bookings + sessions for the demo customer/nurse pairs, mirroring the frontend's seeded booking ids (50015005) so deep-links line up. Keep this optional — the cleaner demo is to create bookings by actually running the funnel once the frontend is real.

Idempotency: guard every insert on a stable natural key (phone number, variant option-set hash) so re-running the seeder on an already-seeded DB is a no-op — the same discipline the existing SeedPaymentGatewaysAsync uses.

3.3 Migration hygiene notes (document, mostly)

  • Confirm no pending model changes (dotnet ef migrations has-pending-model-changes or equivalent) — the tree is clean today; keep it that way.
  • Note (do not fix here — it's Phase 7) that boot-time MigrateAsync races under multi-instance start-up and needs the app login to hold DDL rights; the deploy-time migration split belongs there.
  • The three promised-but-missing forward-dep FKs (refunds.ticket_id, nurse_clawbacks.*_payout_id, invoices.partner_center_id) are not in this phase — they're an additive migration in Phase 6 alongside the money-correctness work.

4. Mocks & seams in this phase

None introduced. The demo seeder must go through the real handlers/maintainer where an invariant is at stake (verification flip, search reindex) rather than raw-inserting, so the seeded world is identical to one built by real usage.

5. Critical rules you must not get wrong

  • The demo seeder is Development-only. Gate on IsDevelopment(). Seeding fake nurses/customers into a production DB would be a data-integrity and trust disaster.
  • A nurse only appears in search if is_searchable=1 — which requires verified + accepting + an active variant + a nurse_search_index row. Seed the causes, let the maintainer compute the index; don't fake the index.
  • Money stays IRR BIGINT; variant prices are digit-strings on the wire. Seed valid amounts.
  • Idempotent seeding. Re-running must not duplicate nurses/variants/areas.
  • Don't touch the reference HasData seeds or the migration files — add alongside.

6. Definition of Done

On top of the shared definition-of-done.md:

  • A documented one-command local DB + a Development-gated SeedDemoWorldAsync; dotnet build/dotnet test green; the seeder is idempotent (run twice, same result).
  • Against a fresh local DB, after boot, GET /api/v1/search/nurses (via Swagger/curl) returns the seeded verified nurse(s) with variants — proven without any frontend change.
  • The verified demo nurse has a trust badge, variants, and coverage areas; the unverified one does not surface in search.
  • Migration state confirmed current (no pending model changes) and the local ef database update path documented.

7. How to test (what a human can verify after this phase)

  1. Drop/recreate the local DB, dotnet run → observe migrate + reference seed + demo seed in the logs.
  2. Swagger → POST /api/v1/search/nurses (or the documented search route) with a Tehran city filter → expect the seeded verified nurse(s) with priced variants in the result page.
  3. GET /api/v1/nurses/{id}/trust_badge for the verified nurse → verified badge; for the unverified → not verified.
  4. Re-run the app → the demo seeder logs "already seeded / no-op" and counts don't double.

8. Hand off & document (close the phase)

  • Update the runbook (Phase 0) with the local DB + demo-seed steps.
  • Note in server/CLAUDE.md that a Development demo seeder exists and what it creates.
  • Report: the exact demo world (ids, phones, which nurse is verified), so the frontend de-mock phase can log in as those accounts. Save a memory note that migrations are current and the real gap was seed data + local DB.