docs: update ARCHITECTURE.md and README for history feature (v2)
This commit is contained in:
@@ -132,13 +132,15 @@ sofarr/
|
|||||||
│ │ ├── emby.js # Proxy routes to Emby API
|
│ │ ├── emby.js # Proxy routes to Emby API
|
||||||
│ │ ├── sabnzbd.js # Proxy routes to SABnzbd API
|
│ │ ├── sabnzbd.js # Proxy routes to SABnzbd API
|
||||||
│ │ ├── sonarr.js # Proxy routes to Sonarr API
|
│ │ ├── sonarr.js # Proxy routes to Sonarr API
|
||||||
│ │ └── radarr.js # Proxy routes to Radarr API
|
│ │ ├── radarr.js # Proxy routes to Radarr API
|
||||||
|
│ │ └── history.js # GET /api/history/recent — recently completed downloads
|
||||||
│ ├── middleware/
|
│ ├── middleware/
|
||||||
│ │ ├── requireAuth.js # httpOnly cookie auth enforcement
|
│ │ ├── requireAuth.js # httpOnly cookie auth enforcement
|
||||||
│ │ └── verifyCsrf.js # CSRF double-submit cookie validation
|
│ │ └── verifyCsrf.js # CSRF double-submit cookie validation
|
||||||
│ └── utils/
|
│ └── utils/
|
||||||
│ ├── cache.js # MemoryCache class (Map + TTL + stats)
|
│ ├── cache.js # MemoryCache class (Map + TTL + stats)
|
||||||
│ ├── config.js # Multi-instance service configuration parser
|
│ ├── config.js # Multi-instance service configuration parser
|
||||||
|
│ ├── historyFetcher.js # Fetch + cache Sonarr/Radarr history; event classification
|
||||||
│ ├── logger.js # File logger (DATA_DIR/server.log)
|
│ ├── logger.js # File logger (DATA_DIR/server.log)
|
||||||
│ ├── poller.js # Background polling engine + timing
|
│ ├── poller.js # Background polling engine + timing
|
||||||
│ ├── qbittorrent.js # qBittorrent client with auth + torrent mapping
|
│ ├── qbittorrent.js # qBittorrent client with auth + torrent mapping
|
||||||
@@ -209,6 +211,7 @@ sofarr/
|
|||||||
| `sabnzbd.js` | `/api/sabnzbd` | Yes (`requireAuth`) | Yes | Proxy to SABnzbd API |
|
| `sabnzbd.js` | `/api/sabnzbd` | Yes (`requireAuth`) | Yes | Proxy to SABnzbd API |
|
||||||
| `sonarr.js` | `/api/sonarr` | Yes (`requireAuth`) | Yes | Proxy to Sonarr API |
|
| `sonarr.js` | `/api/sonarr` | Yes (`requireAuth`) | Yes | Proxy to Sonarr API |
|
||||||
| `radarr.js` | `/api/radarr` | Yes (`requireAuth`) | Yes | Proxy to Radarr API |
|
| `radarr.js` | `/api/radarr` | Yes (`requireAuth`) | Yes | Proxy to Radarr API |
|
||||||
|
| `history.js` | `/api/history` | Yes (`requireAuth`) | No (GET only) | Recently completed downloads from Sonarr/Radarr history |
|
||||||
|
|
||||||
**`requireAuth`** (`server/middleware/requireAuth.js`) reads the `emby_user` cookie (signed if `COOKIE_SECRET` is set) and attaches the parsed `{ id, name, isAdmin }` user to `req.user`. Returns `401` if the cookie is absent, tampered, or schema-invalid.
|
**`requireAuth`** (`server/middleware/requireAuth.js`) reads the `emby_user` cookie (signed if `COOKIE_SECRET` is set) and attaches the parsed `{ id, name, isAdmin }` user to `req.user`. Returns `401` if the cookie is absent, tampered, or schema-invalid.
|
||||||
|
|
||||||
@@ -230,6 +233,8 @@ sofarr/
|
|||||||
|
|
||||||
**`sanitizeError.js`** — Redacts secrets from error message strings before they are logged or returned in API responses. Patterns: URL query-param secrets (`apikey=`, `token=`, etc.), HTTP auth headers (`Authorization:`, `X-Emby-Authorization:`, etc.), Bearer tokens, and basic-auth credentials in URLs.
|
**`sanitizeError.js`** — Redacts secrets from error message strings before they are logged or returned in API responses. Patterns: URL query-param secrets (`apikey=`, `token=`, etc.), HTTP auth headers (`Authorization:`, `X-Emby-Authorization:`, etc.), Bearer tokens, and basic-auth credentials in URLs.
|
||||||
|
|
||||||
|
**`historyFetcher.js`** — Fetches history records from all Sonarr/Radarr instances for a configurable date window (`since`). Results are cached under `history:sonarr` / `history:radarr` for 5 minutes. Exports `classifySonarrEvent` / `classifyRadarrEvent` (returns `'imported'` | `'failed'` | `'other'`) and `invalidateHistoryCache`.
|
||||||
|
|
||||||
**`logger.js`** — Simple file appender writing timestamped messages to `DATA_DIR/server.log`.
|
**`logger.js`** — Simple file appender writing timestamped messages to `DATA_DIR/server.log`.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -342,6 +347,8 @@ Users are matched to downloads via tags in Sonarr/Radarr:
|
|||||||
| `poll:radarr-tags` | `[{id, label}]` | Radarr tag API |
|
| `poll:radarr-tags` | `[{id, label}]` | Radarr tag API |
|
||||||
| `poll:qbittorrent` | `[torrent, ...]` | qBittorrent all torrents |
|
| `poll:qbittorrent` | `[torrent, ...]` | qBittorrent all torrents |
|
||||||
| `emby:users` | `Map<lowerName, displayName>` | Full Emby user list (60s TTL) |
|
| `emby:users` | `Map<lowerName, displayName>` | Full Emby user list (60s TTL) |
|
||||||
|
| `history:sonarr` | `[record, ...]` — flat array with `_instanceUrl` / `_instanceName` | Sonarr history (5 min TTL, fetched on-demand by `/api/history/recent`) |
|
||||||
|
| `history:radarr` | `[record, ...]` — flat array with `_instanceUrl` / `_instanceName` | Radarr history (5 min TTL, fetched on-demand by `/api/history/recent`) |
|
||||||
|
|
||||||
### TTL Strategy
|
### TTL Strategy
|
||||||
|
|
||||||
@@ -587,6 +594,50 @@ Admin-only per-user download counts (fetches live from APIs, not cached).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `GET /api/history/recent`
|
||||||
|
|
||||||
|
Returns recently completed (imported or failed) downloads from Sonarr/Radarr history for the authenticated user, filtered to the last `days` days.
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `days` | integer | `RECENT_COMPLETED_DAYS` env (default `7`) | How many days back to search. Capped at 90. |
|
||||||
|
| `showAll` | `"true"` | — | (Admin) Return records for all tagged users, not just the current user |
|
||||||
|
|
||||||
|
**Response (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user": "Alice",
|
||||||
|
"isAdmin": false,
|
||||||
|
"days": 7,
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"type": "series",
|
||||||
|
"outcome": "imported",
|
||||||
|
"title": "Show.S01E01.720p",
|
||||||
|
"seriesName": "My Show",
|
||||||
|
"coverArt": "https://…/poster.jpg",
|
||||||
|
"completedAt": "2026-05-15T18:00:00.000Z",
|
||||||
|
"quality": "720p",
|
||||||
|
"instanceName": "Main Sonarr",
|
||||||
|
"arrLink": "https://sonarr.example.com/series/my-show",
|
||||||
|
"allTags": ["alice"],
|
||||||
|
"matchedUserTag": "alice",
|
||||||
|
"arrRecordId": 1234,
|
||||||
|
"failureMessage": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `outcome` is `"imported"` or `"failed"`. Records with other event types (e.g. `grabbed`) are filtered out.
|
||||||
|
- `failureMessage` is only included when the authenticated user is an admin and `outcome` is `"failed"`.
|
||||||
|
- `arrRecordId` is only included for admin users.
|
||||||
|
- Results are sorted newest first.
|
||||||
|
- History data is cached server-side for 5 minutes (`history:sonarr` / `history:radarr` cache keys).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 10. Frontend Architecture
|
## 10. Frontend Architecture
|
||||||
|
|
||||||
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`.
|
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`.
|
||||||
@@ -701,6 +752,7 @@ The status panel refreshes on a fixed 5-second timer and shows each SSE client w
|
|||||||
| Variable | Required | Default | Description |
|
| Variable | Required | Default | Description |
|
||||||
|----------|:--------:|---------|-------------|
|
|----------|:--------:|---------|-------------|
|
||||||
| `POLL_INTERVAL` | No | `5000` | Poll interval in ms. Set to `0`, `off`, or `false` to disable background polling (on-demand mode). |
|
| `POLL_INTERVAL` | No | `5000` | Poll interval in ms. Set to `0`, `off`, or `false` to disable background polling (on-demand mode). |
|
||||||
|
| `RECENT_COMPLETED_DAYS` | No | `7` | Default lookback window (days) for `GET /api/history/recent`. Overridable per-request via `?days=`. Max 90. |
|
||||||
| `LOG_LEVEL` | No | `info` | `debug`, `info`, `warn`, `error`, `silent` |
|
| `LOG_LEVEL` | No | `info` | `debug`, `info`, `warn`, `error`, `silent` |
|
||||||
|
|
||||||
### Instance JSON Format
|
### Instance JSON Format
|
||||||
@@ -835,6 +887,7 @@ graph TB
|
|||||||
sab_r[sabnzbd.js\n/api/sabnzbd]
|
sab_r[sabnzbd.js\n/api/sabnzbd]
|
||||||
sonarr_r[sonarr.js\n/api/sonarr]
|
sonarr_r[sonarr.js\n/api/sonarr]
|
||||||
radarr_r[radarr.js\n/api/radarr]
|
radarr_r[radarr.js\n/api/radarr]
|
||||||
|
history_r[history.js\n/api/history]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Utilities
|
subgraph Utilities
|
||||||
@@ -845,6 +898,7 @@ graph TB
|
|||||||
tokenstore[tokenStore.js\ntokens.json]
|
tokenstore[tokenStore.js\ntokens.json]
|
||||||
sanitize[sanitizeError.js]
|
sanitize[sanitizeError.js]
|
||||||
logger[logger.js]
|
logger[logger.js]
|
||||||
|
historyfetcher[historyFetcher.js]
|
||||||
end
|
end
|
||||||
|
|
||||||
entry --> appfactory
|
entry --> appfactory
|
||||||
@@ -854,11 +908,13 @@ graph TB
|
|||||||
appfactory --> hm & rl & cp & ej
|
appfactory --> hm & rl & cp & ej
|
||||||
appfactory -->|pre-CSRF| auth
|
appfactory -->|pre-CSRF| auth
|
||||||
appfactory --> verifycsrf
|
appfactory --> verifycsrf
|
||||||
appfactory --> dashboard & emby_r & sab_r & sonarr_r & radarr_r
|
appfactory --> dashboard & emby_r & sab_r & sonarr_r & radarr_r & history_r
|
||||||
|
|
||||||
dashboard & emby_r & sab_r & sonarr_r & radarr_r --> requireauth
|
dashboard & emby_r & sab_r & sonarr_r & radarr_r & history_r --> requireauth
|
||||||
auth --> tokenstore
|
auth --> tokenstore
|
||||||
dashboard --> cache & poller & config & qbt
|
dashboard --> cache & poller & config & qbt
|
||||||
|
history_r --> cache & config & historyfetcher
|
||||||
|
historyfetcher --> cache & config
|
||||||
poller --> cache & config & qbt & logger
|
poller --> cache & config & qbt & logger
|
||||||
qbt --> config & logger
|
qbt --> config & logger
|
||||||
auth & dashboard -.-> sanitize
|
auth & dashboard -.-> sanitize
|
||||||
|
|||||||
Reference in New Issue
Block a user