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

79 lines
4.8 KiB
Markdown

# Component Dependency — SlpSoftware Production API
## Dependency Matrix
| Component | Depends On | Communication Pattern |
|---|---|---|
| `SlpModularCms.Api.SlpSoftware` (`Program.cs`) | `CmsHost` (Core), `ModuleOrchestrator` (Core) | Direct method calls (`ConfigureServices`/`ConfigurePipeline`) at startup |
| `SlpModularCms.Api` (`Program.cs`) | `CmsHost` (Core), `ModuleOrchestrator` (Core) | Same as above — unchanged behavior, now via the shared method instead of inline code |
| `CmsHost` | Existing `Core.Hosting.*` extension methods (`AddCoreInfrastructure`, `AddCmsCors`, `AddCmsRateLimiting`, `AddCmsHealthChecks`, `AddCmsSecurityHeaders`, `AddCmsObservability`, `AddCmsDataProtection`, `AddCmsLogging`, `UseCmsSentry`, `UseCmsSecurityHeaders`, `UseCmsStaticContent`, `MapCmsHealthChecks`, `MapSentryTunnel`, `MapCmsSpaFallbacks`, `MigrateCoreDatabase`) | Direct method calls — no new dependencies introduced, purely re-composing existing ones |
| `OfferingsController` | `IOfferingsService` | Constructor-injected interface (DI) |
| `IOfferingsService` (`OfferingsService`) | `IOfferingRepository` | Constructor-injected interface (DI) |
| `IOfferingRepository` (`OfferingRepository`) | `OfferingsDbContext` | Constructor-injected `DbContext` (DI, scoped) |
| `OfferingsDbContext` | MariaDB (`DefaultConnection`) | EF Core, `UseMySQL`, TLS-enforced connection string (SECURITY-01) |
| `OfferingsModule` | `OfferingsDbContext`, `IOfferingRepository`/`OfferingRepository`, `IOfferingsService`/`OfferingsService` | DI registration (`RegisterServices`) + migration application (`UseModule`) |
| `SlpModularCms.Api.SlpSoftware` | `SlpModularCms.Modules.Offerings`, `.Identity`, `.Availability`, `.Master` (project references) | Module DLLs discovered dynamically by `ModuleOrchestrator` at runtime — **not** an explicit list in code |
| `SlpModularCms.Api` | `SlpModularCms.Modules.Identity`, `.Availability`, `.Master` (project references — **no** `.Offerings` reference) | Same discovery mechanism; `Api` never loads `Offerings` because it never references that project |
## Data Flow
```mermaid
sequenceDiagram
box rgba(246,224,94,0.4) Website
participant Visitor as Site Visitor
end
box rgba(159,122,234,0.4) External Frontend
participant FE as React Frontend
end
box rgba(99,179,237,0.4) Api.SlpSoftware
participant Ctrl as OfferingsController
participant Svc as OfferingsService
participant Repo as OfferingRepository
participant DB as OfferingsDbContext
end
Visitor->>FE: Loads website
FE->>Ctrl: GET /api/v1/offerings
Ctrl->>Svc: GetPublicOfferingsAsync()
Svc->>Repo: GetAllAsync()
Repo->>DB: query non-deleted, order by DisplayOrder
DB-->>Repo: Offering rows
Repo-->>Svc: List<Offering>
Svc-->>Ctrl: List<OfferingDto>
Ctrl-->>FE: 200 OK, JSON array
FE-->>Visitor: Renders offering cards
```
Text alternative: a Site Visitor's browser loads the frontend, which calls the public `GET /api/v1/offerings` endpoint; the request flows Controller → Service → Repository → DbContext and the resulting offerings flow back up the same chain to render as cards (yellow = visitor-facing website, purple = the external frontend app, blue = the new Api.SlpSoftware backend components).
```mermaid
sequenceDiagram
box rgba(246,224,94,0.4) Admin
participant Admin as CMS Administrator
end
box rgba(99,179,237,0.4) Api.SlpSoftware
participant Ctrl as OfferingsController
participant Svc as OfferingsService
participant Repo as OfferingRepository
participant DB as OfferingsDbContext
end
Admin->>Ctrl: POST /api/v1/offerings/admin (AdminOnly)
Ctrl->>Svc: CreateAsync(request)
Svc->>Repo: GetFeaturedAsync()
Repo-->>Svc: currently-featured Offering (or none)
Svc->>Repo: UpdateAsync(previous featured to unfeature, if any)
Svc->>Repo: AddAsync(new Offering)
Repo->>DB: persist changes
DB-->>Repo: saved Offering
Repo-->>Svc: Offering
Svc-->>Ctrl: OfferingAdminDto
Ctrl-->>Admin: 201 Created
```
Text alternative: a CMS Administrator's create request flows through the same layered chain, with the Service first checking for and clearing any existing featured offering before persisting the new one, enforcing the exactly-0-or-1-featured rule from US-10 (yellow = the admin actor, blue = the new backend components).
## Notes
- `SlpModularCms.Core` is the shared dependency for both Client projects (`CmsHost`) but has **no** dependency in the other direction — `Core` does not reference `Modules.Offerings` or any other module, preserving the existing module-isolation pattern.
- The Gitea Actions pipeline (external to the application dependency graph) is not shown here — its retargeting (FR-9, D-15) is an Operations-phase, deployment-time concern, not an application-level dependency.