Commit Graph

173 Commits

Author SHA1 Message Date
neon 4197a8fc2e feat: add Hardcover import workflow
Add a preview-and-review workflow that matches Hardcover books by cached IDs, ISBNs, slugs, and title/author confidence before updating BookOrbit read status.

Import reading progress only when requested and safe, preserve existing local progress, and keep status imports resilient when progress writes fail.

Closes #394
2026-06-22 02:43:07 +00:00
neon 2143b00cc5 fix(scanner): handle watched moves across docker libraries
Emit transfer events when watcher reconciliation moves a book between libraries. Keep file-as-book and folder-as-book watcher paths consistent across Docker, direct runs, and delayed copy-delete moves.

Closes #386
2026-06-21 19:15:39 +00:00
neon 1988ed0956 feat(metadata): add empty entity cleanup controls
Expose zero-book first-class metadata entities in browse so admins can review orphaned rows before deleting them.

Keep inline metadata values out of empty filtering because they only exist through book metadata rows.

Closes #370
2026-06-21 04:16:44 +00:00
neon f1a7ba6070 fix(cover): refresh cached cover URLs
Update cover mutation paths to bump the book timestamp used for cover cache busting, and carry per-book collapsed-series cover versions so child covers do not reuse a representative book timestamp.
2026-06-20 20:52:43 +00:00
neon c4ab4a9153 feat: add in-app release notes viewer
Introduces a new feature to fetch, parse, and display app release notes
and update highlights directly within the UI.

- Backend: Adds the `release-notes` module to fetch GitHub releases
  and parse markdown highlight sections.
- Frontend: Adds the `WhatsNew` dialog and view, including custom
  markdown rendering and an image lightbox for update media.
- Tools: Adds validation scripts (`whats-new-lint`, `whats-new-preview`)
  to ensure release content parses correctly before publication.
