33b122d22b
Build and Push Docker Image / build (push) Successful in 1m46s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m33s
CI / Security audit (push) Successful in 1m56s
CI / Swagger Validation & Coverage (push) Successful in 2m35s
CI / Tests & coverage (push) Successful in 2m51s
Ombi's TV API nests all request data (requestedUser, approved, available, denied, requested, requestedDate) inside childRequests[] sub-objects. The application previously only inspected top-level properties, causing TV shows to consistently display 'unknown' status, 'unknown' user, and no request date. Changes: - OmbiRetriever._hydrateRequest(): hydrate requestedUser on each childRequests entry and promote requestedDate to top level - getRequestStatus() (server + client): aggregate status flags from childRequests[] when top-level properties are absent - Client date display: fallback to childRequests[0].requestedDate - Add 18 unit tests covering childRequests hydration, status aggregation, and date promotion Closes #53
53 KiB
53 KiB
Changelog
All notable changes to this project will be documented in this file. Format follows Keep a Changelog. This project adheres to Semantic Versioning.
[1.7.25] - 2026-05-27
Fixed
- Ombi TV Request Status, User, and Date Resolution (Issue #53) — Resolved the root cause where Ombi TV show requests consistently displayed "unknown" status, "unknown" user, and missing request dates. The Ombi API nests all TV request data (
requestedUser,approved,available,denied,requested,requestedDate) insidechildRequests[]sub-objects, while the application previously only inspected top-level properties.OmbiRetriever._hydrateRequest()now hydratesrequestedUseron eachchildRequestsentry and promotesrequestedDatefromchildRequests[0]to the top level.getRequestStatus()(server and client) now aggregates status flags fromchildRequests[]when top-level properties are absent.- Client-side date display now falls back to
childRequests[0].requestedDateas a defensive measure.
[1.7.24] - 2026-05-27
Enhanced
- Gitea Actions Prioritization — Optimized CI/CD workflow pipeline executions to prioritize critical
build-imageDocker compilation runs. Redundant security audits, tests, coverage generation, and RAML packages are now bypassed onrelease/**branches (which have already passed validation during development ondevelop). - Workflow Concurrency Controls — Configured active concurrency groups with
cancel-in-progress: trueinside bothci.ymlandbuild-image.ymlpipelines, ensuring obsolete running jobs are aborted instantly when newer commits are pushed.
[1.7.23] - 2026-05-27
Enhanced
- Request Card Link Alignment (Issue #55) — Aligned deep links on request cards (Ombi link and administrator Sonarr/Radarr deep links) with the elegant, inline
.service-iconstyling used across the downloads and history dashboard cards. Replaced bulky button elements with clean hoverable icons in a shared.service-icons-container, maintaining strict administrator-only visibility for *arr links.
[1.7.22] - 2026-05-27
Fixed
- Theme Switcher Multi-Button Support (Issue #54) — Resolved a frontend bug where themes other than Light failed to apply because the Javascript code queried a non-existent
#theme-toggleelement. Re-engineered the switcher to query all.theme-btnselectors inside.theme-switcher, managing and toggling the active class styling acrosslight,dark, andmonothemes, and cleanly persisting choices to local storage. - Ombi TV Requester User Extraction Fallback (Issue #53) — Resolved a bug where TV show requests from Ombi displayed as "unknown" user because requester attributes were nested under non-standard properties (
user,requestedBy,ombiUser,requestedByUser, and nested seasons/child requests arrays). Added robust multi-layer extraction logic on both backend and frontend layers to resolve requester usernames under all TV request structures. - Configurable Webhook Commit Delay & Retry Loop (Issue #53) — Mitigated race conditions between Ombi webhook events and database commits by introducing a configurable
OMBI_WEBHOOK_REFRESH_DELAY_MSdelay (defaulting to2000) and a smart 3-attempt retry polling loop to verify updated requester data before cache updates.
Added
- *Admin Request Card arr Library Lookup (Issue #53) — Added library deep-link lookup for admin views. Request cards now query Sonarr and Radarr library caches and dynamically render deep-link navigation icons in the card actions container if the item exists.
- Request Date/Time Presentation — Added request date and time display inside request cards formatted as
YYYY-MM-DD HH:MM. - Unknown (Ombi) Dotted Underline Tooltip — Added user-friendly placeholder "Unknown (Ombi)" with dotted underline and explanatory hover tooltip when user details are unavailable from the Ombi database.
- Expanded Test Coverage — Introduced two new frontend DOM test suites (
tests/frontend/ui/theme.test.jsandtests/frontend/ui/requests.test.js) and robust backend unit test assertions intests/unit/ombiHelpers.test.js.
[1.7.21] - 2026-05-26
Fixed
- Ombi Webhook Test Loopback Fallback — Resolved a persistent failure on the Ombi webhook test button when Sofarr sits behind a reverse proxy or in loopback-restricted environments. When the outbound request to the public webhook URL (
SOFARR_BASE_URL) fails due to loopback/NAT routing limits, the server now transparently falls back to a secure local loopback request (127.0.0.1) with smart TLS detection and SSL error bypass (rejectUnauthorized: false). - Resilient Webhook Mocks in Tests — Updated integration test assertions to verify the loopback fallback path under both HTTP and HTTPS configurations, ensuring full compatibility across dev, test, and production container environments.
[1.7.21] - 2026-05-26
Fixed
- Webhook Loopback & Hairpin NAT Connectivity — Implemented a robust local loopback fallback inside the Ombi webhook testing endpoint to bypass NAT loopback and DNS resolution issues common in setups behind reverse proxies. When the outbound request to the public URL fails, the server automatically routes the request internally via
127.0.0.1using automatic TLS credentials detection and SSL validation bypass for loopback requests. Added comprehensive integration tests verifying the fallback behavior. - Dedicated Webhook Base URL Support — Added support for a new
SOFARR_WEBHOOK_BASE_URLenvironment variable insideserver/routes/sonarr.js,server/routes/radarr.js, andserver/routes/ombi.js. This allows setups behind reverse proxies to declare an internal/custom base URL specifically for webhooks, enabling Sonarr, Radarr, and Ombi to send webhook events directly to the server via internal container networking, resolving503 Service Unavailableerrors.
[1.7.20] - 2026-05-26
Fixed
- Ombi Requesting User Hydration (Issue #51) — Resolved a bug where Sofarr displayed "Unknown" for the requesting user on requests even when the Ombi database contains valid user information. Added automatic requestedUser object hydration on the server side by fetching the full user list from
/api/v1/Identity/Usersand caching it in memory. If a request is missing the nestedrequestedUserdetails but possesses a validrequestedUserId, Sofarr automatically resolves and binds the user's username/alias. Added robust unit tests safeguarding the client and the retriever. Resolves Gitea Issue #51.
Changed
- Aligned Gitea Interaction Skill — Updated
.windsurf/skills/gitea-interaction/SKILL.mdto align with the Antigravitytea-interactionhybrid interaction model guidelines, detailing when to use the editor extension versus the Gitea CLI.
[1.7.19] - 2026-05-25
Fixed
- Requests Mobile CSS Overflow Fix (Issue #49) — Resolved the remaining viewport overflow on narrow screens by adding
min-width: 0to.request-cardto allow proper flexbox and grid shrinking, reducing mobile.main-tabspadding to0 8px, and tightening.requests-containermobile padding to8px. - *Admin arr Badge Links on Active Downloads (Issue #50) — Fixed the missing Sonarr/Radarr badges and links on active downloads for admin users. Enabled robust
_instanceUrlpropagation during queue/history metadata compilation (buildMetadataMaps) and active matching (DownloadMatcher.js) to ensure link generation succeeds. Added complete schema documentation in OpenAPI.
[1.7.18] - 2026-05-24
Fixed
- Mobile overflow on Requests tab — Request cards no longer extend off the right edge of the screen on mobile browsers. Removed
white-space: nowrapfrom.request-titleto allow text truncation with ellipsis, addedoverflow-x: hiddento.requests-listas a safety net, and added@media (max-width: 768px)rules to reduce padding and tighten gaps on mobile. Resolves Gitea Issue #49.
[1.7.17] - 2026-05-24
Fixed
- Blocklist-Search Persistent Failure (Regression from v1.7.16) — Identified and corrected the true root cause of the blocklist-and-search feature being non-functional. The v1.7.16 fix correctly cast both sides of the queue ID comparison to
String, but the lookup was performed againstdownloadClientRegistry.getAllDownloads(), which returns raw download-client data (qBittorrent, SABnzbd, etc.) that never hasarrQueueIdpopulated — that field is only assigned byDownloadMatcher.jsduring the SSE build phase from the *arr cache. For qBittorrent torrents specifically,QBittorrentClient.normalizeDownload()does not setarrQueueIdat all, so the lookup always returnedundefinedand the request was rejected with403. The permission check inPOST /api/dashboard/blocklist-searchnow looks up the queue record directly from the Sonarr/Radarr queue cache (poll:sonarr-queue/poll:radarr-queue) whererecord.idis the numeric queue ID, usingString()casting on both sides to handle the DOM-dataset (string) vs API response (number) type difference. Resolves Gitea Issue #48 (regression).
[1.7.16] - 2026-05-24
Fixed
- Blocklist-Search Queue ID Type Mismatch — Resolved a bug where the "Blocklist and search" action consistently returned
403 Download not found or permission deniedfor all users. The server-side download lookup inserver/routes/dashboard.jsused strict equality (===) to comparearrQueueIdvalues, but the value sent from the SPA client (read from a DOMdata-*attribute) is always astring, while the value populated from the Radarr/Sonarr queue API is anumber. Both sides are now cast toStringbefore comparison, resolving the false-negative match failure. Resolves Gitea Issue #48.
[1.7.15] - 2026-05-24
Fixed
- Ombi Webhook Authentication Failures — Resolved a critical issue where Ombi webhook notifications failed to authenticate because Ombi's built-in notification agent does not support custom HTTP headers (such as
X-Sofarr-Webhook-Secret). Added a query parameter authentication fallback (?secret=) to all/api/webhook/*endpoints (Sonarr, Radarr, and Ombi) and configured Ombi webhook registration to automatically append this secret query parameter. Resolves Gitea Issue #47.
[1.7.14] - 2026-05-24
Fixed
- Undefined Reference Error in Background Poller — Resolved a critical runtime exception in the background scheduler loop (
server/utils/poller.js) wherelogToFilewas called on cache updates but was never imported at the top of the file, previously triggering[Poller] Poll error: logToFile is not definedon every interval loop.
[1.7.13] - 2026-05-24
Changed
- Comprehensive OpenAPI & Swagger Specification Remediation — Bumped the API documentation version to
1.7.13and fully documented all operational rate-limiting configurations, exemptions, and bypasses inserver/openapi.yaml(including general cover-art exclusions, failed-only login trackers, webhook limiters, and rate-limit exempt root health probes). - Aligned Health Check Endpoint Implementation — Enhanced the express application factory
/healthendpoint to dynamically require and return the active version frompackage.json, keeping it fully aligned with the production entrypoint server logic. - Synchronized Security & System Architecture Docs — Aligned security matrices and threat mitigations in
SECURITY.mdand rate-limiting testing configurations inARCHITECTURE.md.
Added
- Swagger API Coverage Verification Integration — Implemented comprehensive assertions within
tests/integration/swagger-coverage.test.jsto dynamically verify that all newly added logging and debug endpoints (/api/debug/*) are fully represented in the active specification, raising test suite coverage to 876 passing checks.
[1.7.12] - 2026-05-24
Added
- Real-Time Server-Side Log Streaming (
GET /api/debug/server-logs) — Introduced an in-process output stream capturer that intercepts standard output (stdout) and standard error (stderr), strips terminal ANSI escape codes, maintains a rolling 1000-line buffer, and streams live operational logs via Server-Sent Events (SSE). Resolves Gitea Issue #45. - Real-Time Client-Side Console Log Stream (
GET & POST /api/debug/client-logs) — Added a browser-side console override in the frontend SPA that interceptsconsole.log,console.warn, andconsole.errorcalls. Logs are queued and posted to/api/debug/client-logsin optimized batches every 2000ms (or when the queue reaches 20 items), which are then buffered and streamed over SSE to debugging developers. Resolves Gitea Issue #46. - Subnet IP Filtering (
LOG_ALLOW_SUBNETS) — Implemented an optional CIDR-based subnet validation check usingipaddr.jsto restrict access to debug logs endpoints to specific internal IP ranges (e.g.127.0.0.1/32,192.168.1.0/24). Works natively with reverse proxies whenTRUST_PROXYis enabled. - Multi-Layered Security Bypass & Auth — Debug endpoints are guarded globally by
ENABLE_LOG_STREAM=true(off by default) and require Emby admin sessions, standard HTTP Basic Auth fallback checks against Emby, or high-priority bypass via headerX-Webhook-Secret. - Swagger UI Specification Coverage — Fully documented all four new endpoints, headers, and schemas in
server/openapi.yaml, with automated verification integrated insideswagger-coverage.test.js. - Architectural & Developer Guides — Updated
ARCHITECTURE.md(with system overview, diagrams, stdout/stderr hooks, and threat mitigations) andREADME.md(deploy instructions and querying parameters).
[1.7.11] - 2026-05-24
Fixed
- Sonarr full-season and multi-episode blocklist-search failures — Fixed a bug where clicking the "Blocklist & Search" button on television downloads representing full-season packs or multi-episode releases failed with a
400 Bad Requesterror. We relaxed the API validation onPOST /api/dashboard/blocklist-searchto makearrContentIdoptional. InDownloadMatcher.js, we exposedarrContentIds(holding all episode IDs associated with the matched release) andarrSeriesId(holding the series ID). - Enhanced blocklist re-search commands — The backend now triggers multi-episode
EpisodeSearchcommands using theepisodeIdsarray when blocklisting multi-episode releases, and falls back to a series-wideSeriesSearchcommand using theseriesIdwhen no specific episode IDs can be resolved. - OpenAPI Schema and Test Coverage — Updated the OpenAPI specifications (
server/openapi.yaml) to reflect optional/new parameters onBlocklistSearchRequest, and implemented new integration tests intests/integration/dashboard.test.jsto safeguard fallback and multi-episode search commands.
[1.7.10] - 2026-05-24
Fixed
- Ombi webhook race condition fix — Introduced a 2000ms delay in the webhook background worker for Ombi events before fetching new requests. This resolves a race condition where Ombi triggers the webhook before its database has committed the new request, which previously caused the request to be missing from the subsequent API fetch. Resolves Gitea Issue #42.
- Ombi webhook statistics in status panel — Integrated Ombi webhook metrics into the admin status panel. The backend now retrieves the Ombi webhook configuration status and aggregates its events received and polls skipped metrics. The frontend displays these metrics (consistently formatted as
O:<count>) under the Webhooks status card.
[1.7.9] - 2026-05-23
Fixed
- Ombi webhook PascalCase payload parsing (Re-release) — Correctly packaged and released the Ombi webhook parsing logic to handle PascalCase property names (e.g.,
NotificationType,RequestId) sent by C#/.NET applications. This ensures standard Ombi webhook integrations function correctly. Resolves Gitea Issue #42.
[1.7.8] - 2026-05-23
Fixed
- Ombi webhook PascalCase payload parsing — Updated the Ombi webhook parsing logic to correctly handle payloads formatted with PascalCase property names (e.g.
NotificationType,RequestId), which are often sent by C#/.NET applications. This ensures that new requests generated via Ombi's standard webhook integrations are correctly processed and displayed in the frontend dashboard. Resolves Gitea Issue #42.
[1.7.7] - 2026-05-23
Fixed
- Ombi webhook NewRequest parsing support — Added
'NewRequest'toVALID_EVENT_TYPESandOMBI_EVENTSsets inserver/routes/webhook.js. When a user submits a new media request in Ombi, the backend now successfully parses the webhook payload, bypasses the request cache, fetches the latest request list, and broadcasts it to all connected dashboard screens via SSE in real time. Previously, these webhook payloads were rejected with a400 Bad Requesterror. Resolves Gitea Issue #42. - Ombi webhook integration tests — Implemented a robust integration test suite in
tests/integration/webhook.test.jsvalidatingPOST /api/webhook/ombipayloads, secret keys, duplicate/replay protection, input checks, and cache refresh triggers.
[1.7.6] - 2026-05-23
Fixed
- Bypass rate limiter for cover art — Exempted
GET /api/dashboard/cover-artrequests from the global API rate limiter. Resolves Gitea Issue #43. - Ombi request cache bypass — Added a
forcecache-bypassing flag toOmbiRetriever's cache refresh mechanism, enabling real-time cache updates upon receiving Ombi webhooks or manual request list reloads. Resolves Gitea Issue #42.
[1.7.5] - 2026-05-23
Fixed
- Ombi webhook settings persistence — Fixed a bug where enabling the Ombi webhook from the frontend was successfully processed by the server but not stored on the Ombi side. The payload submitted to Ombi now retrieves the database
idof the settings row first and merges it back into thePOSTpayload. This ensures Entity Framework Core on the Ombi backend performs an update on the correct database row, enabling the webhook and letting its status persist successfully. Resolves Gitea Issue #41.
[1.7.4] - 2026-05-23
Fixed
- Ombi webhook registration in production — Fixed a bug where
/api/ombi/webhook/enableand other/api/ombi/*endpoints returned404 (Not Found)in production. The core Express app factory (server/app.js) registered the router, but the production server entry point (server/index.js) duplicated Express setup instead of utilizing the factory, omitting theombiRoutesregistration entirely. Re-registered the router inserver/index.js, resolves Gitea Issue #40.
[1.7.3] - 2026-05-23
Fixed
- Download client dropdown filter icons and type badges — Refactored the download client selector dropdown in
client/src/ui/filters.jsto render utilizing the design system classes.download-client-option,.download-client-icon,.download-client-option-label, and.download-client-type. Options now display the client's brand SVG logo (e.g.,sabnzbd.svg,qbittorrent.svgloaded from/images/clients/) next to the configured display name, along with a clean pill badge indicating the client type. This resolves visual ambiguity when multiple download clients share the same name (such as"i3omb"for SABnzbd and qBittorrent).
[1.7.2] - 2026-05-22
Fixed
- Webhook configuration check — Ombi webhook status now correctly checks for
SOFARR_BASE_URLandSOFARR_WEBHOOK_SECRETenvironment variables. The webhook displays as disabled when either is missing, matching the existing *ARR webhook behavior. - Common webhook config endpoint — Added
GET /api/webhook/configendpoint that returns whether bothSOFARR_BASE_URLandSOFARR_WEBHOOK_SECRETare configured, along with a list of missing items. Used by the client to determine webhook enablement status across all webhook types. - Client-side webhook status — Updated
fetchWebhookStatus()to call/api/webhook/configand use the result to determine if Sonarr and Radarr webhooks are enabled. Webhook status now depends on both the service-specific webhook being configured AND the Sofarr webhook config being valid. - Webhook config endpoint authentication — Added
requireAuthmiddleware toGET /api/webhook/configto enforce authentication, matching the OpenAPI contract and preventing unauthenticated information disclosure. - Ombi webhook enable/test config validation —
POST /api/ombi/webhook/enableandPOST /api/ombi/webhook/testnow validateSOFARR_BASE_URLandSOFARR_WEBHOOK_SECRETbefore making outbound Ombi API calls, returning400with descriptive errors when either is missing. Previously these routes would construct malformed URLs (undefined/api/webhook/ombi) if the environment variables were unset. - Test environment cleanup —
tests/integration/webhook.test.jsafterEachnow cleans upEMBY_URL,SOFARR_BASE_URL,SONARR_INSTANCES, andRADARR_INSTANCESto ensure hermetic test isolation.
[1.8.0] - 2026-05-21
Added
Ombi PALDRA Integration
- OmbiRetriever — New PALDRA-compliant retriever extending
ArrRetriever, registered inarrRetrieverRegistryalongside Sonarr/Radarr. Manages Ombi request data with 5-minute TTL cache and lookup maps by TMDB/TVDB/IMDB IDs. - OmbiClient — Low-level Ombi API client for HTTP communication (movie/TV requests, search by external ID, connection test).
getOmbiInstances()— New config function inserver/utils/config.jsfollowing the existing multi-instance JSON array pattern; supports bothOMBI_INSTANCESand legacyOMBI_URL/OMBI_API_KEYformats.- PALDRA registry Ombi methods —
getOmbiRetrievers(),getOmbiRequests(),getOmbiRequestsByType(),findOmbiRequest()added toarrRetrieverRegistry. - External ID matching — Downloads are matched to Ombi requests using TVDB ID → TMDB ID (TV) and TMDB ID → IMDB ID (movies); falls back to an Ombi search link when no request exists.
getOmbiLink()/getOmbiSearchLink()— New helpers inDownloadAssembler.jsfollowing thegetSonarrLink/getRadarrLinkpattern.- Service icon layout — Downloads and history cards now render inline SVG icons (Ombi for all users; Sonarr/Radarr for admins) instead of linked series/movie names. CSS
.service-icons-containerand.service-iconclasses added topublic/style.css. - OpenAPI —
NormalizedDownloadschema extended withombiLink,ombiRequestId,ombiTooltipnullable string properties;Ombitag added to the spec. OMBI_INSTANCES/OMBI_URL/OMBI_API_KEY— New environment variables documented in.env.sample,README.md,ARCHITECTURE.md, andSECURITY.md.
Changed
DownloadMatcher.js—matchSabSlots,matchSabHistory, andmatchTorrentsare nowasync; each matched download object is enriched withombiLink,ombiRequestId, andombiTooltipviaaddOmbiMatching().DownloadBuilder.js—buildUserDownloadsacceptsombiRetrieverandombiBaseUrlin its options object and passes them through to matching context.- Dashboard routes — Both the REST endpoint and SSE stream now resolve the Ombi retriever from the PALDRA registry and include it in the download-building context.
arrRetrievers.js— PALDRA registry now importsOmbiRetriever, maps'ombi'inretrieverClasses, and initialises instances fromgetOmbiInstances().ARCHITECTURE.md— PALDRA section updated with OmbiRetriever description, registry API additions, and directory-structure entries. Technology stack table updated.SECURITY.md— Threat model extended with Ombi API key exposure and rate-limit exhaustion mitigations.README.md— Prerequisites and new Ombi Integration (Optional) configuration section added.
[1.7.1] - 2026-05-21
Added
RAML 1.0 Package Generation
- Automated RAML generation in CI/CD — Added RAML 1.0 package generation to the existing
swaggerjob in.gitea/workflows/ci.yml. The pipeline now generates a downloadableraml-packageartifact on every push and PR, available from the Actions run page with 14-day retention. - RAML generation scripts — Created three new scripts in
scripts/:generate-openapi.js— Bootstraps the Express app in test mode, fetches the merged OpenAPI 3.1 spec from/api/swagger.json, and exports it to disk.downgrade-openapi.js— Downgrades OpenAPI 3.1 to 3.0 for RAML compatibility (existing RAML converters don't support 3.1).simple-raml-converter.js— Converts OpenAPI 3.0 to RAML 1.0 format using a custom converter (modern RAML converters are unmaintained).package-raml.js— Creates a versioned tar.gz archive containing the RAML spec, original OpenAPI spec, version metadata, and README.
- RAML artifact structure — Each artifact includes:
api.raml— RAML 1.0 specificationopenapi-merged.json— Original merged OpenAPI 3.1.0 spec (for reference)version.json— Metadata (version, commit SHA, timestamp, tool used)README.md— Origin, conversion details, known limitations, and verification steps
- npm scripts — Added three new scripts to
package.json:generate:openapi— Generates merged OpenAPI specgenerate:raml— Downgrades and converts to RAMLpackage:raml— Packages the RAML artifact
- Spectral ruleset — Created minimal
.spectral.ymlruleset to make the existing Spectral lint step functional (previously failing silently due to missing ruleset). - OpenAPI JSON endpoint — Added
/api/swagger.jsonendpoint toserver/app.jsto serve the raw merged OpenAPI spec as JSON, enabling programmatic access.
Changed
- Dependencies added —
archiver(^7.0.1) for creating tar.gz archives.
[1.7.0] - 2026-05-21
Added
Swagger UI & OpenAPI 3.1 Documentation
- Swagger UI at
/api/swagger— Interactive API documentation served viaswagger-ui-express; publicly accessible with a custom authentication banner (public/swagger-auth-banner.js) that explains the cookie-based + CSRF-token authentication flow for testing endpoints directly in the browser. - OpenAPI 3.1 specification — Central
server/openapi.yamlfile containing base metadata, security schemes (CookieAuth,CsrfToken), and reusable component schemas:NormalizedDownload— standardised download object returned by all PDCA clientsDashboardPayload— SSE payload shape ({ user, isAdmin, downloads, downloadClients })ErrorResponse— standard error envelope with redacted detailsBlocklistSearchRequest— payload for the admin blocklist-and-search operationWebhookPayload— Sonarr/Radarr webhook event structureHistoryItem— deduplicated history record with upgrade-availability flagStatusResponse— server metrics, polling timings, cache stats, and webhook metrics
- Hybrid documentation approach — Per-endpoint details are documented directly in route handler files (
server/routes/*.js) using JSDoc@openapicomments.swagger-jsdocmerges these comments with the central YAML at runtime, keeping documentation close to the code while maintaining shared schemas in one place. - Comprehensive endpoint coverage — All implemented endpoints are documented:
- Authentication:
POST /api/auth/login,GET /api/auth/me,GET /api/auth/csrf,POST /api/auth/logout - Dashboard:
GET /api/dashboard/stream(SSE),GET /api/dashboard/user-downloads(deprecated),GET /api/dashboard/cover-art,POST /api/dashboard/blocklist-search - Status:
GET /api/status - History:
GET /api/history/recent - Webhooks:
POST /api/webhook/sonarr,POST /api/webhook/radarr - Proxy routes: Sonarr, Radarr, SABnzbd, and Emby authenticated proxies
- Public health:
GET /health,GET /ready
- Authentication:
- Machine-usable extensions — Every documented endpoint includes:
x-code-sampleswith cURL, JavaScript fetch, and TypeScript examplesx-integration-notessection in descriptions for AI agents and automated tooling- Realistic request/response examples and full JSON Schema definitions
- Coverage validation test suite —
tests/integration/swagger-coverage.test.js(22 tests) validates that:- The OpenAPI spec loads without YAML parse errors
- Every Express route appears in the merged spec
- All schema and response examples are valid JSON
- Required security schemes (
CookieAuth,CsrfToken) are defined and referenced correctly - The Swagger UI HTML endpoint (
GET /api/swagger) returns200
- CI/CD validation job — Added "Swagger Validation & Coverage" job in
.gitea/workflows/ci.ymlthat runs on every push:- Lints
server/openapi.yamlwith@stoplight/spectral-cli - Runs
npm test -- tests/integration/swagger-coverage.test.jsto verify coverage
- Lints
Changed
- Dependencies added —
swagger-ui-express(^5.0.1),swagger-jsdoc(^6.2.8),yamljs(^0.3.0), and@stoplight/spectral-cli(^6.16.0 dev dependency) for OpenAPI generation, UI serving, and spec linting.
Security
- Swagger UI public access — The Swagger UI endpoint (
/api/swagger) is publicly accessible by design for convenience. All documented API endpoints still enforce authentication (emby_usercookie) and CSRF protection (X-CSRF-Tokenheader for mutations) as before. The authentication banner in the UI explicitly instructs users to log in viaPOST /api/auth/loginfirst before testing protected endpoints.
[1.6.0] - 2026-05-21
Added
- Staged history loading with SSE push — history data from Sonarr/Radarr is now fetched in stages;
history-updateSSE events push incremental results to the dashboard so the "Recently Completed" tab populates without waiting for the full fetch. - Frontend unit tests — added Vitest + jsdom test suite covering
client/src/modules (formatters, storage, state). - Comprehensive tests for staged history loading — backend tests verify the new background-fetch behaviour, cache TTL handling, and SSE emission.
- Integration test coverage — new integration and unit tests for
dashboard,emby,sonarr,radarr, andsabnzbdroutes.
Changed
- Technical-debt remediation — service extraction — extracted matching and assembly logic from the monolithic
dashboard.js(~1,360 lines → ~284 lines) into dedicated, testable services:DownloadMatcher.js— SABnzbd slot / torrent → *arr record matchingDownloadAssembler.js— cover-art, episode, link, blocklist-eligibility helpersDownloadBuilder.js— orchestrator that reads the cache snapshot and builds the final user-facing download listTagMatcher.js— tag extraction, sanitisation, and user-ownership matchingWebhookStatus.js— webhook configuration status aggregation
- Frontend architecture — migrated from a monolithic
public/app.jsto vanilla ES modules underclient/src/, bundled by Vite intopublic/app.js. - History pagination — replaced custom date-based cursor pagination with the retriever's built-in pagination (
pageSize=100, up to 10 pages / 1,000 records total). Eliminates the 40-second response-time regression seen with large histories. - Status endpoint path — admin status route moved from
/api/status/statusto/api/statusfor consistency with the router mount point. - Background-fetch safety — the poller no longer overwrites the cache with empty data when a background history fetch fails or returns no records.
- SABnzbd progress calculation — progress is now computed from
slot.mbandslot.mbleft/slot.mbmissingbecause the SABnzbd queue API does not expose apercentagefield. - Speed formatting consistency —
updateDownloadCard()now callsformatSpeed()so all speed values display with uniform units (B/s, KB/s, MB/s, GB/s). - Status-panel error handling — the panel now surfaces error messages (e.g.
403for non-admin users) instead of showing a blank box.
Fixed
- CSRF token reference errors — fixed
ReferenceErrorbugs inapi.js,auth.js, and the blocklist handler wherecsrfTokenandcurrentUserwere accessed as bare variables instead of via the sharedstateobject. - Logout button — fixed undefined variable references in
handleLogoutClickthat prevented the logout flow from completing. - Missing progress bar for SABnzbd — SABnzbd downloads now render a correct progress bar instead of showing
undefined%. - Status route 404 — corrected the Express router mount so
GET /api/statusresponds instead of returning HTML 404. - Status button DOM ID — fixed element-ID mismatch (
status-btnvsstatus-toggle) that prevented the admin status button from toggling the panel. - Tab selection — fixed tab switching to use
data-tabattributes after the DOM IDs were removed during the ES-module migration. - CSP violations and
ignoreAvailablereference error — resolved frontend CSP compliance issues and a missing-variable error in the history tab. - Docker client-build stage — removed
client/from.dockerignoreso the multi-stage Dockerfile can run the Vite build during image creation. - Unmatched torrent exclusion — torrents that cannot be matched to a Sonarr/Radarr record are now correctly omitted from the download display.
- Blocklist button CSRF — fixed a
ReferenceErrorthat occurred when a non-admin user clicked the blocklist button.
Breaking Changes
- Unmatched torrents are no longer displayed — torrents that do not match a Sonarr/Radarr queue or history record are excluded from the dashboard. Users who previously saw direct torrent-client downloads will no longer see them unless the *arr services track the item.
- Frontend build process changed — the monolithic
public/app.jssource file has been replaced by a Vite build fromclient/src/. Any custom deployment scripts that copy or modifypublic/app.jsdirectly must be updated to runnpm run build(or use the provided Dockerfile, which already does this). - Status API endpoint path changed — the admin status endpoint moved from
/api/status/statusto/api/status. External integrations, monitoring checks, or scripts hitting the old path will receive404.
[1.5.5] - 2026-05-20
Added
- Download client logos — Added SVG logos for all supported download clients (SABnzbd, qBittorrent, Transmission, rTorrent, Deluge). Logos appear in the download client filter picker and on download cards.
- Download client logo in filter picker — Multi-select download client filter now displays client logos alongside names for visual identification.
- Download client logo in download cards — Download cards now display the client logo in the bottom-right corner (32×32px). Positioned absolutely within the card.
- SABnzbd speed display — SABnzbd downloads now display the overall queue speed for the currently active download only. Speed is fetched from the client status API and applied to the downloading slot.
- Speed formatting — Speed values are now formatted with appropriate units (B/s, KB/s, MB/s, GB/s) instead of raw bytes.
Fixed
- Missing pieces display for SABnzbd — Removed incorrect "missing x of y" text display for SABnzbd downloads. This information is only relevant for torrent clients (qBittorrent, rTorrent) and is now only shown for those clients.
- Logo duplication on page reload — Fixed download client logos and user tags appearing twice during page load. Updated
updateDownloadCard()to remove old elements before adding new ones. - Logo positioning — Fixed download client logos appearing stacked at bottom-right of browser window instead of bottom-right of each card. Added
position: relativeto.download-cardto provide proper positioning context.
[1.5.4] - 2026-05-19
Added
- Multi-select download client filter — Active Downloads tab now includes a dropdown filter that allows users to select multiple download clients. Shows client name and type (e.g., "Main (qbitt)"). Includes Select All / Deselect All buttons. Selection persists across sessions via localStorage.
- Download client ordering — Downloads are now sorted by client order (matching the configuration order). Downloads from the first configured client appear first.
- Client metadata in downloads — Download objects now include
client,instanceId, andinstanceNamefields for client identification and filtering. - SSE payload extension — SSE stream now includes
downloadClientsarray with all configured clients for UI ordering/filtering. - Automatic migration — Existing single-select filter selection automatically migrates to new multi-select format on first load.
Fixed
- SABnzbd size and speed display — Fixed SABnzbd downloads showing undefined size and speed values. Now correctly uses
slot.mbfor size calculation andslot.kbpersecfor per-slot speed from cached data.
[1.5.3] - 2026-05-19
Fixed
- Status panel rendering regression — the status panel was rendering as a small blank box due to an undefined
--backgroundCSS variable. Added the missing variable to all three themes (light, dark, mono). - Status panel content destruction —
showDashboard()was callingsp.innerHTML = ''which destroyed thestatus-contentdiv inside the status panel. Removed this destructive line. - Webhooks panel visibility sync — the webhooks panel was incorrectly visible on app load. Added explicit hiding of
webhooks-sectioninshowDashboard()to keep it in sync with the status panel (both show/hide together viatoggleStatusPanel()). - Webhooks panel DOM structure — reverted webhooks-section to be a sibling of status-panel (not nested inside it), preventing innerHTML operations from affecting webhook elements.
[1.5.2] - 2026-05-19
Fixed
- Status panel close button CSP compliance — replaced inline
onclickhandler withaddEventListenerto comply with CSP nonce policy.
[1.5.1] - 2026-05-19
Fixed
- Webhook endpoints not reachable in production —
server/index.js(the production entry point) was missing thewebhookRoutesimport and mount. Onlyserver/app.js(the test factory) had the routes registered. As a result everyPOST /api/webhook/*request in a running container fell through to theverifyCsrfmiddleware and was rejected with403 CSRF token missing. Addedapp.use('/api/webhook', webhookRoutes)inindex.jsimmediately afterauthRoutesand beforeverifyCsrf, matching the order inapp.js.
[1.5.0a] - 2026-05-19
Fixed
- Status panel close button — the
×button now correctly hides the status panel and stops the auto-refresh timer. The button was previously using an inlineonclickattribute which was silently blocked by the server's CSP nonce policy. Replaced withaddEventListenerwired afterinnerHTMLis set, consistent with all other button handlers in the application.
[1.5.0] - 2026-05-19
Changed
ARCHITECTURE.md— consolidated the two existing architecture documents (root concise reference anddocs/ARCHITECTURE.mddeep-dive) into a single comprehensive reference at the project root. The merged document covers all 11 sections: Introduction, High-Level Architecture, Pluggable Architecture Layers (PDCA + PALDRA), Webhook System, Data Flow and Real-time Updates, Caching and Smart Polling, Key Subsystems, Directory Structure, Configuration and Environment Variables, Security Model, and Technology Stack. Includes full Mermaid diagrams for system overview, polling cycle, webhook path, UI state machine, and download matching pipeline.docs/ARCHITECTURE.md— removed; content fully merged into rootARCHITECTURE.md.
[1.4.0] - 2026-05-19
Added
Webhook Integration (Phases 1–5.1)
- Webhook receiver endpoints —
POST /api/webhook/sonarrandPOST /api/webhook/radarraccept push events from Sonarr and Radarr with shared-secret validation (X-Sofarr-Webhook-Secretheader). Dashboard updates in < 1 second after any grab, import, or failure. - Selective cache invalidation — each incoming event is classified into queue events (
Grab,Download,DownloadFailed,ManualInteractionRequired) or history events (DownloadFolderImported,ImportFailed,EpisodeFileRenamed,MovieFileRenamed). Only the affected cache key is refreshed via a lightweight re-poll of that instance, rather than a full poll cycle. - SSE broadcast on webhook — after refreshing the cache,
pollAllServices()is called as a fire-and-forget, triggering an immediate SSE push to every connected browser. - Notification management API proxy —
GET /api/sonarr/api/v3/notificationandPOST /api/sonarr/api/v3/notification(and Radarr equivalents) proxy the full *arr Notification API through sofarr with auth + CSRF enforcement. - One-click webhook setup —
POST /api/sonarr/webhook/enableandPOST /api/radarr/webhook/enableauto-configure the Sofarr webhook notification connection inside the respective *arr service.POST /api/sonarr/webhook/testand/radarr/webhook/testtrigger a test event. - Webhooks Configuration UI — collapsible panel in the dashboard allowing users to enable, test, and view webhook status for each configured Sonarr/Radarr instance, with per-trigger type indicators showing which event types are active.
SOFARR_WEBHOOK_SECRETenvironment variable — required for webhook endpoints to accept requests. Generate withopenssl rand -hex 32.SOFARR_BASE_URLenvironment variable — public URL of sofarr, used by the one-click setup to tell Sonarr/Radarr where to POST events.
Smart Polling Optimization (Phase 5)
- Webhook metrics tracking —
cache.jsnow maintains per-instance and global webhook metrics (lastWebhookTimestamp,eventsReceived,pollsSkipped) viagetWebhookMetrics(),updateWebhookMetrics(),incrementPollsSkipped(),getGlobalWebhookMetrics(). - Conditional poll skipping —
poller.jscallsshouldSkipInstancePolling()before each Sonarr/Radarr fetch. If all instances of a type have received a webhook event within the fallback timeout window, their queue/history API calls are skipped entirely; existing cached data has its TTL extended instead. - Webhook fallback — if no webhook events have been received globally for
WEBHOOK_FALLBACK_TIMEOUTminutes (default: 10), the poller forces a full poll regardless of per-instance state. Logged as[Poller] Webhook fallback triggered. - Poll-skip logging — logs
[Poller] Skipping sonarr/radarr polling for N instance(s) with active webhookswhen polling is skipped. WEBHOOK_FALLBACK_TIMEOUTenvironment variable — minutes before fallback polling (default:10).WEBHOOK_POLL_INTERVAL_MULTIPLIERenvironment variable — internal multiplier for TTL calculations when webhooks are active (default:3).- Phase 5.1 metrics connection —
webhook.jscallscache.updateWebhookMetrics(instance.url)after every successfully validated event, activating the smart skip logic for that instance.
Security Hardening (Phase 6)
- Dedicated webhook rate limiter — 60 requests per minute per IP on
/api/webhook/*, stricter than the global 300/15 min API limiter. Bypassed in tests viaSKIP_RATE_LIMIT=1. - Strict input validation —
validatePayload()rejects: non-object bodies, missing/non-string/overlongeventType, unrecognised event type values (allowlist of 18 known *arr event types), non-stringinstanceName. Returns400with a descriptive message. - Replay protection —
isReplay()tracks recently-seen(eventType, instanceName, date)tuples in aMapwith a 5-minute TTL. Duplicate events within the window return200 { received: true, duplicate: true }without triggering a cache refresh or SSE broadcast. - 35 new webhook integration tests — cover secret validation (missing/wrong/unconfigured), payload validation (all invalid cases), replay protection, happy-path acceptance of all relevant event types, metrics increment, and secret-never-leaks assertions.
Documentation (Phase 6)
README.md— updated architecture overview diagram, added Webhooks section with quick-setup guide, added PDCA/PALDRA/webhook architecture table, added Webhooks & Smart Polling env var section, added webhook API endpoints, updated test count.CHANGELOG.md— this entry.SECURITY.md— added webhook threat model rows, webhook-specific hardening checklist, and rate-limit table entry for webhook endpoints.ARCHITECTURE.md(root) — new concise top-level architecture reference describing all pluggable layers and the full webhook + polling optimization flow..env.sample— addedWEBHOOK_FALLBACK_TIMEOUTandWEBHOOK_POLL_INTERVAL_MULTIPLIERwith explanatory comments; updated NOTES section.
Changed
poller.js—pollAllServices()now conditionally skips Sonarr/Radarr fetches when webhooks are active; extends TTL of existing cache entries instead of overwriting with empty data.cache.js— exports four new webhook metrics helpers alongside the existingMemoryCachesingleton.webhook.js— importedexpress-rate-limit; addedvalidatePayload(),isReplay(),VALID_EVENT_TYPESallowlist,recentEventsMap, and per-routewebhookLimitermiddleware.
[1.3.0] - 2026-05-17
Added
- History tab — new "Recently Completed" tab showing imported and failed downloads from Sonarr/Radarr history for the last N days (configurable via the days input, persisted in
localStorage). Auto-refreshes every 5 minutes. - History deduplication — when a failed download has subsequently been imported successfully, only the successful record is shown. If the most recent record for an item is a failure but the episode/movie is already on disk (upgrade attempt), the record is flagged as
availableForUpgrade. - "Upgrade available" badge — failed history cards where the content is already on disk display an amber badge to indicate this is a failed upgrade rather than a missing item.
- "Hide upgrade failures" toggle — checkbox in the history tab to filter out failed records that are already available on disk. State persists in
localStorage. Tooltip explains the behaviour and matches the episode/multi-episode tooltip style. - Blocklist & Search button — admin-only button on download cards with an "Import Pending" caution. Removes the download from the client with
blocklist=true(preventing re-grab of the same release) then immediately triggers anEpisodeSearch/MoviesSearchcommand in Sonarr/Radarr. Shows a confirmation dialog, loading/success/error states. Kicks a background poll on success. POST /api/dashboard/blocklist-search— new admin-only endpoint backing the above button. AcceptsarrQueueId,arrType,arrInstanceUrl,arrInstanceKey,arrContentId,arrContentType.- Title link home navigation — the sofarr logo/title in the header now navigates to the default view (Active Downloads tab, close status panel, reset "Show all users" toggle) without a page reload.
- Version footer link — the version string in the dashboard footer links to the source repository.
Changed
- History records are now deduplicated server-side before being sent to the client — only the most relevant record per content item per instance is returned.
- Import-issue badge tooltip now uses the themed
var(--surface)/var(--text-primary)/var(--border)CSS variables, matching the episode and toggle tooltip style. - Poller now stores
_instanceKeyon Sonarr/Radarr queue records in the cache, enabling the backend to look up API credentials for blocklist operations without an additional configuration lookup. - Blocklist & Search button — now available on all admin downloads (not just those with import issues), and also available to non-admin users when: import issues are present, OR (for qBittorrent torrents only) the download is more than 1 hour old AND has less than 100% availability.
- Download object — added
canBlocklistboolean field to indicate whether the current user can blocklist a given download. - qBittorrent torrent data — added
addedOntimestamp field to enable age-based blocklist eligibility checks.
[1.2.2] - 2026-05-17
Changed
- Header logo — uses the higher-resolution 192px favicon source rendered at 56px for better visual balance alongside the title text.
[1.2.1] - 2026-05-17
Added
- Version footer — the dashboard footer now displays the running app version (e.g.
sofarr v1.2.1), fetched from the/healthendpoint on page load.
[1.2.0] - 2025-05-17
Security
- Docker secrets support — all sensitive environment variables (
COOKIE_SECRET,EMBY_API_KEY,SABNZBD_API_KEY,SONARR_API_KEY,RADARR_API_KEY,QBITTORRENT_PASSWORD) now support the standard_FILEvariant for loading values from mounted secret files (e.g.COOKIE_SECRET_FILE=/run/secrets/cookie_secret). - Weak secret warning — server now warns at startup if
COOKIE_SECRETis shorter than 32 characters. - EMBY_URL validation — validates the Emby URL scheme at startup and warns on misconfiguration.
- Improved error sanitization —
sanitizeError()now also redacts hostnames from full request URLs that may appear in axios error messages. - Graceful shutdown —
SIGTERMandSIGINThandlers now stop the background poller and drain open HTTP connections before exiting. Prevents data loss and zombie processes ondocker stop.
Compliance
- MIT LICENSE file added to project root.
- Copyright headers added to key server source files (
index.js,poller.js,config.js,sanitizeError.js,loadSecrets.js). security.txt(/.well-known/security.txt) added for responsible disclosure.
Configuration
- URL validation added to
config.js— all configured service instance URLs are validated for scheme (http/https) and well-formedness at startup; malformed URLs emit a warning instead of crashing.
Docker / Deployment
docker-compose.yamlupdated with commented Option B (Docker secrets_FILEpattern) alongside the existing plain-env Option A..dockerignoreupdated —tests/,coverage/,vitest.config.js,CHANGELOG.md,SECURITY.md,LICENSE,.markdownlint.jsonexcluded from the production image.
CI
docs-checkworkflow added — separate Gitea Actions workflow that lints all Markdown files and validates Mermaid diagram syntax on every push that touches.mdfiles. Both jobs usecontinue-on-error: trueso documentation issues never block a release.- Mermaid diagrams in
docs/ARCHITECTURE.mdfixed — replaced invalid\nin stateDiagram transition labels, Unicode arrows/dashes, and double-spaces in flowchart edge definitions.
[1.1.2] - 2025-05-15
Changed
- Server startup message now includes the current version (
sofarr v1.1.2).
[1.1.1] - 2025-05-14
Fixed
- Docker/TrueNAS SCALE healthcheck: dynamic HTTP/HTTPS selection based on
TLS_ENABLEDenvironment variable. Prevents containers from being stuck in "starting" state whenTLS_ENABLED=false.
[1.1.0] - 2025-05-13
Added
- Episode display — TV show download cards now show episode information (S01E01 format with title). Multi-episode packs show a "Multiple episodes" badge with a tooltip listing all episodes.
- Episode tooltip — solid background colour (theme-dependent) for readability.
- Sonarr queue and history API requests now include
includeEpisode=true.
[1.0.0] - 2025-05-01
Added
- Initial release.
- SABnzbd queue and history integration.
- qBittorrent torrent integration.
- Sonarr and Radarr queue/history matching with user tag filtering.
- Emby/Jellyfin authentication.
- Server-Sent Events (SSE) real-time dashboard.
- Per-request CSP nonce, CSRF double-submit, HSTS, Permissions-Policy.
- Background polling with configurable interval and on-demand fallback.
- Docker multi-stage build, non-root user, read-only filesystem.
- TLS support with bundled snakeoil certificate.