Commit Graph

455 Commits

Author SHA1 Message Date
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
neon c92ab8cf49 fix(library): skip file write for encrypted PDFs
pdf-lib cannot re-encrypt files, so saving an encrypted PDF strips its encryption but writes back still-encrypted byte streams, silently corrupting the file. Skipping the file write entirely prevents this corruption while allowing DB metadata to persist separately.

Fixes #368
2026-06-18 01:20:24 +00:00
neon 39e53a5d36 fix(books): treat missing read status as unread for sorting
Books without a row in user_book_status returned NULL and sorted differently than books explicitly marked as 'unread'. Coalescing the missing status to 'unread' ensures both explicitly and implicitly unread books are grouped and ordered consistently.

Fixes #367
2026-06-18 01:11:30 +00:00
neon 65f4fa7fca fix(library): only process books marked for rename in bulk rename
The bulk rename job was incorrectly iterating over every book in the library instead of restricting the operation to books that actually require a rename. This caused unnecessary processing and logged excessive noise. We now compute the preview first and filter the list of candidate book IDs down to those with the 'will_rename' status before executing the rename loop.

Closes #361
2026-06-17 23:14:30 +00:00
neon ceb2bb72d6 chore: add tests for BE strategies, repositories, and FE composables
Expand coverage across previously untested modules to close gaps identified
in the coverage audit.

BE: entity-manager strategies (inline, junction, author), koreader chapter
extractor, series and achievement repositories.

