Update the `updatedAt` field on the `books` table whenever book metadata is
automatically fetched or bulk updated. Additionally, filter codecov reports
to only include server coverage and ignore test/config files, and update
the README codecov badge to reflect this.
Closes#186
* feat(metadata): add RanobeDB metadata provider
Introduces a new metadata provider to fetch book metadata from RanobeDB.
- Adds `ranobedb.client` to interact with the RanobeDB API.
- Implements `ranobedb.mapper` to map RanobeDB data to the internal metadata schema.
- Maps native Japanese titles to subtitles.
- Uses the original language as a fallback for the language field.
- Parses specific release dates to determine the original publication year.
- Registers RanobeDB as an available provider in the preferences UI.
- Adds `ranobedb_id` to the metadata database schema.
- Removes the `bookId` override when manually performing a metadata search in the UI.
Closes#114
Add a user preference to configure the label displayed underneath book
and series cards in grid view. The option allows users to select
between hiding labels completely, showing book titles, or showing series
titles.
Closes#159
When a newly filled metadata field is locked before saving, the subsequent
save causes a 409 conflict because the field was locked but not yet saved.
This introduces a new endpoint and DTO to update both metadata and locks
simultaneously, resolving the conflict.
Closes#172
Allow readers to choose which representative series cover appears in collapsed grid view so browsing can prioritize first, latest, or first unread volumes.
Closes#160
The database check constraint for the library reading threshold was previously enforcing a maximum value of 1, causing an internal server error when configuring a threshold greater than 1% via the UI. The constraint max has been increased to 100 to support percentage-based values and align with the mark_as_finished constraint.
Closes#142
Allow users to choose how cover artwork is fitted so mixed aspect ratios render cleanly in cards, lists, carousels, and widgets.
Persist display preferences through shared types and user-preferences endpoints so these settings stay consistent across sessions and devices.
Closes#152
When DATABASE_URL is not provided, entrypoint.sh assembled it by
interpolating POSTGRES_USER, POSTGRES_PASSWORD and POSTGRES_DB directly
into a postgres:// URL. Any reserved URL character in those values (for
example a '#', '/' or '?' in the password) produced an invalid URL, and
pg-connection-string's new URL() call then threw ERR_INVALID_URL,
preventing the container from starting.
Percent-encode the user, password and database components via
encodeURIComponent before building the URL so passwords containing
special characters work without manual escaping.
Co-authored-by: MohammadYusif <MohammadYusif@users.noreply.github.com>
The Content Security Policy was previously blocking external images,
preventing admin-configured OIDC provider icons from loading.
This allows https: images to be loaded while continuing to block
plain http: to maintain mixed-content protection.
Closes#131
Introduce a user-preferences module and database schema to sync theme
settings (theme, accent, radius, background, brightness) across devices.
Users can now enable theme sync to persist their customization choices.
Closes#146
Add a dialog to batch-edit metadata for multiple selected books.
Support add/remove/replace/clear modes for list fields, with clear as an explicit
frontend-only mode.
Fix scalar input border rendering, year field validation/coercion, tags replace
optimistic updates, and submit error handling.
Closes#150
allow users to trigger file write-back and rename from the edit metadata tab as a single explicit action.
return write and rename outcomes with library setting flags so the UI can show clear result and warning states.
Closes#143
Align recommendation data and rendering with per-book media format semantics.
Expose audiobook state from recommendation queries and render audiobook cards as square while preserving portrait and spine treatment for non-audiobooks.
Closes#149
Align Book Dock metadata validation with the database year constraint to prevent intermittent finalize failures from malformed extracted years.
Return a user-safe validation message when metadata constraints are hit.
Closes#144
Enable cross-platform path sanitization by default to prevent file and folder renaming failures
caused by invalid special characters or reserved names on various filesystems. Update the frontend
settings UI description to clarify the behavior of this setting, and update tests to reflect the
new default and verify correct sanitization behavior.
Closes#134
Add UI and backend support for testing metadata provider credentials
before saving. Include parsing and validation for Bearer tokens,
display test results in the settings panel, and execute a test
connection specifically against the Hardcover provider.
Closes#127
* 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
On old hardware (e.g. AMD Turion/Phenom, Intel Core 2), sharp's musl binary
crashes with SIGILL during NestJS module init. Combined with bufferLogs: true
and restart: unless-stopped, this produces a silent infinite restart loop -
only the migration success message is ever visible.
Add check_cpu_sse42 to entrypoint.sh: greps /proc/cpuinfo for sse4_2, exits
with a clear error if absent, no-op when /proc/cpuinfo is unavailable. Also
add a System Requirements section to README documenting the SSE4.2 minimum.
Closes#116
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.
Validate DATABASE_URL with pg's connection-string parser so unix-socket DSNs with empty authority hosts pass while malformed/non-postgres values are rejected.
Closes#106
* fix(scanner): clamp precision-unsafe inodes to avoid lossy matching
Clamp inode values above Number.MAX_SAFE_INTEGER to 0 before scanner, upload,
and file-event matching paths consume them.
Add regression tests for Unraid-style inodes so precision-unsafe values
cannot collapse file detection during scans.
Lower server coverage thresholds from 85 to 80 as requested for this branch.
Closes#84
* fix(scanner): reuse existing book for same-library folder renames
Keep the original book id when a folder path changes so scan does not create a duplicate entry with no files. Use inode first and hash fallback for precision-unsafe inode values while keeping book_per_file behavior unchanged.
Book titles and author names can contain characters that are invalid on Windows,
SMB/NFS shares, or cloud sync platforms (e.g., `:`, `?`, `*`, `"`). Without
sanitization these characters appear verbatim in generated file and folder names,
which causes failures on Windows filesystems, broken NFS/SMB mounts, and sync issues.
Add a toggleable app setting that, when enabled, sanitizes illegal filesystem
characters in all pattern-resolved paths (upload, rename, book-dock finalize, and
download). Each token value is sanitized before substitution: invalid chars are
replaced with `_`, trailing dots and spaces are stripped, and Windows reserved names
(CON, AUX, NUL, COM1-COM9, LPT1-LPT9) get a suffix to avoid conflicts. The setting
defaults to off to preserve existing behavior.
Closes#103
document cross-reader sync limitations in the KOReader settings panel.
set clear expectations for direction-specific accuracy so users understand why resume location can differ between KOReader and the web reader.
Extracts the in-memory caching logic previously duplicated across
UserStatisticsService and StatisticsService into a new shared
StatsCache class. This standardizes cache keys, improves invalidation
by scope (such as userId), and makes the dashboard widget and
statistics caching more robust.
The previous SQL query used a LATERAL join and complex aggregations that caused series collapsing to fail. This refactors the query to use a unified base_rows CTE and explicit window functions for fetching series covers, ensuring that series are correctly grouped and displayed.
Closes#87
The backend previously only checked for new releases from GitHub
during application bootstrap. If the server stayed up for a long time,
the UI would never be notified of new releases.
The AppInfoService now re-checks GitHub when the frontend requests
app info, using a 10-minute cache TTL to balance freshness with
API rate limits.
Add books.id ASC tiebreaker to all 7 OPDS_SORT_MAP sort orders so
pagination is deterministic even when books share identical addedAt
timestamps (e.g. after a bulk library scan). Without a tiebreaker,
PostgreSQL returns equal-valued rows in an undefined order, causing
books to appear on multiple pages or not at all.
Make catalog feed IDs context-specific: filtered views (by library,
collection, smartScope, author, series, or search query) now produce
distinct URNs instead of all sharing urn:bookorbit:catalog. This
aligns with the OPDS spec requirement that <id> uniquely identify
the logical feed.
Remove the dead idQuery.$dynamic() block from paginatedBookQuery
that was built and conditionally mutated but never awaited.
Closes#93
support toggling membership in the collection sheet so users can remove books without leaving the details and quick-view flow.
cover add/remove paths plus access edge cases with frontend and backend tests.
Closes#85
* feat(users): tag/genre content filters for per-user access control
Add granular per-user content filtering by tags and genres, allowing admins to set include/exclude filter rules that are enforced across all book-serving surfaces.
- Implement DB schema, repository, and SQL helpers for content filters
- Wire enforcement across core domains (Books, Collections, OPDS, Kobo sync)
- Add REST endpoints for managing filter rules
- Redesign UserFormDrawer to support configuring content restrictions
- Add read-only Restrictions tab in account settings and user list badges
- Add comprehensive unit tests and fix existing fixtures
Closes#68
* test(client): update account tab expectations for restrictions
Use each group's displayed tier description instead of the next tier description so titles and copy stay consistent across all tiered cards. Refresh achievement seed names for books, pages, hours, long-book, and library ladders, and adjust books_finished_3 to a 35-book threshold to match the new progression.
Enable author image upload and removal from the author detail edit panel.
Persist author photos with generated thumbnails and enforce server-side validation plus access-scoped API routes.
Cover the new controller and service behavior with author module tests.
Prevent enrichment from applying unrelated author records when provider search returns low-signal names.
Rank multiple candidates and require strong name compatibility before accepting a match.
Treat lookup failures as provider failures instead of silently falling back to raw search hits.
* 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.
Add file rename feature that renames physical book files on disk when
naming-relevant metadata fields are updated (title, authors, series,
seriesIndex, publishedYear).
Closes#69
Add per-user startedAt and finishedAt filtering, sorting, and timezone-safe date normalization.
Surface compact read-date editing in Details and return canonical date-only status payloads.
Preserve manual dates across status updates and reject null status patches.
Closes#66
* feat(docker): expose Book Dock folder as a configurable drop zone
Add support for an external book drop folder that users can bind-mount
into the container via the BOOK_DROP_HOST_PATH env var. Files copied or
moved into this folder are automatically picked up and ingested by the
existing Book Dock file watcher.
- docker-compose.yml: add BOOK_DROP_HOST_PATH volume override so the
book-dock path is separately bind-mountable
- .env.example: document BOOK_DROP_HOST_PATH with an example NAS path
- AppInfoResponse: add bookDockPath so the UI can show the
container-internal path admins need to bind-mount
- AppInfoService: compute and return bookDockPath from storage config
- BookDockSettings: display drop folder path with BOOK_DROP_HOST_PATH hint
- useAppInfo: expose bookDockPath ref from the app-info API response
- README: document the optional drop folder in the quickstart section
When BOOK_DROP_HOST_PATH is unset, the volume defaults to
./data/app/book-dock, preserving full backward compatibility.
No watcher code changes required - the watcher already watches
the correct container-internal path.
Closes#58
* fix(book-dock): resolve symlink paths so rescan stays idempotent
BookDockWatcherService and BookDockIngestService were building
bookDockPath with path.join(), which preserves symlinks. `@parcel/watcher`
resolves symlinks internally when reporting events (using realpath), so the
absolutePath values stored by watcher events and the paths computed by
walkAndIngest during rescan never matched. This caused findByAbsolutePath
to always return undefined on rescan, creating duplicate DB records for
every file already tracked via a watcher event.
Fix: both services now call `realpath(bookDockPath)` in
onApplicationBootstrap (after mkdir), updating the stored path to the
canonical real path that the watcher also uses. Rescan is now idempotent
regardless of symlinks in the data directory.
Verified by E2E test: 3 consecutive rescans produce zero new records.
* fix(books): switch progress reset to per-file controls
Remove the book-level reset action and endpoint, and keep reset scoped to each file row to avoid accidental bulk clears.
Add tests for per-file reset behavior and progress formatting edge cases while keeping migration import filters aligned with the new progress expectations.
closes#57
* ci: increase commitlint body line limit to 250
Allow longer explanatory commit bodies so CI accepts existing project commit patterns without forcing manual hard wraps at 100 characters.
Block Google Books from being enabled without credentials because unauthenticated API calls now fail quota checks. Normalize legacy enabled configs without keys to disabled.
Use BigInt stat reads for upload and file-event paths to preserve 64-bit inode values. Clamp inodes that exceed PostgreSQL bigint range to 0 before persistence and inode lookups, and add regression coverage for insert, upsert, restore, and move-detection paths.
Fixes#48
- Add tap-to-cycle footer modes (pages, time/session, chapter) along with elapsed reading time and chapter progress on the slider
- Prevent screen dimming during reading using the Wake Lock API
- Add keyboard shortcuts (t/s/b/f/Esc/?/Home/End) with an integrated help modal
- Add go-to-location support (page number or percentage) and a resume-reading toast on book open