Introduce a per-indexer ratioLimit alongside seedingTimeMinutes to control torrent cleanup. Updates include: documentation (scheduler and settings pages), types and API (saved indexer config now includes ratioLimit), setup and management UI (new TorrentSeedingFields component, modal wiring, validation and handlers), and processor logic (cleanup-seeded-torrents now requires AND-semantics between time and ratio; 0 disables a criterion, both 0 = never cleaned, undefined client ratio with ratioLimit>0 = not met). Tests were added/updated to cover ratio-only, time+ratio, missing-ratio, and UI interactions. Default behavior: ratioLimit defaults to 0 (no ratio requirement).
Introduce a per-request release blocklist to auto-block permanently failing releases and provide admin management. Changes include:
- Database: add BlockedRelease model (blocked_releases) to Prisma schema with unique (requestId, releaseKey) and indexes; documented in backend database docs.
- Service & utils: new blocklist.service, release-key and filter helpers for normalization and matching; processors updated to emit auto-blocks (monitor-download, organize-files, search processors, RSS).
- HTTP API: add admin endpoints GET/DELETE /api/admin/blocklist, DELETE /api/admin/blocklist/[id], and GET /api/admin/blocklist/by-request/[requestId].
- Admin UI: new /admin/blocklist page and numerous React components (toolbar, filters, table, rows, pagination, skeleton, chips, date picker) with URL-driven state hook and per-row unblock UX.
- Tests: add unit/integration tests for service, routes, utils, and updated processor tests.
The blocklist is idempotent (upsert), filters search results before ranking (interactive search shows badges only), and admin-only APIs require auth. This commit wires docs, API, DB, frontend and tests for the new feature.
Introduce a complete admin System Logs feature: adds frontend components (filters, date picker, active filter chips, rows, detail panel, skeletons, pagination, toolbar, user typeahead, and styles) under src/app/admin/logs/components, plus hooks (useAutoRefreshControl, useLogsUrlState, useUserSearch) and types. Add constants for job labels and log filters, wire URL-driven filters/search/date-range/hasError/user/audiobookQuery with pause-on-interact behavior and page-size options. Update API route (/api/admin/logs) to support the expanded query params and exported where-builder. Update documentation (TABLEOFCONTENTS and admin-dashboard) and add/adjust tests for the new admin logs UI and API behavior.
Introduce API token allowlist support and documentation. Adds a new backend docs page for API tokens and updates TABLEOFCONTENTS. Implements API token constants and a compiled matcher (isEndpointAllowed) with support for single-segment :placeholders and an isWrite flag. Split getCurrentUser into a JWT-only helper and added getCurrentUserAsync to recognize rmab_ API tokens; updated the audiobooks search route to use getCurrentUserAsync. Update API docs UI (EndpointCard and api-docs page) to surface Write badges and disable "Try it" for mutating endpoints, and add a profile warning in ApiTokensSection. Add tests for the allowlist matcher and middleware, and adjust existing route tests/mocks accordingly.
Implement Plex-compatible file-extension coercion to avoid Plex silently ignoring .mp4 (and single-file .m4a) audiobooks (issue #166). Adds a DB migration and configuration key (plex_format_coercion_enabled, default true), exposes a toggle in the setup wizard and Admin Paths settings, and persists/reads the setting in the admin/setup APIs.
Introduces src/lib/utils/format-coercion.ts (coerceToPlexCompatible) and related constants in src/lib/constants/audio-formats.ts (PLEX_COMPATIBLE_EXTENSIONS, COERCION_RENAME_MAP, DRM_EXTENSIONS, TRANSCODE_REQUIRED_EXTENSIONS). The organize-files processor now runs coercion after organizing/tagging and before generating the filesHash and triggering scans; coercion is idempotent, never overwrites existing targets, logs warnings on DRM/transcode/permission errors, and is non-fatal.
Adds unit tests for the coercion util and updates processor & setup UI tests. Updates documentation (TABLEOFCONTENTS, file-organization, fixes/file-hash-matching, settings-pages) describing behavior, config, and constraints.
Introduce an indexer-wide option to skip automatic searches for books with future release dates (config key: `indexer.skip_unreleased`, default ON). Adds a GET/PUT admin API for indexer options, a UI toggle on the Indexers settings tab (persisted on save), and persistence of a request-level releaseDate in the Prisma schema.
Adds a new request status `awaiting_release` and wires it through constants, UI components (StatusBadge, RequestCard, RecentRequestsTable, Audiobook card/modal, RequestActions), API request flows (bookdate swipe, request creation, manual search, request PATCHs, request listing groups), and services. Implements a pure release-date utility (isUnreleased / shouldSkipAutoSearch) and updates background processors: monitor-rss-feeds (skip matches but do not mutate status), retry-missing-torrents (drives bidirectional transitions between awaiting_search and awaiting_release and queues searches when appropriate), and request-creator/bookdate swipe (gate initial auto-search). Adds tests for the swipe gate and other related test updates. Logs transitions and gate decisions for observability.
Introduce a unified CANCELLABLE_STATUSES constant and add confirmation UI for cancelling requests. RequestActionsDropdown and RequestCard now show a ConfirmModal before cancelling and use the shared CANCELLABLE_STATUSES to gate cancel actions. The API route imports the constant to enforce server-side validation and uses Prisma.DbNull for selectedTorrent when withdrawing an awaiting-approval request. Tests updated to expect Prisma.DbNull. Improves UX and centralizes cancel logic to avoid duplicated status lists.
Refactor bulk-import scanner to make ASIN extraction and search-string cleaning more robust, and add tests.
- Tighten and case-insensitize the ASIN regex, always return ASIN in uppercase.
- Export and use cleanSearchString (replaces inline folder-name sanitization in the scan route).
- When merging discoveries across folders, derive folderName/relativePath consistently and re-extract ASIN from the merged common parent if available.
- Add comprehensive unit/integration tests for extractAsinFromString, cleanSearchString, buildSearchTerm, and discoverAudiobooks (with an ffprobe mock).
These changes improve detection of ASINs in varied naming patterns, reduce duplicated cleanup logic, and ensure merged groups correctly inherit ASIN metadata.
- Add cancel action to RequestActionsDropdown for admins
- Add cancel button to RequestCard for users
- Implement DELETE handler in /api/requests/[id] with:
- Status gate: only cancellable if pending_approval or awaiting_approval
- Clears selectedTorrent (Prisma.DbNull) on cancel
- Fires on-grab notification job after cancel
- Tests: cancel flows for both statuses, rejection for non-cancellable status
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves conflicts in src/lib/integrations/audible.service.ts.
main switched the ASIN-detail fallback from HTML scraping to the JSON
catalog API (fetchAudibleDetailsFromApi), removing scrapeAudibleDetails.
The PR's lookupAsinFast was a fail-fast variant of the same pattern that
getAudiobookDetails now performs (Audnexus -> catalog API), so it's
redundant.
- Drop the lookupAsinFast method (delete entire HEAD-side conflict block)
- Take main's fetchAudibleDetailsFromApi verbatim (the scrapeAudibleDetails
maxRetries parameterization is moot)
- In bulk-import scan route, swap lookupAsinFast for getAudiobookDetails
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch nightly discovery refresh to scrape Audible's curated HTML storefronts (popular, new releases, category pages) while keeping real-time user paths on the JSON catalog API. Add robust HTML resilience knobs (increased retries, capped jittered backoff, AdaptivePacer changes and per-batch cooldowns) to avoid failing nightly jobs during 503 storms. Implement multi-narrator capture via a new extractAllNarrators helper and update parsers to preserve all narrator anchors. Introduce two-pass dedup: in-memory deduplicateAndCollectGroups + collapseByExistingWorks that consults the works table, export metadataScore for consistent representative selection, and persist dedup groups (fire-and-forget). Wire collapseByExistingWorks into search/author/series routes and make defensive dedup in the refresh processor. Add HTML parsing helpers, runtime/lang-aware parsing, jitteredBackoff cap, and tests for the new behaviors.
Add support for selecting individual audio files during manual and bulk imports and pass that selection through the scan, API, job queue, processor and organizer.
Key changes:
- API: scan now returns audioFiles for each discovered book and emits a new 'grouping' progress phase; execute and manual-import routes accept file lists (audioFiles / selectedFiles) and validate them.
- Scanner: group loose audio files by metadata (title/author/narrator), deduplicate multi-part sets (CD1/CD2) across folders, and return audioFiles + groupingKey; add concurrency limit for ffprobe reads and merge groups post-scan.
- Job queue & processor: OrganizeFiles payload now includes selectedFiles; processors forward selectedFiles to the FileOrganizer and to cleanup logic.
- File organizer & cleanup: filter to only selectedFiles when organizing; cleanup now deletes only the selected files (if provided) instead of removing the whole directory.
- UI: Manual import browser and bulk import wizard updated to show per-file selection, track checkedFiles, toggle all, and send selected files to the API; ConfirmPhase updated to allow checking/unchecking files and prevents starting import with no files selected.
- Filesystem browse: removed expensive per-subfolder stats to keep browsing responsive (now lists subdirectories without nested stat calls).
Overall this change enables finer-grained imports, reduces accidental deletion of unselected files, and improves scan grouping for multi-folder audiobooks.
Add sessions_invalidated_at to users (migration + Prisma schema) to support immediate session revocation. Set sessionsInvalidatedAt when an admin revokes a user's login token and enforce revocation checks in auth middleware and the refresh endpoint (compare token iat against sessionsInvalidatedAt). Add optional iat fields to JWT payload types. Scrub token from browser history after token-login. Consolidate rate-limiting logic into src/lib/utils/rateLimit.ts (rename/merge previous auth/apiToken rate limiter implementations), remove the old apiTokenRateLimit.ts, and update imports and tests to use the new module.
Introduce a Bulk Import feature for admins to scan server folders, match discovered audiobook folders against Audible, review matches, and queue batch imports.
What changed:
- Added documentation: documentation/features/bulk-import.md and TABLEOFCONTENTS update.
- Backend: SSE scan endpoint (POST /api/admin/bulk-import/scan) streams discovery and matching events; execute endpoint (POST /api/admin/bulk-import/execute) validates paths, creates/resolves audiobook & request records, and queues organize_files jobs. Both endpoints enforce admin-only access and validate allowed root directories (download_dir, media_dir, /bookdrop).
- Frontend: Modal wizard and steps for folder selection, scan progress, and match review (BulkImportWizard + ScanFolderStep, ScanProgressStep, MatchReviewStep + shared types).
- Utilities: bulk-import-scanner for folder discovery and ffprobe metadata extraction; shared types for scanned books/events.
- UI: Added Bulk Import quick action to admin dashboard (src/app/admin/page.tsx).
Key details:
- Audible searches are rate-limited (≈1.5s) and matching results include library/request status checks.
- Reuses existing organize_files job queue and manual-import pipeline; no new database tables introduced (state is ephemeral during the wizard).
- Includes error handling, path normalization, and security checks for allowed directories.
This commit wires frontend, backend, and docs together to provide an admin-only multi-step bulk import workflow.
Introduce a per-user "ignored audiobooks" feature to suppress auto-requests. Changes include:
- Database: add Prisma model IgnoredAudiobook and SQL migration to create ignored_audiobooks table with indexes and FK to users.
- Backend: new API routes to list, add, delete, and check ignored audiobooks (/api/user/ignored-audiobooks, /check/:asin, /:id). Add annotateWithIgnoreStatus utility and integrate it into multiple audiobook list endpoints (popular, new-releases, category, search, authors, series).
- Request creator: add ignore-list check (with sibling-ASIN expansion) and a bypassIgnore option for manual requests; return an 'ignored' reason when blocked.
- Frontend: hooks (useIsIgnored, useToggleIgnore, useIgnoredList) and UI updates — AudiobookCard shows an "Ignored" indicator and AudiobookDetailsModal adds an ignore toggle and propagates local state changes.
- Misc: adjust deduplication duration tolerance (to 5% / min 10 minutes), tweak SWR refresh intervals for shelves/syncing, and small logging/info updates.
- Tests: add unit tests for request-creator ignore logic and update existing tests/mocks to account for ignore annotation; extend prisma test helper with ignoredAudiobook mock.
This commit implements the ignore-list end-to-end (DB, server, client, and tests) so users can ignore specific ASINs and have auto-request flows respect that preference.
Include the requesting user's ID as an additional argument when enqueueing immediate shelf sync jobs so the job has user context. Updated the route implementation and adjusted affected tests (goodreads-shelves-id, hardcover-shelves-id, and hardcover-shelves routes tests) to expect the extra 'user-1' parameter.
Introduce an autoRequest boolean on Goodreads and Hardcover shelves (default true) so users can pause/resume automatic request creation. Schema, API handlers, hooks and types were updated to accept and persist autoRequest when creating or updating shelves; add endpoints only trigger an immediate resync when the feed/token changes. The shelf sync core and service code now respect autoRequest (skipping request creation and annotating logs when disabled). UI updates include an AddShelf toggle, manage/update payload changes, shelf list props, and visual indicators + toggle actions in the shelf cards.
Introduce file and directory permission settings (fileChmod, dirChmod) end-to-end. UI: new controls in Paths settings with octal validation and defaults (664/775). API: GET exposes defaults; PUT validates octal strings and upserts configuration keys (file_chmod, dir_chmod) and clears related cache keys. Runtime: read config values in file utilities and services (FileOrganizer, direct-download, chapter-merger, epub-fixer) to apply mkdir modes and chmod files/dirs; FileOrganizer now accepts fileMode/dirMode and getFileOrganizer reads/parses DB settings. Docker: add UMASK option to docker-compose and propagate/apply UMASK in entrypoint/app-start scripts. Tests: update mocks to account for config service usage.
Introduce cursor-based pagination and group counts for /api/requests (status groups, nextCursor, counts) and fetch one extra record to detect next page. Add a client-side My Requests experience: useSWRInfinite hook (useMyRequests) with smart polling for active requests, tabbed filters, badges, skeletons, load-more, and animated list entries. Update RequestCard and admin actions to treat awaiting_search as cancellable. Adjust Plex processors to ignore requests with status 'denied' when matching new media. Add static ffmpeg in the Docker image and remove preinstalled ImageMagick to avoid transitive deps. Update tests to account for pagination/take+1 and the new hook/UX behavior.
Persist and apply customSearchTerms across ebook workflows and searches. Updated admin search-terms PATCH to enqueue addSearchEbookJob for ebook requests. Included customSearchTerms when creating ebook request records in audiobooks/[asin]/fetch-ebook, audiobooks/[asin]/select-ebook and requests/[id]/fetch-ebook. Reworked requests/[id]/select-ebook to handle being passed either an audiobook or ebook request (resolve parent audiobook, reuse existing ebook request if present) and to propagate parent.customSearchTerms when creating new ebook requests. Modified search-ebook.processor to read customSearchTerms from the request record, use it as the effective search title (with logging), and pass the modified audiobook title into Anna's Archive and indexer searches so custom terms are honored.
Add an entrypoint step to execute idempotent SQL data migrations (prisma db execute) from prisma/data-migrations/*.sql so fixes that prisma db push doesn't handle are applied on startup. Add normalize-local-usernames.sql to normalize local users' plex_username and plex_id to lowercase. Update interactive search and search-indexers processor to prefer the user-provided/custom search title (searchTitle / effectiveSearchTitle) when ranking torrents and adjust debug logs to show the ranking title alongside the audiobook title/author for clearer diagnostics.
Replace default Anna's Archive base URL from https://annas-archive.li to https://annas-archive.gl across docs, UI components, API routes, processors, services, and tests. Add comprehensive tests for the admin manual-import API route and enhance the manual-import route to fetch missing ASIN details from Audnexus and create audiobook records with proper error handling and logging. Update related test expectations and FlareSolverr test usages to reflect the new default URL.
Introduce per-user configurable home page sections and a unified Audible cache/category model. Adds Prisma models (UserHomeSection, AudibleCacheCategory) and migrations to create tables and remove legacy popular/new_release flags; updates schema.prisma accordingly. Add API routes for user home sections, live Audible categories, and category-based audiobook listing, and refactor popular/new-releases/covers routes to read from AudibleCacheCategory. Frontend: new HomeSection component, HomeSectionConfigModal, useHomeSections hook, and homepage changes to render dynamic sections plus image fallback to a placeholder SVG. Also add placeholder_cover.svg and tests for home sections and the audible refresh processor.
Add support for interactive ebook searches and streamline search job routing. Key changes:
- RequestActionsDropdown: loosened status checks for search/adjust actions, route interactive search to an ebook-specific modal when the request is an ebook, and pass request.customSearchTerms to the ebook search modal.
- API: interactive-search-ebook route now supports two flows (direct ebook requests and audiobook sidecar ebook searches), updates validation logic, checks for existing child ebook requests only in sidecar mode, and improves logging. manual-search route now dispatches addSearchEbookJob for ebook requests and addSearchJob for audiobooks.
- RequestCard: removed manual/interactive search UI, related hooks and modal usage (interactive search is handled via the admin dropdown/modal now).
These changes enable direct ebook interactive search flows, prevent invalid searches based on request type/status, and ensure the correct background job is enqueued per request type.
Drop the duplicated `id` alias from JWT payloads and related token generation across auth providers and endpoints. The TokenPayload interface no longer includes `id`; middleware now derives `user.id` from `sub` when attaching the authenticated user to requests. Update tests accordingly. This reduces redundancy and ensures the canonical user identifier is `sub`.
Introduce a shared useApiTokens hook to centralize API token CRUD and UI state (fetch, create, delete, copy, formatting). Refactor ApiTab and ApiTokensSection to consume the hook and remove duplicated logic. Add getInstanceUrl utility for client origin used in curl examples. Include an id alias in TokenPayload and add id into generated JWTs across auth routes and providers; update tests accordingly. Improve auth middleware typing and add debug logging around lastUsedAt updates. Add admin logging when creating a token with a role that differs from the target user's role.
Introduce full API token support: add a Prisma migration to create api_tokens table and indexes; add types, constants and a generateApiToken utility (hashed token + prefix). Update admin and user token routes to use the generator, enforce per-user active token caps, and integrate rate-limit checks. Add an interactive API docs page with TokenInput, EndpointCard and ResponseViewer components, plus a protected page route. Improve confirmation UX with an accessible ConfirmDialog (focus trap, Escape to close, animations) and wire confirm flows into admin/profile token sections; also update ConfirmModal to accept node messages. Add dialog CSS animations and enhance clipboard error handling. Update related middleware, utils and tests to reflect changes.
Normalize local account usernames by trimming and lowercasing across the stack. Added a Prisma migration to lowercase existing plex_username and rewrite local plex_id values for non-deleted accounts. Updated LocalAuthProvider, admin login route, and setup completion to use normalized usernames when looking up, creating, and storing users (including plexId `local-{username}`). Added/updated tests to assert case-insensitive lookups, storage of lowercased usernames/plexIds, and duplicate username rejection.
Query Audnexus (Audible) to backfill missing metadata during manual imports and file organization. Adds getAudibleService imports and calls to fetch audiobook details by ASIN, then backfills series, seriesPart, seriesAsin, year (from releaseDate) and narrator when missing and updates the DB. Failures are non-fatal and logged; logs were added to surface enrichment steps. Also uses the resolved series/seriesPart when building organization metadata.