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:
@@ -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`.
|
||||
Reference in New Issue
Block a user