docs(diagrams): review + fix all .puml files; touch all to trigger render
Build and Push Docker Image / build (push) Successful in 31s
CI / Security audit (push) Successful in 51s
CI / Tests & coverage (push) Successful in 1m6s
Render PlantUML Diagrams / Render .puml → .png (push) Failing after 47s

seq-auth:
- startAutoRefresh() -> startSSE(), stopAutoRefresh() -> stopSSE()
- Cookie secure flag: 'secure (prod)' -> 'secure (if TRUST_PROXY)'

component:
- Fix typo creatApp -> createApp
- Add GET /csrf, POST /logout to browser->auth arrow
- Add GET /stream (SSE) to browser->dashboard arrow

class-server:
- Add subscribers Set, onPollComplete(), offPollComplete() to Poller class

class-data:
- Add SSE Event /stream shape alongside API Response /user-downloads
- Add sser *-- dl relationship

state-ui:
- Fix invalid multi-line transition labels with raw Unicode arrows
  (broke PlantUML parser); replace with valid \n escapes on single line

seq-dashboard, seq-polling, state-poller, activity-matching:
- Whitespace touch to trigger render-diagrams CI workflow
This commit is contained in:
2026-05-17 10:20:52 +01:00
parent 28f2aa17d8
commit 9751dbf98d
9 changed files with 28 additions and 17 deletions
+1
View File
@@ -2,6 +2,7 @@
!theme plain !theme plain
title sofarr — Download Matching Activity Diagram title sofarr — Download Matching Activity Diagram
start start
:Read cached data from MemoryCache; :Read cached data from MemoryCache;
+9
View File
@@ -183,6 +183,14 @@ package "sofarr Internal Models" {
+ downloads : Download[] + downloads : Download[]
} }
class "SSE Event\n/stream (data: frame)" as sser {
+ user : string
+ isAdmin : boolean
+ downloads : Download[]
' Same shape as /user-downloads response.
' Pushed after every poll cycle completes.
}
class "Status Response\n/status" as statr { class "Status Response\n/status" as statr {
+ server : ServerInfo + server : ServerInfo
+ polling : PollingInfo + polling : PollingInfo
@@ -212,6 +220,7 @@ package "sofarr Internal Models" {
} }
apir *-- dl apir *-- dl
sser *-- dl
statr *-- si statr *-- si
statr *-- pi statr *-- pi
} }
+3
View File
@@ -136,11 +136,14 @@ package "server/utils" {
- polling : boolean - polling : boolean
- lastPollTimings : PollTimings|null - lastPollTimings : PollTimings|null
- intervalHandle : number|null - intervalHandle : number|null
- subscribers : Set<Function>
-- --
+ startPoller() : void + startPoller() : void
+ stopPoller() : void + stopPoller() : void
+ pollAllServices() : Promise<void> + pollAllServices() : Promise<void>
+ getLastPollTimings() : PollTimings|null + getLastPollTimings() : PollTimings|null
+ onPollComplete(cb) : void
+ offPollComplete(cb) : void
-- --
- timed(label, fn) : TimedResult - timed(label, fn) : TimedResult
} }
+3 -3
View File
@@ -16,7 +16,7 @@ package "Browser" as browser {
package "Express Server" as server { package "Express Server" as server {
[index.js\nEntry Point] as entry [index.js\nEntry Point] as entry
[app.js\ncreatApp() factory] as appfactory [app.js\ncreateApp() factory] as appfactory
package "Middleware" { package "Middleware" {
[helmet\n(CSP nonce, HSTS)] as hm [helmet\n(CSP nonce, HSTS)] as hm
@@ -111,8 +111,8 @@ poller --> sonarr : tags + queue + history
poller --> radarr : tags + queue + history poller --> radarr : tags + queue + history
qbt --> qbit : login + torrents/info qbt --> qbit : login + torrents/info
appjs --> auth : POST /login\nGET /me appjs --> auth : POST /login\nGET /me\nGET /csrf\nPOST /logout
appjs --> dashboard : GET /user-downloads\nGET /status appjs --> dashboard : GET /stream (SSE)\nGET /user-downloads\nGET /status
es --> html : serve static es --> html : serve static
@enduml @enduml
+4 -4
View File
@@ -54,14 +54,14 @@ alt Valid credentials
Never sent to the client. Never sent to the client.
31-day TTL, atomic JSON write. 31-day TTL, atomic JSON write.
end note end note
auth -> auth : Set emby_user cookie\n{ id, name, isAdmin }\nhttpOnly, sameSite=strict\nsecure (prod), signed (COOKIE_SECRET)\nrememberMe=true → Max-Age 30d\nrememberMe=false → session cookie auth -> auth : Set emby_user cookie\n{ id, name, isAdmin }\nhttpOnly, sameSite=strict\nsecure (if TRUST_PROXY), signed (COOKIE_SECRET)\nrememberMe=true → Max-Age 30d\nrememberMe=false → session cookie
auth -> auth : Generate csrfToken\n(32-byte random hex) auth -> auth : Generate csrfToken\n(32-byte random hex)
auth -> auth : Set csrf_token cookie\nhttpOnly=false (JS-readable)\nsameSite=strict, secure (prod) auth -> auth : Set csrf_token cookie\nhttpOnly=false (JS-readable)\nsameSite=strict, secure (if TRUST_PROXY)
auth --> browser : { success: true, user, csrfToken } auth --> browser : { success: true, user, csrfToken }
browser -> browser : store csrfToken in memory browser -> browser : store csrfToken in memory
browser -> browser : fadeOutLogin() browser -> browser : fadeOutLogin()
browser -> browser : showDashboard() browser -> browser : showDashboard()
browser -> browser : startAutoRefresh() browser -> browser : startSSE()
browser -> browser : dismissSplash() browser -> browser : dismissSplash()
else Invalid credentials else Invalid credentials
emby --> auth : 401 Error emby --> auth : 401 Error
@@ -82,7 +82,7 @@ browser -> browser : store new csrfToken in memory
== Logout == == Logout ==
user -> browser : Click Logout user -> browser : Click Logout
browser -> browser : stopAutoRefresh() browser -> browser : stopSSE()
browser -> auth : POST /api/auth/logout\n(no CSRF required — auth routes\nexempt; sameSite:strict protects) browser -> auth : POST /api/auth/logout\n(no CSRF required — auth routes\nexempt; sameSite:strict protects)
activate auth activate auth
auth -> auth : Parse emby_user cookie → user auth -> auth : Parse emby_user cookie → user
+1
View File
@@ -2,6 +2,7 @@
!theme plain !theme plain
title sofarr — Dashboard SSE Stream Sequence title sofarr — Dashboard SSE Stream Sequence
actor User as user actor User as user
participant "Browser\n(app.js)" as browser participant "Browser\n(app.js)" as browser
participant "Express\n/api/dashboard" as dashboard participant "Express\n/api/dashboard" as dashboard
+1
View File
@@ -2,6 +2,7 @@
!theme plain !theme plain
title sofarr — Background Polling Cycle title sofarr — Background Polling Cycle
participant "index.js\n(startup)" as entry participant "index.js\n(startup)" as entry
participant "Poller" as poller participant "Poller" as poller
participant "Config" as config participant "Config" as config
+1
View File
@@ -2,6 +2,7 @@
!theme plain !theme plain
title sofarr — Poller State Diagram title sofarr — Poller State Diagram
[*] --> CheckConfig : startPoller() [*] --> CheckConfig : startPoller()
state CheckConfig <<choice>> state CheckConfig <<choice>>
+5 -10
View File
@@ -43,15 +43,12 @@ state Dashboard {
state "Status Panel Closed" as status_closed state "Status Panel Closed" as status_closed
[*] --> rendering [*] --> rendering
rendering --> rendering : SSE message received rendering --> rendering : SSE message received\n-> renderDownloads()
→ renderDownloads()
rendering --> rendering : Theme change rendering --> rendering : Theme change
status_closed --> status_open : Click "Status" btn status_closed --> status_open : Click "Status" btn\n(admin only)
(admin only) status_open --> status_closed : Click close (x)
status_open --> status_closed : Click close (×) status_open --> status_open : 5s timer -> renderStatusPanel()
status_open --> status_open : 5s timer
→ renderStatusPanel()
[*] --> status_closed [*] --> status_closed
@@ -66,8 +63,6 @@ state Dashboard {
} }
} }
Dashboard --> LoginForm : Logout Dashboard --> LoginForm : Logout\n(stopSSE, clear state)
(stopSSE,
clear state)
@enduml @enduml