Files
baya-monorepo/dev/post-phase/refinement/RUNBOOK.md
T
2026-07-13 16:00:34 +03:30

12 KiB
Raw Blame History

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).

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 — 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.

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/:

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:

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/)

dotnet run --project src/API/Baya.Web.Api/Baya.Web.Api.csproj

On boot the API applies all EF migrations and seeds roles against the (empty) local DB, then listens on https://localhost:5002 — Swagger at https://localhost:5002/swagger. In Development it also seeds a sandbox payment gateway and the demo world (below). It does not seed the old admin/qw123321 account anymore (refinement-phase-5); a break-glass admin is created only if you set Seed:AdminUsername / Seed:AdminPassword (see below), and the day-to-day admin path is the phone-OTP demo admins.

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/)

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)

The old username+password admin/qw123321 account is no longer auto-seeded (refinement-phase-5 — no committed credential). To bootstrap a break-glass username+password admin, set both secrets before boot, then log in via the API (not the web UI, which is phone-OTP only):

cd server/src/API/Baya.Web.Api
dotnet user-secrets set "Seed:AdminUsername" "admin"
dotnet user-secrets set "Seed:AdminPassword" "<a-strong-password>"

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.
  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/1.1 and HTTP/2 (Kestrel Protocols: Http1AndHttp2, refinement-phase-5 — the previous HTTP/2-only default broke non-TLS HTTP/1.1 hops). Over TLS the client negotiates h2 via ALPN, so gRPC and fetch both work; plain-HTTP hops fall back to HTTP/1.1.
  • Secrets fail fast. On a fresh clone with no user-secrets the API refuses to start with Refusing to start: required secret configuration is missing… — set the connection-string user-secret (step 3) and boot again. Deployed environments must additionally supply real IdentitySettings JWE keys and Seams:FieldEncryption keys (Development uses dev-only defaults from appsettings.Development.json).
  • Enable the secret-scan pre-commit hook once per clone so a stray credential can't be committed: git config core.hooksPath .githooks (see .githooks/README.md).
  • Behind a reverse proxy, list its address in ForwardedHeaders:KnownProxies (or a CIDR in :KnownNetworks) so the rate limiter partitions on the real client IP, not the proxy's.
  • 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.
  • 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 will formalise), run from server/:
    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

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: Refusing to start: required secret configuration is missing… The connection-string user-secret isn't set (or still the placeholder). Do setup step 3.
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>).