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

5.6 KiB

Components — SlpSoftware Production API

1. CmsHost (new — SlpModularCms.Core.Hosting)

Purpose: Shared hosting-composition logic extracted from SlpModularCms.Api/Program.cs (FR-3), consumed by both SlpModularCms.Api and SlpModularCms.Api.SlpSoftware.

Responsibilities:

  • Compose the standard set of service registrations every Client project needs (logging, Sentry, module discovery, core infrastructure, CORS, rate limiting, health checks, security headers, observability, Data Protection, controllers).
  • Compose the standard HTTP pipeline every Client project needs (migrations, exception handling, security headers, rate limiting, OpenAPI/Scalar in Development, HTTPS redirection, static content, CORS, module middleware, authentication/authorization, controllers, health checks, Sentry tunnel, SPA fallbacks).
  • Not responsible for: bootstrapping the WebApplicationBuilder itself, or loading appsettings.local.json — those two lines stay in each project's own Program.cs (decision Q1 = B: two granular methods, not one entrypoint that owns everything).

Interfaces: ConfigureServices(WebApplicationBuilder), ConfigurePipeline(WebApplication, ModuleOrchestrator) — see component-methods.md.


2. SlpModularCms.Api.SlpSoftware (new Client project)

Purpose: The new deployable Client (FR-1), first project in the Clients solution folder. Eventually the production host for test.slpsoftware.nl / slpsoftware.nl (Operations phase, D-15).

Responsibilities:

  • Reference SlpModularCms.Core, SlpModularCms.Modules.Identity, SlpModularCms.Modules.Availability, SlpModularCms.Modules.Master, and the new SlpModularCms.Modules.Offerings (FR-2) — module composition is driven entirely by which Module projects are referenced (ModuleOrchestrator discovers modules from .dll files on disk, not from any list in Program.cs).
  • Thin Program.cs: bootstrap the builder, load appsettings.local.json, call CmsHost.ConfigureServices/ConfigurePipeline.
  • Own its own appsettings.json / appsettings.Development.json / appsettings.local.json (per the dotnet-appsettings skill pattern already used by Api).

Interfaces: None beyond Program.cs itself — it's a composition root, not a library.


3. Offering (new entity — SlpModularCms.Modules.Offerings.Data.Entities)

Purpose: The persisted record behind both the public read contract (FR-6) and admin management (FR-7).

Responsibilities: Hold Id (Guid, per Q4 = A), Title, Description, Price, PriceNote, Features (ordered list), CtaLabel, Featured, DisplayOrder, IsDeleted, DeletedAt (per Q5 = B, soft delete).


4. OfferingsDbContext (new — SlpModularCms.Modules.Offerings.Data)

Purpose: Module-isolated EF Core context for the Offering entity, following the existing per-module DbContext pattern (MasterDbContext, AvailabilityDbContext) — MariaDB via UseMySQL, its own migrations assembly.

Responsibilities: DbSet<Offering> Offerings; model configuration (max lengths for Title/Description/Price/PriceNote/CtaLabel per SECURITY-05).


5. IOfferingRepository / OfferingRepository (new — SlpModularCms.Modules.Offerings.Repositories)

Purpose: Data-access layer between the service and OfferingsDbContext, mirroring Modules.Master's ICmsInstanceRepository/CmsInstanceRepository (decision Q2 = A: keep the Repository+Service pattern consistent across modules, even though this module is individually simple).

Responsibilities: CRUD against Offering rows, always excluding soft-deleted rows except where the service explicitly needs them; ordering by DisplayOrder.


6. IOfferingsService / OfferingsService (new — SlpModularCms.Modules.Offerings.Services)

Purpose: Business orchestration layer — the one component that knows the rules from stories.md that a repository alone shouldn't own.

Responsibilities:

  • Enforce the "at most one featured offering" exclusivity rule (US-10) when creating/updating.
  • Own the DisplayOrder semantics for both reorder interactions: full-list reorder from drag-and-drop (US-08) and adjacent swap from the up/down buttons (US-09).
  • Assign Id (new Guid) and initial DisplayOrder (append to end) on creation.
  • Apply soft-delete (US-06/US-07 — deleting the last remaining offering is always allowed, decision Q4 in requirements.md).
  • Map between Offering entities and the DTOs used by the controller.

7. OfferingsController (new — SlpModularCms.Modules.Offerings.Controllers)

Purpose: Single HTTP-facing component for the module (decision Q3 = B: one controller, mixed authorization per action, rather than a public/admin split into two controllers).

Responsibilities: Expose GET /api/v1/offerings ([AllowAnonymous], FR-6) and the admin CRUD + reorder actions ([Authorize(Policy = "AdminOnly")], FR-7) on the same controller, delegating all logic to IOfferingsService.


8. OfferingsModule (new — SlpModularCms.Modules.Offerings)

Purpose: IModule implementation, following the exact pattern of MasterModule/AvailabilityModule.

Responsibilities: RegisterServices — register OfferingsDbContext (MySQL, non-locking history repository per the existing convention), IOfferingRepository/OfferingRepository, IOfferingsService/OfferingsService. UseModule — apply pending OfferingsDbContext migrations at startup.


8 components: 1 shared hosting component (CmsHost), 1 new Client project, and 6 components making up the Offerings module (entity, DbContext, repository, service, controller, module registration).