Frontend (Unit 2 completion): dual dev-server tooling (pnpm dev:slave, pnpm dev:all), per-instance browser tab titles, and a backend capability check (SystemController + useSystemCapabilities + ModuleGuard) so a Master-only page is hidden on a slave instance instead of assuming every backend has every module. Master/slave protocol fixes surfaced by actually running master and slave side by side locally: - Deactivating a CMS instance (Inactive) now releases the slave's master gate instead of leaving it stuck on its last pushed status. - The periodic integrity check now also re-pushes status to every reachable slave (previously URL-verification only) and runs once immediately on startup. - Added the originally-specified (but never implemented) slave-pull path: a slave now periodically polls its own status from the master (GET /api/v1/SlaveStatus) and fails open to Available if the master is unreachable for too long, complementing the existing push. - The slave's own Settings page can no longer "successfully" change local availability while the master controls it; it's now locked with an explanatory banner and the backend rejects the write with 409 instead of silently no-op'ing it. - CMS instance status badges now match the dashboard's color/icon styling instead of a plain grey badge. Also corrected the master-cms-module design docs to match this as-built behavior, and flagged (without a full rewrite) a larger, pre-existing divergence between its inception-stage application design and what construction actually built. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.6 KiB
5.6 KiB
Code Summary — Unit 1: master-backend
New Project: SlpModularCms.Modules.Master
| File | Description |
|---|---|
SlpModularCms.Modules.Master.csproj |
Project file; references Core; adds Microsoft.Extensions.Http.Resilience 9.6.0 |
Options/MasterModuleOptions.cs |
Configuration POCO: IntegrityCheckIntervalMinutes, HttpTimeoutSeconds, MasterUrl, CacheMinutes, ApiKey |
Data/Entities/CmsInstanceStatus.cs |
Enum: Available=0, NotAvailable=1, Inactive=2 |
Data/Entities/CmsInstance.cs |
EF Core entity with all domain fields including LastIntegrityCheckFailedAt |
Data/MasterDbContext.cs |
Per-module DbContext; table MasterCmsInstances; configured via OnModelCreating |
Models/CmsInstanceDto.cs |
Record DTO (excludes ApiKey); ExcludeFromCodeCoverage |
Models/CreateCmsInstanceRequest.cs |
Record request for POST; ExcludeFromCodeCoverage |
Models/UpdateStatusRequest.cs |
Record request for status update; ExcludeFromCodeCoverage |
Models/UpdateStatusResult.cs |
Record result with Success + SlaveContactSuccess; ExcludeFromCodeCoverage |
Repositories/ICmsInstanceRepository.cs |
Interface: GetAllAsync, GetActiveAsync, GetByIdAsync, AddAsync, Update, SaveChangesAsync |
Repositories/CmsInstanceRepository.cs |
EF Core implementation; GetActiveAsync excludes Inactive |
Services/IApiKeyProtector.cs |
Interface: Protect/Unprotect |
Services/ApiKeyProtector.cs |
Data Protection wrapper; purpose string "SlpModularCms.Master.ApiKey" |
Services/ISlaveApiClient.cs |
Interface: RegisterMasterAsync, PushStatusAsync, GetRegisteredMasterUrlAsync |
Services/SlaveApiClient.cs |
Typed HTTP client; X-Master-Api-Key header on each call; fail-open (returns false on exception) |
Services/MasterServiceDependencies.cs |
Record aggregating 6 CmsInstanceService dependencies; ExcludeFromCodeCoverage |
Services/ICmsInstanceService.cs |
Interface: GetAllAsync, AddAsync, UpdateStatusAsync, VerifyIntegrityAsync |
Services/CmsInstanceService.cs |
Business logic; HttpContext → config fallback for MasterUrl; never returns ApiKey in DTO |
BackgroundServices/IntegrityCheckBackgroundService.cs |
PeriodicTimer; per-tick IServiceScope; catches all exceptions per tick |
Controllers/CmsInstanceController.cs |
[Authorize(Policy="OwnerOnly")]; GET / POST / PUT /{id}/status |
MasterModule.cs |
IModule implementation; DI registration; db.Database.Migrate() in UseModule; ExcludeFromCodeCoverage |
Migrations/.gitkeep |
Placeholder; run CLI to generate migration (see below) |
New Project: SlpModularCms.Modules.Master.Tests
| File | Description |
|---|---|
SlpModularCms.Modules.Master.Tests.csproj |
xUnit + NSubstitute + FluentAssertions + EF InMemory |
Repositories/CmsInstanceRepositoryTests.cs |
EF InMemory; covers all repository methods |
Services/ApiKeyProtectorTests.cs |
Uses EphemeralDataProtectionProvider; round-trip + invalid ciphertext tests |
Services/SlaveApiClientTests.cs |
FakeHttpMessageHandler; tests success/failure/exception paths + header assertion |
Services/CmsInstanceServiceTests.cs |
NSubstitute; covers all business logic branches including HttpContext fallback |
BackgroundServices/IntegrityCheckBackgroundServiceTests.cs |
PeriodicTimer integration; verifies exception isolation |
Controllers/CmsInstanceControllerTests.cs |
NSubstitute ICmsInstanceService; verifies all HTTP response codes |
Modified Files
| File | Change |
|---|---|
src/SlpModularCms.Api/SlpModularCms.Api.csproj |
Added ProjectReference to SlpModularCms.Modules.Master |
SlpModularCms.sln |
Added both new projects with GUIDs and src folder nesting |
EF Core Migration
After building the solution, run:
dotnet ef migrations add InitialCreate --project src/SlpModularCms.Modules.Master --startup-project src/SlpModularCms.Api
This generates the Migrations/ folder contents. The migration is applied automatically on startup via db.Database.Migrate() in MasterModule.UseModule.
Notes
Microsoft.Extensions.Http.Resilienceversion9.6.0— verify/update duringdotnet restoreif a newer version is available for .NET 10IntegrityCheckIntervalMinutes = 0in tests forces immediate PeriodicTimer ticks (valid for test scenarios only)- Slave-side endpoints (
/api/v1/master/register,/api/v1/master/status,/api/v1/master/registered-url) are implemented in Unit 2 (slave-availability-extension)
Addendum — 2026-07-04 (added outside this unit's original scope, in local-dev-master-slave-setup follow-up fixes)
This unit predates the following; see slave-availability-extension/functional-design/business-rules.md Rule Set 5 and application-design/application-design.md for the full picture:
| File | Description |
|---|---|
Controllers/SlaveStatusController.cs |
New. [AllowAnonymous] GET /api/v1/SlaveStatus; authenticates via X-Master-Api-Key header matched against each active CmsInstance's decrypted key; lets a slave pull its own status instead of relying solely on the master's push |
Services/ICmsInstanceService.cs / CmsInstanceService.cs |
GetStatusForApiKeyAsync(plainApiKey) added; UpdateStatusAsync's Inactive branch now pushes Available/null to release the gate (previously a no-op); VerifyIntegrityAsync now also re-pushes persisted status to every reachable active slave each cycle |
BackgroundServices/IntegrityCheckBackgroundService.cs |
Now runs one tick immediately on startup, in addition to the periodic timer |
Models/SlaveStatusPollResponse (in ICmsInstanceService.cs) |
New record: IsAvailable, DisableMessage |