151 lines
10 KiB
Markdown
151 lines
10 KiB
Markdown
# 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 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:
|
||
|
||
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:
|
||
|
||
- **2–3 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: **2–4 `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).
|
||
- **1–2 customer accounts** each with a `customer_profiles` row, **1–2 `patients`** (with relation/conditions
|
||
once [Phase 3](refinement-phase-3-contract-batch.md) REQ-005 lands — until then, the fields the DTO supports),
|
||
and **1–2 `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 (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-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.
|