Files
baya-monorepo/dev/shared-working-context/reports/backend-phase-0-report.md
T
hamid 765cc632d5 backend phase 0: foundation, cross-cutting seams & starter cleanup
Remove the Order demo (entity/feature/repo/config/gRPC/proto) and the three
pre-marketplace migrations; regenerate a fresh InitialBaseline migration.

Stand up the REST surface (PingController + System/Ping CQRS) proving the
Mediator -> behaviors -> OperationResult -> ApiResult envelope end to end.

Close wiring gaps: register LoggingBehavior (outermost) and add the built-in
rate limiter (per-IP global + otp/auth/sensitive policies), placed before
authentication.

Add current-user + audit plumbing: ICurrentUser (HttpContext + null impls),
rename BaseEntity audit fields to CreatedAt/ModifiedAt (DateTimeOffset) +
CreatedById/ModifiedById, stamped by a new AuditFieldInterceptor.

Introduce five cross-cutting seams (IDateTimeProvider, IFieldEncryptor,
ICacheService, IObjectStorage, INotificationDispatcher) with in-memory/local
mocks registered via AddCrossCuttingSeams.

Add Baya.Test.Foundation (encryptor, audit interceptor, ping handler) and
update docs, contracts (swagger.v1.json), handoff, report, and mocks registry.
2026-06-30 22:48:41 +03:30

6.8 KiB

Backend Phase 0 — Foundation, cross-cutting seams & starter cleanup — Report (2026-06-28)

What was built

  • Starter cleanup. Removed the Order demo end-to-end: entity (Domain/Entities/Order), feature folder (Application/Features/Order), IOrderRepository/OrderRepository, OrderConfig, User.Orders nav, IUnitOfWork.OrderRepository, the gRPC OrderGrpcServices + proto + wiring + /GrpcUserOrder map. Deleted the three pre-marketplace migrations (2021/2022/2023) + snapshot.
  • Fresh baseline migration Migrations/20260628191947_InitialBaseline — Identity (usr schema) + UserRefreshTokens, with audit columns CreatedAt/ModifiedAt (datetimeoffset), CreatedById/ModifiedById (int). No Orders table.
  • Audit base type. BaseEntity/IAuditableEntity in Domain/Common/BaseEntity.cs: CreatedAt/ModifiedAt as DateTimeOffset, CreatedById/ModifiedById as int? (renamed from the old CreatedTime/ModifiedDate DateTime pair, to match CONVENTIONS.md §6).
  • Current-user plumbing. ICurrentUser (Application/Contracts/Common) with HttpContextCurrentUser + NullCurrentUser (Infrastructure.Identity/Identity/CurrentUser/), registered Scoped; AddHttpContextAccessor() wired.
  • Audit interceptor. AuditFieldInterceptor (Infrastructure.Persistence/Interceptors/), a SaveChangesInterceptor stamping audit fields from ICurrentUser + IDateTimeProvider. Replaces the old DateTime.Now date hook in ApplicationDbContext (the _cleanString Persian-normalisation hook stays). Registered via AddInterceptors in AddPersistenceServices.
  • Five cross-cutting seams (interfaces in Application/Contracts/Common, mocks in Infrastructure.CrossCutting/Seams/, registered by AddCrossCuttingSeams(config)): IDateTimeProviderSystemDateTimeProvider, IFieldEncryptorSymmetricFieldEncryptor, ICacheServiceMemoryCacheService, IObjectStorageLocalDiskObjectStorage, INotificationDispatcherLogNotificationDispatcher.
  • Pipeline + rate limiting. Registered LoggingBehavior<,> as the outermost Mediator behavior. Added AddRateLimitingPolicies() (WebFramework/ServiceConfiguration) — built-in rate limiter with a per-IP global limit + named policies otp/auth/sensitive; app.UseRateLimiter() placed before app.UseAuthentication().
  • REST surface. Controllers/V1/PingController (sealed, BaseController, ISender) with GetStatus and a rate-limited GetStatusRateLimited, backed by the Features/System/Queries/Ping CQRS feature — proves REST → Mediator → behaviors → OperationResultApiResult envelope.
  • Tests. New Baya.Test.Foundation project: SymmetricFieldEncryptor round-trip + deterministic hash, AuditFieldInterceptor add/update stamping (SQLite), PingQueryHandler happy path.

