Files
slp-modular-cms/aidlc-docs/features/slpsoftware-api/inception/application-design/services.md
T
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

2.8 KiB

Services — SlpSoftware Production API

IOfferingsService

Layer: Service (business orchestration), between OfferingsController and IOfferingRepository.

Why this service exists (per decision Q2 = A, mirroring ICmsInstanceService in Modules.Master): the repository alone can't own the rules that span more than a single row — the "at most one featured offering" exclusivity check (US-10) reads and writes two rows in one logical operation, and both reorder interactions (US-08 full reorder, US-09 adjacent swap) recompute DisplayOrder across multiple rows. A controller calling the repository directly would either duplicate this orchestration or risk skipping it.

Orchestration patterns:

  1. Create/Update with featured exclusivity (US-10): when a request sets Featured = true, the service first calls GetFeaturedAsync(); if a different offering currently holds it, that offering is un-featured (via UpdateAsync on it) in the same logical operation before the requested offering is saved as featured. Exact transactional boundaries (single DB transaction vs. sequential saves) are a Functional Design decision for the Offerings unit, not decided here.
  2. Full reorder (US-08): ReorderAsync receives the complete ordered list of IDs from the drag-and-drop UI and reassigns DisplayOrder sequentially (0, 1, 2, ...) to match.
  3. Adjacent swap (US-09): MoveUpAsync/MoveDownAsync locate the neighboring offering by DisplayOrder and swap the two DisplayOrder values. A no-op (or a clearly-defined error) at the boundaries (first item moving up, last item moving down) — exact behavior for an out-of-bounds call is a Functional Design detail.
  4. Soft delete (US-06/US-07, Q5 = B): DeleteAsync sets IsDeleted = true / DeletedAt = now rather than removing the row. The repository's read methods (GetAllAsync, GetByIdAsync, GetFeaturedAsync) always exclude soft-deleted rows, so callers never need to remember to filter — deleting the last remaining offering (US-07) is unaffected by this and remains always allowed.
  5. Public vs. admin projections: GetPublicOfferingsAsync returns OfferingDto (the exact FR-6 contract shape: id, title, description, price, priceNote, features, ctaLabel, featured). GetAllForAdminAsync returns OfferingAdminDto, which additionally exposes DisplayOrder (and, if useful in the admin UI, IsDeleted/DeletedAt are not exposed since deleted rows are never returned to any caller).

No other services are introduced by this feature. CmsHost (components.md #1) is a static composition helper, not a service in the DI/business-orchestration sense — it has no business rules, only infrastructure wiring, so it is documented under Components/Component Methods rather than here.