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.
This commit is contained in:
hamid
2026-06-30 22:48:41 +03:30
parent 53a40dc51d
commit 765cc632d5
75 changed files with 1539 additions and 1418 deletions
@@ -0,0 +1,93 @@
# 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)`):
`IDateTimeProvider``SystemDateTimeProvider`, `IFieldEncryptor``SymmetricFieldEncryptor`,
`ICacheService``MemoryCacheService`, `IObjectStorage``LocalDiskObjectStorage`,
`INotificationDispatcher``LogNotificationDispatcher`.
- **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 → `OperationResult``ApiResult` 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_status``200` 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.md`*Project 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`.