# Domain Entities — Unit 1: master-backend ## Entity Overview ```mermaid graph TD MasterDbCtx["MasterDbContext\n(per-module EF Core DbContext)"] CmsInst["CmsInstance\n(aggregate root)"] Status["CmsInstanceStatus\n(enum)"] Opts["MasterModuleOptions\n(config POCO)"] DP["IDataProtector\n(ApiKey encryption)"] DTO["CmsInstanceDto\n(API response shape)"] CreateReq["CreateCmsInstanceRequest\n(API input)"] UpdateReq["UpdateStatusRequest\n(API input)"] UpdateRes["UpdateStatusResult\n(API response for status update)"] MasterDbCtx -->|"owns"| CmsInst CmsInst -->|"has"| Status CmsInst -->|"ApiKey encrypted via"| DP CmsInst -->|"projected to"| DTO CreateReq -->|"creates"| CmsInst UpdateReq -->|"mutates status of"| CmsInst UpdateRes -->|"returned from UpdateStatusAsync"| CmsInst Opts -->|"IntegrityCheckIntervalMinutes"| MasterDbCtx Opts -->|"MasterUrl fallback"| MasterDbCtx classDef entity fill:#9ae6b4,stroke:#2f855a,stroke-width:2px,color:#000 classDef infra fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000 classDef dto fill:#FFC107,stroke:#F57F17,stroke-width:1px,color:#000 classDef config fill:#CE93D8,stroke:#6A1B9A,stroke-width:1px,color:#000 class CmsInst entity class MasterDbCtx,DP infra class Status,DTO,CreateReq,UpdateReq,UpdateRes dto class Opts config ``` Text alternative: MasterDbContext owns CmsInstance; CmsInstance has a Status enum and its ApiKey is encrypted via IDataProtector; request/response shapes map to and from CmsInstance. --- ## CmsInstance **Table**: `CmsInstances` (owned by `MasterDbContext`, migrations in `SlpModularCms.Modules.Master`) | Field | Type | Nullable | Notes | |-------|------|----------|-------| | `Id` | `Guid` | No | Primary key | | `Name` | `string` | No | Friendly display name; required | | `Url` | `string` | No | Base URL of slave CMS API; must start with `http://` or `https://` | | `ApiKey` | `string` | No | Encrypted via ASP.NET Core Data Protection before storage; decrypted before HTTP calls | | `Status` | `CmsInstanceStatus` | No | Default: `Available` on creation | | `DisableMessage` | `string?` | Yes | Required when `Status = NotAvailable`; null otherwise | | `LastContactedAt` | `DateTimeOffset?` | Yes | Null = never successfully contacted; set on successful registration or integrity check | | `LastStatusPushedAt` | `DateTimeOffset?` | Yes | Null = status never successfully pushed; set after successful `PushStatusAsync` | | `LastIntegrityCheckFailedAt` | `DateTimeOffset?` | Yes | Null = no pending failure; set when integrity check cannot reach slave or re-registration fails; cleared on next successful contact | --- ## CmsInstanceStatus ```csharp public enum CmsInstanceStatus { Available = 0, NotAvailable = 1, Inactive = 2, } ``` | Value | Meaning | Master Contacts Slave? | |-------|---------|----------------------| | `Available` | Slave is enabled; normal operation | Yes (status push + integrity checks) | | `NotAvailable` | Slave is disabled; `DisableMessage` served to end-users | Yes (status push + integrity checks) | | `Inactive` | Soft-removed; greyed out in UI | On the transition **into** `Inactive`: one final push (`Available`, no message) to release the gate. Afterwards: **No** further contact — excluded from `GetActiveAsync`, so no more pushes/integrity checks/re-pushes | > **Updated 2026-07-04**: previously `Inactive` meant no HTTP contact at all, including on the transition itself — this left slaves stuck on their last pushed status after being detached. See BR-CONTACT-02 in `business-rules.md`. --- ## MasterModuleOptions **Config section**: `"MasterModule"` in `appsettings.json` | Property | Type | Default | Side | Notes | |----------|------|---------|------|-------| | `IntegrityCheckIntervalMinutes` | `int` | `60` | Master | Interval for `IntegrityCheckBackgroundService` | | `MasterUrl` | `string?` | `null` | Master | Fallback public URL of this master CMS; used by background service when `HttpContext` is unavailable | | `CacheMinutes` | `int` | `60` | Slave | Slave pull cache interval (used by Unit 2) | | `ApiKey` | `string?` | `null` | Slave | Slave API key for validating incoming master requests (used by Unit 2) | --- ## CmsInstanceDto (API Response) **Rule**: `ApiKey` is **never** included (NFR-MASTER-03). | Property | Type | Notes | |----------|------|-------| | `Id` | `Guid` | | | `Name` | `string` | | | `Url` | `string` | | | `Status` | `string` | Serialized as string (`"Available"` / `"NotAvailable"` / `"Inactive"`) | | `DisableMessage` | `string?` | | | `LastContactedAt` | `DateTimeOffset?` | | | `LastStatusPushedAt` | `DateTimeOffset?` | | | `LastIntegrityCheckFailedAt` | `DateTimeOffset?` | Visible in UI so owner knows which slaves have pending check failures | --- ## CreateCmsInstanceRequest (API Input) | Property | Type | Validation | |----------|------|-----------| | `Name` | `string` | Required, non-empty | | `Url` | `string` | Required; must start with `http://` or `https://` | | `ApiKey` | `string` | Required, non-empty | --- ## UpdateStatusRequest (API Input) | Property | Type | Validation | |----------|------|-----------| | `Status` | `CmsInstanceStatus` | Required; must be valid enum value | | `DisableMessage` | `string?` | Required and non-empty when `Status = NotAvailable`; ignored otherwise | --- ## UpdateStatusResult (Service Return / API Response) | Property | Type | Notes | |----------|------|-------| | `Success` | `bool` | Always `true` when status persisted to DB (DB is the authority) | | `SlaveContactSuccess` | `bool` | `true` if the HTTP push to the slave succeeded; `false` if it failed (slave unreachable). Applies to `Inactive` transitions too — reflects whether the gate-release push succeeded, not a hardcoded `true` | > **Updated 2026-07-04**: `SlaveContactSuccess` for `Inactive` used to always be hardcoded `true` (no push happened, so nothing could fail) — now reflects the real result of the release push. --- ## SlaveStatusPollResponse (API Response) — Added 2026-07-04 Response shape for the new slave-pull endpoint (`GET /api/v1/SlaveStatus`, `SlaveStatusController`), used by `MasterStatusPollingBackgroundService` on the slave side (see `slave-availability-extension/functional-design/domain-entities.md`). This is the counterpart of the push-based flow above — added to close a gap versus the original inception requirements (FR-MASTER-06/07), which specified a slave-initiated pull in addition to the push that construction actually implemented. | Property | Type | Notes | |----------|------|-------| | `IsAvailable` | `bool` | `true` when `CmsInstance.Status == Available` | | `DisableMessage` | `string?` | `CmsInstance.DisableMessage` | Identified by matching the caller's plain API key (header `X-Master-Api-Key`) against each active `CmsInstance`'s decrypted key — there is no separate slave-identity field, the shared key doubles as the credential. No `[Authorize]`/JWT on this endpoint.