# User Stories — SlpSoftware Production API Breakdown approach: **persona-based** (approved). Acceptance criteria format: **Given/When/Then** (approved). --- ## Epic A — Site Visitor: Viewing Offerings ### US-01 — View the list of available offerings **As a** Site Visitor, **I want** to see the current list of offerings on the website, **so that** I can compare what's available and pick one that fits my needs. **Acceptance Criteria** - **Given** one or more offerings exist in the CMS, **when** the frontend requests `GET /api/v1/offerings`, **then** the response is `200 OK` with a JSON array of offerings in display order, each including `id`, `title`, `description`, `price`, `priceNote`, `features`, `ctaLabel`, and `featured`. - **Given** the offerings are returned, **when** rendered, **then** the array order is the same order the CMS Administrator configured (FR-5 `DisplayOrder`) — no re-sorting happens client-side or server-side beyond that stored order. **Traceability**: FR-5, FR-6 --- ### US-02 — See the recommended offering highlighted **As a** Site Visitor, **I want** to see which offering is the "most chosen" one, **so that** I have a quick recommendation if I'm unsure which package to pick. **Acceptance Criteria** - **Given** the CMS Administrator has marked exactly one offering as featured, **when** `GET /api/v1/offerings` is called, **then** exactly one item in the response has `"featured": true` and all others have `"featured": false`. - **Given** no offering has been explicitly marked as featured, **when** `GET /api/v1/offerings` is called, **then** every item has `"featured": false` (no forced default featured item). **Traceability**: FR-5, FR-6; enforcement mechanism defined in US-10 --- ### US-03 — Website stays functional with zero offerings **As a** Site Visitor, **I want** the site to still work correctly even if no offerings have been configured yet, **so that** I don't encounter a broken page during initial setup or content maintenance. **Acceptance Criteria** - **Given** zero offerings exist in the CMS, **when** the frontend requests `GET /api/v1/offerings`, **then** the response is `200 OK` with an empty JSON array `[]` — not an error response. - **Given** this empty-array response, **then** it is the frontend's responsibility (out of scope for this feature) to decide how to render an empty state; the API's only obligation is a valid, non-error response. **Traceability**: FR-6; decision Q4 = A (allow empty list, no deletion guard) --- ## Epic B — CMS Administrator: Managing Offerings ### US-04 — Create a new offering **As a** CMS Administrator, **I want** to create a new offering with title, description, price, price note, features, and CTA label, **so that** I can add a new package/service to the website without a code deploy. **Acceptance Criteria** - **Given** I am authenticated with at least the `Administrator` role, **when** I submit a new offering with all required fields (`Title`, `Description`, `Price`, `PriceNote`, at least one `Feature`, `CtaLabel`), **then** the offering is persisted and immediately appears in `GET /api/v1/offerings` at the end of the display order. - **Given** I create an offering without marking it featured, **when** it is saved, **then** `featured` defaults to `false`. **Traceability**: FR-5, FR-7 --- ### US-05 — Edit an existing offering **As a** CMS Administrator, **I want** to edit an existing offering's content, **so that** I can correct or update pricing and copy as the business changes. **Acceptance Criteria** - **Given** an existing offering, **when** I update any of its fields and save, **then** `GET /api/v1/offerings` reflects the new values on the next request. - **Given** I edit an offering, **when** I save it, **then** its `id` (stable slug) and `DisplayOrder` are not changed as a side effect of the edit — only reordering (US-08/US-09) changes order. **Traceability**: FR-5, FR-7 --- ### US-06 — Delete an offering **As a** CMS Administrator, **I want** to delete an offering that's no longer relevant, **so that** the website doesn't show outdated packages. **Acceptance Criteria** - **Given** more than one offering exists, **when** I delete one of them, **then** it no longer appears in `GET /api/v1/offerings`, and the remaining offerings keep their relative display order. **Traceability**: FR-7 --- ### US-07 — Delete the last remaining offering **As a** CMS Administrator, **I want** to be able to delete the last remaining offering if needed, **so that** I'm not blocked from clearing content during a redesign or content pause, even though it temporarily leaves the site with nothing to show. **Acceptance Criteria** - **Given** exactly one offering exists, **when** I delete it, **then** the deletion succeeds (no blocking validation error) and `GET /api/v1/offerings` subsequently returns `[]`. **Traceability**: FR-7; decision Q4 = A (deletion is never blocked to prevent an empty list) --- ### US-08 — Reorder offerings via drag-and-drop **As a** CMS Administrator, **I want** to reorder offerings by dragging them into a new position in the list, **so that** I can control the order visitors see them in without editing a numeric field. **Acceptance Criteria** - **Given** two or more offerings, **when** I drag one to a new position and the change is saved, **then** the `DisplayOrder` values are updated so `GET /api/v1/offerings` reflects the new order. **Traceability**: FR-5, FR-7; decision Q5 = A+B --- ### US-09 — Reorder offerings via up/down buttons **As a** CMS Administrator, **I want** an alternative to drag-and-drop — explicit "move up" / "move down" controls per row, **so that** I can reorder offerings accurately even without a mouse, or when drag-and-drop is impractical (accessibility, precision). **Acceptance Criteria** - **Given** an offering that is not first in the list, **when** I use its "move up" control, **then** it swaps display order with the offering immediately before it. - **Given** an offering that is not last in the list, **when** I use its "move down" control, **then** it swaps display order with the offering immediately after it. - **Given** the first offering in the list, **then** its "move up" control is disabled (and symmetrically for "move down" on the last offering). **Traceability**: FR-5, FR-7; decision Q5 = A+B (accessible fallback alongside drag-and-drop) --- ### US-10 — Mark an offering as featured (system-enforced exclusivity) **As a** CMS Administrator, **I want** the system to guarantee that at most one offering is marked "featured" at any time, **so that** I don't accidentally end up with a confusing website showing more than one "most chosen" badge (per the external hand-off doc's expectation, which the frontend itself does not enforce). **Acceptance Criteria** - **Given** offering A is currently featured, **when** I mark offering B as featured, **then** offering A is automatically un-featured in the same operation — the system never persists more than one featured offering at a time. - **Given** no offering is currently featured, **when** I mark one as featured, **then** exactly that one becomes featured. - **Given** the currently featured offering, **when** I explicitly un-feature it (without featuring another), **then** zero offerings are featured — this is a valid state (see US-02). **Traceability**: FR-5, FR-7; decision Q3 = A --- ### US-11 — Receive validation feedback on invalid input **As a** CMS Administrator, **I want** clear validation errors when I submit incomplete or malformed offering data, **so that** I can fix my mistake instead of silently corrupting the website's content. **Acceptance Criteria** - **Given** I submit an offering missing a required field (`Title`, `Description`, `Price`, `PriceNote`, `CtaLabel`, or `Features`), **when** I save, **then** the request is rejected with a validation error identifying which field(s) are invalid, and no partial record is persisted. - **Given** I submit a field exceeding its defined maximum length, **when** I save, **then** the request is rejected the same way. **Traceability**: FR-7; SECURITY-05 (Input Validation) --- ### US-12 — Admin actions require the Administrator role **As a** CMS Administrator, **I want** offering management to be inaccessible to anyone without at least the `Administrator` role, **so that** unauthenticated visitors or lower-privileged users can't alter the website's content. **Acceptance Criteria** - **Given** an unauthenticated request, **when** it targets any admin offerings endpoint (create/edit/delete/reorder), **then** it is rejected with `401 Unauthorized`. - **Given** an authenticated request from a `User`-role account (below `Administrator` in the hierarchy), **when** it targets any admin offerings endpoint, **then** it is rejected with `403 Forbidden`. - **Given** an authenticated request from an `Administrator`- or `Owner`-role account, **when** it targets any admin offerings endpoint, **then** it is permitted (subject to the other acceptance criteria above). **Traceability**: FR-7; SECURITY-06, SECURITY-08 --- ## Persona-to-Story Mapping | Persona | Stories | |---|---| | Site Visitor | US-01, US-02, US-03 | | CMS Administrator | US-04, US-05, US-06, US-07, US-08, US-09, US-10, US-11, US-12 | ## INVEST Self-Check | Story | Independent | Negotiable | Valuable | Estimable | Small | Testable | |---|---|---|---|---|---|---| | US-01..US-03 | ✅ each stands alone | ✅ display details open | ✅ core public value | ✅ | ✅ | ✅ Given/When/Then | | US-04..US-07 | ✅ each CRUD op independent | ✅ UI details open | ✅ core admin value | ✅ | ✅ | ✅ Given/When/Then | | US-08, US-09 | ✅ each interaction mode independent | ✅ exact UI open | ✅ usability | ✅ | ✅ | ✅ Given/When/Then | | US-10 | ✅ | ✅ | ✅ prevents a real content bug | ✅ | ✅ | ✅ Given/When/Then | | US-11, US-12 | ✅ | ✅ | ✅ security/data-quality value | ✅ | ✅ | ✅ Given/When/Then | --- **2 personas, 12 user stories (3 Site Visitor, 9 CMS Administrator), all with Given/When/Then acceptance criteria and explicit traceability to functional requirements and/or Security Baseline rules.**