backend phase 1: config, reference & platform signals
Lay the cross-cutting platform backbone every later phase reads from. Adds the first marketplace EF migration baseline (new `ops` schema) and the mechanisms b2..b15 reuse: typed runtime config, an append-only audit trail, an analytics event log, the holiday/bank-closure calendar, in-app notifications, and the internal support-alert worklist. Schema & migration - New `ops` schema + migration InitialMarketplaceBaseline with 6 tables: PlatformConfigs (IAuditable), AuditLogs (append-only), SystemEvents, IranianHolidays, Notifications, SupportAlerts — with indexes/uniques and FKs to usr.Users. Seeded 12 config keys + 7 sample holidays via HasData. Domain / Application - IAuditable marker + [AuditRedacted] attribute; entities + string-code constant holders (config data_type, holiday type, alert type/severity/status). - Facade contracts: IPlatformConfig, IHolidayCalendar, IAnalyticsSink, IAuditLogger, INotificationService, ISupportAlertService; DTOs + PagedResult<T>; evolved the INotificationDispatcher.Notification record to carry Type + DataJson; Pagination helper. - 14 CQRS commands/queries (+ validators) wiring the endpoints to the facades. Infrastructure - DB-backed facade implementations in Persistence/Services/; real in-app INotificationDispatcher (removes the b0 log stub); notification-retention hosted service (purge is_read=1 AND age>90d). - Extended AuditFieldInterceptor to also append an old/new-diff audit_logs row for every IAuditable change in the same transaction (PII redacted). - Registered all facades + hosted service in AddPersistenceServices; removed the dispatcher registration from AddCrossCuttingSeams. API - 5 controllers: admin PlatformConfig/Holidays/Audit/SupportAlerts ([Authorize(DynamicPermission)]) + current-user Notifications ([Authorize]), all tenant-scoped and paginated. 16 Swagger paths total. Money-correctness & safety rules honoured - Config read at compute time (cached, parsed by data_type), never hardcoded; every config change is audited in the same transaction; audit_logs is append-only (no update/delete path); support alerts are admin-only; notifications are tenant-scoped; analytics is fire-and-forget. Tests & docs - 18 new foundation tests over in-memory SQLite (config typing + audit, holidays, notifications + tenancy + retention, support alerts, analytics); build clean (0 new code warnings), 22 tests green; migration applied to the dev DB and swagger.v1.json refreshed. - Updated server Project map + CONVENTIONS, product data-model doc 12 (seeded config defaults), config-reference contract, mock registry, backend handoff/ STATUS/report. Follow-ups: add FK constraints for SupportAlerts.BookingId (b9) and ReviewId (b14) when those tables land. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# After backend-phase-1 — what b2…b15 and the frontend can rely on
|
||||
|
||||
The **platform backbone** is live. Six cross-cutting tables exist in a new **`ops` schema** on top of
|
||||
b0's `InitialBaseline`, seeded with real config + a sample holiday calendar. The mechanisms every later
|
||||
phase needs — typed config, holiday math, audit trail, analytics, in-app notifications, support alerts —
|
||||
are built **once, here**, behind Application contracts. **Reuse them; do not re-create the tables.**
|
||||
|
||||
## Internal contracts b2…b15 must depend on (never reinvent)
|
||||
All are DI-registered (Scoped) and implemented in `Baya.Infrastructure.Persistence/Services/`:
|
||||
|
||||
- **`IPlatformConfig`** — `GetConfig<T>(key)` (cached, parsed by `data_type`), `SetConfig(key,value)`
|
||||
(audited, evicts cache), `ListAsync`, `GetConfigChangeHistory(key)`. **Read money-critical constants
|
||||
here at compute time — never hardcode.** Seeded keys: `platform_fee_rate`, `vat_rate` (0.10),
|
||||
`dispute_window_hours` (72), `booking_payment_deadline_minutes` (30), `nurse_response_deadline_hours`,
|
||||
`nurse_payout_interval_days`, `evv_location_tolerance_meters`, `min_rating_for_support_alert`,
|
||||
`bnpl_merchant_of_record`, `bnpl_provider_commission_rate`, `bnpl_settlement_timing`,
|
||||
`cancellation_tiers`. **Snapshot the rate you use onto the priced row** — a later config change must not
|
||||
re-price it.
|
||||
- **`IHolidayCalendar`** — `IsHoliday`, `IsBankClosed`, `NextBusinessDay` (skips bank-closed days + the
|
||||
Iranian banking weekend = Friday), plus admin CRUD. **b13 payout scheduling calls `NextBusinessDay`.**
|
||||
- **`IAuditLogger`** — `WriteAsync(entityType, entityId, action, changedFields?)` for state changes with
|
||||
no row diff, plus `GetTrailAsync`. Row-level diffs on **`IAuditable`** entities are written
|
||||
automatically by the extended `AuditFieldInterceptor` (mark an entity `IAuditable`; annotate encrypted
|
||||
props `[AuditRedacted]`). `platform_configs` is the first `IAuditable` entity. **`audit_logs` is
|
||||
append-only — never update/delete it.**
|
||||
- **`IAnalyticsSink`** — `EmitAsync(name, props)`; fire-and-forget (`system_events`). **Never** route
|
||||
compliance facts here — those go to `IAuditLogger`.
|
||||
- **`INotificationDispatcher`** — `DispatchAsync(Notification(userId, type, title, body?, dataJson?))`
|
||||
now writes a **real in-app `notifications` row** (b0 stub gone). This is how booking/payment/review
|
||||
domains mint a user notification. `data_json` is a **typed, versioned deep-link payload** — version it.
|
||||
- **`INotificationService`** — per-user reads/commands (list unread-first, unread count, mark read/all,
|
||||
purge). Always tenant-scoped to `ICurrentUser`.
|
||||
- **`ISupportAlertService`** — `RaiseAsync(type, entityType, entityId, severity, bookingId?, reviewId?)`
|
||||
for review/EVV/verification/payment flows to call, plus assign/resolve/list. **Support alerts are
|
||||
admin-only — never surface them on a user-facing route or in a user `notification`.**
|
||||
|
||||
## Live endpoints (contract: `dev/contracts/domains/config-reference.md`)
|
||||
Admin (`DynamicPermission`): `platform_config/*`, `holidays/*`, `audit/get_audit_trail`,
|
||||
`support_alerts/*`. Current-user (`Authorize`): `notifications/*` (f14 notification center;
|
||||
f15 admin config/holidays/audit/alerts). Envelope unchanged (camelCase body, snake_case URLs); lists
|
||||
paginated `page`/`page_size`.
|
||||
|
||||
## Migration / schema
|
||||
New migration **`20260701193257_InitialMarketplaceBaseline`** — the marketplace baseline every later
|
||||
phase adds onto. Applied cleanly to the dev DB (tables + indexes + seed present). Tables live in **`ops`**
|
||||
(keep new marketplace tables off `usr`).
|
||||
|
||||
## Follow-ups later phases must close
|
||||
- **FK constraints for `support_alerts.booking_id` / `review_id`.** The columns exist now (no FK yet).
|
||||
**b9** (bookings) adds `FK SupportAlerts.BookingId → Bookings`; **b14** (reviews) adds
|
||||
`FK SupportAlerts.ReviewId → Reviews`. Do it in the migration that creates those tables.
|
||||
- **Make-it-real seams (🟡):** `IHolidayCalendar` (real feed/sync), `IAnalyticsSink` (warehouse),
|
||||
`IJobScheduler` retention (Hangfire/Quartz), `INotificationDispatcher` SMS/push channels — see
|
||||
`reports/mocks-registry.md`.
|
||||
|
||||
## Caveat (unchanged from b0)
|
||||
Non-Development Serilog targets the `logDb` connection; Development boots against the configured
|
||||
`SqlServer`. A reachable SQL Server is required to run the API (it applies migrations + seeds on boot).
|
||||
Reference in New Issue
Block a user