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

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:
2026-08-02 16:23:09 +02:00
co-authored by Claude Sonnet 5
parent b6e9c07c06
commit cfb06b28b6
79 changed files with 4069 additions and 94 deletions
@@ -0,0 +1,37 @@
# Code Generation Summary — Unit: Offerings
Implements all 12 user stories (US-01US-12) and FR-4/5/6/7/8, per `offerings-code-generation-plan.md`.
## Backend — `SlpModularCms.Modules.Offerings`
- **Domain**: `Offering` entity (13 fields per `domain-entities.md`), `OfferingsDbContext` — table `OfferingsOfferings`, `Features` stored as a JSON column (`List<string>` with an EF Core `ValueComparer`), soft-delete enforced via a global `HasQueryFilter`.
- **Repository**: `IOfferingRepository`/`OfferingRepository` — CRUD plus `GetMaxDisplayOrderAsync`, `GetFeaturedAsync`, `GetPreviousAsync`/`GetNextAsync` (adjacent-swap lookups), and a `BeginTransactionAsync` wrapper.
- **Service**: `IOfferingsService`/`OfferingsService` — BR-OFF-01 (featured exclusivity), BR-OFF-02 (soft delete, never blocked), BR-OFF-03 (reorder + adjacent-swap boundaries), BR-OFF-04 (validation, enforced at the model-binding layer). Multi-row operations (featured swap, reorder, adjacent swap) run inside an explicit EF Core transaction per NFR Design Pattern 1. `LastModifiedByUserId` set on every create/update/delete (NFR-OFF-03); one `LogInformation` structured log entry per mutation.
- **Models**: `OfferingDto` (public), `OfferingAdminDto` (admin, adds `DisplayOrder`), `CreateOfferingRequest`/`UpdateOfferingRequest` (DataAnnotations + a custom `FeaturesValidationAttribute` for the 1-10-items/≤200-chars rule), `ReorderOfferingsRequest`.
- **Controller**: `OfferingsController` — route/auth table exactly per `component-methods.md` (`GET /api/v1/offerings` public + `[EnableRateLimiting("offerings-public")]`; admin CRUD/reorder/move under `AdminOnly`).
- **Module**: `OfferingsModule` (`IModule`) — registers the DbContext (Pomelo MySQL, `NonLockingMySQLHistoryRepository`), repository, service; migrates on startup.
- **Rate limiting**: new `offerings-public` `FixedWindowLimiter` policy added to `SlpModularCms.Core.Hosting.ServiceCollectionExtensions.AddCmsRateLimiting`, config-driven via `RateLimiting:OfferingsPublic` (added to `Api.SlpSoftware/appsettings.json`).
- **Migration**: `InitialCreate` (EF Core, generated against `Api.SlpSoftware` as startup project).
- **Wiring**: `SlpModularCms.Modules.Offerings`/`.Tests` added to the solution (Application/Modules and Tests/Modules folders per `CLAUDE.md`); `<ProjectReference>` added to `Api.SlpSoftware.csproj`.
- **Tests**: 38 tests — `OfferingRepositoryTests` (EF Core InMemory), `OfferingsServiceTests` (NSubstitute), `OfferingsControllerTests`. All four business rules covered, plus the soft-delete query filter.
## Frontend — `frontend/src/features/offerings/`
- **Dependency**: `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities` added (Functional Design Q1 = A).
- **Schema**: `schemas/offering.ts` (zod, mirrors BR-OFF-04 exactly).
- **Services** (API-calling hooks): `useOfferings`, `useOffering` (derived from the admin-list cache), `useCreateOffering`, `useUpdateOffering`, `useDeleteOffering`, `useReorderOfferings`, `useMoveOffering`.
- **Hooks** (feature-local): `useOfferingsDnd` — dnd-kit sensor setup, drag-end reordering, optimistic local state, delegates persistence to `useReorderOfferings`.
- **Components**: `OfferingsList` (`DndContext`/`SortableContext`), `OfferingRow` (drag handle, featured toggle, move up/down, edit/delete actions, all `data-testid`s per `frontend-components.md`), `DeleteOfferingDialog`, `OfferingForm` (react-hook-form + zod, dynamic features list managed via `setValue` rather than `useFieldArray` since `features` is a plain `string[]`).
- **Pages**: `OfferingsListPage`, `OfferingFormPage` (shared by create/edit, per Functional Design Q2).
- **Routing/nav/i18n**: three routes under `authenticatedRoute` (`/offerings`, `/offerings/new`, `/offerings/$id/edit`), each behind `RoleGuard(Owner, Administrator)` + `ModuleGuard(requiredModule="Offerings")`; `Sidebar.tsx` nav entry; full `nav.offerings`/`offerings.*` key sets added to both `en`/`nl` locale files.
- **Tests**: schema tests, an `OfferingsListPage` integration suite (title/list/empty-state/delete-confirm/delete-cancel/move-button-boundaries/auth-redirect), an `OfferingFormPage` suite (create/validation-error/edit-prefill) — all via MSW-mocked handlers in `features/offerings/mocks/handlers.ts`, following the `features/cms` test-style precedent.
## Verification
- Backend: full solution build succeeded; full test suite green, 414/414 (38 new in `Modules.Offerings.Tests`, no regressions elsewhere).
- Frontend: `pnpm build` (tsc + vite) succeeded; `pnpm lint` clean (one `react-hooks/set-state-in-effect` violation in `useOfferingsDnd` found and fixed — switched to the "adjust state during render" pattern); `pnpm test` green, 254/254 across 41 files (43 new).
## Deviations From the Plan Worth Noting
- `useFieldArray` was planned implicitly for the dynamic features list but doesn't type-check cleanly against a plain `string[]` field — used `useWatch` + `setValue` instead, a standard react-hook-form alternative for primitive arrays.
- `Modules.Offerings.csproj` does not reference `Microsoft.EntityFrameworkCore.Design` — confirmed by inspecting `Modules.Master.csproj` that this package belongs on the *startup* project (`Api.SlpSoftware`, which already has it), not on every project with a `DbContext`.