Introduce a LIBRARY_BROWSE_ROOT environment variable (defaulting to the
current directory) to restrict directories users can browse and select when
setting up library paths. Add a PathPolicyService on the backend to enforce
path containment checks securely, and update the frontend FolderPickerModal
to prevent navigating above the configured browse root.
Closes#567
Import Booklore and Grimmory reading sessions, personal ratings, and progress state through the migration pipeline.
Preserve book-level progress as primary-file progress when source file progress rows are absent.
Closes#482Closes#483
Add server-backed jump buckets so large library, collection, and smart-scope listings can jump directly to sorted sections without loading every row up front.
Closes#163
Select all acquisition content files from the database for each book
instead of only the primary file. Return multiple acquisition links
in the OPDS catalog feed for books available in multiple formats,
and update unit and E2E tests to verify catalog output and downloads.
Closes#224
Virtual sibling cleanup is now deferred until the final missing pass instead of being handled eagerly during upsert. This ensures that real nested book folders are not prematurely marked as missing before their own candidates can be processed. A new validation step against the disk state guarantees only genuinely missing books are removed.
Closes#170
The duplicate detection logic in BookDockFinalizeService previously identified books as duplicates if they had the same title, even if their authors differed, when ISBNs were unavailable. This has been updated to include author names in the duplicate check for books missing ISBNs to prevent incorrect duplicate identifications.
Closes#175
* feat(library): add bulk rename based on file naming settings
Provides a way to rename all books in a library according to the user's
configured file naming template without requiring manual metadata edits.
Includes a streaming endpoint to report progress back to the frontend.
Closes#121
* test(library): add e2e tests for bulk file rename
Introduce a comprehensive E2E test suite for the bulk rename functionality to verify behavior across different library modes (book per file, book per folder) and handle edge cases like naming collisions or special characters.
Additionally fix a TypeScript compilation error in BulkRenameView.vue by replacing `replaceAll` with a regex-based `replace` for broader target compatibility.
---------
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Parallel refresh requests from a single client (multi-tab SPA, mobile tab restore)
tripped reuse-detection and logged users out every few hours. Track rotation
provenance via new rotated_at + replaced_by_token_hash columns and accept revoked-
token reuse only inside a configurable grace window with a chain that reaches a
live, non-logout link. Scope the rotate UPDATE with revoked_at IS NULL so a
concurrent logout or admin revoke fails the racing refresh instead of handing the
user a fresh access token.
Closes#120
Prevent unauthorized users from seeing integrations they cannot use and keep settings routing consistent with API authorization behavior. Add hardcover_sync and enforce it server-side so tab visibility and endpoint access stay aligned.
* fix(server): deduplicate migration user-state upsert batches
Prevent migration failures when source data contains duplicate user-state rows for the same target keys. Keep the latest row per upsert key and prefer richer reading progress signals on timestamp ties, then cover the behavior with focused unit and e2e tests.
Closes#71
* ci: raise commitlint body line limit to 500 chars
Allow longer commit body lines so convention checks do not fail on detailed but still concise commit explanations in this repository.
- Remove author insights and duplicate suggestions panels, composables, DTOs, and all backend support
- Add genre and tag duplicate detection, merge, and dismiss with server-side paginated scan results
- Consolidate per-entity-type tools into a single entity-manager module backed by the Strategy pattern
- Support browse, merge, rename, delete, split, and dismiss across all entity types (author, genre, tag, narrator, publisher, language, series)
- Replace old tools module with unified entity-manager controller, service, and repository; add 85+ unit tests
Core performance overhaul - new library_dir_scan_state schema table persists per-directory
mtimes so unchanged directories are skipped entirely on subsequent scans, reducing filesystem
stat() calls from O(all-files) to O(changed-dirs) for large libraries.
Incremental scan (walk.ts / scanner.service.ts):
- New library_dir_scan_state table (bigint mtime, FK to library_folders ON DELETE CASCADE)
- findBookCandidates / findLooseFileCandidates now accept knownDirMtimes and return WalkResult
(candidates + skippedDirs + unchangedDirs + dirMtimes) instead of bare BookCandidate[]
- Per-dir mtime skip with 3 safety rules: never skip disc dirs, never skip dirs with disc
subdirs, never skip non-root dirs whose parent mtime changed (guards stem-named merging)
- Root directory now also skippable - parent-mtime check conditionally skipped for root since
root-level files are never stem-merged (explicitly excluded in buildBookCandidates)
- Settings hash invalidation: SHA-256 of {allowedFormats, formatPriority, excludePatterns,
organizationMode} forces full scan when settings change or on first scan after server restart
- isInUnchangedDir guard prevents false book-missing marks for skipped dirs
- Math.round() on stat().mtimeMs before storing - fixes bigint rejection of macOS fractional
milliseconds (e.g. 1776138191093.9556) in PostgreSQL
Walk improvements:
- Bounded concurrency: subdirs processed in chunks of 50 to avoid EMFILE on deep trees
- format + role cached on FileStat to avoid re-classifying the same file twice
- statFilesIntoAcc helper extracted to deduplicate parallel stat logic
- skippedDirs set propagated up from permission-denied dirs
ScanJobStore:
- acquireStartLock / releaseStartLock prevent concurrent scan starts for the same library
- markPendingRescan / consumePendingRescan queue a follow-up scan if one arrives while running
waitForStability:
- Accepts optional knownMtimeMs to skip a redundant stat() call when mtime is already known
File event processor (Issue 25, 11, 20):
- Own-book restore: check file's own book before searching all missing books, fixing false
book-moved results when an editor saves with a new inode (atomic-save pattern)
- Settings cache per library avoids repeated DB lookups during reconcile of many missing books
- Partial move guard: verify old file is gone before reassigning inode (multi-file books)
Live notifications (scanner.service.ts / file-watcher.service.ts):
- scanBookFolder now emits scan:books:added via existing WebSocket infrastructure so the
frontend grid updates live when the file watcher adds a book (all 3 code paths covered)
- bufferWatcherNotification() with 30s debounce fires one ScanCompleted notification per
library summarising watcher-driven additions and removals ('Novels updated - 3 added')
- Removals wired in FileWatcherService.process() and reconcile() on book-missing events
- findLibraryName() added to ScannerRepository for notification title lookup