Files
baya-monorepo/dev/post-phase/refinement/RUNBOOK.md
T
2026-07-13 01:14:35 +03:30

189 lines
9.9 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.
# Run the whole app locally — Balinyaar bring-up runbook
The one copy-pasteable procedure for standing up **both** projects on one machine and watching a real,
authenticated request cross the wire. Established by **Refinement Phase 0**
([refinement-phase-0-bring-up.md](refinement-phase-0-bring-up.md)).
After this you have: the API on `https://localhost:5002` (against a local SQL Server), the web client on
`http://localhost:3000`, and a working phone-OTP login backed by the real backend. **Auth is the only real
domain** until [Refinement Phase 4](refinement-phase-4-frontend-de-mock.md) — everything else in the UI is
still an in-browser mock.
---
## Prerequisites
- **.NET 10 SDK** (`dotnet --version`) — preview is fine.
- **Node.js 20+** and **npm** (`node --version`).
- **Docker** (Desktop or engine) for the local database — or your own SQL Server on `localhost:1433`.
---
## One-time setup
### 1. Trust the ASP.NET Core HTTPS dev certificate
Without this the browser (and `fetch`) rejects `https://localhost:5002` and every call fails with an opaque
network error.
```bash
dotnet dev-certs https --trust
```
Accept the OS prompt. (macOS/Windows trust the cert; on Linux see the .NET docs for the per-distro step.)
### 2. Start a local SQL Server
From `server/`:
```bash
cd server
docker compose up -d
```
This runs SQL Server 2022 (Developer edition) on `localhost:1433` with a **dev-only** SA password
(`Balinyaar_Dev1433`, defined in `server/docker-compose.yml` — not a secret, never used in production).
Give it ~2030s on first start (`docker compose ps` shows `healthy`).
> Already have a SQL Server? Skip this and point the connection string in step 3 at it instead.
### 3. Point the API at the local database (via user-secrets — never a committed file)
The committed `appsettings*.json` carry a **placeholder** connection string on purpose. Supply the real
local one through `dotnet user-secrets` so no working credential ever lands in git. From the API project:
```bash
cd server/src/API/Baya.Web.Api
dotnet user-secrets set "ConnectionStrings:SqlServer" "Server=localhost,1433;Database=Baya;User Id=sa;Password=Balinyaar_Dev1433;TrustServerCertificate=True;Encrypt=False;"
```
The `Password` must match `MSSQL_SA_PASSWORD` in `docker-compose.yml`. User-secrets auto-load only in the
Development environment, so this never affects a deployed build.
> **Env-var alternative** (e.g. for CI/containers): set `ConnectionStrings__SqlServer` (double underscore =
> the `:` config separator) instead of using user-secrets.
> PowerShell: `$env:ConnectionStrings__SqlServer = "Server=localhost,1433;Database=Baya;User Id=sa;Password=Balinyaar_Dev1433;TrustServerCertificate=True;Encrypt=False;"`
> bash: `export ConnectionStrings__SqlServer="Server=localhost,1433;Database=Baya;User Id=sa;Password=Balinyaar_Dev1433;TrustServerCertificate=True;Encrypt=False;"`
---
## Run it (two terminals)
### Terminal 1 — backend (from `server/`)
```bash
dotnet run --project src/API/Baya.Web.Api/Baya.Web.Api.csproj
```
On boot the API applies all EF migrations and seeds roles + an admin user + a sandbox payment gateway
against the (empty) local DB, then listens on **`https://localhost:5002`** — Swagger at
`https://localhost:5002/swagger`.
In **Development** it additionally runs the **demo-world seeder** (Refinement Phase 1): verified/unverified
demo nurses with priced variants + Tehran coverage (and therefore real `nurse_search_index` rows), plus demo
customers with patients and addresses. The console logs `Demo world seeded: 3 nurse(s), 2 customer(s)…` (or
`already seeded — no-op` on a subsequent run). It is **idempotent** and **never runs outside Development**.
### Terminal 2 — frontend (from `client/`)
```bash
cd client
npm install # first time only
npm run dev
```
Serves **`http://localhost:3000`**. It reads the API base URL from `client/.env.development`
(`NEXT_PUBLIC_API_URL = https://localhost:5002`). If you run the API on a different port/scheme, create
`client/.env.local` with your `NEXT_PUBLIC_API_URL=…` (it overrides `.env.development`, is git-ignored).
---
## Demo accounts (Development seed)
The demo seeder creates these loginable accounts (phone-OTP; any 6-digit code you read from the console/dev
endpoint works). Use them to see the real path populated:
| Phone | Role | Who | State |
| --- | --- | --- | --- |
| `09120000001` | nurse | زهرا عزیزی (female) | **verified**, 3 variants, whole-city + 2 districts |
| `09120000002` | nurse | علی کریمی (male) | **verified**, 2 variants, 3 districts |
| `09120000003` | nurse | مریم احمدی (female) | **unverified** — not discoverable in search |
| `09120000010` | customer | سارا محمدی (female) | 2 patients, 1 Tehran address |
| `09120000011` | customer | رضا حسینی (male) | 1 patient, 1 Tehran address |
| `09120000020` | admin (`super_admin`) | نگار مدیری (female) | full backoffice — **lands on `/admin`**, sees every console incl. RBAC |
| `09120000021` | admin (`finance`) | کامران مالی (male) | scoped backoffice — lands on `/admin`, sidebar shows only the money consoles (`useAdminCapabilities` gating) |
| `admin` / `qw123321` | admin | reference super-admin (username+password) | not a phone-OTP login — the frontend uses the phone admins above |
The **phone-OTP admins** (`09120000020` / `09120000021`, refinement-phase-2) are how you reach the `/admin`
console through the same web login flow as everyone else — admin sub-roles are server-granted, never
self-selectable via `me/select_role`. Log in with either phone exactly like a nurse/customer; role
hydration routes you to `/admin`. To reach the **nurse** app, log in as a verified nurse phone
(`09120000001`); a fresh customer can also become a nurse in-app (SelectRole → `me/select_role`) and is
then routed to `/nurse` after the next `/me`.
Prove search works without the frontend: open Swagger →
`GET /api/v1/search/nurses?service_category_id=1&city_id=101` returns the two verified nurses' variants;
`service_category_id=3` (only the unverified nurse) returns an empty page.
## Log in (the real round-trip)
1. Open **`http://localhost:3000/fa/login`**.
2. Enter an Iranian mobile number (e.g. the verified nurse `09120000001`, or any demo phone above) and request
the code.
3. Get the 6-digit OTP one of two ways:
- **Read the server console** (Terminal 1) — SMS is mocked, so the code is logged:
`MOCK SMS — OTP code 123456 for phone ending in 0001`.
- **Or hit the Development-only helper** (handy for scripts/e2e):
`GET https://localhost:5002/api/v1/dev/last_otp/09120000001`
`{ "data": { "phone": "09120000001", "code": "123456" }, ... }`.
This endpoint returns **404 outside Development** and is superseded by real SMS in
[Refinement Phase 8](refinement-phase-8-external-rails.md).
4. Enter the code and submit → role hydration routes you to the app for your role: a customer to the family
home (`/`), a nurse to `/nurse`, an admin to `/admin` (refinement-phase-2).
5. **Verify in DevTools → Network:** `POST /api/v1/auth/request_otp`, `POST /api/v1/auth/verify_otp`, and
`GET /api/v1/me` all return **200** with the `ApiResult` envelope, and there is **no CORS error** in the
console. That is the first real authenticated request between the two projects.
---
## Good to know
- **The API speaks HTTP/2** (Kestrel `Protocols: Http2`, for gRPC). Browsers negotiate h2-over-TLS
automatically, so `fetch` just works; for `curl` add `--http2`.
- **Only `auth` is real by default.** 21 of 22 client service domains default to an in-browser mock
(`USE_*_MOCK = true`); the home, search, bookings, etc. are fake in-memory data until Refinement Phase 4.
- **The DB self-migrates + self-seeds**, so pointing at an empty local instance is enough — including the
Development demo world (nurses, variants, search rows, customers) from
[Refinement Phase 1](refinement-phase-1-database-and-seed.md).
- **Explicit migration path (deploy / migrations-off-boot).** Boot-time `MigrateAsync` is the dev convenience;
to apply migrations without booting the app (the direction [Refinement Phase 7](refinement-phase-7-unattended-ops.md)
will formalise), run from `server/`:
```bash
dotnet ef database update --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api
```
(`dotnet tool install --global dotnet-ef` if the `ef` command is missing. It reads the same connection
string, so set the user-secret / env var first.) This applies **schema only** — the reference + demo seeds
run on the next app boot.
- **Allowed browser origins** are configuration-driven (`Cors:AllowedOrigins`), defaulting to
`http://localhost:3000` in Development. A deployed environment lists its real web origin(s).
## Stopping / resetting
```bash
docker compose down # stop the DB, keep its data
docker compose down -v # stop the DB and wipe the volume (fresh migrate + reference + demo seed next run)
```
Re-running `dotnet run` against an **existing** seeded DB does not duplicate anything — both the reference and
the demo seeders are idempotent (the demo seeder guards each persona on its phone number). To get a clean demo
world, wipe the volume (`docker compose down -v`) and boot again.
## Troubleshooting
| Symptom | Fix |
| --- | --- |
| Browser: `net::ERR_CERT_AUTHORITY_INVALID` on `:5002` | Run `dotnet dev-certs https --trust` (setup step 1). |
| API startup: `Login failed for user 'sa'` / connect timeout | DB not up or wrong password — check `docker compose ps` and that the user-secrets password matches `docker-compose.yml`. |
| Console: `...has been blocked by CORS policy` | `UseCors` missing/mis-ordered, or the browser origin isn't in `Cors:AllowedOrigins`. It must sit after `UseRouting` and before the rate limiter. |
| `dotnet user-secrets` errors with "could not find UserSecretsId" | Run it from `server/src/API/Baya.Web.Api` (the project with `<UserSecretsId>`). |