f77a23cb25
Two-tier service model the marketplace is priced and searched on. Admin catalog skeleton (categories + EAV option groups/values, addable as data not migrations; NULL category = cross-category) and the nurse pricing layer (nurse_service_variants — the atomic bookable unit: category + one value per required dimension at the nurse's own IRR price and price unit). - New `catalog` schema via one additive migration; Price BIGINT (no floats), on the wire as a string of digits; total = price + unit + session_count. - Duplicate-listing guard: deterministic option_set_hash + filtered UNIQUE(nurse_id, service_category_id, option_set_hash) WHERE deleted_at IS NULL + friendly 409 pre-check. One value per dimension; required groups (incl. cross-category) enforced; deactivate, never delete. - Public catalog browse cached behind a CatalogCache generation token, invalidated on any admin write. IVariantSnapshotSerializer shipped for b8. - Contract (catalog.md) + handoff + report published; swagger refreshed. 122 tests green; zero new build warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 KiB
11 KiB
Backend status log (append-only)
One block per completed backend phase. Newest at the top. Backend lane writes here; frontend reads.
backend-phase-5 — Service catalog & nurse pricing variants — 2026-07-02
- Shipped: five tables via one additive migration (
ServiceCatalogAndNurseVariants) — newcatalogschemaServiceCategories/ServiceOptionGroups(nullableservice_category_id= cross-category) /ServiceOptionValues/NurseServiceVariants(PriceBIGINT IRR,PriceUnit,SessionCount?,DisplayName,OptionSetHash) /NurseServiceVariantOptions(UNIQUE(variant_id, option_group_id)); seed of 5 categories (nameFa+nameEn) viaHasData. 16 CQRS slices across 3 controllers (catalogpublic browse,admin_catalogCRUD + set-active,nurse_variantscreate/update/set-active/ list/get). Duplicate-listing guard =OptionSetHash+ filteredUNIQUE(nurse_id, service_category_id, option_set_hash) WHERE deleted_at IS NULL+ 409 pre-check. Public catalog reads cached behind aCatalogCachegeneration token (invalidate on any admin write). ShipsIVariantSnapshotSerializer(pure, for b8). No new seam. - Contracts: dev/contracts/domains/catalog.md + openapi snapshot refreshed (yes — 14 new catalog/admin_catalog/nurse_variants paths; 71 total).
- Mocked: none — this phase mocks nothing and adds no
reports/mocks-registry.mdrow. - Gate: build clean (0 new code warnings) / tests green (122 pass: +10 handler/serializer unit,
+11
Baya.Test.Apiintegration). Migration verified to apply on a real SQL Server; swagger exposes all b5 paths. Adversarial 4-dimension review: 0 confirmed findings. - Handoff: backend/handoff/after-backend-phase-5.md
- Notes for frontend: the variant is the bookable unit (not the nurse).
priceis a string of IRR digits; the total isprice+priceUnit+sessionCount, never price alone. A NULL-category option group is cross-category (render it under every category; required ones must be answered). Duplicate identical listing → 409; missing required dimension → 400.displayNameauto-generates (editable). Deactivate, never delete. Routes are action-style POST (admin_catalog/create_category,nurse_variants/create, …); groups/values are admin-authored (only categories are seeded).
backend-phase-4 — Geography, addresses & nurse service areas — 2026-07-02
- Shipped: five tables via one migration (
GeographyAddressesServiceAreas) — newgeoschemaProvinces1:NCities1:NDistricts(+NurseServiceAreas) andusr.CustomerAddresses; seed (31 provinces + capital cities + Tehran's 22 مناطق viaHasData); 20 CQRS slices across 4 controllers (geopublic lookups incl./tree,admin_geoCRUD + set_active,nurse_service_areas,customer_addresses); newIGeocoderseam (deterministic mock,Seams:Geocoding); per-domain repos onIUnitOfWork; enc value converters for the address PII columns;409 Conflictadded toOperationResult/BaseController. Whole-city (district_id NULL) uniqueness via a filtered-index pair; single-primary address via filteredUNIQUE(customer_id) WHERE is_primary=1; geo reads cached behind a generation-token scheme with invalidate-on-admin-write. - Contracts: dev/contracts/domains/geography-addresses.md + openapi snapshot refreshed (yes — 20 new geo/service-area/address paths).
- Mocked:
IGeocoder→ 🟡 (see reports/mocks-registry.md). - Gate: build clean (0 new code warnings) / tests green (103 pass: +16
Baya.Test.Apiintegration, +12 handler unit tests). MigrationGeographyAddressesServiceAreasapplies on startup; swagger exposes all b4 paths. - Handoff: backend/handoff/after-backend-phase-4.md
- Notes for frontend:
districtId=nullmeans whole city (a real choice) everywhere. Duplicate service area → 409. Addresses come back decrypted for the owner withlatitude/longitude(nullable when ungeocoded — geocoding is mocked). Routes are action-style (admin_geo/create_city,nurse_service_areas/add,customer_addresses/create, …). Admin geo needs an admin token.
backend-phase-3 — Identity: profiles, patients & nurse bank accounts — 2026-07-02
- Shipped: four
usrtables via one migration (IdentityProfilesPatientsBankAccounts) —NurseProfiles(1:1Users; guardedis_verifiedno public setter; read-only aggregates; soft-delete),CustomerProfiles(thin payer; enc emergency contact),Patients(care recipient, tenancy-scoped;is_activearchive; encinitial_medical_notes),NurseBankAccounts(enciban+UNIQUE(iban_hash)+ filteredUNIQUE(nurse_id) WHERE is_primary=1; استعلام شبا inquiry fields); 15 CQRS slices across 4 controllers (nurse_profiles,customer_profiles,patients,nurse_bank_accounts); newIBankAccountOwnershipVerifierseam (mock = deterministic شبا match); per-domain repositories onIUnitOfWork; enc value converters for the new PII columns. Also activated FluentValidation repo-wide (AddApplicationServicesnow registers everyAbstractValidator<T>— theValidateCommandBehavior/model-state filter were previously starved). - Contracts: dev/contracts/domains/identity-profiles.md + openapi snapshot refreshed (yes — new nurse/customer/patient/bank paths).
- Mocked:
IBankAccountOwnershipVerifier→ 🟡 (see reports/mocks-registry.md). - Gate: build clean (0 new code warnings) / tests green (75 pass: +13
Baya.Test.Apiintegration, +15 handler unit tests). Migration applied to the dev DB on startup; swagger exposes all b3 paths. - Handoff: backend/handoff/after-backend-phase-3.md
- Notes for frontend: role scoping needs the role claim in the token — refresh after
select_rolebefore calling these. IBAN comes back masked (last-4).isVerified/aggregates are read-only. Patientget/updateof another customer's id → 404. Addresses/service-areas are deferred to b4.
backend-phase-2 — Identity: phone-OTP auth, sessions & roles (REST) — 2026-07-02
- Shipped: the six-endpoint REST auth surface (
auth/request_otp,auth/verify_otp,auth/refresh,auth/logout,me,me/select_role) wrapping the existing JWE/TOTP/RBAC engine; newusr.UserSessions(refresh-token rotation + revoke-all on replayed token);usr.Usersextended (Gender,NationalIdenc NULL,ShahkarVerifiedAtauto-reset on phone change,PhoneHashUNIQUE,IsActive,DeletedAt+ soft-delete filter); phone/email/national-id encrypted at rest (EF value converter overIFieldEncryptor);usr.UserRolesgrant/revoke audit trail + revoked filter; 7 roles seeded;ISmsSenderseam (mock logs the code); 3 auth config keys;OperationResult/BaseControllerlearned 401/403. - Contracts: dev/contracts/domains/identity-auth.md + openapi snapshot refreshed (yes — 22 paths).
- Mocked:
ISmsSender→ 🟡 (see reports/mocks-registry.md). - Gate: build clean (0 new code warnings) / tests green (47 pass: 10 new
Baya.Test.Apiintegration + 14 new handler unit tests). MigrationIdentitySessionsAndUserExtensionsapplied to the dev DB; full §7 flow verified live (OTP in log, tokens, 401/403/429, rotation, replay-revoke, logout stamp-kill). - Handoff: backend/handoff/after-backend-phase-2.md
- Notes for frontend: exact paths are
request_otp/verify_otp/select_role(snake_case transformer — not theotp/requestsketch). Bodies camelCase. Fresh users:roles: []→ role router →me/select_role→ refresh tokens to pick up role claims./mephone is masked. SMS is mocked — read the OTP from the server log.
backend-phase-1 — Config, reference & platform signals — 2026-07-02
- Shipped: first marketplace migration baseline (
InitialMarketplaceBaseline, newopsschema) with 6 tables (PlatformConfigs,AuditLogs,SystemEvents,IranianHolidays,Notifications,SupportAlerts) + seed (12 config keys, 7 holidays); platform-signal facadesIPlatformConfig/IHolidayCalendar/IAnalyticsSink/IAuditLogger/INotificationService/ISupportAlertService(Persistence/Services/);AuditFieldInterceptorextended to write append-onlyaudit_logsrows forIAuditableentities; real in-appINotificationDispatcher(b0 stub removed); notification-retention hosted service; 5 controllers (admin config/holidays/audit/support-alerts + current-user notifications). - Contracts:
dev/contracts/domains/config-reference.md+ openapi snapshot refreshed (yes — 16 paths). - Mocked:
IHolidayCalendar,IAnalyticsSink, retentionIJobScheduler→ 🟡;INotificationDispatcherflipped to in-app-real 🟡 (SMS/push deferred). See reports/mocks-registry.md. - Gate: build clean (0 new code warnings) / tests green (22 pass: 4 identity + 18 foundation). Migration applied to the dev DB; API boots with all 16 paths in Swagger; retention job runs on startup.
- Handoff: backend/handoff/after-backend-phase-1.md
- Notes for frontend: f14 =
notifications/*(envelope unchanged; unread-first lists;data_jsonis a typed deep-link payload). f15 = adminplatform_config/*,holidays/*,audit/get_audit_trail,support_alerts/*(DynamicPermission). Paginationpage/page_size(default 50, max 100).
backend-phase-0 — Foundation, cross-cutting seams & starter cleanup — 2026-06-28
- Shipped: removed the
Orderdemo (entity/feature/repo/config/gRPC) + 3 old migrations; freshInitialBaselinemigration; REST surface (PingController+System/PingCQRS);ICurrentUser+AuditFieldInterceptor; five cross-cutting seams (IDateTimeProvider,IFieldEncryptor,ICacheService,IObjectStorage,INotificationDispatcher) with mocks;LoggingBehavior+ rate limiter (per-IP global +otp/auth/sensitive). - Contracts:
dev/contracts/openapi/swagger.v1.jsonpublished (envelope + ping schemas). - Mocked: the 5 seams above → 🟡 (see reports/mocks-registry.md).
- Gate: build clean (0 new warnings) / tests green (10 pass). Live API verified vs
192.168.100.14(migration applied + seeded; ping200; rate-limit429). - Handoff: backend/handoff/after-backend-phase-0.md
- Notes for frontend:
ApiResultenvelope is fixed (camelCase body, snake_case URLs);GET /api/v1/ping/get_statusis live to wire types against;429on over-limit.