# 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.