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

151 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](refinement-phase-0-bring-up.md) · **Unlocks:** real
> search/discovery/booking data for [Phase 4](refinement-phase-4-frontend-de-mock.md)
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../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 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.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_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](refinement-phase-7-unattended-ops.md) 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](refinement-phase-3-contract-batch.md) 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](refinement-phase-7-unattended-ops.md)) 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](refinement-phase-6-money-correctness.md) 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](../../phases/_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.