docs(diagrams): review + fix all .puml files; touch all to trigger render
Some checks failed
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

View File

@@ -2,6 +2,7 @@
!theme plain
title sofarr — Download Matching Activity Diagram
start
:Read cached data from MemoryCache;

View File

@@ -183,6 +183,14 @@ package "sofarr Internal Models" {
+ 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 {
+ server : ServerInfo
+ polling : PollingInfo
@@ -212,6 +220,7 @@ package "sofarr Internal Models" {
}
apir *-- dl
sser *-- dl
statr *-- si
statr *-- pi
}

View File

@@ -136,11 +136,14 @@ package "server/utils" {
- polling : boolean
- lastPollTimings : PollTimings|null
- intervalHandle : number|null
- subscribers : Set<Function>
--
+ startPoller() : void
+ stopPoller() : void
+ pollAllServices() : Promise<void>
+ getLastPollTimings() : PollTimings|null
+ onPollComplete(cb) : void
+ offPollComplete(cb) : void
--
- timed(label, fn) : TimedResult
}

View File

@@ -16,7 +16,7 @@ package "Browser" as browser {
package "Express Server" as server {
[index.js\nEntry Point] as entry
[app.js\ncreatApp() factory] as appfactory
[app.js\ncreateApp() factory] as appfactory
package "Middleware" {
[helmet\n(CSP nonce, HSTS)] as hm
@@ -111,8 +111,8 @@ poller --> sonarr : tags + queue + history
poller --> radarr : tags + queue + history
qbt --> qbit : login + torrents/info
appjs --> auth : POST /login\nGET /me
appjs --> dashboard : GET /user-downloads\nGET /status
appjs --> auth : POST /login\nGET /me\nGET /csrf\nPOST /logout
appjs --> dashboard : GET /stream (SSE)\nGET /user-downloads\nGET /status
es --> html : serve static
@enduml

View File

@@ -54,14 +54,14 @@ alt Valid credentials
Never sent to the client.
31-day TTL, atomic JSON write.
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 : 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 }
browser -> browser : store csrfToken in memory
browser -> browser : fadeOutLogin()
browser -> browser : showDashboard()
browser -> browser : startAutoRefresh()
browser -> browser : startSSE()
browser -> browser : dismissSplash()
else Invalid credentials
emby --> auth : 401 Error
@@ -82,7 +82,7 @@ browser -> browser : store new csrfToken in memory
== 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)
activate auth
auth -> auth : Parse emby_user cookie → user

View File

@@ -2,6 +2,7 @@
!theme plain
title sofarr — Dashboard SSE Stream Sequence
actor User as user
participant "Browser\n(app.js)" as browser
participant "Express\n/api/dashboard" as dashboard

View File

@@ -2,6 +2,7 @@
!theme plain
title sofarr — Background Polling Cycle
participant "index.js\n(startup)" as entry
participant "Poller" as poller
participant "Config" as config

View File

@@ -2,6 +2,7 @@
!theme plain
title sofarr — Poller State Diagram
[*] --> CheckConfig : startPoller()
state CheckConfig <<choice>>

View File

@@ -43,15 +43,12 @@ state Dashboard {
state "Status Panel Closed" as status_closed
[*] --> rendering
rendering --> rendering : SSE message received
→ renderDownloads()
rendering --> rendering : SSE message received\n-> renderDownloads()
rendering --> rendering : Theme change
status_closed --> status_open : Click "Status" btn
(admin only)
status_open --> status_closed : Click close (×)
status_open --> status_open : 5s timer
→ renderStatusPanel()
status_closed --> status_open : Click "Status" btn\n(admin only)
status_open --> status_closed : Click close (x)
status_open --> status_open : 5s timer -> renderStatusPanel()
[*] --> status_closed
@@ -66,8 +63,6 @@ state Dashboard {
}
}
Dashboard --> LoginForm : Logout
(stopSSE,
clear state)
Dashboard --> LoginForm : Logout\n(stopSSE, clear state)
@enduml