Adds SlpModularCms.Api.SlpSoftware and extracts shared CmsHost composition
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

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
This commit is contained in:
2026-08-02 01:28:39 +02:00
co-authored by Claude Sonnet 5
parent dcc82cdf62
commit fa389e42ee
51 changed files with 3119 additions and 127 deletions
@@ -0,0 +1,57 @@
# Code Generation Summary — Unit: SlpSoftware Client Setup
## Moved (brownfield relocation, Step 1)
- `src/SlpModularCms.Api/Extensions/StaticContentExtensions.cs``src/SlpModularCms.Core/Hosting/StaticContentExtensions.cs` (namespace `SlpModularCms.Api.Extensions``SlpModularCms.Core.Hosting`; embedded-resource name updated to match)
- `src/SlpModularCms.Api/Extensions/WebsitePlaceholder.html``src/SlpModularCms.Core/Hosting/WebsitePlaceholder.html`
- Deleted the now-empty `src/SlpModularCms.Api/Extensions/` directory
## Created
- `src/SlpModularCms.Core/Hosting/CmsHostOptions.cs` — empty extension-point class (NFR Design Pattern 2)
- `src/SlpModularCms.Core/Hosting/CmsHost.cs``ConfigureServices`/`ConfigurePipeline`, the shared composition both Client projects call
- `src/SlpModularCms.Api.SlpSoftware/` — new Client project: `.csproj`, `Program.cs`, `appsettings.json`, `appsettings.Development.json` (own isolated local dev database, Infrastructure Design Q1 = B), `Properties/launchSettings.json` (ports 5286/7222, distinct from `Api`'s 5284/7221 so both can run simultaneously)
- `src/SlpModularCms.Api.Tests/` — new test project: `.csproj`, `PipelineTests.cs` (4 tests: `/health` reachable, required security headers present on an HTML response with HSTS's Development-only absence explicitly asserted, `/admin` fallback 404s correctly with no build present, the `sentry-tunnel` rate limiter engages past its configured permit limit)
## Modified
- `src/SlpModularCms.Api/Program.cs` — rewritten to the thin form calling `CmsHost.ConfigureServices`/`ConfigurePipeline`; no behavior change
- `src/SlpModularCms.Api/SlpModularCms.Api.csproj` — removed the `WebsitePlaceholder.html` embedded-resource item (moved to `Core`)
- `src/SlpModularCms.Core/SlpModularCms.Core.csproj` — added the `WebsitePlaceholder.html` embedded-resource item
- `SlpModularCms.sln` — added `SlpModularCms.Api.SlpSoftware` (nested under `Clients`) and `SlpModularCms.Api.Tests` (nested directly under `Tests`, mirroring `Core.Tests`'s placement)
- `README.md` — documented the new project, `CmsHost`, and the local dev database isolation
## Not Created (explicit N/A, per the plan)
- No `Program.Coverage.cs` for `Api.SlpSoftware` — no pipeline tests target it in this unit (Q1 = C)
- No `appsettings.local.json` for `Api.SlpSoftware` — git-ignored, personal-per-developer, created locally when needed
- No API/Repository layer code, no database migrations — this unit has neither (Unit 2 "Offerings" does)
- No `.gitea/workflows/*.yaml` or Gitea Actions variable changes — Operations-phase scope (D-7/D-15)
## Post-Completion Fix: Dev Port Collision + Frontend Dev Script
Prompted by a user question about a frontend `pnpm dev:slpsoftware` script, investigation surfaced a real bug: `Api.SlpSoftware`'s `launchSettings.json` had been assigned HTTPS port **7222**, which is already `SlpModularCms.Api.Slave`'s port (confirmed via `frontend/.env.example` and `Api.Slave/Properties/launchSettings.json`). Fixed:
- `src/SlpModularCms.Api.SlpSoftware/Properties/launchSettings.json` — HTTPS port 7222 → **7223** (HTTP 5286 unchanged, already free)
- `src/SlpModularCms.Api.SlpSoftware/appsettings.Development.json``MasterModule:MasterUrl` updated to match (`https://localhost:7223`)
- `README.md` — corrected port note, with an explicit call-out of the 7222 collision reason
Added (frontend, not part of the original per-unit plan, but a direct, symmetrical follow-up to the port fix):
- `frontend/package.json``dev:slpsoftware` script (`vite --mode slpsoftware --port 5175`), mirroring `dev:slave`
- `frontend/.env.example` — documented the `.env.slpsoftware.local` pattern (`VITE_API_BASE_URL=https://localhost:7223`, `VITE_APP_TITLE=SlpModularCms (SlpSoftware)`)
## Post-Completion Fix: CORS Origin Mismatch Blocked the Setup Page
`Api.SlpSoftware/appsettings.Development.json`'s `Cors:AllowedOrigins` had been copied from `Api`'s own file verbatim, listing `localhost:5173` — but `pnpm dev:slpsoftware` serves the frontend on port **5175**. The mismatch caused the frontend's `/api/v1/Setup/status` check to fail on CORS, and `router.tsx`'s `InitGuard` fails open toward the login page (not setup) on any fetch error — so the setup page never appeared, even against a genuinely empty database (verified directly via the local MariaDB, not assumed). Fixed: `Cors:AllowedOrigins` now lists `localhost:5175`.
## Post-Completion Fix: GlobalExceptionHandler Logged Expected 401s as Errors
Pre-existing bug, unrelated to this feature's own scope (not introduced by the `CmsHost` extraction) but surfaced by testing the new instance: `src/SlpModularCms.Core/Exceptions/GlobalExceptionHandler.cs` logged every exception at `LogError` before mapping it to a status code, so a routine `UnauthorizedException` (e.g. an invalid/missing refresh token — the normal state for a fresh, never-logged-in session) was indistinguishable from a genuine unhandled 500 in the console and in Sentry. Fixed: exceptions that map to a status below 500 now log at `LogWarning`; only genuinely unmapped exceptions (500) log at `LogError`. Affects every Client project equally (`Api`, `Api.Slave`, `Api.SlpSoftware`), not just this feature. No existing test asserted log level; full suite reran green (196 + 4).
## Build and Test Verification (Step 13.5)
Two real build fixes were needed and applied during this step (not deviations from the plan — the plan didn't anticipate these, since they only surface once the code actually compiles):
1. `SlpModularCms.Core.csproj` was missing a `Scalar.AspNetCore` package reference — `CmsHost.ConfigurePipeline` calls `MapScalarApiReference()`, which moved into `Core` along with the rest of the pipeline composition but the package reference hadn't followed it. Added.
2. `CmsHost.cs` and the moved `StaticContentExtensions.cs` were missing explicit `using Microsoft.Extensions.Hosting;` / `using Microsoft.Extensions.DependencyInjection;``Core` is an `Microsoft.NET.Sdk` project (not `Sdk.Web`), so it does not get ASP.NET Core's implicit usings the way `Api` (`Sdk.Web`) did. Added both.
After both fixes:
- **Build**: ✅ Success (full solution, all 13 projects)
- **`SlpModularCms.Core.Tests`**: ✅ 196 passed, 0 failed — no regression from the Step 1 file move
- **`SlpModularCms.Api.Tests`** (new): ✅ 4 passed, 0 failed — `/health` reachable, required security headers present on an HTML response (with HSTS's Development-only absence correctly asserted, not overlooked), `/admin` fallback 404s correctly with no build present, the `sentry-tunnel` rate limiter engages past its configured permit limit
**Local environment note**: running the new tests required a local MariaDB (the same one `Api` itself needs to run, per the README). It was stopped; started it via `podman machine start` + `podman start mariadb` for this verification, and left both running afterward for continued local development (including Unit 2 "Offerings").