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
5.5 KiB
Business Logic Model — Unit: Offerings
Flow 1: Create/Update an Offering (with Featured Exclusivity)
sequenceDiagram
box rgba(246,224,94,0.4) Admin
participant Admin as CMS Administrator
end
box rgba(99,179,237,0.4) Frontend
participant Form as OfferingFormPage
end
box rgba(159,122,234,0.4) Backend
participant Ctrl as OfferingsController
participant Svc as OfferingsService
participant Repo as OfferingRepository
end
Admin->>Form: Fills form, toggles Featured, submits
Form->>Form: Validate against zod schema (BR-OFF-04, client-side mirror)
Form->>Ctrl: POST or PUT with offering data
Ctrl->>Svc: CreateAsync or UpdateAsync
Svc->>Svc: Re-validate (BR-OFF-04, server-side, authoritative)
alt Featured set to true
Svc->>Repo: GetFeaturedAsync
Repo-->>Svc: currently-featured Offering or none
Svc->>Repo: UpdateAsync (unfeature previous, if any)
end
Svc->>Repo: AddAsync or UpdateAsync (this offering)
Repo-->>Svc: saved Offering
Svc-->>Ctrl: OfferingAdminDto
Ctrl-->>Form: 200/201 with the saved offering
Form-->>Admin: Redirect to offerings list
Text alternative: the admin fills the create/edit page (a full page, not a modal — Functional Design Q2), client-side validation runs first for immediate feedback, then the server re-validates authoritatively; if Featured is being set to true, the service un-features any previously-featured offering in the same operation before saving (yellow = admin actor, blue = frontend page, purple = backend layers).
Flow 2: Delete an Offering (with Confirmation)
sequenceDiagram
box rgba(246,224,94,0.4) Admin
participant Admin as CMS Administrator
end
box rgba(99,179,237,0.4) Frontend
participant List as OfferingsListPage
participant Dialog as DeleteOfferingDialog
end
box rgba(159,122,234,0.4) Backend
participant Ctrl as OfferingsController
participant Svc as OfferingsService
end
Admin->>List: Clicks delete on a row
List->>Dialog: Open confirmation dialog (Functional Design Q4)
Admin->>Dialog: Confirms
Dialog->>Ctrl: DELETE request
Ctrl->>Svc: DeleteAsync (always allowed, BR-OFF-02)
Svc-->>Ctrl: success
Ctrl-->>List: 204, remove row from list
Text alternative: deletion always shows a confirmation dialog first (a frontend-only safeguard); once confirmed, the delete request is unconditionally accepted by the backend, including for the last remaining offering.
Flow 3: Reorder via Drag-and-Drop (US-08)
sequenceDiagram
box rgba(246,224,94,0.4) Admin
participant Admin as CMS Administrator
end
box rgba(99,179,237,0.4) Frontend
participant List as OfferingsListPage
end
box rgba(159,122,234,0.4) Backend
participant Ctrl as OfferingsController
participant Svc as OfferingsService
end
Admin->>List: Drags a row to a new position (dnd-kit)
List->>List: Reorders local row state optimistically
List->>Ctrl: PUT reorder with the full ordered id list
Ctrl->>Svc: ReorderAsync(orderedIds)
Svc->>Svc: Reassign DisplayOrder sequentially to match
Svc-->>Ctrl: success
Ctrl-->>List: 204 (or revert local state on failure)
Text alternative: dragging a row reorders the frontend's local list immediately (perceived responsiveness), then sends the complete new order to the backend, which reassigns every offering's DisplayOrder to match in one operation.
Flow 4: Reorder via Up/Down Buttons (US-09)
sequenceDiagram
box rgba(246,224,94,0.4) Admin
participant Admin as CMS Administrator
end
box rgba(99,179,237,0.4) Frontend
participant List as OfferingsListPage
end
box rgba(159,122,234,0.4) Backend
participant Ctrl as OfferingsController
participant Svc as OfferingsService
end
Admin->>List: Clicks "move up" on a row
List->>Ctrl: POST move-up for that offering id
Ctrl->>Svc: MoveUpAsync(id)
Svc->>Svc: Swap DisplayOrder with the<br/>preceding offering (BR-OFF-03)
Svc-->>Ctrl: success
Ctrl-->>List: 204, list refetches or reorders locally
Text alternative: an explicit per-row button swaps the offering's position with its immediate neighbor — the accessible alternative to drag-and-drop, symmetric for "move down".
Flow 5: Featured Toggle from the List Row (Functional Design Q5)
sequenceDiagram
box rgba(246,224,94,0.4) Admin
participant Admin as CMS Administrator
end
box rgba(99,179,237,0.4) Frontend
participant List as OfferingsListPage
end
box rgba(159,122,234,0.4) Backend
participant Ctrl as OfferingsController
participant Svc as OfferingsService
end
Admin->>List: Clicks the "featured" star icon on a row
List->>Ctrl: PUT update using the row's already-loaded data,<br/>with Featured flipped
Ctrl->>Svc: UpdateAsync
Note over Svc: Same BR-OFF-01 exclusivity logic as the form path
Svc-->>Ctrl: success
Ctrl-->>List: 200, list reflects the new featured offering
Text alternative: the quick list-row star action is not a separate backend capability — it calls the exact same update endpoint as the full edit form, just pre-filled from data the list already has in memory, so BR-OFF-01's exclusivity rule applies identically no matter which UI path the admin used (resolves Functional Design Q5: both the form toggle and the list-row action work, backed by one shared mechanism).