4.1 KiB
After backend-phase-3 — profiles, patients & nurse bank accounts are live
On top of the b2 auth spine, the people behind the accounts now exist. A nurse has a seller profile
(that b6 will verify and b5 will hang service variants off), a customer has a payer profile and their
patients, and a nurse has payout bank accounts with an automated IBAN-ownership inquiry. Contract:
dev/contracts/domains/identity-profiles.md; machine
schema: dev/contracts/openapi/swagger.v1.json (refreshed).
What the frontend (f2-b3) can now build
- Nurse profile bootstrap:
POST api/v1/nurse_profiles/upsert(bio, experience, education,specializationsJson),GET api/v1/nurse_profiles/me, and the pause/resume togglePOST api/v1/nurse_profiles/set_accepting_bookings.isVerifiedand the rating/booking aggregates are read-only (verification is b6) — render them, never send them. - Customer profile:
POST api/v1/customer_profiles/upsert(emergency contact) +GET …/me. - "Who is care for" (patients):
create/list(paginated) /get/{id}/update/{id}/archive/{id}underapi/v1/patients.gender(male/female) is required. A customer only ever sees/edits their own patients — someone else's id returns 404. - Nurse bank-account settings:
add(returns the account withmatchedNationalIdset by the شبا inquiry),list(IBAN masked, last-4),set_primary/{id}, andverify_ownership/{id}to re-run the inquiry.
Rules baked into the API (don't fight them client-side)
- Refresh after
select_role. These endpoints authorize on the role claim in the access token; the token minted before role selection lacks it. Login →select_role→ refresh (or re-login) → then call profile/patient/bank endpoints. Otherwise you get403. - Guarded verification — no field/endpoint sets
isVerified; a nurse is not bookable until b6. - Tenancy — patients and bank accounts are strictly owner-scoped; cross-tenant reads/writes are
404. - IBAN is masked on the wire (last-4 only); the full value is encrypted at rest.
matchedNationalIdis the money-mule-prevention gate for the first payout (enforced in b13). It isnulluntil the inquiry runs;addruns it automatically. The bank rail is mocked at MVP.- Duplicate IBAN → a clean
400(viaiban_hashuniqueness), not a server error.
What's mocked
- IBAN ownership (
IBankAccountOwnershipVerifier→ 🟡). Deterministic fake استعلام شبا: every IBAN matches except the configured mismatch IBAN (Seams:BankOwnership:MismatchIban, defaultIR000000000000000000000000) which returnsmatchedNationalId=false. No real bank/KYC call.
Schema / migration
Migration 20260702042131_IdentityProfilesPatientsBankAccounts (applied to the dev DB on startup):
usr.NurseProfiles, usr.CustomerProfiles, usr.Patients, usr.NurseBankAccounts — with the 1:1
uniques, UNIQUE(iban_hash), filtered single-primary index, guarded is_verified, encrypted PII columns
(iban, account_holder_name, emergency contacts, initial_medical_notes), and soft-delete on
NurseProfiles. None of the CUT columns (verification_status, response_rate, … ,
customer_profiles.national_id_verified_at) exist.
Deferred to later phases (do not build against these yet)
- Addresses & nurse service areas → b4 (need province/city/district + geocoder).
is_verifiedflip → b6 (verification pipeline).- Payout gating on
matched_national_id→ b13. - Aggregate recompute (rating/reviews/completed) → b9/b14.
- Customer national-ID KYC — intentionally not collected; never gate browsing/booking on it.
Note for the whole backend chain
FluentValidation was previously inert (no validators registered). b3 activates it in
AddApplicationServices — every AbstractValidator<T> now runs via ValidateCommandBehavior and the
ModelStateValidationAttribute controller filter. Consequence: for route-supplied ids, don't add a
body validator rule on that id (e.g. patients/update/{id} validates the body, whose Id is 0).