@startuml MonitorAlbumStream - Automatic Mode Happy Path skinparam sequenceMessageAlign center skinparam responseMessageBelowArrow true title MonitorAlbumStream: Automatic Mode (Fire-and-Forget) actor Client participant "gRPC Server" as Server participant "WorkflowRegistry" as Registry participant "EventBus" as Bus participant "monitorWorkflow\n(background goroutine)" as Workflow database "PostgreSQL" as DB participant "MusicAgregatorService" as Service participant "IndexerService" as Indexer participant "MagnetResolver" as Magnet participant "TorrentClient\n(qBittorrent)" as QBit == 1. Initialize Stream == Client -> Server: MonitorAlbumStream() Server -> Client: stream established Client -> Server: StartMonitorRequest note right: album_id, quality\nmode = AUTOMATIC == 2. Start or Subscribe to Workflow == Server -> Registry: GetOrCreate(albumID, quality) alt New workflow Registry --> Server: (entry, created=true) Server -> Bus: Subscribe(topic) note right #lightblue: Subscribe BEFORE\nstarting goroutine\n(no missed events) Server -> Workflow: go workflow.run(entry.Ctx) note right #lightyellow: Background goroutine.\nDecoupled from stream context.\nClient can disconnect freely. else Existing workflow Registry --> Server: (entry, created=false) Server -> Bus: Subscribe(topic) Server -> DB: Replay album_events\n(since last seq) DB --> Server: historical events Server ->> Client: replayed events end == 3. Event Bridge (concurrent) == note over Client, Workflow #lightblue **Left side**: Event bus → gRPC stream bridge **Right side**: Workflow executing in background Both run concurrently. Client disconnect only stops the bridge. end note ||| Workflow -> DB: album_events.Create(FETCHING_METADATA) Workflow -> Bus: Publish(FETCHING_METADATA) Bus ->> Server: event notification Server ->> Client: StatusUpdate(FETCHING_METADATA) Workflow -> Service: getAlbumWithPersist() Workflow -> DB: workflow_runs.Create(albumID, quality) Workflow -> DB: album_events.Create(CHECKING_OWNED) Workflow -> Bus: Publish(CHECKING_OWNED) Bus ->> Server: event notification Server ->> Client: StatusUpdate(CHECKING_OWNED) Workflow -> Indexer: Search() Workflow -> DB: album_events.Create(PARSING_RESULTS) Workflow -> Bus: Publish(PARSING_RESULTS) Bus ->> Server: event notification Server ->> Client: StatusUpdate(PARSING_RESULTS) Workflow -> Workflow: filterByQuality + selectBest Workflow -> DB: saveTorrentAndDownload() note right: DB save BEFORE qBit add\n(prevents orphan torrents) Workflow -> QBit: AddMagnet() Workflow -> DB: album_events.Create(COMPLETE) Workflow -> Bus: Publish(COMPLETE) Bus ->> Server: event notification Server ->> Client: MonitorAlbumResponse note right #lightgreen: Final result Workflow -> DB: workflow_runs.SetCompleted() Workflow -> Registry: Remove(albumID, quality) == 4. Client Disconnect (Fire-and-Forget) == note over Client, Workflow #lightyellow Client can disconnect at ANY point during the workflow. The workflow goroutine continues independently. Another client can subscribe to the same workflow later. end note @enduml