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
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 inProgram.cs. Dat betekent: welke modules een Client-project host, wordt volledig bepaald door welke Module-projecten dat.csprojreferenceert, niet door code inProgram.cszelf.SlpModularCms.Api/Program.csbevat, 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.Mastervolgt het patroon:Controllers/→Services/(I{X}Service/{X}Service) →Repositories/(I{X}Repository/{X}Repository) →Data/{X}DbContext.cs, plusModels/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 entrypoint — CmsHost.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 methodes — CmsHost.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 (ICmsInstanceRepository → ICmsInstanceService → 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)