All checks were successful
Build and Push Docker Image / build (push) Successful in 24s
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()
157 lines
5.0 KiB
Plaintext
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
|