170 lines
12 KiB
Markdown
170 lines
12 KiB
Markdown
# 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) | |
|