Files
slp-modular-cms/aidlc-docs/features/master-cms-module/construction/master-backend/functional-design/business-logic-model.md
T

7.3 KiB

Business Logic Model — Unit 1: master-backend

Flow 1 — AddAsync (Add Slave CMS)

Trigger: POST /api/v1/CmsInstances (Owner only)

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)

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)

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.