Introduce a provider-based notification system and wire it through the API and admin UI. Added INotificationProvider + notification service implementation and providers (apprise, discord, ntfy, pushover), plus a GET /api/admin/notifications/providers endpoint to expose provider metadata. Refactored code to use provider type strings (removed enum coupling), updated masking/encryption calls, and simplified the test notification endpoint to accept backendId or type+config and call sendToBackend directly.
UI: NotificationsTab now fetches provider metadata and renders provider cards and dynamic config forms (fields driven by provider metadata). Added config field rendering, improved backend cards, and edit/delete actions.
APIs: New providers route, updated admin notification CRUD routes to validate provider types dynamically, updated test route schema. Added download-client categories POST API to fetch categories from clients and wired postImportCategory handling in download-client routes.
Other notable changes: BookDate now fetches Claude models dynamically from Anthropic's Models API; added paginated model fetch helper. Added ALLOW_WEAK_PASSWORD flag exposure to auth providers and password change logic. Doc updates and various tests added/updated. File-organization doc clarifies EPERM fix using stream-based copy.
Extend multi-download-client support to include Transmission and NZBGet and introduce per-client custom download paths. Adds protocol mapping and new client types, Transmission/NZBGet integration services, API CRUD and validation changes, UI components/modal updates and live path previews, and manager routing by protocol. Includes DB migrations (download_path on download_history, interactive_search_access on users), schema updates, and related processor/service fixes and tests to ensure backward compatibility and proper path resolution.
improve container startup for rootless Podman, plus related refactors and tests. Key changes:
- Add/modify Audiobookshelf-related code and wiring (src/lib/services/audiobookshelf/api.ts, library service refs) and update documentation TABLEOFCONTENTS to reference ABS implementation.
- Detect user namespace in docker/unified app-start.sh and redis-start.sh and skip gosu when running in rootless Podman to preserve UID mapping; improve startup logging and verification.
- Add utility/service files (auth-token-cache.service.ts, credential-migration.service.ts, cleanup-helpers.ts) and corresponding tests; update chapter-merger and metadata-tagger utilities/tests.
- Update many admin/auth API routes and tests to reflect changes in settings and integrations.
- Remove large AI agent and Audiobookshelf implementation guide docs (AGENTS.md and the implementation guide) and add README note about AI-assisted workflow.
These changes enable Audiobookshelf backend mode, improve compatibility with rootless container runtimes, and include cleanup/refactor work and unit tests.
Ensure ensureCategory applies reverse path mapping (local → remote) before creating or editing qBittorrent categories. Uses PathMapper.reverseTransform to compute the remote save path and updates logging and error details to reference the transformed path. Adds integration tests covering category creation, updating, and no-op when the remote path already matches.
Implements support for configuring both qBittorrent and SABnzbd simultaneously, including migration from legacy config, protocol-aware routing, and protocol filtering. Adds new CRUD API routes for download clients, new UI management components, and updates setup and settings flows to use the new multi-client architecture. Updates documentation to describe the new structure and usage.
Introduces a full notification system with support for Discord and Pushover backends, event triggers, and message formatting. Adds backend services, processors, and API endpoints for managing notifications, as well as a new Notifications tab in the admin settings UI. Updates documentation, database schema, and tests to cover notification features and approval workflow improvements. Also changes project license from MIT to AGPL v3.
Introduces support for custom OpenAI-compatible AI providers with configurable base URLs, including UI, backend validation, and connection testing. Enhances qBittorrent integration to support HTTP Basic Auth for reverse proxies, adds detailed debug logging, and updates documentation for both features. Also improves login page description logic and AI prompt generation for recommendations.
Replaces scattered console statements with a unified RMABLogger across backend API routes and services. Adds LOG_LEVEL-based filtering, job-aware database persistence, and context-based logging. Updates documentation to describe the new logging system and usage patterns. Also documents qBittorrent CSRF header fix
Implements configurable indexer flag bonuses/penalties for torrent ranking, including UI for admin settings and support in ranking-algorithm. Adds an option to disable SSL certificate verification for qBittorrent connections (for self-signed certs), with UI in both setup and admin settings, and persists the setting. Updates documentation, API routes, and ranking logic to support these features. Also includes minor UI improvements and bug fixes.
Adds detection of local users for authentication validation and login, prevents role changes for OIDC users, and clarifies user management UI. Enhances active downloads API to include speed and ETA from qBittorrent, and improves file path handling in download monitoring. Also updates torrent tagging and user info returned by APIs.
Introduces `asin` and `isbn` fields to the PlexLibrary schema and database, with migration and indexing for fast lookups. Updates scan and recently-added processors to persist ASIN/ISBN from both Plex and Audiobookshelf backends. Enhances matching logic to prioritize exact ASIN matches using the new fields, improving match accuracy for Audiobookshelf users. Also includes minor improvements: fixes cover art handling for cached thumbnails, adds download URL validation in Prowlarr and qBittorrent integrations, and updates documentation to reflect these changes.
Root cause: Singleton caching issue
The qBittorrent service uses a singleton pattern with a configLoaded flag.
Once initialized, it NEVER re-reads the database config, even when the
user changes settings via the admin settings page.
Flow showing the bug:
1. Wizard saves download_dir to database ✓
2. First torrent: service reads config, creates singleton, sets configLoaded=true ✓
3. User changes download_dir in settings page ✓ (database updated)
4. Next torrent: getQBittorrentService() returns CACHED singleton ✗
5. Cached singleton has OLD download_dir value in this.defaultSavePath ✗
6. Category check shows "already has correct save path: /old/path" ✗
7. Download goes to wrong location ✗
The singleton check (line 745):
if (!qbittorrentService || !configLoaded) {
// Only runs if service doesn't exist or config failed
}
Once both exist, this block is SKIPPED forever!
Fix:
1. Added invalidateQBittorrentService() function
- Resets qbittorrentService = null
- Resets configLoaded = false
- Forces reload from database on next use
2. Call invalidation from settings APIs:
- After updating paths (download_dir, media_dir)
- After updating download client (URL, credentials)
3. Next torrent addition:
- getQBittorrentService() sees null singleton
- Re-reads config from database
- Creates new service with current download_dir
- Category updated with correct path
Benefits:
- Settings changes take effect immediately
- No app restart needed
- Category save path always matches current config
- Download client credentials always current
Updated documentation to explain singleton invalidation pattern.
Root cause: We were blindly calling createCategory (409 if exists) then
editCategory (409 for unknown reason), but editCategory was failing and
the category kept its old save path, causing downloads to wrong location.
The 409 from editCategory doesn't mean 'already has this path', it means
the operation failed. Without checking first, we don't know why.
New approach:
1. GET /torrents/categories first to check current state
2. If category doesn't exist: create it with correct save path
3. If category exists with wrong path: edit to update save path
4. If category exists with correct path: skip (no API call needed)
Benefits:
- Avoids unnecessary 409 errors
- Only calls editCategory when actually needed
- Logs show exactly what's happening (create/update/skip)
- Handles both savePath and save_path response formats (v4.4.0+ compat)
- Better error logging with full response details
This ensures category save path is ACTUALLY updated when user changes
download_dir setting, fixing downloads going to wrong location.
References:
- qBittorrent API docs: https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)
- Issue #15969: save_path vs savePath inconsistency in API v2.8.4+
qBittorrent's /torrents/editCategory endpoint returns 409 (Conflict) when
the category already has the specified save path (no change needed).
This is expected behavior when:
- User hasn't changed download_dir setting since last torrent
- Category already has correct save path
Previously logged as warning with full error stack trace, making it look
like an error when it's actually normal operation.
Changes:
- Check for 409 status code from editCategory
- Log friendly message: 'Category already has save path: /path'
- Only log unexpected errors with console.warn
Now both createCategory and editCategory handle 409 gracefully:
- createCategory 409 = category exists
- editCategory 409 = category already has this path
Both are expected, not errors.
The 409 (Conflict) status code from qBittorrent's createCategory endpoint
means 'category already exists', which is expected behavior, not an error.
Previously, the full error object was logged with console.log(), making it
look like an error in the logs when it's actually normal operation.
Changes:
- Check for 409 status code specifically
- Log friendly message: 'Category already exists' for 409
- Only log unexpected errors with console.warn
- Cleaner logs that don't alarm users
The flow still works correctly:
1. Try createCategory (succeeds if new, 409 if exists)
2. Always editCategory to update save path to match current config
3. Both operations complete successfully
Previously, when a user changed the download_dir setting after initial
setup, the qBittorrent category "readmeabook" would retain the old save
path. This could cause torrents to download to the wrong location,
depending on qBittorrent's Automatic Torrent Management (ATM) settings.
Root cause:
- ensureCategory() only created the category if it didn't exist
- createCategory API is idempotent but doesn't update existing categories
- If download_dir changed from /downloads to /downloads/RMAB, category
would still have savePath=/downloads
qBittorrent behavior:
- If ATM enabled: category savePath overrides per-torrent savepath
- If ATM disabled: per-torrent savepath takes precedence
Fix:
- ensureCategory() now calls both createCategory AND editCategory
- createCategory: ensures category exists (idempotent)
- editCategory: updates save path to match current download_dir config
- This guarantees category path is always synced with database config
Benefits:
- Users can change download_dir setting and it takes effect immediately
- Works regardless of ATM settings in qBittorrent
- No manual qBittorrent category management needed
Updated documentation/phase3/qbittorrent.md to explain category
management and save path synchronization.