Files
baya-monorepo/archive/build-chain/working-context/reports/backend-phase-3-report.md
T
2026-08-02 18:48:32 +03:30

5.2 KiB

Backend phase 3 report — Identity: profiles, patients & nurse bank accounts

What was built

  • Four domain entities (Baya.Domain/Entities/Identity/): NurseProfile, CustomerProfile, Patient, NurseBankAccount. NurseProfile.is_verified is write-guarded (private setter + MarkVerified()/MarkUnverified() — only b6 calls it); is_accepting_bookings toggled via a domain method; the search aggregates are read-only.
  • One EF migration IdentityProfilesPatientsBankAccounts (schema usr): 1:1 uniques on user_id, UNIQUE(iban_hash), filtered UNIQUE(nurse_id) WHERE is_primary=1, soft-delete on NurseProfiles, encrypted PII columns, audit fields. No CUT columns.
  • 15 CQRS slices under Features/Identity/{Commands|Queries}/ + 4 sealed : BaseController controllers (NurseProfilesController, CustomerProfilesController, PatientsController, NurseBankAccountsController). Reads project to DTOs; lists paginate; the ownership-inquiry endpoints are rate-limited (sensitive policy).
  • New seam IBankAccountOwnershipVerifier (Application Contracts/Common) + mock MockBankAccountOwnershipVerifier (CrossCutting), registered in AddCrossCuttingSeams, config-selected.
  • Persistence: four per-domain repositories on IUnitOfWork (NurseProfileRepository, CustomerProfileRepository, PatientRepository, NurseBankAccountRepository); encrypted-PII value converters for the new columns wired in ApplicationDbContext.OnModelCreating; an atomic SetPrimaryAsync (clear-then-set in one transaction) so the single-primary index never trips.
  • Infra fix: AddApplicationServices now registers every AbstractValidator<T> as IValidator<T> so the pre-existing ValidateCommandBehavior and ModelStateValidationAttribute filter actually validate (they had no validators registered before this phase — validation was silently inert).

What is now testable, and exactly how (mirrors the phase §7)

Log in as a nurse and a customer (b2 OTP flow), refreshing the token after select_role so the role claim is present. Then:

  1. Nurse profilePOST api/v1/nurse_profiles/upsert → row created is_verified=0, is_accepting_bookings=0; GET …/me shows aggregates at 0. No path sets is_verified.
  2. Accepting-bookingsPOST …/set_accepting_bookings flips it; verified untouched.
  3. Customer profilePOST api/v1/customer_profiles/upsert with emergency contact → GET …/me round-trips it through the encrypted column.
  4. Patient CRUDcreate (gender required) / list / get/{id} / update/{id} / archive/{id}.
  5. Tenancy — customer B calling get/update on customer A's patient id → 404.
  6. Bank account + inquiryPOST api/v1/nurse_bank_accounts/add (normal IBAN) → matched_national_id=true, vendor ref recorded; list shows the IBAN masked.
  7. Mismatch — add the mismatch IBAN → matched_national_id=false.
  8. Duplicate IBAN — re-add the same IBAN → clean 400 via iban_hash uniqueness.
  9. Primary flip — add a 2nd account, set_primary/{id2} → account 2 primary, account 1 not; never two primaries.

Automated coverage: 15 handler unit tests (NSubstitute) covering profile upsert, role forbidden, patient CRUD + cross-customer 404, bank add match/mismatch/duplicate + set-primary flip/not-owned; 13 WebApplicationFactory integration tests (one+ per controller: happy path, 401, validation 400, tenancy 404, mask, duplicate, primary flip, mismatch). dotnet build clean (0 new code warnings); dotnet test green (75 pass).

What is mocked / waiting on a real service

  • IBankAccountOwnershipVerifier (🟡) — deterministic fake استعلام شبا. Make-it-real steps in reports/mocks-registry.md. Reused seams: IFieldEncryptor, ICurrentUser, IDateTimeProvider.

Contracts produced / consumed

  • Produced: dev/contracts/domains/identity-profiles.md; dev/contracts/openapi/swagger.v1.json refreshed (adds all nurse-profile / customer-profile / patient / bank-account paths).
  • Consumed: b2 auth (login/roles), b0 seams (IFieldEncryptor/ICurrentUser/IDateTimeProvider), b1 IPlatformConfig (available; not needed this phase).

Follow-ups for later phases

  • b4: customer_addresses + nurse_service_areas (need geography + geocoder) — deferred here.
  • b6: the is_verified flip (verification-confirm transaction); Shahkar/KYC populate national_id; the bank_account_verification step couples to NurseBankAccounts.
  • b13: first-payout gate on matched_national_id = true.
  • b9/b14: recompute average_rating/total_reviews/total_completed_bookings (read-only here).
  • Chain-wide: validators are now active — new phases must ensure route-supplied ids aren't validated in the body command, and can rely on FluentValidation for input rejection.

Decisions taken (flagged for confirmation)

  • A thin customer_profiles row is auto-provisioned on a customer's first patient (so patient registration needs no separate profile step). Recorded in product/data-model/01-identity-and-access.md.
  • IBAN is returned masked (last-4) on every read; first account added is primary by default.