Files
SluijsensandClaude Sonnet 5 cfb06b28b6
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
Adds the Offerings module and retargets the CI/CD pipeline to Api.SlpSoftware
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
2026-08-02 16:23:09 +02:00

5.2 KiB
Raw Permalink Blame History

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-testids 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.