Adds 2 units and docs for unit 3. nfr-requirements plan
This commit is contained in:
+191
@@ -0,0 +1,191 @@
|
||||
# Business Logic Model — Unit 1: master-backend
|
||||
|
||||
## Flow 1 — AddAsync (Add Slave CMS)
|
||||
|
||||
**Trigger**: `POST /api/v1/CmsInstances` (Owner only)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
box rgba(99,179,237,0.3) API Layer
|
||||
participant Ctrl as CmsInstanceController
|
||||
end
|
||||
box rgba(154,230,180,0.3) Service Layer
|
||||
participant Svc as CmsInstanceService
|
||||
participant Repo as CmsInstanceRepository
|
||||
participant Client as SlaveApiClient
|
||||
end
|
||||
box rgba(246,224,94,0.3) Infrastructure
|
||||
participant DP as IDataProtector
|
||||
participant Ctx as IHttpContextAccessor
|
||||
participant Opts as MasterModuleOptions
|
||||
end
|
||||
box rgba(200,200,200,0.3) Persistence
|
||||
participant DB as MasterDbContext
|
||||
end
|
||||
|
||||
Ctrl->>Svc: AddAsync(request)
|
||||
Note over Svc: Validate Name, Url, ApiKey not empty
|
||||
Note over Svc: Validate Url starts with http or https
|
||||
Svc->>DP: Protect(request.ApiKey)
|
||||
DP-->>Svc: encryptedApiKey
|
||||
Svc->>Repo: AddAsync(new CmsInstance)
|
||||
Note over Svc,Repo: Status=Available, LastContactedAt=null
|
||||
Svc->>Repo: SaveChangesAsync()
|
||||
Repo->>DB: INSERT CmsInstances
|
||||
Note over Svc: Determine masterUrl
|
||||
Svc->>Ctx: try get base URL from HttpContext
|
||||
alt HttpContext available
|
||||
Ctx-->>Svc: masterUrl from request
|
||||
else HttpContext unavailable
|
||||
Svc->>Opts: read MasterUrl
|
||||
Opts-->>Svc: configured masterUrl
|
||||
end
|
||||
Svc->>DP: Unprotect(encryptedApiKey)
|
||||
DP-->>Svc: plainApiKey
|
||||
Svc->>Client: RegisterMasterAsync(slaveUrl, plainApiKey, masterUrl)
|
||||
alt Registration success
|
||||
Client-->>Svc: true
|
||||
Svc->>Repo: UpdateAsync (LastContactedAt = UtcNow)
|
||||
Svc->>Repo: SaveChangesAsync()
|
||||
Repo->>DB: UPDATE CmsInstances
|
||||
else Registration failed
|
||||
Client-->>Svc: false
|
||||
Note over Svc: LastContactedAt stays null (owner can see)
|
||||
end
|
||||
Svc-->>Ctrl: CmsInstanceDto
|
||||
Ctrl-->>Ctrl: return 201 Created
|
||||
```
|
||||
|
||||
Text alternative: Controller calls service; service validates, encrypts ApiKey, persists entity, determines master URL from HttpContext or config, attempts slave registration, updates LastContactedAt on success; always returns DTO regardless of registration outcome.
|
||||
|
||||
---
|
||||
|
||||
## Flow 2 — UpdateStatusAsync (Set Slave Status)
|
||||
|
||||
**Trigger**: `PUT /api/v1/CmsInstances/{id}/status` (Owner only)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
box rgba(99,179,237,0.3) API Layer
|
||||
participant Ctrl as CmsInstanceController
|
||||
end
|
||||
box rgba(154,230,180,0.3) Service Layer
|
||||
participant Svc as CmsInstanceService
|
||||
participant Repo as CmsInstanceRepository
|
||||
participant Client as SlaveApiClient
|
||||
end
|
||||
box rgba(246,224,94,0.3) Infrastructure
|
||||
participant DP as IDataProtector
|
||||
end
|
||||
box rgba(200,200,200,0.3) Persistence
|
||||
participant DB as MasterDbContext
|
||||
end
|
||||
|
||||
Ctrl->>Svc: UpdateStatusAsync(id, status, disableMessage)
|
||||
Svc->>Repo: GetByIdAsync(id)
|
||||
Repo->>DB: SELECT CmsInstances WHERE Id
|
||||
DB-->>Repo: CmsInstance or null
|
||||
Repo-->>Svc: entity or null
|
||||
alt Entity not found
|
||||
Svc-->>Ctrl: throw NotFoundException
|
||||
end
|
||||
Note over Svc: Validate DisableMessage required if NotAvailable
|
||||
alt Validation fails
|
||||
Svc-->>Ctrl: throw ValidationException
|
||||
end
|
||||
alt newStatus = Inactive
|
||||
Svc->>Repo: UpdateAsync (Status=Inactive, DisableMessage=null)
|
||||
Svc->>Repo: SaveChangesAsync()
|
||||
Repo->>DB: UPDATE CmsInstances
|
||||
Svc-->>Ctrl: UpdateStatusResult(Success=true, SlaveContactSuccess=true)
|
||||
else newStatus = Available or NotAvailable
|
||||
Svc->>Repo: UpdateAsync (Status, DisableMessage)
|
||||
Svc->>Repo: SaveChangesAsync()
|
||||
Repo->>DB: UPDATE CmsInstances
|
||||
Svc->>DP: Unprotect(entity.ApiKey)
|
||||
DP-->>Svc: plainApiKey
|
||||
Svc->>Client: PushStatusAsync(slaveUrl, plainApiKey, status, disableMessage)
|
||||
alt Push success
|
||||
Client-->>Svc: true
|
||||
Svc->>Repo: UpdateAsync (LastStatusPushedAt = UtcNow)
|
||||
Svc->>Repo: SaveChangesAsync()
|
||||
Repo->>DB: UPDATE CmsInstances
|
||||
Svc-->>Ctrl: UpdateStatusResult(Success=true, SlaveContactSuccess=true)
|
||||
else Push failed
|
||||
Client-->>Svc: false
|
||||
Svc-->>Ctrl: UpdateStatusResult(Success=true, SlaveContactSuccess=false)
|
||||
end
|
||||
end
|
||||
Ctrl-->>Ctrl: return 200 OK with UpdateStatusResult
|
||||
```
|
||||
|
||||
Text alternative: Controller calls service with id and new status; service loads entity, validates, updates DB, then for non-Inactive transitions decrypts ApiKey and pushes status to slave; returns SlaveContactSuccess=false if push fails but DB is always the authority.
|
||||
|
||||
---
|
||||
|
||||
## Flow 3 — VerifyIntegrityAsync (Background Integrity Check)
|
||||
|
||||
**Trigger**: `IntegrityCheckBackgroundService` periodic timer (every `IntegrityCheckIntervalMinutes`)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
box rgba(200,200,200,0.3) Background
|
||||
participant Timer as PeriodicTimer
|
||||
participant BgSvc as IntegrityCheckBackgroundService
|
||||
end
|
||||
box rgba(154,230,180,0.3) Service Layer
|
||||
participant Svc as CmsInstanceService
|
||||
participant Repo as CmsInstanceRepository
|
||||
participant Client as SlaveApiClient
|
||||
end
|
||||
box rgba(246,224,94,0.3) Infrastructure
|
||||
participant DP as IDataProtector
|
||||
participant Opts as MasterModuleOptions
|
||||
end
|
||||
box rgba(200,200,200,0.3) Persistence
|
||||
participant DB as MasterDbContext
|
||||
end
|
||||
|
||||
Timer->>BgSvc: Tick
|
||||
BgSvc->>Svc: VerifyIntegrityAsync()
|
||||
Svc->>Repo: GetActiveAsync()
|
||||
Repo->>DB: SELECT WHERE Status != Inactive
|
||||
DB-->>Repo: list of CmsInstance
|
||||
Repo-->>Svc: instances
|
||||
loop for each instance
|
||||
Svc->>DP: Unprotect(instance.ApiKey)
|
||||
DP-->>Svc: plainApiKey
|
||||
Svc->>Opts: read MasterUrl
|
||||
Opts-->>Svc: masterUrl
|
||||
Svc->>Client: GetRegisteredMasterUrlAsync(slaveUrl, plainApiKey)
|
||||
alt Slave unreachable
|
||||
Client-->>Svc: throws or returns null
|
||||
Svc->>Repo: UpdateAsync (LastIntegrityCheckFailedAt = UtcNow)
|
||||
Svc->>Repo: SaveChangesAsync()
|
||||
Repo->>DB: UPDATE CmsInstances
|
||||
else Slave reachable
|
||||
Client-->>Svc: registeredMasterUrl
|
||||
alt URLs match
|
||||
Svc->>Repo: UpdateAsync (LastContactedAt = UtcNow, LastIntegrityCheckFailedAt = null)
|
||||
Svc->>Repo: SaveChangesAsync()
|
||||
Repo->>DB: UPDATE CmsInstances
|
||||
else URL mismatch
|
||||
Svc->>Client: RegisterMasterAsync(slaveUrl, plainApiKey, masterUrl)
|
||||
alt Re-registration success
|
||||
Client-->>Svc: true
|
||||
Svc->>Repo: UpdateAsync (LastContactedAt = UtcNow, LastIntegrityCheckFailedAt = null)
|
||||
Svc->>Repo: SaveChangesAsync()
|
||||
Repo->>DB: UPDATE CmsInstances
|
||||
else Re-registration failed
|
||||
Client-->>Svc: false
|
||||
Svc->>Repo: UpdateAsync (LastIntegrityCheckFailedAt = UtcNow)
|
||||
Svc->>Repo: SaveChangesAsync()
|
||||
Repo->>DB: UPDATE CmsInstances
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Svc-->>BgSvc: done
|
||||
```
|
||||
|
||||
Text alternative: Background timer triggers integrity service; for each non-Inactive slave: decrypts key, retrieves registered master URL, clears failure flag on match, re-registers on mismatch, sets LastIntegrityCheckFailedAt when slave is unreachable or re-registration fails.
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
# Business Rules — Unit 1: master-backend
|
||||
|
||||
## BR-01 — Status Update Decision Logic
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Start(["UpdateStatusAsync called"])
|
||||
CheckExists{"Entity exists\nfor given id?"}
|
||||
NotFound["Throw NotFoundException\n404 to caller"]
|
||||
CheckMsg{"newStatus = NotAvailable\nAND disableMessage\nis null or empty?"}
|
||||
ValidationErr["Throw ValidationException\nDisableMessage required"]
|
||||
CheckInactive{"newStatus\n= Inactive?"}
|
||||
SetInactive["Status = Inactive\nDisableMessage = null\nNo HTTP push\nSlaveContactSuccess = true"]
|
||||
PersistStatus["Persist Status + DisableMessage\nto MasterDbContext"]
|
||||
DecryptKey["Decrypt ApiKey\nvia IDataProtector"]
|
||||
PushSlave["PushStatusAsync\nto slave endpoint"]
|
||||
PushOk{"HTTP push\nsucceeded?"}
|
||||
UpdatePushed["LastStatusPushedAt = UtcNow\nSave"]
|
||||
ReturnOk["Return UpdateStatusResult\nSuccess=true\nSlaveContactSuccess=true"]
|
||||
ReturnWarn["Return UpdateStatusResult\nSuccess=true\nSlaveContactSuccess=false"]
|
||||
Done(["Return result to controller"])
|
||||
|
||||
Start --> CheckExists
|
||||
CheckExists -->|"no"| NotFound
|
||||
CheckExists -->|"yes"| CheckMsg
|
||||
CheckMsg -->|"yes — invalid"| ValidationErr
|
||||
CheckMsg -->|"no — valid"| CheckInactive
|
||||
CheckInactive -->|"yes"| SetInactive --> Done
|
||||
CheckInactive -->|"no"| PersistStatus --> DecryptKey --> PushSlave --> PushOk
|
||||
PushOk -->|"yes"| UpdatePushed --> ReturnOk --> Done
|
||||
PushOk -->|"no"| ReturnWarn --> Done
|
||||
|
||||
classDef decision fill:#FFC107,stroke:#F57F17,stroke-width:2px,color:#000
|
||||
classDef action fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000
|
||||
classDef terminal fill:#9ae6b4,stroke:#2f855a,stroke-width:2px,color:#000
|
||||
classDef error fill:#FC8181,stroke:#C53030,stroke-width:2px,color:#000
|
||||
class CheckExists,CheckMsg,CheckInactive,PushOk decision
|
||||
class PersistStatus,DecryptKey,PushSlave,UpdatePushed,SetInactive action
|
||||
class Start,Done terminal
|
||||
class NotFound,ValidationErr error
|
||||
```
|
||||
|
||||
Text alternative: Load entity (404 if missing) → validate DisableMessage required for NotAvailable → for Inactive skip push → for others persist, decrypt key, push to slave, set SlaveContactSuccess based on push result.
|
||||
|
||||
---
|
||||
|
||||
## BR-02 — Integrity Check Decision Logic
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Start(["VerifyIntegrityAsync\nper instance"])
|
||||
GetUrl["GetRegisteredMasterUrlAsync\n(slaveUrl, plainApiKey)"]
|
||||
Reachable{"Slave\nreachable?"}
|
||||
SetFailed["LastIntegrityCheckFailedAt = UtcNow\nSave — continue to next"]
|
||||
UrlMatch{"registeredMasterUrl\n= expected masterUrl?"}
|
||||
ClearOk["LastIntegrityCheckFailedAt = null\nLastContactedAt = UtcNow\nSave"]
|
||||
ReRegister["RegisterMasterAsync\n(slaveUrl, plainApiKey, masterUrl)"]
|
||||
RegOk{"Re-registration\nsucceeded?"}
|
||||
ClearAfterReg["LastIntegrityCheckFailedAt = null\nLastContactedAt = UtcNow\nSave"]
|
||||
SetFailedReg["LastIntegrityCheckFailedAt = UtcNow\nSave"]
|
||||
Next(["Next instance"])
|
||||
|
||||
Start --> GetUrl --> Reachable
|
||||
Reachable -->|"no"| SetFailed --> Next
|
||||
Reachable -->|"yes"| UrlMatch
|
||||
UrlMatch -->|"match"| ClearOk --> Next
|
||||
UrlMatch -->|"mismatch"| ReRegister --> RegOk
|
||||
RegOk -->|"yes"| ClearAfterReg --> Next
|
||||
RegOk -->|"no"| SetFailedReg --> Next
|
||||
|
||||
classDef decision fill:#FFC107,stroke:#F57F17,stroke-width:2px,color:#000
|
||||
classDef action fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000
|
||||
classDef terminal fill:#9ae6b4,stroke:#2f855a,stroke-width:2px,color:#000
|
||||
classDef error fill:#FC8181,stroke:#C53030,stroke-width:2px,color:#000
|
||||
class Reachable,UrlMatch,RegOk decision
|
||||
class GetUrl,SetFailed,ClearOk,ReRegister,ClearAfterReg,SetFailedReg action
|
||||
class Start,Next terminal
|
||||
```
|
||||
|
||||
Text alternative: For each active slave — attempt to get its registered master URL; if unreachable set failure flag; if reachable and URL matches clear flag; if mismatch re-register; clear flag on success, set flag on failure.
|
||||
|
||||
---
|
||||
|
||||
## Validation Rules
|
||||
|
||||
| Rule | Field | Condition | Error |
|
||||
|------|-------|-----------|-------|
|
||||
| BR-VAL-01 | `Name` | Must not be null or whitespace | `Name is required` |
|
||||
| BR-VAL-02 | `Url` | Must not be null or whitespace | `Url is required` |
|
||||
| BR-VAL-03 | `Url` | Must start with `http://` or `https://` | `Url must be a valid absolute HTTP(S) URL` |
|
||||
| BR-VAL-04 | `ApiKey` | Must not be null or whitespace (on create) | `ApiKey is required` |
|
||||
| BR-VAL-05 | `DisableMessage` | Required (non-empty) when `Status = NotAvailable` | `DisableMessage is required when status is NotAvailable` |
|
||||
| BR-VAL-06 | `Status` | Must be a valid `CmsInstanceStatus` enum value | `Invalid status value` |
|
||||
| BR-VAL-07 | `id` (update) | CmsInstance with given id must exist | `CmsInstance not found` (404) |
|
||||
|
||||
---
|
||||
|
||||
## Status Transition Rules
|
||||
|
||||
| From | To | DisableMessage | HTTP Push | Notes |
|
||||
|------|----|---------------|-----------|-------|
|
||||
| Any | `Available` | Clear to null | Yes | Slave re-enabled |
|
||||
| Any | `NotAvailable` | Required, non-empty | Yes | Slave disabled with message |
|
||||
| Any | `Inactive` | Clear to null | **No** | Master stops all contact |
|
||||
| `Inactive` | `Available` | Clear to null | Yes | Reactivation |
|
||||
| `Inactive` | `NotAvailable` | Required, non-empty | Yes | Reactivation with disable |
|
||||
|
||||
---
|
||||
|
||||
## ApiKey Encryption Rules
|
||||
|
||||
| Rule | Description |
|
||||
|------|-------------|
|
||||
| BR-ENC-01 | `ApiKey` is encrypted via `IDataProtector` before writing to `MasterDbContext` |
|
||||
| BR-ENC-02 | `ApiKey` is decrypted via `IDataProtector` immediately before each HTTP call requiring it |
|
||||
| BR-ENC-03 | `ApiKey` is **never** included in `CmsInstanceDto` or any other API response |
|
||||
| BR-ENC-04 | `ApiKey` is accepted in `CreateCmsInstanceRequest` on creation only; no update endpoint for ApiKey |
|
||||
|
||||
---
|
||||
|
||||
## Master URL Resolution Rules
|
||||
|
||||
| Context | Resolution Strategy |
|
||||
|---------|-------------------|
|
||||
| Controller-originated calls (Add) | Derive from `HttpContext.Request` scheme + host + (optional port) via `IHttpContextAccessor` |
|
||||
| Background service calls (Integrity Check) | Read `MasterModuleOptions.MasterUrl` from configuration |
|
||||
| `MasterModuleOptions.MasterUrl` is null in background context | Log a warning; skip registration/integrity for that cycle |
|
||||
|
||||
---
|
||||
|
||||
## HTTP Contact Exclusion Rules
|
||||
|
||||
| Rule | Description |
|
||||
|------|-------------|
|
||||
| BR-CONTACT-01 | Instances with `Status = Inactive` are excluded from `GetActiveAsync` and never contacted via HTTP |
|
||||
| BR-CONTACT-02 | Status push is skipped when transitioning any status → `Inactive` |
|
||||
| BR-CONTACT-03 | Integrity check runs only against instances where `Status != Inactive` |
|
||||
|
||||
---
|
||||
|
||||
## Background Service Rules
|
||||
|
||||
| Rule | Description |
|
||||
|------|-------------|
|
||||
| BR-BG-01 | `IntegrityCheckBackgroundService` resolves `ICmsInstanceService` via `IServiceScopeFactory` per tick (not injected directly, as service is Scoped) |
|
||||
| BR-BG-02 | Each tick creates and disposes its own `IServiceScope` |
|
||||
| BR-BG-03 | Exceptions within a single slave's integrity check are caught, logged, and do not abort processing for remaining slaves |
|
||||
| BR-BG-04 | If `MasterModuleOptions.MasterUrl` is null or empty, the background service logs a warning and skips the entire integrity check for that cycle |
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
# 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 | **No** — all HTTP contact is halted |
|
||||
|
||||
---
|
||||
|
||||
## 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 HTTP push to slave succeeded; `false` if push failed (slave unreachable); not applicable for `Inactive` transitions (returns `true`) |
|
||||
Reference in New Issue
Block a user