What is now testable (and exactly how)

  • Build/test gate: dotnet build Baya.sln → 0 errors, 0 new warnings; dotnet test Baya.sln → 10 pass (4 existing identity + 6 new foundation). verified.
  • Live API: verified end-to-end against SQL Server 192.168.100.14 (Development env). On boot the InitialBaseline migration applied and the default users seeded. dotnet run --project src/API/Baya.Web.Api/... → open /swagger:
    • GET /api/v1/ping/get_status200 with body { "data": { "service": "Baya.Web.Api", "status": "ok", "serverTimeUtc": "<utc>" }, "isSuccess": true, "statusCode": 200, "message": "Success", "requestId": "<trace>" } (the standard ApiResult<T> envelope).
    • GET /api/v1/ping/get_status_rate_limited from one IP → first 5 = 200, then 429.
    • The Order REST/gRPC endpoints are gone; the swagger doc shows only the two ping paths.

Run note: in non-Development the Serilog MSSqlServer sink targets the logDb connection (Server=sql_server2022), which must resolve or the host fails to build (pre-existing config, unrelated to this phase). Development logs to console/file, so verify there.

What is mocked / waiting on a real service

All five seams are 🟡 (mock behind DI seam) — see reports/mocks-registry.md rows for IFieldEncryptor, ICacheService, IObjectStorage, INotificationDispatcher (and IDateTimeProvider, not external). Each mock lives in Baya.Infrastructure.CrossCutting/Seams/; swapping to a real provider is a registration change in AddCrossCuttingSeams. INotificationDispatcher does not write yet — the in-app notifications write lands in b15.

Contracts

  • Produced: the ApiResult/OperationResult envelope shape. The first machine swagger.json snapshot is published at dev/contracts/openapi/swagger.v1.json (paths ping/get_status + ping/get_status_rate_limited; schemas ApiResult, ApiResultStatusCode, ApiResultOfPingQueryResult, PingQueryResult). Casing on the wire: camelCase body/envelope properties, snake_case URL segments.
  • Consumed: none.

Docs updated

  • server/CLAUDE.mdProject map (Order removed; CrossCutting Seams/, Persistence Interceptors/, Identity CurrentUser/, Baya.Test.Foundation, new seams note), Startup wiring (new registrations
    • rate-limiter-before-auth pipeline order), CQRS example (Order → generic + Ping).
  • server/CONVENTIONS.md — §6 "as built" note (audit base type + interceptor) and a new "Money is IRR BIGINT — integer-only, no floats" rule.
  • dev/shared-working-context/reports/mocks-registry.md — five seam rows → 🟡 with files + config keys.

Follow-ups for later phases

  • b1: extend AuditFieldInterceptor to also write append-only audit_logs rows; evolve the baseline migration with the marketplace schema; IHolidayCalendar seed.
  • b2: ISmsSender seam + auth/OTP REST surface; apply the otp/auth rate-limit policies to those endpoints.
  • Integration tests: a WebApplicationFactory<Program> test project (CONVENTIONS §10) was not scaffolded this phase; add it when the first real feature area lands so the HTTP pipeline (routing, auth, envelope translation, 429) is covered automatically.
  • Logging config (pre-existing): the non-Development Serilog MSSqlServer sink points at logDb = Server=sql_server2022. If that host isn't reachable in an environment, the API won't start there. Out of scope for this phase — flag for whoever owns environment/infra config.

Status

All Definition-of-Done items met: build clean (0 new warnings), 10 tests green, REST controller live through Swagger with the OperationResult envelope, LoggingBehavior + rate limiter wired, ICurrentUser + audit interceptor + the five seams in place, migration applied + DB seeded, docs/ contracts/handoff/registry updated, swagger snapshot published. Live verification done against 192.168.100.14.