5.9 KiB
Backend Phase 1 — Config, reference & platform signals — Report (2026-07-02)
What was built
- First marketplace migration baseline
20260701193257_InitialMarketplaceBaseline(on top of b0'sInitialBaseline) creating a newopsschema with six tables:PlatformConfigs(uniqueKey, audit fields,IAuditable),AuditLogs(append-only; indexes on(EntityType,EntityId)+OccurredAt; nullable FK →usr.Users),SystemEvents(append-only; indexes onName/OccurredAt/UserId),IranianHolidays(uniqueHolidayDate),Notifications(index(UserId,IsRead,CreatedAt); FK → Users),SupportAlerts(indexes onStatus/Type; nullableBookingId/ReviewIdcolumns without FK yet; FK → Users onOwnerUserId).- Seeded via
HasData: 12platform_configskeys and 7 sample holidays (Nowruz block + Revolution Day + Nature Day + a religious day).
- Domain: entities under
Configuration/Audit/Analytics/Holidays/Notifications/SupportAlerts; theIAuditablemarker +[AuditRedacted]attribute (Domain/Common); string-code constant holders (ConfigDataType,AuditAction,HolidayType,SupportAlertType/Severity/Status). - Application: facade contracts
IPlatformConfig/IHolidayCalendar/IAnalyticsSink/IAuditLogger/INotificationService/ISupportAlertService; DTOs +PagedResult<T>; evolved theINotificationDispatcher.Notificationrecord to carryType+DataJson; aPagination.Normalizehelper; 14 CQRS commands/queries (+ validators) wiring the endpoints to the facades. - Persistence (
Services/): DB-backed implementations of every facade; the realInAppNotificationDispatcher(supersedes and removes the b0LogNotificationDispatcher);NotificationRetentionHostedService(intervalBackgroundService). TheAuditFieldInterceptorwas extended (not duplicated) to append anaudit_logsrow with an old/new diff for everyIAuditablechange, in the same transaction, redacting[AuditRedacted]properties. Registered all facades + hosted service inAddPersistenceServices; removed theINotificationDispatcherregistration fromAddCrossCuttingSeams. - API: 5 sealed
BaseControllercontrollers — adminPlatformConfigController/HolidaysController/AuditController/SupportAlertsController([Authorize(DynamicPermission)]) and current-userNotificationsController([Authorize]).
What is now testable (and exactly how)
dotnet test Baya.sln → 22 pass (4 identity + 18 new foundation), build clean (0 new code warnings;
only pre-existing NU1903/NU1510 package advisories remain). Foundation tests run against a real
ApplicationDbContext over in-memory SQLite (OpsTestHost, seed applied via EnsureCreated):
- Typed config —
GetConfig<decimal>("vat_rate")==0.10,<int>("dispute_window_hours")==72,<int>("booking_payment_deadline_minutes")==30; cache hit on re-read. - Config change is audited —
SetConfig("platform_fee_rate","0.18")→ new value read back (cache evicted) + oneaudit_logsrow (updated, actor, old0.15/new0.18); missing key →false. - Holidays —
IsBankClosed(2026-03-21)==true,IsHoliday(non-holiday)==false,NextBusinessDay(2026-03-21)→ a later open, non-Friday day; upsert/delete round-trip. - Notifications — dispatch → list unread-first → unread count 1 → mark read → 0; tenancy (another user sees nothing and cannot mark your row); retention deletes only read >90d (unread >90d and read <90d survive).
- Support alerts — raise (
low_rating,review,42) → list open → assign → resolve; resolved is terminal. - Analytics —
EmitAsyncinserts asystem_eventsrow.
Live: the migration was applied to the dev DB (Server=87.107.152.16); the API boots with all 16
Swagger paths present, and the notification-retention hosted service runs its purge query on startup.
Swagger snapshot refreshed at dev/contracts/openapi/swagger.v1.json (fetched over HTTP/2 — the gRPC
plugin makes the REST port HTTP/2-only, so a browser or HTTP/2 client is needed to hit Swagger by hand).
What is mocked / waiting on a real service
See reports/mocks-registry.md. New/changed 🟡: IHolidayCalendar (seeded table → real feed/sync),
IAnalyticsSink (system_events row → warehouse), retention IJobScheduler
(NotificationRetentionHostedService interval runner → Hangfire/Quartz), INotificationDispatcher
(now real in-app write; SMS/push channels deferred). Selection is by registration, never if(mock).
Contracts produced
dev/contracts/domains/config-reference.md (live as of b1; consumers f14/f15) + refreshed
openapi/swagger.v1.json.
Decisions recorded (were not pinned by product docs)
Seeded config defaults marked provisional in product/data-model/12-audit-config-and-reference.md:
platform_fee_rate 0.15, nurse_response_deadline_hours 24, evv_location_tolerance_meters 200,
min_rating_for_support_alert 2, bnpl_provider_commission_rate 0.07, bnpl_settlement_timing
immediate, and a 3-tier cancellation_tiers default. Confirm before launch — all are config-driven.
Follow-ups for later phases
- b9 / b14: add the FK constraints
SupportAlerts.BookingId → BookingsandSupportAlerts.ReviewId → Reviewswhen those tables land (columns already exist). - List orderings use the monotonic
Id(newest-first, deterministic, cross-provider) rather than theDateTimeOffsetcolumn — SQLite can'tORDER BY/compareDateTimeOffset; equivalent on SQL Server. - Notification retention filters the age cutoff in memory (after a server-side
IsReadfilter) so the bulk delete translates on every provider; fine for a bounded, off-peak job. - Integration tests (
WebApplicationFactory<Program>, CONVENTIONS §10) still not scaffolded — the HTTP pipeline (auth 401/403, envelope) is covered by the live Swagger boot but not automated; add the project when convenient.