# Logical Components — Unit 2: slave-availability-extension ## Component Overview ```mermaid graph TD subgraph Controllers MC["MasterController\n(new)"] end subgraph Services IMAS["IMasterAvailabilityService"] MAS["MasterAvailabilityService\n(volatile static cache)"] IMAKP["IMasterApiKeyProtector"] MAKP["MasterApiKeyProtector"] MASD["MasterAvailabilityServiceDependencies\n(record)"] end subgraph Repositories IMRR["IMasterRegistrationRepository"] MRR["MasterRegistrationRepository"] end subgraph Data AVDBCTX["AvailabilityDbContext\n(new)"] MR["MasterRegistration\n(entity)"] end subgraph Middleware AVMW["AvailabilityMiddleware\n(extended)"] end subgraph External DP["IDataProtectionProvider\n(ASP.NET Core)"] end MC -->|delegates| IMAS MAS -.->|implements| IMAS MAS -->|uses| MASD MASD -->|contains| IMRR MASD -->|contains| IMAKP MRR -.->|implements| IMRR MAKP -.->|implements| IMAKP MRR -->|reads/writes| AVDBCTX AVDBCTX -->|owns| MR MAKP -->|wraps| DP AVMW -->|InvokeAsync param| IMAS classDef interface fill:#fff,stroke:#63b3ed,stroke-width:2px,color:#2b6cb0 classDef impl fill:#63b3ed,stroke:#2b6cb0,color:#fff classDef entity fill:#FFC107,stroke:#F57F17,color:#000 classDef infra fill:#9ae6b4,stroke:#2f855a,color:#000 classDef middleware fill:#CE93D8,stroke:#6A1B9A,color:#000 classDef external fill:#eee,stroke:#999,color:#333 class IMAS,IMRR,IMAKP interface class MAS,MRR,MAKP,MASD impl class MR,AVDBCTX entity class MC infra class AVMW middleware class DP external ``` Text alternative: `MasterController` delegates to `IMasterAvailabilityService`. `MasterAvailabilityService` uses `MasterAvailabilityServiceDependencies` which holds `IMasterRegistrationRepository` and `IMasterApiKeyProtector`. Repository uses `AvailabilityDbContext`. `MasterApiKeyProtector` wraps `IDataProtectionProvider`. `AvailabilityMiddleware` receives `IMasterAvailabilityService` as third `InvokeAsync` parameter. --- ## Component Specifications ### 1. `MasterRegistration` (Entity) **Namespace**: `SlpModularCms.Modules.Availability.Data.Entities` | Property | Type | Notes | |----------|------|-------| | `Id` | `Guid` | PK; always `new Guid("00000000-0000-0000-0000-000000000001")` | | `MasterUrl` | `string` | Required; max 500 | | `ApiKey` | `string` | Required; max 2000 (encrypted via Data Protection) | | `RegisteredAt` | `DateTimeOffset` | Set once on creation | | `LastContactedAt` | `DateTimeOffset?` | Updated on every valid master call | --- ### 2. `AvailabilityDbContext` **Namespace**: `SlpModularCms.Modules.Availability.Data` ```csharp public class AvailabilityDbContext : DbContext { public DbSet MasterRegistrations => Set(); } ``` | Aspect | Decision | |--------|----------| | Migration assembly | `SlpModularCms.Modules.Availability` | | Table | `AvailabilityMasterRegistrations` | | Applied at | `AvailabilityModule.UseModule` → `MigrateAsync()` | | Connection string | `ConnectionStrings:DefaultConnection` (same as other DbContexts) | --- ### 3. `IMasterRegistrationRepository` / `MasterRegistrationRepository` **Namespace**: `SlpModularCms.Modules.Availability.Repositories` ```csharp public interface IMasterRegistrationRepository { Task GetAsync(); Task UpsertAsync(MasterRegistration registration); Task SaveChangesAsync(); } ``` | Method | Notes | |--------|-------| | `GetAsync()` | Loads singleton by fixed Id; returns `null` if row does not exist | | `UpsertAsync(registration)` | `Add` if not tracked; `Update` if tracked or found by Id | | `SaveChangesAsync()` | Explicit save; service controls transaction boundary | **Registration**: `services.AddScoped()` --- ### 4. `IMasterApiKeyProtector` / `MasterApiKeyProtector` **Namespace**: `SlpModularCms.Modules.Availability.Services` ```csharp public interface IMasterApiKeyProtector { string Protect(string plainApiKey); string? Unprotect(string encryptedApiKey); // null on CryptographicException } ``` **Registration**: `services.AddSingleton()` --- ### 5. `MasterAvailabilityServiceDependencies` (Record) **Namespace**: `SlpModularCms.Modules.Availability.Services` ```csharp [ExcludeFromCodeCoverage] public record MasterAvailabilityServiceDependencies( IMasterRegistrationRepository Repository, IMasterApiKeyProtector KeyProtector, ILogger Logger ); ``` **Registration**: `services.AddScoped()` --- ### 6. `IMasterAvailabilityService` / `MasterAvailabilityService` **Namespace**: `SlpModularCms.Modules.Availability.Services` ```csharp public interface IMasterAvailabilityService { Task RegisterAsync(string masterUrl, string apiKey); Task PushStatusAsync(string apiKey, bool isAvailable, string? disableMessage); Task GetRegisteredUrlAsync(string apiKey); MasterGateStatus GetMasterStatus(); } ``` **Static fields** (in implementation): ```csharp private static volatile bool _masterIsAvailable = true; private static volatile string? _masterDisableMessage = null; ``` **Key validation helper** (private, reused across all 3 write-path methods): ```csharp private async Task ValidateApiKeyAsync(string apiKey) { var registration = await _deps.Repository.GetAsync(); if (registration is null) return null; var stored = _deps.KeyProtector.Unprotect(registration.ApiKey); return stored == apiKey ? registration : null; } ``` **Registration**: `services.AddScoped()` --- ### 7. `MasterGateStatus` (Record) **Namespace**: `SlpModularCms.Modules.Availability.Services` ```csharp [ExcludeFromCodeCoverage] public record MasterGateStatus(bool IsAvailable, string? DisableMessage); ``` --- ### 8. `MasterController` **Namespace**: `SlpModularCms.Modules.Availability.Controllers` ```csharp [ApiController] [Route("[controller]")] // → /api/v1/master via ApiPrefixConvention public class MasterController : ControllerBase { [HttpPost("register")] // POST /api/v1/master/register [HttpPost("status")] // POST /api/v1/master/status [HttpGet("registered-url")] // GET /api/v1/master/registered-url } ``` **Constructor**: `MasterController(IMasterAvailabilityService svc)` — single dependency, no record wrapper needed. **Auth**: No `[Authorize]` attribute — API key validated in `MasterAvailabilityService`. **Response on 401**: `Unauthorized()` — no body to avoid leaking registration state. --- ### 9. `AvailabilityMiddleware` (Extended) **Extended fields** (added to existing class): ```csharp // Updated bypass prefix list private static readonly string[] _bypassPrefixes = [ "/api/v1/Availability/status", "/api/v1/Auth/", "/api/v1/Setup/status", "/api/v1/master/" // NEW ]; ``` **Updated `InvokeAsync` signature**: ```csharp public async Task InvokeAsync( HttpContext context, IAvailabilityService localSvc, IMasterAvailabilityService masterSvc) ``` --- ## Dependency Registration Summary All new registrations added to `AvailabilityModule.RegisterServices`: ```csharp // Data services.AddDbContext((sp, options) => options.UseSqlServer(sp.GetRequiredService() .GetConnectionString("DefaultConnection"))); // Security services.AddDataProtection(); services.AddSingleton(); // Repositories services.AddScoped(); // Services services.AddScoped(); services.AddScoped(); ``` And in `AvailabilityModule.UseModule`: ```csharp using var scope = app.ApplicationServices.CreateScope(); await scope.ServiceProvider .GetRequiredService() .Database.MigrateAsync(); ``` --- ## New Files Summary | File | Project | Type | |------|---------|------| | `Data/Entities/MasterRegistration.cs` | Availability | Entity | | `Data/AvailabilityDbContext.cs` | Availability | DbContext | | `Repositories/IMasterRegistrationRepository.cs` | Availability | Interface | | `Repositories/MasterRegistrationRepository.cs` | Availability | Implementation | | `Services/IMasterApiKeyProtector.cs` | Availability | Interface | | `Services/MasterApiKeyProtector.cs` | Availability | Implementation | | `Services/MasterGateStatus.cs` | Availability | Record | | `Services/MasterAvailabilityServiceDependencies.cs` | Availability | Record | | `Services/IMasterAvailabilityService.cs` | Availability | Interface | | `Services/MasterAvailabilityService.cs` | Availability | Implementation | | `Controllers/MasterController.cs` | Availability | Controller | | `Middleware/AvailabilityMiddleware.cs` | Availability | Modified (extended) | | `AvailabilityModule.cs` | Availability | Modified (registration + migration) | | `Data/Migrations/*` | Availability | EF Core auto-generated |