docs: update architecture docs and diagrams for recent changes
All checks were successful
Build and Push Docker Image / build (push) Successful in 24s
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()
This commit is contained in:
@@ -107,6 +107,8 @@ sofarr/
|
||||
│ │ ├── sabnzbd.js # Proxy routes to SABnzbd API
|
||||
│ │ ├── sonarr.js # Proxy routes to Sonarr API
|
||||
│ │ └── radarr.js # Proxy routes to Radarr API
|
||||
│ ├── middleware/
|
||||
│ │ └── requireAuth.js # httpOnly cookie auth middleware
|
||||
│ └── utils/
|
||||
│ ├── cache.js # MemoryCache class (Map + TTL + stats)
|
||||
│ ├── config.js # Multi-instance service configuration parser
|
||||
@@ -117,6 +119,9 @@ sofarr/
|
||||
│ ├── index.html # HTML shell: splash, login, dashboard
|
||||
│ ├── app.js # All frontend logic (auth, rendering, status)
|
||||
│ ├── style.css # Themes, layout, responsive design
|
||||
│ ├── favicon.ico # Multi-size favicon (16/32/48px)
|
||||
│ ├── favicon-32.png # 32px PNG favicon
|
||||
│ ├── favicon-192.png # 192px PNG (apple-touch-icon / PWA)
|
||||
│ └── images/ # Logo / splash screen assets
|
||||
├── Dockerfile # Production container image
|
||||
├── docker-compose.yaml # Example compose deployment
|
||||
@@ -135,7 +140,7 @@ Responsibilities:
|
||||
- Load environment variables via `dotenv`
|
||||
- Configure structured logging with level filtering (`LOG_LEVEL`)
|
||||
- Redirect `console.*` to both stdout and `server.log`
|
||||
- Mount Express middleware (CORS, cookie-parser, JSON, static files)
|
||||
- Mount Express middleware (cookie-parser, JSON, static files)
|
||||
- Mount route modules under `/api/*`
|
||||
- Start the background poller
|
||||
|
||||
@@ -143,12 +148,14 @@ Responsibilities:
|
||||
|
||||
| Module | Mount Point | Auth Required | Purpose |
|
||||
|--------|------------|---------------|---------|
|
||||
| `auth.js` | `/api/auth` | No | Login, session check, logout |
|
||||
| `dashboard.js` | `/api/dashboard` | Yes (cookie) | Aggregated download data, status |
|
||||
| `emby.js` | `/api/emby` | No | Proxy to Emby API |
|
||||
| `sabnzbd.js` | `/api/sabnzbd` | No | Proxy to SABnzbd API |
|
||||
| `sonarr.js` | `/api/sonarr` | No | Proxy to Sonarr API |
|
||||
| `radarr.js` | `/api/radarr` | No | Proxy to Radarr API |
|
||||
| `auth.js` | `/api/auth` | No (public) | Login, session check, logout |
|
||||
| `dashboard.js` | `/api/dashboard` | Yes (`requireAuth`) | Aggregated download data, status |
|
||||
| `emby.js` | `/api/emby` | Yes (`requireAuth`) | Proxy to Emby API |
|
||||
| `sabnzbd.js` | `/api/sabnzbd` | Yes (`requireAuth`) | Proxy to SABnzbd API |
|
||||
| `sonarr.js` | `/api/sonarr` | Yes (`requireAuth`) | Proxy to Sonarr API |
|
||||
| `radarr.js` | `/api/radarr` | Yes (`requireAuth`) | Proxy to Radarr API |
|
||||
|
||||
`requireAuth` (`server/middleware/requireAuth.js`) reads the `emby_user` httpOnly cookie and attaches the parsed user to `req.user`. Returns `401` if the cookie is absent or malformed.
|
||||
|
||||
> **Note:** The proxy routes (`emby`, `sabnzbd`, `sonarr`, `radarr`) use legacy single-instance env vars and are largely unused by the dashboard — they exist for direct API access. The dashboard reads from the poller cache.
|
||||
|
||||
@@ -208,7 +215,7 @@ When a user requests `/api/dashboard/user-downloads`:
|
||||
1. User submits credentials via the login form
|
||||
2. Backend calls Emby `POST /Users/authenticatebyname`
|
||||
3. On success, fetches full user profile to determine admin status
|
||||
4. Sets an `httpOnly` cookie (`emby_user`) containing: `{ id, name, isAdmin, token }`
|
||||
4. Sets an `httpOnly` cookie (`emby_user`) containing: `{ id, name, isAdmin }` — the Emby `AccessToken` is intentionally **not** stored in the cookie
|
||||
5. Cookie expires after 24 hours
|
||||
6. All subsequent dashboard requests read this cookie for identity
|
||||
|
||||
@@ -253,6 +260,7 @@ Users are matched to downloads via tags in Sonarr/Radarr:
|
||||
| `poll:radarr-history` | `{ records }` — lightweight, no embedded objects | Radarr history |
|
||||
| `poll:radarr-tags` | `[{id, label}]` | Radarr tag API |
|
||||
| `poll:qbittorrent` | `[torrent, ...]` | qBittorrent all torrents |
|
||||
| `emby:users` | `Map<lowerName, displayName>` | Full Emby user list (60s TTL) |
|
||||
|
||||
### TTL Strategy
|
||||
|
||||
@@ -314,7 +322,9 @@ Each matched download produces an object with:
|
||||
| `eta` | string | Estimated time remaining |
|
||||
| `seriesName` / `movieName` | string | Friendly media title |
|
||||
| `episodeInfo` / `movieInfo` | object | Full *arr queue/history record |
|
||||
| `userTag` | string | Matched user tag |
|
||||
| `allTags` | string[] | All resolved tag labels on the series/movie |
|
||||
| `matchedUserTag` | string / null | Tag label matching the requesting user, or `null` |
|
||||
| `tagBadges` | `{label, matchedUser}[]` / undefined | (Admin `showAll` only) Each tag classified against full Emby user list |
|
||||
| `importIssues` | string[] / null | Import warning/error messages |
|
||||
| `downloadPath` | string / null | (Admin) Download client path |
|
||||
| `targetPath` | string / null | (Admin) *arr target path |
|
||||
@@ -346,7 +356,7 @@ Authenticate a user via Emby.
|
||||
{ "success": false, "error": "Invalid username or password" }
|
||||
```
|
||||
|
||||
**Side Effect:** Sets `emby_user` httpOnly cookie (24h TTL).
|
||||
**Side Effect:** Sets `emby_user` httpOnly cookie (24h TTL, `httpOnly`, `secure` in production, `sameSite: strict`). Cookie payload: `{ id, name, isAdmin }` — Emby `AccessToken` is not stored.
|
||||
|
||||
---
|
||||
|
||||
@@ -447,7 +457,7 @@ Admin-only per-user download counts (fetches live from APIs, not cached).
|
||||
|
||||
## 10. Frontend Architecture
|
||||
|
||||
The frontend is a **vanilla JavaScript SPA** with no build step. All logic resides in `app.js` (754 lines), styled by `style.css`, and structured by `index.html`.
|
||||
The frontend is a **vanilla JavaScript SPA** with no build step. All logic resides in `app.js`, styled by `style.css`, and structured by `index.html`.
|
||||
|
||||
### UI States
|
||||
|
||||
@@ -473,7 +483,7 @@ The frontend is a **vanilla JavaScript SPA** with no build step. All logic resid
|
||||
| `handleLogin()` | Authenticate, fade login → splash → dashboard |
|
||||
| `fetchUserDownloads()` | GET `/user-downloads`, update state, re-render |
|
||||
| `renderDownloads()` | Diff-based card rendering (create/update/remove) |
|
||||
| `createDownloadCard()` | Build DOM for a single download card |
|
||||
| `createDownloadCard()` | Build DOM for a single download card; renders tag badges |
|
||||
| `updateDownloadCard()` | Update existing card in-place (progress, speed, etc.) |
|
||||
| `toggleStatusPanel()` | Show/hide admin status panel |
|
||||
| `renderStatusPanel()` | Build status HTML (server, polling, cache, clients) |
|
||||
@@ -489,6 +499,15 @@ Three CSS themes via `data-theme` attribute on `<html>`:
|
||||
|
||||
Theme selection persists in `localStorage`.
|
||||
|
||||
### Tag Badge Rendering
|
||||
|
||||
Download cards render tag badges in the card header:
|
||||
|
||||
- **Normal user view**: a single accent-coloured badge showing the tag label that matched the current user's username (via `matchedUserTag`).
|
||||
- **Admin `showAll` view**: all tags on the download are rendered using `tagBadges[]`:
|
||||
- Tags with **no matching Emby user** → amber badge showing the raw tag label (leftmost)
|
||||
- Tags **matched to a known Emby user** → accent badge showing the Emby display name (rightmost)
|
||||
|
||||
### Auto-Refresh
|
||||
|
||||
The dashboard polls the server at the user-selected interval (1s, 5s, 10s, or Off). The status panel auto-refreshes in sync. Client refresh rate is sent to the server on each request for active-client tracking.
|
||||
|
||||
Reference in New Issue
Block a user