Files
sofarr/docs/diagrams/activity-matching.puml
Gronod 6675e5dcfe
All checks were successful
Build and Push Docker Image / build (push) Successful in 24s
docs: update architecture docs and diagrams for recent changes
ARCHITECTURE.md:
- Directory structure: add middleware/requireAuth.js and favicon assets
- §4.1: remove CORS from middleware list
- §4.2: all proxy routes now auth-required via requireAuth; add
  middleware description
- §6: cookie payload corrected (no token); document secure+sameSite
- §7: add emby:users cache key (60s TTL)
- §8: Download Object table: userTag → allTags/matchedUserTag/tagBadges
- §9 POST /login: document cookie security attributes
- §10: add Tag Badge Rendering section; remove hardcoded line count

Diagrams:
- class-server.puml: add requireAuth middleware module; update
  dashboard.js methods (extractAllTags, extractUserTag w/ username,
  buildTagBadges, getEmbyUsers); add TagBadge value class; add auth
  relationships for all proxy routes
- class-data.puml: Download Object userTag → allTags/matchedUserTag/
  tagBadges; add TagBadge class; remove token from Session Cookie
- seq-auth.puml: cookie payload no longer contains token; add
  secure/sameSite note
- component.puml: remove CORS component; add requireAuth; consolidate
  Emby connection to show tag badge + user-summary usage
- activity-matching.puml: update to extractAllTags/extractUserTag
  (with username); showAll uses hasAnyTag; tagBadges built from
  embyUserMap; add Emby user fetch step; update legend
- seq-dashboard.puml: add emby:users cache lookup / Emby fetch for
  showAll; update matching groups to show tag classification; add
  tag badge rendering note on renderDownloads()
2026-05-16 15:41:23 +01:00

157 lines
5.0 KiB
Plaintext

@startuml activity-matching
!theme plain
title sofarr — Download Matching Activity Diagram
start
:Read cached data from MemoryCache;
note right
poll:sab-queue, poll:sab-history,
poll:sonarr-queue, poll:sonarr-history,
poll:radarr-queue, poll:radarr-history,
poll:sonarr-tags, poll:radarr-tags,
poll:qbittorrent
end note
:Build **seriesMap** from Sonarr queue records
(seriesId → embedded series object);
:Build **moviesMap** from Radarr queue records
(movieId → embedded movie object);
:Build **sonarrTagMap** (tagId → label)
Build **radarrTagMap** (tagId → label);
if (showAll?) then (yes)
:Fetch full Emby user list
Build **embyUserMap** (lowerName → displayName)
[cached 60s];
endif
:Initialise **userDownloads** = [];
partition "Process SABnzbd Queue Slots" {
while (More queue slots?) is (yes)
:Get slot filename (nzbName);
:nzbNameLower = nzbName.toLowerCase();
if (Title matches Sonarr **queue** record?) then (yes)
:series = seriesMap.get(match.seriesId)\n|| match.series;
if (series exists?) then (yes)
:allTags = extractAllTags(series.tags, sonarrTagMap)
matchedUserTag = extractUserTag(series.tags, sonarrTagMap, username);
if (showAll AND hasAnyTag?) then (yes)
:Build download object (type=series)
Add coverArt, status, progress, speed, eta
Add allTags, matchedUserTag
Add tagBadges = buildTagBadges(allTags, embyUserMap)
Add importIssues if any
Add admin fields (paths, arrLink);
:Push to **userDownloads**;
elseif (NOT showAll AND matchedUserTag?) then (yes)
:Build download object (type=series)
Add matchedUserTag;
:Push to **userDownloads**;
endif
endif
endif
if (Title matches Radarr **queue** record?) then (yes)
:movie = moviesMap.get(match.movieId)\n|| match.movie;
if (movie exists?) then (yes)
:allTags = extractAllTags(movie.tags, radarrTagMap)
matchedUserTag = extractUserTag(movie.tags, radarrTagMap, username);
if (showAll AND hasAnyTag?) then (yes)
:Build download object (type=movie)
Add coverArt, status, progress, speed, eta
Add allTags, matchedUserTag, tagBadges
Add importIssues if any
Add admin fields (paths, arrLink);
:Push to **userDownloads**;
elseif (NOT showAll AND matchedUserTag?) then (yes)
:Build download object (type=movie)
Add matchedUserTag;
:Push to **userDownloads**;
endif
endif
endif
endwhile (no)
}
partition "Process SABnzbd History Slots" {
while (More history slots?) is (yes)
:Get slot name (nzbName);
:nzbNameLower = nzbName.toLowerCase();
if (Title matches Sonarr **history** record?) then (yes)
:series = seriesMap.get(match.seriesId)\n|| match.series;
if (series found?) then (yes)
:extractAllTags + extractUserTag(username)
Build download (type=series, completedAt)
Add allTags, matchedUserTag, tagBadges if showAll;
:Push to **userDownloads** if showAll+anyTag or matchedUserTag;
endif
endif
if (Title matches Radarr **history** record?) then (yes)
:movie = moviesMap.get(match.movieId)\n|| match.movie;
if (movie found?) then (yes)
:extractAllTags + extractUserTag(username)
Build download (type=movie, completedAt)
Add allTags, matchedUserTag, tagBadges if showAll;
:Push to **userDownloads** if showAll+anyTag or matchedUserTag;
endif
endif
endwhile (no)
}
partition "Process qBittorrent Torrents" {
while (More torrents?) is (yes)
:Get torrent name;
:torrentNameLower = name.toLowerCase();
if (Matches Sonarr **queue**?) then (yes)
:Resolve series → check tag;
:mapTorrentToDownload() + enrich;
:Push if matches → **continue**;
elseif (Matches Radarr **queue**?) then (yes)
:Resolve movie → check tag;
:mapTorrentToDownload() + enrich;
:Push if matches → **continue**;
elseif (Matches Sonarr **history**?) then (yes)
:Resolve series via seriesMap;
:mapTorrentToDownload() + enrich;
:Push if matches → **continue**;
elseif (Matches Radarr **history**?) then (yes)
:Resolve movie via moviesMap;
:mapTorrentToDownload() + enrich;
:Push if matches → **continue**;
else (no match)
:Skip torrent (unmatched);
endif
endwhile (no)
}
:Return JSON response
{ user, isAdmin, downloads: userDownloads };
stop
legend right
**Title Matching Logic**
(bidirectional substring, case-insensitive):
""rTitle.includes(dlTitle) || dlTitle.includes(rTitle)""
**Tag Matching Logic** (tagMatchesUser):
1. Exact: tag.toLowerCase() === username
2. Sanitised: sanitizeTagLabel(tag) === sanitizeTagLabel(username)
(handles Ombi-mangled email-style usernames)
**extractAllTags**: returns all resolved tag labels
**extractUserTag**: returns the ONE label matching current user
**buildTagBadges**: classifies each tag against full Emby user
list → { label, matchedUser: displayName | null }
end legend
@enduml