FE: reader settings/progress composables, notifications, live-scan books,
entity manager, and API wrapper modules (author, statistics, entity-manager).
2026-06-17 21:41:16 +00:00
neon ed87c14135 test(user): use valid PNG fixture for avatar test (sharp 0.35 libpng) 2026-06-17 20:25:12 +00:00
dependabot[bot] 2bb3b6e5d9 chore(deps): bump the production-dependencies group across 1 directory with 15 updates (#366)
Bumps the production-dependencies group with 15 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@embedpdf/vue-pdf-viewer](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/viewers/vue) | `2.14.3` | `2.14.4` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.3.0` | `4.3.1` |
| [@tanstack/vue-virtual](https://github.com/TanStack/virtual/tree/HEAD/packages/vue-virtual) | `3.13.28` | `3.13.29` |
| [dompurify](https://github.com/cure53/DOMPurify) | `3.4.8` | `3.4.11` |
| [reka-ui](https://github.com/unovue/reka-ui) | `2.9.9` | `2.9.10` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.0` | `4.3.1` |
| [vue](https://github.com/vuejs/core) | `3.5.35` | `3.5.38` |
| [@nestjs/common](https://github.com/nestjs/nest/tree/HEAD/packages/common) | `11.1.24` | `11.1.27` |
| [@nestjs/core](https://github.com/nestjs/nest/tree/HEAD/packages/core) | `11.1.24` | `11.1.27` |
| [@nestjs/platform-fastify](https://github.com/nestjs/nest/tree/HEAD/packages/platform-fastify) | `11.1.24` | `11.1.27` |
| [@nestjs/platform-socket.io](https://github.com/nestjs/nest/tree/HEAD/packages/platform-socket.io) | `11.1.24` | `11.1.27` |
| [@nestjs/websockets](https://github.com/nestjs/nest/tree/HEAD/packages/websockets) | `11.1.24` | `11.1.27` |
| [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) | `5.8.0` | `5.9.2` |
| [mysql2](https://github.com/sidorares/node-mysql2) | `3.22.4` | `3.22.5` |
| [sharp](https://github.com/lovell/sharp) | `0.34.5` | `0.35.1` |



Updates `@embedpdf/vue-pdf-viewer` from 2.14.3 to 2.14.4
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/main/viewers/vue/CHANGELOG.md)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.14.4/viewers/vue)

Updates `@tailwindcss/vite` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/@tailwindcss-vite)

Updates `@tanstack/vue-virtual` from 3.13.28 to 3.13.29
- [Release notes](https://github.com/TanStack/virtual/releases)
- [Changelog](https://github.com/TanStack/virtual/blob/main/packages/vue-virtual/CHANGELOG.md)
- [Commits](https://github.com/TanStack/virtual/commits/@tanstack/vue-virtual@3.13.29/packages/vue-virtual)

Updates `dompurify` from 3.4.8 to 3.4.11
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.4.8...3.4.11)

Updates `reka-ui` from 2.9.9 to 2.9.10
- [Release notes](https://github.com/unovue/reka-ui/releases)
- [Commits](https://github.com/unovue/reka-ui/compare/v2.9.9...v2.9.10)

Updates `tailwindcss` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/tailwindcss)

Updates `vue` from 3.5.35 to 3.5.38
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.35...v3.5.38)

Updates `@nestjs/common` from 11.1.24 to 11.1.27
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.27/packages/common)

Updates `@nestjs/core` from 11.1.24 to 11.1.27
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.27/packages/core)

Updates `@nestjs/platform-fastify` from 11.1.24 to 11.1.27
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.27/packages/platform-fastify)

Updates `@nestjs/platform-socket.io` from 11.1.24 to 11.1.27
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.27/packages/platform-socket.io)

Updates `@nestjs/websockets` from 11.1.24 to 11.1.27
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.27/packages/websockets)

Updates `fast-xml-parser` from 5.8.0 to 5.9.2
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.8.0...v5.9.2)

Updates `mysql2` from 3.22.4 to 3.22.5
- [Release notes](https://github.com/sidorares/node-mysql2/releases)
- [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md)
- [Commits](https://github.com/sidorares/node-mysql2/compare/v3.22.4...v3.22.5)

Updates `sharp` from 0.34.5 to 0.35.1
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.34.5...v0.35.1)

---
updated-dependencies:
- dependency-name: "@embedpdf/vue-pdf-viewer"
  dependency-version: 2.14.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@tanstack/vue-virtual"
  dependency-version: 3.13.29
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: dompurify
  dependency-version: 3.4.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: reka-ui
  dependency-version: 2.9.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: tailwindcss
  dependency-version: 4.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: vue
  dependency-version: 3.5.38
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@nestjs/common"
  dependency-version: 11.1.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@nestjs/core"
  dependency-version: 11.1.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@nestjs/platform-fastify"
  dependency-version: 11.1.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@nestjs/platform-socket.io"
  dependency-version: 11.1.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@nestjs/websockets"
  dependency-version: 11.1.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: fast-xml-parser
  dependency-version: 5.9.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: mysql2
  dependency-version: 3.22.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: sharp
  dependency-version: 0.35.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-06-17 13:55:50 -06: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 9b4408c610 fix(server): fix floating point precision in series index formatting
String/math based extraction of floating point series index fraction parts caused floating point precision artifacts. This extracts formatting into a shared utility and uses string splitting to avoid precision issues.

Closes #360
2026-06-17 05:36:48 +00:00
neon 6eec39cb8a fix: allow resetting migration source setup
Add an API endpoint and UI action to reset the migration source configuration when no migration runs have been created yet.

Closes #354
2026-06-17 05:24:58 +00:00
neon 7b48d49351 fix(server): accept empty requests with unsupported media types
Inject an empty JSON object for requests that have an unsupported content type but zero content length. Fastify defaults to rejecting these with a 415 error.

Closes #348
2026-06-17 05:07:44 +00:00
neon 0cddaf050c feat(metadata): add fallback scanner for EPUBs with missing ISBNs
Extract ISBNs from the EPUB spine contents if missing from the OPF metadata.

Closes #350
2026-06-15 23:24:56 +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 d13fc929e3 feat(metadata): support calibre 9.x opf3 metadata formats
Calibre 9.x (opf3) writes provider identifiers as bare `prefix:value` text
inside `<dc:identifier>` instead of using `opf:scheme` attributes. It also
stores custom-column values like page count and subtitle in a
`calibre:user_metadata` JSON blob. This adds parsing for these fallback
formats to correctly extract metadata from Calibre-processed EPUBs.

Closes #341
2026-06-15 04:26:27 +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
neon daa0001877 fix(metadata): prioritize physical hardcover editions over audiobooks
Rank physical and ebook editions higher than audiobooks in hardcover
metadata mapping. Do not allow audiobooks to inherit page counts from
the base book to prevent incorrect metadata.

Closes #334
2026-06-14 23:06:44 +00:00
neon dc82774291 fix(scanner): populate per-file audio duration on upload
The upload paths saved only the aggregate book_metadata duration, leaving book_files.durationSeconds
null so the player summed to zero. Extract and aggregate per-file duration on new uploads and added
audio files, matching the scanner.

Ref #330
2026-06-14 22:50:01 +00:00
neon 5d96ba1a55 fix(metadata): improve hardcover edition matching algorithm
The previous edition matching logic often resulted in suboptimal edition assignments, tracking text books against audiobooks or selecting editions with drastically different page counts. A more rigorous matching strategy is required to ensure users get accurate edition metadata from Hardcover.

Closes #333
2026-06-14 22:34:08 +00:00
neon 101c8d4cce fix(metadata): restore Goodreads fetching via autocomplete fallback when detail pages are WAF-gated 2026-06-14 20:19:15 +00:00
neon e3ee046dc5 fix(metadata): normalize zero page count to null to prevent save errors
When a metadata provider returns a page count of 0 (e.g. unknown), the frontend would submit 0, causing a validation error (HTTP 400). This change normalizes 0 to null on both the client (before sending) and server (during validation).

Closes #329
2026-06-14 07:13:01 +00:00
neon c7f3e8eacb fix(metadata): allow saving edits to re-locked metadata fields
Removes the `assertManualUpdateAllowedForLockTransition` guard that incorrectly rejected the legitimate UI flow of unlocking a field, editing its value, and re-locking it before saving.

Closes #328
2026-06-14 06:44:37 +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 d9f3f31503 refactor(auth): split OidcCleanupService into service and job
Extract DB deletion logic into OidcCleanupService as a pure method
returning counts. Move the @Cron trigger, logging, and error handling
into a new OidcCleanupJob, matching the AuditCleanupJob pattern.
2026-06-08 22:53:44 +00:00
neon 69c09c5849 feat(metadata): import cover image from OPF sidecar guide and manifest
Sidecar OPF files can link to a sibling cover image, but the extractor
always passed null for the cover buffer, silently dropping the image.

Parse coverHref from three sources in priority order:
1. EPUB2 <guide><reference type="cover" href="..."/>
2. EPUB3 <manifest><item properties="cover-image" href="..."/>
3. Calibre <meta name="cover" content="id"/> resolved via manifest

Resolve the href relative to the OPF file directory and read the image
file. Fall back to null cover if the file is missing or unreadable,
so a broken cover link does not fail the import.

Closes #273
2026-06-08 22:27:00 +00:00
neon 058555b765 fix(auth): surface actionable error when OIDC issuer resolves to private address
The SSRF guard was throwing a generic 'URL host is not allowed' with no
indication that the block was due to a private or local IP, or how to
resolve it. Users with a LAN-hosted OIDC provider had no way to diagnose
the failure.

Introduce PrivateAddressException extends BadRequestException in ssrf.utils
so callers can distinguish private-address blocks from other validation
errors. The OIDC connection test catches it and re-throws with a message
that names OIDC_ALLOW_LOCAL_ISSUERS=true as the fix.

Closes #264
2026-06-08 22:00:36 +00:00
neon a1b417ccb8 chore(kobo): fix invalidateSnapshot log format and remove unbounded bookIds field
The debug log in invalidateSnapshot did not follow the project logging
convention ([event] [phase] key=value ... - message) and logged all
reset book IDs, which could produce unbounded single-line output for
large libraries.

Fix the event name, add the [end] phase marker, add durationMs, drop
the bookIds field, and remove the no-snapshot early-return log (noisy
no-op path).
2026-06-08 21:48:21 +00:00
Logan Selley 9de47fe67d fix(kobo): enhance kobodeviceservice to manage device snapshots (#277)
* fix(kobo): enhance kobodeviceservice to manage device snapshots

Refactor KoboDeviceService to include a syncService for invalidating user snapshots when devices are created or revoked. Update tests to verify snapshot invalidation behavior and ensure proper service instantiation. This change improves the synchronization process for user devices in the Kobo ecosystem.

* test(kobo): update device revocation tests for snapshot invalidation

Enhance the test cases for the KoboDeviceService's revokeDevice method to verify that the syncService's invalidateSnapshot is called correctly and that database deletion methods are invoked the expected number of times. This ensures accurate testing of device revocation behavior and snapshot management.

---------

Co-authored-by: Logan-Selley <logan@selleytech.com>
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-06-08 15:45:13 -06:00
neon a54108b97d fix(series): keep startup alive when series backfill fails (#271)
* fix(series): keep startup alive when series backfill fails

The series id backfill ran inside SeriesIdentityService.onModuleInit
without any error handling, so any failure rejected the lifecycle hook
and aborted NestApplication bootstrap, leaving the app unable to start.
On databases where the book_series unique arbiter index is missing or
unusable, the backfill INSERT ... ON CONFLICT throws and bricks startup.

Wrap the backfill in a guard so failures are logged and startup
continues, and surface the underlying driver error (Drizzle hides the
real cause behind its 'Failed query' wrapper) so the root DB issue is
diagnosable from logs.

Closes #270

* fix(series): resolve series backfill timeout on large libraries

The NodePg pool has statement_timeout: 30_000 ms, which is the right
guard for API queries but too aggressive for the one-time startup
backfill. On libraries with 100k+ books the bulk UPDATE against
book_metadata hits the limit before completing, leaving series_id
NULL for all books and repeating the timeout on every subsequent
restart.

Two changes:

1. Wrap the backfill in a transaction and issue SET LOCAL
   statement_timeout = 0 at the start. SET LOCAL scopes the change to
   this transaction only, so the pool timeout applies to every other
   query unchanged. The transaction also makes all three backfill
   statements atomic: previously a successful INSERT followed by a
   timed-out UPDATE left book_series populated but series_id still
   NULL everywhere.

2. Add a functional B-tree index on lower(btrim(series_name)) in
   book_metadata. The backfill UPDATE joins on that expression; without
   an index the planner falls back to a full sequential scan and hash
   join on every startup. With the index, subsequent startups use a
   nested-loop index scan that short-circuits immediately when no rows
   need updating.

Measured on 250k books: first migration completes without timeout;
subsequent startups finish the no-op check in ~150ms.

Closes #270
2026-06-08 15:32:11 -06:00
neon c0a7aef075 feat(books): add file upload UI to book detail files tab 2026-06-08 20:44:43 +00:00
neon 8bee4905bb feat(book): implement file rename and delete interactions
- Adds rename and delete file capabilities to Book Details > Files tab
- Automatically promotes a fallback file to primary upon deletion
- Refactors path update logic to correctly apply db folder path updates
- Ensures file type format badges in header search use global format colors
- Replaces legacy file property overrides with rename-only DTO

Closes #214
2026-06-08 14:36:29 -06:00
neon 638e5f15df fix: select format in findAllBookFiles 2026-06-08 05:42:08 +00:00
neon e62cb743ac fix(scanner): treat pt ## audiobook subfolders as part folders
The scanner disc directory pattern previously recognized cd, disc, disk, part, and side, but not pt. This caused Pt subfolders to be treated as separate books instead of being merged into the parent audiobook. This change adds pt as an accepted disc/part folder prefix.

Closes #262
2026-06-07 23:37:31 -06:00
neon f54752568f fix(library): rename all book files during metadata write
Ensure all files associated with a book are renamed or moved when the
primary file is renamed. Previously, the rename service only computed
new target paths for the primary file, abandoning companion files (e.g.,
covers, sidecars) or failing to rename them properly.

Compute new target paths for all content and secondary files, manage
batch rollbacks on failure, and handle internal naming collisions. Clean
up unused single-file rename repository and service methods.

Closes #265
2026-06-07 23:37:31 -06:00
Logan Selley d8e84aa531 feat(kobo): sync star ratings from ratebook analytics events (#269)
* feat(kobo): handle ratebook events in koboanalyticsservice

This commit adds the BookModule to the KoboModule imports, allowing for better integration with book-related functionalities. Additionally, the KoboAnalyticsService has been updated to handle 'RateBook' events, enabling the processing of user ratings through the new bookService. Tests have been added to ensure the correct handling of these events and their interactions with the reading session service.

* test(kobo): fill ratebook testing gaps

---------

Co-authored-by: Logan-Selley <logan@selleytech.com>
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-06-07 23:33:23 -06:00
neon 54ca05ef04 fix(opds): strip xml 1.0 illegal control characters from metadata
Remove invalid XML 1.0 characters from book metadata. These characters can cause lenient OPDS clients like KOReader to incorrectly parse the feed and shift entry boundaries onto the wrong acquisition link.

Closes #260
2026-06-06 22:21:39 +00:00
neon 0d6a27331e build(deps): bump golang.org/x/text in kepubify to fix CVEs
The kepubify binaries contained golang.org/x/text v0.3.6 which
triggers CVE-2021-38561 and CVE-2022-32149 in Trivy. This commit
updates the module to the latest version and rebuilds the binaries.
2026-06-06 22:09:16 +00:00
neon d60bcac4b0 build(deps): update kepubify binaries to go 1.26.4
The previous kepubify binaries were built with an older Go version
that triggered Trivy's CVE-2026-42504 vulnerability in the standard
library. This updates the binaries to the latest Go version to resolve
the CI pipeline failure.
2026-06-06 21:54:09 +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 cb2a7b7f48 fix(kobo): capture paired analytics progress
Use paired Kobo open and leave analytics events to derive progress deltas and active reading time.

Format negative reading-log deltas without an extra plus sign.
2026-06-06 18:42:43 +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 04a4fdd0eb fix(server): handle stream errors when patching zip archives
Add an error event listener to the source stream when appending entries during zip patching to prevent unhandled ENOENT errors from crashing the Node process.

Closes #226
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
Logan Selley 81e0531ece feat(kobo): sync native reading sessions from analytics events (#245)
* feat(kobo): integrate analytics service into kobodevicecontroller

- added analytics service to the kobodevicecontroller to handle analytics events.
- updated the analyticsevent method to process incoming events and handle errors gracefully.
- enhanced unit tests to cover new analytics functionality, including successful ingestion and error handling scenarios.
- updated module imports to include the new analytics service and resolver.

* test(architecture): add kobo analytics resolver service to architecture boundaries tests

* refactor(kobo): enhance analytics handling and improve tests

- Updated KoboDeviceController to include analyticsService in constructor and added test for handling non-Error rejections.
- Enhanced KoboAnalyticsResolverService tests to cover scenarios where book rows do not exist and when primary file IDs are set without corresponding EPUB files.
- Modified KoboAnalyticsService to integrate bookIdentityService for resolving volume IDs and added tests for various edge cases in event ingestion, including handling invalid timestamps and logging warnings for non-Error exceptions.

These changes improve the robustness of analytics processing and ensure comprehensive test coverage for new functionalities.

* fix(kobo): enhance leavecontent event validation and update tests

- Improved validation logic in KoboAnalyticsService for LeaveContent events to ensure volume ID is a non-empty string.

---------

Signed-off-by: Logan Selley <37816781+Logan-Selley@users.noreply.github.com>
Co-authored-by: Logan-Selley <logan@selleytech.com>
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-06-06 12:12:41 -06:00