Files
SluijsensandClaude Sonnet 5 fa389e42ee
Continuous Integration / config (pull_request) Successful in 11s
Continuous Integration / changes (pull_request) Successful in 21s
Continuous Integration / backend-build (pull_request) Successful in 6m10s
Continuous Integration / vulnerability-scan (pull_request) Successful in 4m59s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m27s
Continuous Integration / backend-test (pull_request) Failing after 7m48s
Continuous Integration / frontend-build (pull_request) Successful in 2m5s
Continuous Integration / frontend-test (pull_request) Successful in 4m24s
Continuous Integration / frontend-lint (pull_request) Successful in 2m0s
Continuous Integration / publish-test (pull_request) Skipped
Continuous Integration / publish-production (pull_request) Skipped
Continuous Integration / deploy-test (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped
Adds SlpModularCms.Api.SlpSoftware and extracts shared CmsHost composition
Unit 1 of the slpsoftware-api feature (FR-1/FR-2/FR-3): a new Client project
in the Clients solution folder, intended to eventually become the deployed
API for test.slpsoftware.nl/slpsoftware.nl, hosting the same four modules as
SlpModularCms.Api plus a future Offerings module.

- Extracts SlpModularCms.Api/Program.cs's hosting-pipeline composition into
  SlpModularCms.Core.Hosting.CmsHost (ConfigureServices/ConfigurePipeline),
  shared by both Client projects so they cannot drift apart
- Moves StaticContentExtensions.cs + WebsitePlaceholder.html from Api into
  Core, since CmsHost cannot live in Api but Core cannot depend on Api
- Adds SlpModularCms.Api.SlpSoftware with its own isolated local dev database
  and dev ports (5286/7223, distinct from Api's and Api.Slave's)
- Adds SlpModularCms.Api.Tests with WebApplicationFactory-based pipeline
  regression tests (security headers, health check, SPA fallback, rate
  limiting), scoped to Api per NFR Design
- Adds a frontend dev:slpsoftware pnpm script mirroring dev:slave
- Fixes GlobalExceptionHandler logging routine 401s (e.g. an expired/missing
  refresh token) as unhandled errors -- pre-existing, unrelated to this
  feature's own scope, found while testing the new instance

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWyStNL2ZsjrS7FLd7xvvN
2026-08-02 01:28:39 +02:00

10 KiB

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


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)


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.