Adds 2 units and docs for unit 3. nfr-requirements plan

This commit is contained in:
2026-06-29 22:18:37 +02:00
parent 0e01ca1e1c
commit c156107cb1
126 changed files with 15204 additions and 80199 deletions
@@ -0,0 +1,71 @@
# Requirements Clarification Questions — Master CMS Module (Round 2)
Your answers were very clear on the overall model. A few follow-up questions to resolve remaining ambiguities before generating the requirements document.
---
## Clarification 1 — How does the slave CMS know the Master's URL?
Your Q4 answer says the client cannot configure this. But the slave still needs to know where to pull availability status from.
How is the Master URL configured on the slave CMS?
A) Developer sets it in the slave's `appsettings.json` at deployment time (e.g., `MasterModule:MasterUrl`) — clients see the config file but cannot change it via the UI
B) Environment variable only — completely invisible to the client in normal deployments
C) The Master registers itself with the slave at first connection — no manual config needed on slave side
D) Other (please describe after [Answer]: tag below)
[Answer]: C, but I want something to prevent the client from changing the url and this the slave being unable to contact the master. So the master should be able to check whether everything is still stored correctly. We might need an extra service that runs as a cronjob or something similar.
---
## Clarification 2 — Authentication between Master and Slave
When the Master calls the slave's `PUT /api/availability/status` to set its availability, how does it authenticate?
A) The API key stored in the Master's `CmsInstance` record is sent as a header — the slave validates it as a special "master key"
B) The Master uses a JWT token from the slave (Owner account credentials stored in the Master's DB)
C) The slave exposes a separate unauthenticated (but secret-URL-protected) internal endpoint for this
D) A shared API key configured in both Master and slave `appsettings.json`
E) Other (please describe after [Answer]: tag below)
[Answer]: A
---
## Clarification 3 — Slave Availability Cache Duration
Your Q3 answer mentions the slave caches the Master's availability status to reduce traffic. How long should this cache be valid?
A) Configurable — set in the slave's `appsettings.json` (e.g., `MasterModule:CacheMinutes: 60`)
B) Fixed at a reasonable default (e.g., 60 minutes) — no configuration needed
C) Session-based — re-check when the application restarts or a specific event occurs
D) Other (please describe after [Answer]: tag below)
[Answer]: A, but B as a fallback.
---
## Clarification 4 — Slave's Fallback Behavior When Master is Unreachable
Your Q3 answer says: if the Master is unavailable, the slave falls back to its own DB value. What should the default value in the slave DB be before the Master has ever contacted the slave?
A) `Available` — default to open; the Master will disable it if needed
B) `NotAvailable` — default to closed; the Master must explicitly enable it after registration
C) Configurable per slave registration in the Master's `CmsInstance` record
D) Other (please describe after [Answer]: tag below)
[Answer]: A, this whole feature is a safety measure, but it should not block the client from doing anything when something doesn't work correctly.
---
## Clarification 5 — Master UI: Adding / Removing Slave CMSes
On the `/cms` page the Owner manages slave CMS registrations. What actions should be available?
A) Add (name + URL + API key), view list with current status, toggle Available/NotAvailable, remove
B) Add (name + URL + API key), view list with status — no remove (registrations are permanent)
C) Full CRUD: add, edit (name/URL/key), view list with status, toggle, remove
D) Other (please describe after [Answer]: tag below)
[Answer]: B, but also option to set status Available/NotAvailable/Inactive where Inactive makes it greyed out meaning the CMS is no longer used. Also the option to set a message when the master disables a slave. That should be mandatory.
@@ -0,0 +1,125 @@
# Requirements Clarification Questions — Master CMS Module
Please answer each question by filling in the letter choice after the `[Answer]:` tag.
If none of the options match your needs, choose the last option (Other) and describe your preference.
---
## Question 1
The CMS page (`/cms`) currently exists in the frontend but shows no content. What should the Master Module display on this page?
A) A list of registered slave CMS instances with their current availability status and controls to enable/disable them
B) A dashboard combining both slave CMS management and module configuration (e.g., toggle Master Module on/off)
C) Only module configuration — the slave CMS list is managed elsewhere (e.g., a separate admin API)
D) Other (please describe after [Answer]: tag below)
[Answer]: A
---
## Question 2
What are "other CMSes" in this context? How should slave CMS instances be registered with the Master?
A) Other deployed instances of the same SlpModularCms application — registered via URL + API key in the Master's database
B) Abstract "tenants" or "sites" stored in the Master's database — not necessarily running SlpModularCms
C) The slave CMSes share the same database as the Master — just different data rows (multi-tenant, single DB)
D) Other (please describe after [Answer]: tag below)
[Answer]: A
---
## Question 3
When the Master Module sets a slave CMS as "unavailable", how does the slave CMS enforce this?
A) The slave CMS calls the Master's API on every request to check if it is still available (pull model)
B) The Master pushes availability status to the slave CMS (webhook / push model)
C) The slave CMS and Master share a database — the slave reads the Master-controlled status directly from a shared table
D) The Master sets availability via the slave's own availability API endpoint (the existing `PUT /api/availability/status`)
E) Other (please describe after [Answer]: tag below)
[Answer]: E, probably a combination of A and D. For context: As a developer I have clients using the slave CMSes. In the event the client doesn't pay or violate another agreement I want to have the possibility to disable the slave CMS. To prevent the client from circumventing this by editing a value in their own database I want it to pull it from the master. But in the event the master is unavailable it is also stored in their own DB. The slave CMS can also just check their own value to prevent excessive traffic to the master and just get the status only a few times or once per session within a few hours for example.
---
## Question 4
The existing availability middleware currently checks a local `IAvailabilityService`. How should the middleware on a slave CMS know to consult the Master instead of its local service?
A) A new configuration flag in `appsettings.json` (e.g., `MasterModule:MasterUrl`) — if set, the slave uses the Master; if not set, local check
B) The Master Module is installed on the slave CMS too but in "slave mode" — it overrides the local availability service
C) A separate middleware or service replaces the existing one when a Master URL is configured
D) Other (please describe after [Answer]: tag below)
[Answer]: D, The availability check should be hardcoded to check the master if the master module is enabled, but it should also keep sits current functionality. The master module should be enabled by the owner of the master CMS and not by the client. The client should not be able to configure this.
---
## Question 5
The CMS that has the Master Module enabled should be exempt from the external availability check. How should this exemption be implemented?
A) Configuration-based: a flag in `appsettings.json` (e.g., `MasterModule:IsMaster: true`) bypasses the external check entirely
B) Auto-detected: if the Master Module is registered and active, skip the external check automatically
C) The Master CMS still has a local availability check (its own `IAvailabilityService`) but ignores external Master checks
D) Other (please describe after [Answer]: tag below)
[Answer]: B + C
---
## Question 6
Which roles can use the Master Module features (viewing/managing slave CMSes)?
A) Owner only
B) Owner and Administrator
C) All authenticated users can view; only Owner can modify
D) Other (please describe after [Answer]: tag below)
[Answer]: A
---
## Question 7
Does the Master Module require changes to the existing availability middleware (`AvailabilityMiddleware`) in the `Availability` module, or should it be implemented as a new separate module?
A) Extend the existing Availability module — add Master Module logic there
B) Create a new separate module `SlpModularCms.Modules.Master` that works alongside the Availability module
C) Create a new module that REPLACES the Availability module on Master CMS instances
D) Other (please describe after [Answer]: tag below)
[Answer]: B, it should be a separate module, because only the master CMS should install the module. The slaves should not have the module installed and should still be able to use the current functionality.
---
## Question 8
Should the Master Module include a new database entity to store slave CMS registrations (name, URL, current availability status), or reuse an existing entity?
A) Yes — new `CmsInstance` entity in the database (name, URL, status, last updated)
B) New entity but only in-memory / configuration — no database persistence for slave CMSes
C) Reuse the existing `GlobalAvailabilityState` concept, extended with a multi-tenant key
D) Other (please describe after [Answer]: tag below)
[Answer]: A
---
## Question 9
For the frontend: the `/cms` route is currently restricted to the `Owner` role. Should this change?
A) No change — keep `/cms` Owner-only
B) Allow `Administrator` role as well (read-only or full access)
C) Allow any authenticated user to view, but restrict modifications to Owner
D) Other (please describe after [Answer]: tag below)
[Answer]: A
---
## Question 10
Should the Master Module be part of this same codebase (monorepo), or is it a separate deployment concern?
A) Same codebase — a new project `SlpModularCms.Modules.Master` within the existing solution
B) Same codebase AND the frontend changes to show the Master UI are included in this feature
C) Backend only — frontend changes are a separate follow-up feature
D) Other (please describe after [Answer]: tag below)
[Answer]: B
@@ -0,0 +1,169 @@
# Requirements — Master CMS Module
## Intent Analysis
- **User Request**: Add a new "Master" module that fills the `/cms` page, allows the owner to manage the availability of registered slave CMS instances, and integrates with the existing availability check mechanism — while exempting the Master CMS itself from external checks.
- **Request Type**: New Feature
- **Scope Estimate**: Multiple Components — new backend module, new frontend UI, modifications to the existing availability flow on slave CMS instances
- **Complexity Estimate**: Complex — involves a Master/Slave network topology, bi-directional registration, background integrity checks, caching, fallback logic, and API key authentication
---
## System Context
The existing system (`SlpModularCms`) is a modular ASP.NET Core CMS. The Availability module manages system status via `IAvailabilityService` and `AvailabilityMiddleware`. Slave CMSes are other deployed instances of the same SlpModularCms application. The Master Module adds a control plane layer on top of the existing availability system.
---
## Functional Requirements
### FR-MASTER-01 — New Module: `SlpModularCms.Modules.Master`
A new separate module `SlpModularCms.Modules.Master` is created within the existing solution. This module is installed **only on the Master CMS**. Slave CMS instances do **not** install this module. The module registers its own services and HTTP endpoints following the existing `IModule` pattern.
### FR-MASTER-02 — `CmsInstance` Entity
A new `CmsInstance` database entity is added to `SlpModularCms.Core` with the following fields:
- `Id` (Guid, PK)
- `Name` (string, required) — friendly name for the slave CMS
- `Url` (string, required) — base URL of the slave CMS API
- `ApiKey` (string, required) — secret key used by the Master to authenticate against the slave
- `Status` (enum: `Available` / `NotAvailable` / `Inactive`) — the master-controlled availability state
- `DisableMessage` (string, nullable) — required when `Status = NotAvailable`; shown to end-users of the slave
- `LastContactedAt` (DateTimeOffset, nullable) — timestamp of the last successful master → slave contact
- `LastStatusPushedAt` (DateTimeOffset, nullable) — timestamp of the last status push to the slave
### FR-MASTER-03 — Auto-Registration: Master Registers Itself with Slave
When a slave is added to the Master's `CmsInstance` registry, the Master automatically contacts the slave and registers itself (pushes its own URL). The slave stores the master's URL in its local database. No manual configuration is required on the slave side to know the Master URL.
The slave exposes a dedicated internal registration endpoint (`POST /api/internal/master/register`) that accepts the Master's URL and API key. After successful registration, the slave uses the stored Master URL for all future availability pulls.
### FR-MASTER-04 — Integrity Check Service (Background Service)
A background service (periodic, configurable interval) runs on the Master CMS and periodically re-verifies that each registered slave still has the correct Master URL stored. If a slave's registered master does not match, the Master re-pushes its registration to that slave. This prevents clients from circumventing the master by removing or altering the stored master URL.
### FR-MASTER-05 — Status Push: Master Sets Slave Availability
When the Master owner changes a slave's status (to `Available` or `NotAvailable`), the Master immediately calls the slave's existing `PUT /api/availability/status` endpoint (or a dedicated internal endpoint) to push the new status. Authentication uses the `ApiKey` from the `CmsInstance` record, sent as an `X-Master-Api-Key` header.
### FR-MASTER-06 — Slave Pull Model: Periodic Master Check
The slave CMS periodically pulls its availability status from the Master. The pull interval is configurable in the slave's `appsettings.json` (`MasterModule:CacheMinutes`). If this value is not configured, a default of **60 minutes** is used. The pulled status is cached locally in memory.
### FR-MASTER-07 — Slave Fallback Behavior
If the Master CMS is unreachable when the slave attempts a pull:
- The slave falls back to the value currently stored in its **local database** (`GlobalAvailabilityState`).
- The local database value defaults to `Available` before any Master contact has occurred.
- This ensures the feature is a **fail-open** safety measure — clients are not blocked if the Master is down.
### FR-MASTER-08 — Slave Availability Middleware Integration
On a slave CMS, the availability check becomes a **two-phase gate**. The existing `IAvailabilityService` behavior is preserved — the Master adds an additional outer gate, not a replacement.
**Check order:**
1. **Master gate** (outer): If a Master URL is registered in the slave's DB, check the Master-sourced cached status (refreshed per FR-MASTER-06).
- If Master status = `NotAvailable` → block all requests. Only the frontend dashboard route is accessible so that users can see the availability widget with the `DisableMessage`.
- If Master is unreachable → fall back to locally stored Master status (last known value; default `Available`).
- If no Master registered → skip Master gate entirely.
2. **Local gate** (inner): If the Master gate passes (status = `Available` or no Master registered), apply the existing `IAvailabilityService` check as it works today (local `Available` / `Maintenance` / `NotAvailable` logic, Owner/Admin bypass, etc.).
This means: when the Master sets a slave to `Available`, all existing local availability behavior continues unchanged. When the Master sets `NotAvailable`, the local gate is never reached.
The `DisableMessage` from the Master is included in the `503 Service Unavailable` response body and delivered to the slave's frontend availability widget.
### FR-MASTER-09 — Master CMS Exemption
When the Master Module is installed and active on a CMS instance, that CMS is **automatically exempt** from the external Master availability check. It does not pull status from any Master. The Master CMS retains and uses its own local `IAvailabilityService` check (existing behavior unchanged).
### FR-MASTER-10 — Role Access Control
All Master Module management features (viewing and modifying slave CMS registrations) are restricted to the **Owner** role only. This applies to both the backend API endpoints and the frontend `/cms` page.
### FR-MASTER-11 — `/cms` Page: Slave CMS List
The frontend `/cms` page displays a list of all registered slave CMSes. For each slave, the following is shown:
- Name
- URL
- Current status badge (`Available` / `NotAvailable` / `Inactive`)
- Last contacted timestamp
- Disable message (if status is `NotAvailable`)
`Inactive` entries are visually greyed out to indicate they are no longer in active use.
### FR-MASTER-12 — `/cms` Page: Add Slave CMS
The Owner can add a new slave CMS registration by providing:
- Name (required)
- URL (required)
- API key (required)
On save, the Master immediately attempts to register itself with the slave (FR-MASTER-03) and stores the result.
### FR-MASTER-13 — `/cms` Page: Set Slave Status
The Owner can change the status of a registered slave CMS to:
- `Available` — slave is enabled (normal operation)
- `NotAvailable` — slave is disabled; a **mandatory disable message** must be provided
- `Inactive` — slave is greyed out in the UI; no availability enforcement is applied (the Master does not contact the slave)
Slave registrations cannot be deleted; setting to `Inactive` is the "soft removal" mechanism.
### FR-MASTER-14 — Mandatory Disable Message
When the Owner sets a slave's status to `NotAvailable`, a non-empty `DisableMessage` is required. This message is pushed to the slave together with the status change and included in the slave's 503 error response to end-users.
### FR-MASTER-15 — Project Documentation
The root `README.md` already exists and covers project structure, dev setup, authentication, frontend development, database migrations, adding a module, and production setup. The following sections need to be updated or added to reflect this feature.
**Updated: `README.md` — "Database Migraties" section**
The current section only covers migrations in `SlpModularCms.Core`. It must be updated to document the per-module migration pattern (NFR-MASTER-06):
- How to add a migration for a specific module (using `--project src\SlpModularCms.Modules.<Name>`)
- How to apply module-specific migrations
- Note that each module owns its own tables and migrations
**Updated: `README.md` — "Nieuwe Module Toevoegen" section**
Step 3 (`IModule implementeren`) must be extended to document the optional per-module `DbContext` pattern introduced by the Master Module:
- How to add a module-specific `DbContext`
- How to register it at startup via `RegisterServices`
- How to apply its migrations in `UseModule`
**Updated: `README.md` — "Productie Setup" section**
The environment variables list must be extended with the new `MasterModule:` keys:
- `MasterModule__CacheMinutes` (slave instances only)
- `MasterModule__IntegrityCheckIntervalMinutes` (master instance only)
**Updated: `frontend/README.md`**
Currently contains Vite template boilerplate. Replace with project-specific frontend developer documentation (prerequisites, scripts, environment variables). The root README already covers most of this — the frontend README can be a brief pointer to the root README plus frontend-specific notes.
---
## Non-Functional Requirements
### NFR-MASTER-01 — Fail-Open Safety
The entire Master Module is designed as a safety measure, not a blocker. If any part of the Master → Slave communication fails (network error, timeout, misconfiguration), the slave must continue serving requests using its local fallback. End-users must never be blocked solely because the Master is unreachable.
### NFR-MASTER-02 — Configurable Cache Interval
The slave pull interval is configurable via `appsettings.json` (`MasterModule:CacheMinutes`). Default: 60 minutes. This follows the existing `dotnet-appsettings` pattern used in this project.
### NFR-MASTER-03 — API Key Security
The `ApiKey` stored in `CmsInstance` is a secret token used for Master → Slave authentication. It must not be exposed in API list responses. The slave validates the `X-Master-Api-Key` header on all Master-initiated requests.
### NFR-MASTER-04 — Background Service Interval
The Master's integrity check background service interval is configurable in Master CMS `appsettings.json` (`MasterModule:IntegrityCheckIntervalMinutes`). Default: 60 minutes.
### NFR-MASTER-05 — Test Coverage
New backend code must follow the existing test coverage standard (≥ 80%). New services and controllers in `SlpModularCms.Modules.Master` and any slave-side extensions require unit tests.
### NFR-MASTER-06 — Per-Module Database Migrations
Each module manages its own database schema through a module-specific EF Core `DbContext` and a dedicated migrations assembly within that module's project. This ensures that tables belonging to a module are only created on CMS instances where that module is installed:
- `SlpModularCms.Modules.Master` owns the `CmsInstances` table → migrations live in `Modules.Master`
- Slave-side tables (e.g., stored Master registration URL) belong to the module or service that introduces them → migrations live in the corresponding project
- `SlpModularCms.Core` retains only the shared/core entities (users, roles, tokens, `GlobalAvailabilityState`)
- Each module's `Configure(IApplicationBuilder)` method applies its own pending EF Core migrations at startup
- A migration is created for every discrete schema change (one migration per logical change, not batched)
---
## Scope Boundaries
| In Scope | Out of Scope |
|----------|-------------|
| New `SlpModularCms.Modules.Master` project | Slave module as a separate installable package |
| Frontend `/cms` page with slave management UI | Multi-level master hierarchy (master of masters) |
| Slave-side auto-registration endpoint | Real-time push notifications to slave (WebSockets/SignalR) |
| Slave-side availability pull + cache + fallback | Authentication delegation (SSO between master and slave) |
| Master integrity check background service | Slave removal / permanent delete |
| `CmsInstance` entity in `SlpModularCms.Modules.Master` (own DbContext + migrations) | |
| Frontend changes included in this feature | |
| Updated `README.md` (migrations + module guide + prod env vars) | Full README rewrite |
| Updated `frontend/README.md` (project-specific content) | |