2026-06-18 03:37:53 +00:00
Kyungwook Park 6677f2f5ea feat(metadata): add Aladin Korean book metadata provider (#351)
* feat(metadata): add Aladin Korean book metadata provider

Add support for fetching Korean book metadata from Aladin OpenAPI.
Includes search by title/author/ISBN and lookup by ISBN-10/13.

- Add ALADIN to MetadataProviderKey in @bookorbit/types
- Add AladinProviderConfig to ProviderConfigurations
- Create AladinProvider with IdentifiableProvider interface
- Add mapper for Aladin API response to MetadataCandidate
- Register provider in MetadataFetchModule
- Add provider config merge, validation, and connection test
- Include 24 unit tests (provider + mapper)

* feat(client): add Aladin provider UI integration

* refactor(metadata): use 'Aladin' label and favicon-based icon

* feat(metadata): complete Aladin provider end-to-end wiring

Persist the Aladin ItemId across fetch, scoring, locks, and file metadata round-trip, and update the affected tests.

Co-authored-by: Kyungwook Park <288273+kyungw00k@users.noreply.github.com>

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Co-authored-by: Kyungwook Park <288273+kyungw00k@users.noreply.github.com>
2026-06-17 13:33:35 -06:00
neon 9afddc34d2 feat(books): add support for multiple series per book
This introduces a many-to-many relationship between books and series, resolving the previous limitation where a book could only belong to a single series. It updates the database schema, query builders, server DTOs, and the Vue client interface to manage multiple series memberships per book.

Closes #353
2026-06-17 06:15:59 +00:00
neon 96e42d2ac1 feat(settings): add option to turn book spine off for comics
Adds a display preference to conditionally render book spines on comic
formats. The new setting is defaulted to false. Various book cover
surfaces across the application now respect this preference by checking
if the format is a comic before rendering the spine.

Closes #295
2026-06-15 05:56:39 +00:00
neon f525c01a2f feat(query): add up-next in series filter operator
Adds the isUpNext operator to seriesStatus allowing books to be filtered by whether they are the next unread book in a series where the previous book is completed.

Closes #321
2026-06-15 04:58:31 +00:00
neon 1b8454f557 feat(stats): add device and rating achievement evaluators with session source tracking
Introduce devices and rating evaluators backed by a new reading_sessions.source
column, so badges can reward multi-device reading and book ratings. Wire the
source through Kobo and reading-session services and seed the new badges.
2026-06-15 04:03:54 +00:00
neon 49da34ee48 feat(server): add safe directory creation to path service
Introduces PathService.createDirectory with comprehensive path validation
to prevent directory traversal and symlink attacks. It validates parent paths
against a blocked list, ensures the parent is a real directory, and blocks
unsafe characters in folder names.

Filesystem errors like EEXIST and EACCES are mapped to standard HTTP
exceptions to provide clear feedback to the client.

Closes #332
2026-06-14 23:45:59 +00:00
neon 445bb39978 feat(books): add lock status to library filter rules
Closes #327
2026-06-14 23:33:44 +00:00
Chris Mansell aed22712cc feat(metadata): add LubimyCzytac metadata provider (#324)
* feat(metadata): add LubimyCzytac metadata provider

- Add LubimyCzytac scraper and provider implementation
- Add lubimyczytacId field across types, schema, extractors, and metadata service
- Register lubimyczytac provider in module, pipeline, repository, and config service
- Add provider icon, color, link, and UI support in book detail views
- Add migration for lubimyczytac_id column

Closes #304

* fix(books): keep cover card read button opening reader when thumbnail click prefers details

The overlay primary button should always open the reader regardless of
the thumbnailClickAction setting. That setting only controls card click
behaviour.

* fix(books): add explicit read button to hover overlay when thumbnail click prefers details

When thumbnailClickAction is 'details', a dedicated Read button now
appears in the overlay top row so users can still open the reader
directly without changing their preference.
2026-06-13 11:40:53 -06:00
neon d42abb3647 fix(metadata): extract and save audiobook chapters and embedded metadata
The book dock metadata service now routes audio formats through the audio extractor to extract chapters, duration, narrators, and other embedded tags. This ensures that audiobook metadata is correctly mapped and persisted during the import process.

Closes #322
2026-06-13 17:18:40 +00:00
neon b81ba163ce fix(scanner): fallback to original filename when title is missing in patterns
The default upload file naming patterns for single file and folder per book previously relied exclusively on the title. If multiple files lacked a title, they would map to the same path. This changes the default patterns to fall back to the original filename if the title is missing, preventing files without metadata from clumping.

Closes #308
2026-06-12 05:27:59 +00:00
neon b1dff917ad feat(books): add jump rail navigation to book listings (#318)
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
2026-06-11 23:16:58 -06:00
neon c0a7aef075 feat(books): add file upload UI to book detail files tab 2026-06-08 20:44:43 +00:00
neon 204f69b046 feat(metadata): add fetch pipeline diagnostics
The metadata fetch pipeline now gathers and returns detailed diagnostics
about provider selection, candidate results, and the reason for empty
resolved fields (e.g. no active providers, throttled providers, or no
candidates). These diagnostics are logged to improve observability and
make debugging metadata fetch failures significantly easier.
2026-06-06 19:32:34 +00:00
neon feec3c794a fix(koreader): trigger hardcover auto-sync on progress update
KOReader progress updates were updating the database but failing to
emit the progress changed event, causing Hardcover auto-sync to be
skipped. This change adds the missing event emission to the KOReader
service so that external syncs trigger correctly, and patches a query
filter issue in the Hardcover repository.

Closes #258
2026-06-06 18:16:48 +00:00
neon 329d1b6643 feat(metadata): add Kobo metadata provider
Add Kobo as a first-class metadata source so books can keep Kobo IDs across fetch, edit, import, scoring, and file-write flows. Provide the Python cloudscraper setup needed for production fetches while keeping local setup explicit.

Closes #247
2026-06-06 18:16:47 +00:00
neon 913dab0ac5 feat(kobo/koreader): two way page level sync between kobo/koreader and bookorbit (#250)
* feat(kobo): support two-way progress sync

Implement two-way page-level reading progress synchronization between Kobo devices, KOReader, and BookOrbit. This ensures readers can switch between physical devices and the web app without losing their exact page-level reading position.

Closes #249

* fix(settings): restore thumbnail click preference

Restore the main branch thumbnail-click setting after the Kobo branch accidentally dropped it during the rebase. Keep the Kobo architecture allowlist aligned with the new identity service.

* fix(kobo): harden two-way progress sync fallbacks

Preserve Kobo bookmark metadata and tolerate null auth payloads/settings failures.

Cover review-reported regressions with focused tests.
2026-06-05 09:30:25 -06:00
neon cc7ab54a3b feat(settings): add option to change default thumbnail click behavior
Introduce a new display preference to control what happens when clicking
a book cover thumbnail. Users can now choose to open the book details
page instead of launching the reader directly.

Closes #240
2026-06-05 04:19:26 +00:00
neon c2b956d54a feat(metadata): add genre blocklist to exclude unwanted tags
The genre blocklist allows users to define a global list of strings
that are prevented from being applied to a books genre field during
metadata fetches. Case-insensitive filtering executes directly inside
the metadata pipeline before merge strategies run, preventing unwanted
values from persisting across providers.

Closes #235
2026-06-04 16:09:00 +00:00
neon 0257b4553e feat: add continue listening and want to read dashboard scrollers
Add dedicated dashboard scrollers for audiobooks and want-to-read lists to improve content discoverability. Previously, audiobook progress was merged with reading progress, making it difficult to find active audiobooks. Separate the queries to ensure audiobooks are handled exclusively by the new listening scroller and expose the user's want-to-read queue directly on the dashboard for easier access.
2026-06-03 22:14:04 +00:00
neon 936b907352 feat(series): add stack display mode for series thumbnails
Introduces a new 'stack' cover mode for series cards, rendering the covers
as a stacked pile. This mode has also been made the default for series card
display preferences. Settings UI is updated to allow users to toggle this
mode alongside existing mosaic and single-cover options.

Closes #223
2026-06-03 17:56:20 +00:00
neon efa300cecc fix(metadata): restore RanobeDB file metadata and links #207 #210
Preserve RanobeDB IDs through write-back, file import, and load-from-file so saved metadata survives round trips.

Expose saved RanobeDB sources in the book detail surfaces with the provider icon and database link.

Closes #207
Closes #210
2026-06-03 16:40:09 +00:00
Chris Mansell 06657a56a5 feat(books): write metadata directly to audiobook files (#219)
- Extend file write pipeline to write metadata fields directly into audiobook files.
- Map metadata fields to standard audio tag mappings and run ffmpeg to embed tags and cover art for M4B, M4A, MP3, and FLAC files.
- Update UI in edit metadata tab to show file write status and highlight written fields.

Handle edge cases in metadata extraction, file write-back status checks, and comic XML building. This ensures narrators are not incorrectly populated when they match the author/artist, preserves audibleId during chapter extraction, prevents whitespace issue numbers from breaking the XML, and improves the UI to clearly show why write-back is disabled.
2026-06-03 10:03:52 -06:00
neon 73795767da fix(series): use numeric ID for routing to support long titles
Transition from title-based to ID-based routing for series to prevent
404 errors when a series has an excessively long title. This introduces
numeric IDs for series, backed by schema updates, and updates the
frontend router to use seriesId instead of seriesName.

Closes #198
2026-06-02 18:53:14 +00:00
Chris Mansell d06e65a42b feat(audio): embed cover image in audiobook files (#197)
* feat(audio): embed cover image in audiobook files

- Add audio cover embedder
- Implement format writers for M4B, M4A, MP3, and FLAC using ffmpeg
- Add library settings for audio file writes

Closes #145

* fix(library): disable audio file write by default and add ffmpeg timeout

Change the default value of file_write_audio_enabled from true to false in the database schema and migrations. Automatically disable audio writing in the UI if cover writing is toggled off. Add a 60-second timeout to the ffmpeg child process to prevent hanging when embedding covers.

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-06-02 12:40:27 -06:00
neon 2bdc88e2cc feat(dashboard): add up next in series to continue reading
Closes #162
2026-06-01 16:48:21 +00:00
Chris Mansell 53a08788bc feat(metadata): add RanobeDB as metadata provider (#185)
* 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
2026-05-31 22:56:40 -06:00
neon 16e01f6b01 feat(settings): add option to show titles under grid cards (#181)
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
2026-05-31 19:37:28 -06:00
neon 7a1b0120b8 feat(series): add selectable cover modes for collapsed series cards
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
2026-05-31 15:10:00 +00:00
Chris Mansell 26b3c8ba18 feat(settings): add adaptive cover display preferences (#169)
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
2026-05-30 18:57:29 -06:00
neon a1fe471e83 feat(settings): add theme persistence across devices
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
2026-05-30 17:27:08 +00:00
Chris Mansell 100334773c feat(metadata): add bulk metadata edit dialog (#164)
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
2026-05-30 11:21:27 -06:00
neon 6ffce7d7c8 feat(books): add manual write-and-rename action in metadata editor
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
2026-05-30 05:16:33 +00:00
neon 9b50b0ebb7 fix(cover): retain audiobook square covers in discovery carousels
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
2026-05-30 03:58:49 +00:00
neon 54814201b8 feat(metadata): add provider connection testing and token validation
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
2026-05-29 03:34:56 +00:00
Chris Mansell e659111e4e feat(library): add bulk rename tool and e2e coverage (#124)
* 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>
2026-05-28 09:27:03 -06:00
neon 3a72ece26d fix(settings): gate integration tabs by integration permissions
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.
2026-05-26 19:52:32 +00:00
neon 1479989f74 feat(settings): add cross-platform path sanitization option
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
2026-05-25 05:17:17 +00:00
Chris Mansell 956a7a5e9a feat(users): add per-user tag/genre content restrictions (#83)
* 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
2026-05-22 11:01:11 -06:00
neon 75d9161962 feat(library): rename files on metadata update (#76)
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
2026-05-21 11:15:22 -06:00
neon 40bdd661a5 feat(books): add started/finished reading date support
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
2026-05-21 03:39:13 +00:00
neon 0b9027c275 feat(book-dock): expose Book Dock folder as a configurable drop zone (#64)
* 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.
2026-05-20 13:00:56 -06:00
Chris Mansell 418314352e feat: hardcover sync for per user reading status progress and rating (#53)
* feat(server): sync reading progress with Hardcover

* fix(server): update Hardcover sync pending state

* test(server): cover Hardcover admin and repository
2026-05-19 10:49:15 -06:00
neon 8abde907d8 feat(reader): add footer modes, wake lock, keyboard shortcuts, and go-to input
- 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
2026-05-18 22:02:49 -06:00
neon 25117e3d0b fix(download): enforce real file extension in pattern resolver 2026-05-18 17:46:20 -06:00
neon fabd1747d3 fix: add per-org upload pattern defaults for book_per_folder mode (#33)
* fix(settings): add per-org upload defaults for book_per_folder mode

* ci: add maintainer-gated PR GHCR image publish

---------

Signed-off-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-05-18 17:12:13 -06:00