From 88770c5bd0694849c591d87dcebcc0bdaf984176 Mon Sep 17 00:00:00 2001 From: Sluijsens Date: Tue, 28 Jul 2026 16:30:19 +0200 Subject: [PATCH] =?UTF-8?q?U7=20=E2=80=94=20tells=20a=20website=20builder?= =?UTF-8?q?=20what=20they=20need=20to=20know?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WEBSITE_WORKSPACE.md, plus the parts of the README that were still describing the old layout or a manual step the code doesn't need anymore (the key-ring paragraph, mainly - that one was actively wrong now, not just stale). --- README.md | 23 ++++-- WEBSITE_WORKSPACE.md | 80 +++++++++++++++++++ .../gitea-deployment-workflow/audit.md | 33 ++++++++ .../u7-documentation-code-generation-plan.md | 63 +++++++++++++++ .../code/generation-summary.md | 45 +++++++++++ frontend/.env.example | 20 +++++ 6 files changed, 258 insertions(+), 6 deletions(-) create mode 100644 WEBSITE_WORKSPACE.md create mode 100644 aidlc-docs/features/gitea-deployment-workflow/construction/plans/u7-documentation-code-generation-plan.md create mode 100644 aidlc-docs/features/gitea-deployment-workflow/construction/u7-documentation/code/generation-summary.md diff --git a/README.md b/README.md index 37bb3fa..f1ed400 100644 --- a/README.md +++ b/README.md @@ -286,20 +286,26 @@ Op een shared-hosting omgeving (zoals mijnhostingpartner.nl) is er meestal maar | Pad | Inhoud | |---|---| -| `/` | De publieke website van de klant — **geen onderdeel van deze repo**, wordt los aangeleverd/gedeployed in `wwwroot/` | +| `/` | De publieke website van de klant, uit `wwwroot/web/` — **geen onderdeel van deze repo**. Zie [`WEBSITE_WORKSPACE.md`](WEBSITE_WORKSPACE.md) voor het volledige contract (verplichte structuur, verboden/gereserveerde paden, SPA-fallback, same-origin API-calls, CSP, Umami) | | `/admin` | De CMS admin-UI (`frontend/`), gebouwd met `base: '/admin/'` en automatisch gekopieerd naar `wwwroot/admin/` bij `dotnet publish` | | `/api/v1/...` | Deze API | +| `/health` | Infrastructuur-liveness — zie "Health-check endpoint" hieronder | Beide SPA's krijgen een fallback naar hun eigen `index.html` zodat client-side routes (bijv. `/admin/dashboard`) werken; ontbrekende bestanden (met een extensie, bijv. `/admin/assets/x.js`) blijven gewoon 404'en. Zie `Program.cs` (`MapFallbackToFile`) en `SlpModularCms.Api.csproj` (`BuildAndCopyAdminFrontend`-target). +Ontbreekt `wwwroot/web/` nog (een verse deployment vóórdat een website-workspace er iets in heeft gezet), dan start de applicatie gewoon door en toont `/` een ingebouwde placeholder-pagina — `/admin` en `/api/v1` blijven onverminderd werken. + Voor lokale ontwikkeling verandert er niets: `pnpm dev` blijft op `http://localhost:5173` draaien zonder `/admin`-prefix. ### 1. Build & Publish -Compileer de applicatie voor productie — dit bouwt en kopieert automatisch ook de admin-frontend naar `wwwroot/admin/`: +Publiceren gebeurt via de Gitea Actions-pipeline (`.gitea/workflows/continuous_integration.yaml` → `deploy-scp.yaml`), niet met een losse handmatige `dotnet publish`-stap: elke push naar `master` deployt automatisch naar test, productie alleen via een expliciete `workflow_dispatch` met de `deploy_production`-vlag aan. De pipeline publiceert framework-dependent voor `linux-arm64` en bouwt daarbij automatisch ook de admin-frontend mee naar `wwwroot/admin/`. + +Elke deploy plaatst de nieuwe release in een verse map en wisselt pas daarna atomisch over naar `current` — er is dus nooit een moment waarop de applicatie een halfklare release serveert, en `wwwroot/web/` (de klantsite) staat structureel buiten die verwisselde map, zodat een deploy hem nooit kan raken. Volledige host-setup, rollback-procedure en de exacte deploy-stappen staan in de Operations-documentatie van deze feature, niet hier — dit README beschrijft alleen wat er gebouwd is, niet hoe je het voor het eerst inricht. + +Wil je toch lokaal handmatig publiceren (bijv. om de output te inspecteren): ```powershell -dotnet publish src/SlpModularCms.Api -c Release -o ./publish +dotnet publish src/SlpModularCms.Api -c Release -r linux-arm64 --self-contained false -o ./publish ``` -Kopieer daarna de publieke website van de klant naar `./publish/wwwroot/` (alles behalve de `admin/`-submap, die blijft ongemoeid). ### 2. Runtime Configuratie In productie moeten gevoelige instellingen worden doorgegeven via Environment Variables: @@ -308,9 +314,14 @@ In productie moeten gevoelige instellingen worden doorgegeven via Environment Va - `JwtSettings__Issuer` - `JwtSettings__Audience` - `MasterModule__MasterUrl` — publieke URL van deze master-instantie (alleen relevant als de Master CMS Module actief is) +- `Observability__SentryDsn` — leeg is een normale, ondersteunde staat: Sentry wordt dan overgeslagen en alleen console-logging blijft actief +- `SecurityHeaders__AllowedScriptOrigins__0`, `SecurityHeaders__AllowedConnectOrigins__0`, ... — externe origins die de CSP van `/admin` en `/api/v1` mag toestaan (bijv. de Umami-scripthost en Sentry's ingest-endpoint). Zie `appsettings.json → SecurityHeaders` voor de volledige optiesvorm; een onbekende `PathPolicies`-policy-naam laat de applicatie bij **opstarten** falen, niet pas bij de eerste request ### 2a. Data Protection key ring (Master CMS Module) -De API keys van geregistreerde slaves worden versleuteld opgeslagen met ASP.NET Core Data Protection, standaard met een bestandssysteem-key-store. Voor gecontaineriseerde of multi-instance deployments **moet** een persistente key ring geconfigureerd worden (bijv. `PersistKeysToDbContext` of `PersistKeysToAzureBlobStorage`). Zonder dit worden alle opgeslagen API keys onleesbaar zodra de container herstart, waardoor master↔slave-communicatie stopt totdat instanties opnieuw worden toegevoegd. +De API keys van geregistreerde slaves worden versleuteld opgeslagen met ASP.NET Core Data Protection. De key ring wordt **automatisch** persistent opgeslagen in de database (`PersistKeysToDbContext`, met een expliciete, stabiele applicatie-discriminator) — dit hoeft niet meer apart geconfigureerd te worden. Dit is bewust zo gebouwd omdat de atomische release-switch (zie hierboven) bij elke deploy een nieuwe content-root-map gebruikt: zonder een database-backed key ring zou dat elke keer de key ring weggooien en alle opgeslagen slave-API-keys onleesbaar maken. ### 3. Database -Zorg dat de doeltabel bestaat en de migraties zijn uitgevoerd. In productie kan dit via een CI/CD pipeline worden afgehandeld met `dotnet ef migrations script` of door de applicatie bij startup migraties te laten draaien (indien geconfigureerd). +Migraties op `ApplicationDbContext` (Identity/Core) worden bij het opstarten **automatisch** toegepast (`MigrateCoreDatabase()`), en falen hard (fail-fast) als dat niet lukt — de applicatie start dan bewust niet door. Voor productie-deploys wordt vóór elke deploy een databasebackup genomen; zie de Operations-documentatie van deze feature voor de exacte procedure. Per-module migraties (`Modules.Master`, `Modules.Availability`) worden zoals voorheen automatisch toegepast via `UseModule` (zie "Per-module migraties" hierboven) — daar verandert niets aan. + +### 4. Health-check endpoint +`GET /health` geeft alleen **infrastructuur-liveness** aan ("draait het proces en kan het een basale response geven") — géén databasecheck, om te voorkomen dat het endpoint rood kleurt om redenen die niets met "leeft het proces nog" te maken hebben. Dit is expliciet **iets anders** dan `GET /api/v1/Availability/status` (de CMS-eigen aan/uit-schakelaar) of `GET /api/v1/System/capabilities` — een instantie kan gezond zijn en toch bewust uitgeschakeld, en andersom. `/health` staat op de bypass-lijst van `AvailabilityMiddleware`, zodat een uitgeschakelde instantie dit endpoint altijd blijft beantwoorden. diff --git a/WEBSITE_WORKSPACE.md b/WEBSITE_WORKSPACE.md new file mode 100644 index 0000000..8df6f6e --- /dev/null +++ b/WEBSITE_WORKSPACE.md @@ -0,0 +1,80 @@ +# Website Workspace Contract + +Dit document is voor wie de publieke website bouwt die naast de SlpModularCms-admin draait. Je hoeft +de rest van deze repository niet te lezen om een werkende site te kunnen opleveren — dit contract is +compleet genoeg om zelfstandig te volgen. + +## Doelpad + +De website hoort in **`wwwroot/web/`**, in de root van de gepubliceerde applicatie. Dit pad **moet** +minimaal een `index.html` bevatten. Alles onder `wwwroot/web/` is van jou — de applicatie zelf raakt +deze map nooit aan buiten het plaatsen van de bestanden die je aanlevert, en een CMS-deploy verwijdert +of overschrijft de inhoud nooit (zie "Waarom dit veilig is" hieronder). + +``` +wwwroot/ + web/ ← jouw site komt hier (dit contract) + index.html ← verplicht + assets/... + ... + admin/ ← VERBODEN — dit is de CMS admin-UI, hoort niet bij deze repo +``` + +## Verboden en gereserveerde paden + +**Verboden** — plaats hier nooit bestanden: +- `wwwroot/admin/` — dit is de CMS admin-single-page-app, wordt door deze repository zelf beheerd + en bij elke build overschreven +- De applicatie-root zelf (waar de `.dll`-bestanden van de API staan) + +**Gereserveerd** — deze paden bestaan al en je site mag er niet mee botsen: +- `/admin` — de CMS admin-UI +- `/api/v1` — de backend-API +- `/health` — infrastructuur-liveness-check (zie hieronder — dit is geen CMS-functionaliteit) + +Als jouw site een eigen route of bestand op een van deze paden zou plaatsen, wint de gereserveerde +route altijd. + +## Routing (SPA-fallback) + +Voor paden die **geen bestandsextensie** hebben (bijv. `/over-ons`, `/producten/123`) valt de +applicatie terug op **`wwwroot/web/index.html`** — zo werkt client-side routing (React Router, Vue +Router, of vergelijkbaar) zoals verwacht. Voor paden die er wél uitzien als een bestand (bijv. +`/assets/logo.png`) geldt geen fallback: ontbreekt het bestand, dan krijg je gewoon een `404`, niet +per ongeluk de `index.html`. + +Dit betekent: bouw je een Single Page Application, dan hoeft je routing-configuratie niets speciaals +te doen voor deze server — de fallback wordt door de applicatie zelf verzorgd. + +## De API aanroepen + +Roep `/api/v1/...` aan met **relatieve URL's** (bijv. `fetch('/api/v1/System/capabilities')`). Omdat +je site en de API door hetzelfde proces op dezelfde origin worden geserveerd, is dit een **same-origin +request** — er is geen CORS-configuratie nodig, en er hoeft niets ingesteld te worden om dit te laten +werken. + +## Content-Security-Policy + +Jouw site valt onder het **`Relaxed`**-beleid (de standaardpolicy voor alle paden die niet expliciet +`Strict` zijn — `/admin`, `/api/v1` en `/health` krijgen `Strict`, `/` (jouw site) niet). Dit beleid is +bewust minder streng, zodat je niet gebonden bent aan restricties die voor de CMS-admin gelden maar +die je als website-bouwer nooit zou hoeven kennen. De exacte permissieve/strikte policy-definities +staan in code (`SlpModularCms.Core`), niet in configuratie — je hoeft ze niet zelf te lezen om te +weten dat je site onder het permissieve beleid valt. + +## Umami-analytics insluiten + +Als de instantie analytics gebruikt, wordt het Umami-trackingscript geladen via een build-time +omgevingsvariabele op de **admin**-kant (`VITE_UMAMI_SCRIPT_URL` / `VITE_UMAMI_WEBSITE_ID`) — dat +script wordt dus niet door jouw site zelf ingesloten. Wil je dat jouw website ook gemeten wordt via +dezelfde Umami-instantie, vraag dan de scriptregel en het bijbehorende website-ID op bij wie de CMS +beheert, en neem die regel zelf op in je `index.html` (Umami's standaard `