10 KiB
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…InitialBaseline → 20260709…MessagingAndPartnerCenters) tracking every backend phase b0–b15; 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:
- 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.) - 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/qw123321user and one sandbox ZarinPal gateway. There are no nurses, customers, profiles, variants, service areas, ornurse_search_indexrows, soGET /api/v1/search/nursesreturns 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.cs—ApplyMigrationsAsync,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_searchablerequiresis_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 anurse_search_indexrow withis_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*.jsoncarry only a placeholder. - Verify
dotnet ef database update --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Apiworks against the local DB, and that booting also self-migrates. Document both paths (boot-migrate for dev convenience; explicitef database updatefor 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:
- 2–3 nurse accounts (phone-based users with the
nurserole) each with anurse_profilesrow — at least one fully verified (is_verified=1, verificationapproved, a verified primary bank account withmatched_national_id=1) and one unverified (so the trust badge / publish-gate states demo). - Each verified nurse: 2–4
nurse_service_variantsacross price units (with valid option answers for the seeded categories) +nurse_service_areasin Tehran (some whole-citydistrict_id=NULL, some specific districts) → and thereforenurse_search_indexrows withis_searchable=1(drive this through the realISearchIndexMaintainer.RebuildAsync/ the reindex hooks, not by hand-inserting index rows, so the projection stays truthful). - 1–2 customer accounts each with a
customer_profilesrow, 1–2patients(with relation/conditions once Phase 3 REQ-005 lands — until then, the fields the DTO supports), and 1–2customer_addresseswith 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 (5001–5005) 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-changesor equivalent) — the tree is clean today; keep it that way. - Note (do not fix here — it's Phase 7) that boot-time
MigrateAsyncraces 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 + anurse_search_indexrow. 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
HasDataseeds 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 testgreen; 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 updatepath documented.
7. How to test (what a human can verify after this phase)
- Drop/recreate the local DB,
dotnet run→ observe migrate + reference seed + demo seed in the logs. - 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. GET /api/v1/nurses/{id}/trust_badgefor the verified nurse → verified badge; for the unverified → not verified.- 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.mdthat 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.