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

6.3 KiB

Application Design Plan — SlpSoftware Production API

Dit plan beschrijft hoe de high-level applicatie-ontwerp-artefacten voor deze feature worden opgesteld. Beantwoord eerst de vragen hieronder; na jouw goedkeuring wordt het plan uitgevoerd.

Context die ik al heb geverifieerd in de code (niet aangenomen):

  • ModuleOrchestrator (SlpModularCms.Core/Hosting/ModuleOrchestrator.cs) ontdekt modules dynamisch door .dll-bestanden op schijf te scannen — er is nergens een expliciete "welke modules host ik"-lijst in Program.cs. Dat betekent: welke modules een Client-project host, wordt volledig bepaald door welke Module-projecten dat .csproj referenceert, niet door code in Program.cs zelf.
  • SlpModularCms.Api/Program.cs bevat, op de bootstrap-regels na (WebApplication.CreateBuilder, appsettings.local.json), geen enkele project-specifieke branch — alles is generiek/config-gedreven. Dat maakt een verregaande extractie (FR-3) haalbaar.
  • SlpModularCms.Modules.Master volgt het patroon: Controllers/Services/ (I{X}Service/{X}Service) → Repositories/ (I{X}Repository/{X}Repository) → Data/{X}DbContext.cs, plus Models/ voor DTO's/requests en {Module}Module.cs (IModule-implementatie).

Uitvoeringschecklist

  • Stap A — components.md: componenten identificeren (CmsHost-extractie, Offerings-module met sub-componenten) met verantwoordelijkheden
  • Stap B — component-methods.md: methode-signaturen per component (geen gedetailleerde business rules — dat komt in Functional Design)
  • Stap C — services.md: servicedefinities en orkestratiepatronen (o.a. featured-exclusiviteit, reorder-logica uit de user stories)
  • Stap D — component-dependency.md: afhankelijkheidsmatrix + datastroom (Core ↔ Api ↔ Api.SlpSoftware ↔ Offerings)
  • Stap E — application-design.md: consolidatie van bovenstaande in één document
  • Stap F — Consistentiecontrole: komt het ontwerp overeen met requirements.md (FR-1..FR-9) en stories.md (US-01..US-12)?

Vragen

Vraag 1 — Vorm van de CmsHost-extractie (FR-3)

Api/Program.cs bevat, buiten de bootstrap-regels, geen project-specifieke logica. Dat maakt twee uitersten mogelijk voor de extractie.

Hoe ver moet de extractie naar SlpModularCms.Core gaan?

A) Eén volledig entrypointCmsHost.RunAsync(string[] args) bevat de hele samenstelling (services + pipeline + app.Run()); beide Program.cs-bestanden worden dan letterlijk een paar regels (return CmsHost.RunAsync(args); + evt. bootstrap-overrides). Minimaliseert duplicatie/drift maximaal, maar geeft een individueel project weinig ruimte om ooit af te wijken zonder de gedeelde methode te wijzigen. B) Twee gedeelde methodesCmsHost.ConfigureServices(WebApplicationBuilder) en CmsHost.ConfigurePipeline(WebApplication), die elk project vanuit zijn eigen dunne Program.cs aanroept (zoals vandaag al met AddCoreInfrastructure etc. gebeurt, maar dan als één samengestelde aanroep per fase). Iets meer code per project, maar elk project behoudt een zichtbaar Program.cs waarin het makkelijk is om ooit één stap toe te voegen/over te slaan zonder Core te wijzigen. X) Anders (beschrijf hieronder na de Answer:-tag)

Vraag 2 — Architectuurpatroon voor de Offerings-module

Modules.Master gebruikt een Repository+Service-laag (ICmsInstanceRepositoryICmsInstanceService → Controller). Bij Requirements Analysis heb je de Property-Based Testing-extensie overgeslagen met als reden dat dit een eenvoudige CRUD-achtige module is zonder significante bedrijfslogica.

Moet de Offerings-module hetzelfde Repository+Service-patroon volgen (consistent met Master), of is dat voor deze module onnodige indirectie?

A) Repository+Service (consistent met Master) — IOfferingRepository + IOfferingsService, ook al is de module zelf simpel B) Alleen Service, geen Repository — IOfferingsService praat direct met OfferingsDbContext (minder indirectie voor een module die je zelf als eenvoudige CRUD hebt gekarakteriseerd) X) Anders (beschrijf hieronder na de Answer:-tag)

Vraag 3 — Controllersplitsing publiek vs. admin

Requirements.md scheidt al duidelijk het publieke GET /api/v1/offerings (FR-6, anoniem) van de admin-CRUD (FR-7, AdminOnly).

Moet dit ook twee aparte controllers worden, of één controller met gemengde autorisatie per actie?

A) Twee controllers — OfferingsController (publiek, alleen GET) en OfferingsAdminController (CRUD + reorder, AdminOnly) — duidelijke scheiding, moeilijker om per ongeluk een admin-actie anoniem te laten B) Eén controller — OfferingsController met [AllowAnonymous] op de publieke GET en [Authorize(Policy = "AdminOnly")] op de rest X) Anders (beschrijf hieronder na de Answer:-tag)

Vraag 4 — Id-strategie voor nieuw aangemaakte offerings

De drie bestaande referentiewaarden (FR-8) gebruiken leesbare slugs (pakket_01, pakket_02, pakket_03). Het publieke contract (FR-6) verwacht een string-veld id, dus zowel een GUID als een handmatige slug is technisch mogelijk.

Hoe moet de Id van een nieuw aangemaakte offering tot stand komen?

A) Automatisch gegenereerd (GUID as string) — simpel, geen validatie op uniekheid/formaat nodig, consistent met andere entiteiten in dit systeem (bijv. CmsInstance.Id) B) Door de CMS Administrator zelf opgegeven als leesbare slug — consistent met de bestaande pakket_XX-stijl, vereist wel validatie (uniek, toegestane tekens) X) Anders (beschrijf hieronder na de Answer:-tag)

Vraag 5 — Verwijdersemantiek

Requirements.md signaleert een nog open punt bij SECURITY-13 (audit-trail op content-mutaties). US-06/US-07 beschrijven "verwijderen" zonder te specificeren of dat een echte database-delete is of een soft-delete.

Hoe moet "een offering verwijderen" op databaseniveau werken?

A) Hard delete — de rij wordt echt verwijderd uit OfferingsDbContext; simpelst, maar draagt niet bij aan het SECURITY-13-openpunt B) Soft delete — een IsDeleted/DeletedAt-veld, verwijderde offerings worden uit alle queries gefilterd maar blijven in de database staan; simpele, gedeeltelijke invulling van het SECURITY-13-openpunt (geen volledige audit trail, maar wel behoud van de laatste staat vóór verwijdering) X) Anders (beschrijf hieronder na de Answer:-tag)