Adds the Offerings module and retargets the CI/CD pipeline to Api.SlpSoftware
Continuous Integration / config (pull_request) Successful in 12s
Continuous Integration / changes (pull_request) Successful in 22s
Continuous Integration / backend-build (pull_request) Successful in 5m53s
Continuous Integration / vulnerability-scan (pull_request) Successful in 5m46s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m54s
Continuous Integration / backend-test (pull_request) Successful in 7m37s
Continuous Integration / frontend-build (pull_request) Successful in 2m14s
Continuous Integration / frontend-test (pull_request) Successful in 4m59s
Continuous Integration / frontend-lint (pull_request) Successful in 2m2s
Continuous Integration / publish-production (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped
Continuous Integration / publish-test (pull_request) Successful in 7m34s
Continuous Integration / deploy-test (pull_request) Skipped
Continuous Integration / config (pull_request) Successful in 12s
Continuous Integration / changes (pull_request) Successful in 22s
Continuous Integration / backend-build (pull_request) Successful in 5m53s
Continuous Integration / vulnerability-scan (pull_request) Successful in 5m46s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m54s
Continuous Integration / backend-test (pull_request) Successful in 7m37s
Continuous Integration / frontend-build (pull_request) Successful in 2m14s
Continuous Integration / frontend-test (pull_request) Successful in 4m59s
Continuous Integration / frontend-lint (pull_request) Successful in 2m2s
Continuous Integration / publish-production (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped
Continuous Integration / publish-test (pull_request) Successful in 7m34s
Continuous Integration / deploy-test (pull_request) Skipped
Implements Unit 2 "Offerings" (backend module, admin CRUD UI with drag-and-drop reordering, public GET /api/v1/offerings endpoint) and executes the feature's D-15 CI/CD cutover, switching the deploy pipeline's build/publish target from SlpModularCms.Api to SlpModularCms.Api.SlpSoftware. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FWyStNL2ZsjrS7FLd7xvvN
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
# NFR Design Patterns — Unit: Offerings
|
||||
|
||||
## Pattern 1: Transactional Multi-Row Operations (Data Integrity)
|
||||
|
||||
**Decision** (Q1 = A): every `OfferingsService` operation that touches more than one row in a single logical action runs inside one explicit DB transaction (`BeginTransactionAsync`/`CommitAsync`, rolled back on any exception):
|
||||
|
||||
- **Featured-exclusivity swap** (US-10): un-featuring the previously-featured offering and saving the newly-featured one.
|
||||
- **Full reorder** (US-08): reassigning `DisplayOrder` across the entire list from the drag-and-drop UI.
|
||||
- **Adjacent swap** (US-09): swapping `DisplayOrder` between two neighboring offerings.
|
||||
|
||||
**Rationale**: a crash or connection failure mid-operation must never leave the dataset in an inconsistent state (two offerings both un-featured, duplicate `DisplayOrder` values). This closes the open item `services.md` deferred to Functional Design but that was never actually decided there.
|
||||
|
||||
**Pattern**: standard EF Core `DbContext.Database.BeginTransactionAsync()` wrapping the read-modify-write sequence within each of the three `OfferingsService` methods (`CreateAsync`/`UpdateAsync` when `Featured` transitions to `true`, `ReorderAsync`, `MoveUpAsync`/`MoveDownAsync`). Single-row operations (plain create/update without a featured transition, soft-delete) do not need an explicit transaction — a single `SaveChangesAsync()` call is already atomic.
|
||||
|
||||
## Pattern 2: Rate Limiting Applied at the Action Level
|
||||
|
||||
**Decision**: `[EnableRateLimiting("offerings-public")]` is placed on the public `GET` action method only, following the exact precedent in `AuthController` (`[EnableRateLimiting("login")]`/`[EnableRateLimiting("refresh")]` on individual actions, not the whole controller). `OfferingsController`'s admin mutation actions carry no rate-limiting attribute — consistent with NFR-OFF-01 (Q1 = A) scoping the policy to the public endpoint only.
|
||||
|
||||
**No new pattern beyond this** — the `offerings-public` policy itself (config-driven `FixedWindowLimiter`) is already fully specified in `tech-stack-decisions.md`.
|
||||
|
||||
## Pattern 3: Audit Logging — Structured Fields
|
||||
|
||||
**Decision**: `OfferingsService` emits one `LogInformation` structured log entry per create/update/delete, with these fields: `OfferingId` (Guid), `Action` (`"Created"`/`"Updated"`/`"Deleted"`), `LastModifiedByUserId` (Guid, the same value persisted on the entity). No before/after value diffing — consistent with NFR-OFF-03's accepted scope (minimal audit trail, not a full audit-log table).
|
||||
|
||||
## No Other New Patterns
|
||||
|
||||
Scalability and Performance categories are N/A for this unit (see `offerings-nfr-design-plan.md`) — no new pattern required beyond what's already covered above and in `nfr-requirements.md`.
|
||||
Reference in New Issue
Block a user