Files
slp-modular-cms/aidlc-docs/features/master-cms-module/inception/application-design/components.md
T

168 lines
8.9 KiB
Markdown

# Components — Master CMS Module
## Unit 1 — master-backend (`SlpModularCms.Modules.Master`)
### MasterModule
- **Type**: Module registration (`IModule`)
- **Responsibilities**: Registers all master-side services (repository, service, typed HTTP client, background service, options); applies `MasterDbContext` migrations at startup via `UseModule`; does NOT register middleware (master instance has no availability gate)
- **Interface**: `IModule` (`RegisterServices`, `UseModule`)
### MasterDbContext
- **Type**: EF Core `DbContext`
- **Responsibilities**: Per-module DbContext; owns the `CmsInstances` table and its migrations; migrations live in `SlpModularCms.Modules.Master` (NFR-MASTER-06)
- **Entities owned**: `CmsInstance`
### CmsInstance
- **Type**: Domain Entity
- **Responsibilities**: Represents a registered slave CMS instance
- **Fields**:
- `Id``Guid`, primary key
- `Name``string`, friendly display name
- `Url``string`, base URL of slave CMS API
- `ApiKey``string`, secret used by Master to authenticate against slave; never returned in API responses (NFR-MASTER-03)
- `Status``CmsInstanceStatus` enum (`Available` / `NotAvailable` / `Inactive`)
- `DisableMessage``string?`, required when `Status = NotAvailable`
- `LastContactedAt``DateTimeOffset?`
- `LastStatusPushedAt``DateTimeOffset?`
### CmsInstanceStatus
- **Type**: Enum
- **Values**: `Available`, `NotAvailable`, `Inactive`
### ICmsInstanceRepository / CmsInstanceRepository
- **Type**: Repository (data access only)
- **Responsibilities**: CRUD operations on `CmsInstance` via `MasterDbContext`; no business logic
- **Lifetime**: Scoped
### ICmsInstanceService / CmsInstanceService
- **Type**: Service (orchestration)
- **Responsibilities**: Business orchestration — calls repository for data access; calls `ISlaveApiClient` for HTTP side-effects (auto-registration, status push, integrity verification); enforces business rules (e.g., `DisableMessage` required when `NotAvailable`)
- **Lifetime**: Scoped
### ISlaveApiClient / SlaveApiClient
- **Type**: Typed HTTP client
- **Responsibilities**: All Master → Slave HTTP communication (registration, status push, integrity check); adds `X-Master-Api-Key` header; handles HTTP errors and returns success flags
- **Registration**: `services.AddHttpClient<ISlaveApiClient, SlaveApiClient>()`
- **Lifetime**: Transient (managed by `IHttpClientFactory`)
### CmsInstanceController
- **Type**: ASP.NET Core `ControllerBase`
- **Responsibilities**: REST API for slave CMS management; `[Authorize(Policy = "OwnerOnly")]`; delegates to `ICmsInstanceService`
- **Route**: `/api/v1/CmsInstances`
- **Actions**: GET list, POST add, PUT update status
### IntegrityCheckBackgroundService
- **Type**: `BackgroundService`
- **Responsibilities**: Periodic background loop; verifies each non-Inactive slave still has the correct master URL registered; re-registers if mismatch found; interval configurable via `MasterModuleOptions.IntegrityCheckIntervalMinutes` (default 60)
- **Pattern**: Uses `PeriodicTimer`; injects `IServiceScopeFactory` to resolve scoped `ICmsInstanceService` per tick
- **Lifetime**: Singleton (as required by `BackgroundService`)
### MasterModuleOptions
- **Type**: Configuration POCO
- **Fields**:
- `IntegrityCheckIntervalMinutes``int`, default 60 (master-side)
- `CacheMinutes``int`, default 60 (slave-side)
- `ApiKey``string` (slave-side; key the slave uses to validate incoming master requests)
- **Registration**: `services.Configure<MasterModuleOptions>(configuration.GetSection("MasterModule"))`
### DTOs and Request Models
| Type | Fields | Notes |
|------|--------|-------|
| `CmsInstanceDto` | `Id`, `Name`, `Url`, `Status`, `DisableMessage`, `LastContactedAt`, `LastStatusPushedAt` | No `ApiKey` (NFR-MASTER-03) |
| `CreateCmsInstanceRequest` | `Name`, `Url`, `ApiKey` | API key stored securely, never returned |
| `UpdateStatusRequest` | `Status`, `DisableMessage?` | `DisableMessage` required when `Status = NotAvailable` |
---
## Unit 2 — slave-availability-extension (`SlpModularCms.Modules.Availability`)
### MasterRegistration
- **Type**: Domain Entity
- **Responsibilities**: Stores the registered Master CMS URL on the slave side; zero or one records per slave (the slave knows at most one master)
- **Fields**:
- `Id``Guid`, primary key
- `MasterUrl``string`, base URL of the Master CMS
- `RegisteredAt``DateTimeOffset`
### AvailabilityDbContext
- **Type**: EF Core `DbContext` (new, per-module)
- **Responsibilities**: Per-module DbContext introduced in the Availability module for the slave-side entity; owns the `MasterRegistrations` table; migrations live in `SlpModularCms.Modules.Availability`
- **Entities owned**: `MasterRegistration`
### IMasterAvailabilityService / MasterAvailabilityService
- **Type**: Service
- **Responsibilities**: Checks whether a master URL is registered (DB lookup); pulls master-controlled availability status via HTTP GET; caches last known status using static fields + timestamp (same pattern as `PersistentAvailabilityService`); implements fail-open fallback when master is unreachable; respects `MasterModuleOptions.CacheMinutes`
- **Cache pattern**: Static fields `_cachedStatus` (default `Available`) + `_lastFetchedAt`; stale check based on `CacheMinutes`
- **Exemption (FR-MASTER-09)**: No special exemption logic needed — if no `MasterRegistration` record exists in DB (which is the case on a Master instance that never registered itself), the gate is skipped automatically
- **Lifetime**: Scoped (static fields provide cross-request caching)
### AvailabilityMiddleware (extended)
- **Type**: ASP.NET Core Middleware
- **Responsibilities**: Extended with Master gate logic at the **top** of `InvokeAsync`; two-phase check:
1. **Master gate** — calls `IMasterAvailabilityService.GetMasterStatusAsync()`; if no master registered → skip to local gate; if master says `NotAvailable` → 503 with `DisableMessage`; if unreachable → use cached/fallback value (fail-open)
2. **Local gate** — existing `IAvailabilityService` check, unchanged
- **Bypass prefixes**: Extended to also bypass internal master endpoints (`/api/internal/master/`) so registration calls are never blocked
### AvailabilityController (extended)
- **Type**: ASP.NET Core `ControllerBase` (existing class extended)
- **Responsibilities**: New action `RegisterMaster` added; validates `X-Master-Api-Key` header against configured `MasterModuleOptions.ApiKey`; upserts `MasterRegistration` in `AvailabilityDbContext`
- **New route**: `POST /api/internal/master/register`
- **Authentication**: API key validation (no JWT; the registration endpoint is called machine-to-machine)
### RegisterMasterRequest
- **Type**: Request model
- **Fields**: `MasterUrl``string`
### MasterGateResult
- **Type**: Result record
- **Fields**: `HasMaster` (`bool`), `Status` (`CmsInstanceStatus?`), `DisableMessage` (`string?`)
---
## Unit 3 — frontend-cms-page (`frontend/`)
### CmsPage
- **Type**: React page component
- **Route**: `/cms`
- **Responsibilities**: Owner-only route guard; fetches slave list via `useCmsInstances`; renders `CmsInstanceList`; manages dialog open state for Add and Set Status actions
### CmsInstanceList
- **Type**: React component
- **Responsibilities**: Renders a table of `CmsInstance` items; shows Name, URL, Status badge, LastContactedAt, DisableMessage; `Inactive` rows are visually greyed out; provides action triggers (Add button, Set Status button per row)
### AddCmsInstanceDialog
- **Type**: React component (modal dialog using shadcn/ui `Dialog`)
- **Responsibilities**: Form with fields Name, URL, ApiKey (all required); validates before submission; calls `useAddCmsInstance` mutation; closes on success
### SetStatusDialog
- **Type**: React component (modal dialog using shadcn/ui `Dialog`)
- **Responsibilities**: Status dropdown (`Available`, `NotAvailable`, `Inactive`); `DisableMessage` text field rendered and required when status is `NotAvailable`; calls `useUpdateCmsInstanceStatus` mutation; closes on success
### CmsInstanceStatus (TypeScript enum)
- **Values**: `Available`, `NotAvailable`, `Inactive`
### CmsInstance (TypeScript type)
- **Fields**: `id`, `name`, `url`, `status`, `disableMessage`, `lastContactedAt`, `lastStatusPushedAt`
### useCmsInstances
- **Type**: TanStack Query `useQuery` hook
- **File**: `hooks/useCmsInstances.ts`
- **Responsibilities**: GET `/api/v1/CmsInstances`; returns list of `CmsInstance`
### useAddCmsInstance
- **Type**: TanStack Query `useMutation` hook
- **File**: `hooks/useAddCmsInstance.ts`
- **Responsibilities**: POST `/api/v1/CmsInstances`; invalidates `useCmsInstances` query on success
### useUpdateCmsInstanceStatus
- **Type**: TanStack Query `useMutation` hook
- **File**: `hooks/useUpdateCmsInstanceStatus.ts`
- **Responsibilities**: PUT `/api/v1/CmsInstances/{id}/status`; invalidates `useCmsInstances` query on success
---
## Unit 4 — documentation
No new components. Covers README updates only (FR-MASTER-15). See requirements for scope.