Skip to content

fix(filters): fix app file size filtering for books + make the f/e aware of the filter type#817

Open
zachyale wants to merge 1 commit intodevelopfrom
fix/file-size-filter
Open

fix(filters): fix app file size filtering for books + make the f/e aware of the filter type#817
zachyale wants to merge 1 commit intodevelopfrom
fix/file-size-filter

Conversation

@zachyale
Copy link
Copy Markdown
Member

@zachyale zachyale commented Apr 23, 2026

Description

Fixes app book file size filtering so it works on MariaDB and evaluates all book format files instead of just the file w/ the lowest file ID.

Linked Issue: Fixes #808

Changes

The previous file size filter was broken in 2 ways:

  1. The backend query used a MIN(id) subquery comparison to identify a single file row, which was rendered into an SQL shape that Maria rejected, causing a 500 when filtering by file size.
  2. The filter only considered one book file- this meant a book could be excluded even when one of its other valid formats matched the current size bucket. for ex., if a book had a 5 MB EPUB (primary, file ID 1) and a 500 KB PDF (file id 2), filtering for 0-1 MB should still include that book

The changes also add regression coverage in a new AppBookSpecificationTest for the following cases:

  • matching against the first book-format file only
  • not behavior
  • multi-select or
  • multi-select and

Notes:

  • We should backfill this new test in a subsequent PR to guard against future regressions in other filters after this is merged
  • The sidebar-filter is no longer used anywhere since the move to backend-based filtering. This work made it clear we should should kill it in a subsequent PR/do a scan to ensure all filters previously available on the F/E logic are fully available via the new approach. Follow up issue: Fix lubimyczytac + ranobedb + audible app book rating filters #815

Summary by CodeRabbit

  • Bug Fixes

    • Improved file size filtering logic to correctly handle multiple filter modes and ensure accurate book selection based on file size criteria.
  • Tests

    • Added comprehensive integration tests for file size filtering functionality to validate correct behavior across various filtering scenarios.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

📝 Walkthrough

Walkthrough

The withFileSizes method in AppBookSpecification has been refactored to use a BookFileEntity-driven subquery strategy for filtering books by file sizes. It now properly supports "and", "or", and "not" modes and filters only book-format files. A comprehensive integration test suite validates the specification behavior across all modes.

Changes

Cohort / File(s) Summary
File Size Filtering Logic
backend/src/main/java/org/booklore/app/specification/AppBookSpecification.java
Refactored withFileSizes method to use BookFileEntity-driven subqueries instead of correlated joins. Added toFileSizePredicate helper method to centralize bucket-to-range predicate mapping. Now properly implements "and", "or", and "not" filter modes by constructing bucket-specific subqueries and combining them with appropriate logical operators.
Integration Test Suite
backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java
New Spring Boot integration test with in-memory H2 database validating AppBookSpecification.withFileSizes() across all filter modes. Tests multi-range behavior, correct inclusion/exclusion of books based on file-size criteria, and filtering of non-book-format files. Includes createBook helper method for test setup.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

backend, enhancement

Poem

A rabbit hops through queries bright,
Subqueries dancing left and right,
File sizes sorted, AND, OR, NOT—
The filter works! 🐰 We've fixed the plot!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows conventional commit format with 'fix(filters):' prefix, clearly summarizing the main backend fix and frontend awareness update.
Description check ✅ Passed The PR description includes all required template sections: Description explaining the fix, Linked Issue reference to #808, and Changes detailing the implementation.
Linked Issues check ✅ Passed The PR addresses all three objectives from issue #808: fixes the 500 error caused by unsupported MIN(id) subquery SQL, evaluates all file formats instead of just lowest file ID, and adds support for AND/OR/NOT filter modes.
Out of Scope Changes check ✅ Passed All code changes are within scope: AppBookSpecification refactors the withFileSizes method to fix the filtering logic, and AppBookSpecificationTest adds regression coverage for the file-size filter behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/file-size-filter
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/file-size-filter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java (3)

183-210: Test name is slightly misleading — the fixture isn't "only supplementary".

createBook("Only Supplementary Match", 70_000L) still creates one book‑format file (70,000 KB). The scenario you're actually covering is "a supplementary file matches bucket 0 but the book-format file does not, so the book must be excluded from the OR result." Consider renaming the fixture variable (and possibly the test) to something like supplementaryMatchesButBookFormatDoesNot to avoid confusion.

Same note applies to the twin test at lines 212-239.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java`
around lines 183 - 210, Rename the misleading fixture and test identifiers so
they reflect that the supplementary file matches but the book-format file does
not: change the BookEntity created by createBook("Only Supplementary Match",
70_000L) and the test method name to something like
supplementaryMatchesButBookFormatDoesNot (or rename the local variable
onlySupplementaryMatch to supplementaryMatchesButBookFormatDoesNot) and update
the twin test similarly; keep the setup using BookFileEntity
(isBookFormat=false) and the assertion against
AppBookSpecification.withFileSizes unchanged.

280-293: Minor: bookType vs fileName extension mismatch in the helper.

When index >= 1 you set bookType = PDF but the generated filename still ends in .epub. It doesn't affect withFileSizes (which only inspects isBookFormat and fileSizeKb), but it makes the fixtures a bit confusing to read and would trip up anyone who later writes a test using withFileType/hasAudiobookFile on top of createBook.

♻️ Proposed tweak
-            BookFileEntity file = BookFileEntity.builder()
-                    .book(book)
-                    .fileName(title.toLowerCase().replace(' ', '-') + "-" + index + ".epub")
-                    .fileSubPath("")
-                    .isBookFormat(true)
-                    .bookType(index == 0 ? BookFileType.EPUB : BookFileType.PDF)
+            BookFileType type = index == 0 ? BookFileType.EPUB : BookFileType.PDF;
+            String ext = type == BookFileType.EPUB ? ".epub" : ".pdf";
+            BookFileEntity file = BookFileEntity.builder()
+                    .book(book)
+                    .fileName(title.toLowerCase().replace(' ', '-') + "-" + index + ext)
+                    .fileSubPath("")
+                    .isBookFormat(true)
+                    .bookType(type)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java`
around lines 280 - 293, The generated test fixture filenames currently always
use ".epub" even when bookType is set to PDF; update the helper loop that builds
BookFileEntity (in AppBookSpecificationTest) so the fileName extension is
derived from the chosen BookFileType (e.g., use ".epub" when bookType ==
BookFileType.EPUB and ".pdf" when BookFileType.PDF) rather than hardcoding
".epub", ensuring the filename generation logic in the BookFileEntity.builder()
block matches the bookType selection.

34-54: Consider @DataJpaTest over full @SpringBootTest for these specification tests.

These tests only need JPA/EntityManager + a BookRepository, but loading BookloreApplication.class forces the whole application context to come up, which is why you need to disable Flyway, the bookdrop folder, scheduling, OIDC, mock TaskCronService, etc. Switching to @DataJpaTest (with the H2 + create-drop config) would give a much slimmer, faster slice and drop most of these @TestPropertySource entries and the TestConfig. This would also make the tests more focused per the backend test guideline.

As per coding guidelines: "Prefer focused unit tests; use @SpringBootTest only when the Spring context is required in backend code."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java`
around lines 34 - 54, The test class AppBookSpecificationTest currently uses
`@SpringBootTest`(classes = BookloreApplication.class) and a large
TestPropertySource/TestConfig to bring up the whole app; change it to use
`@DataJpaTest` to load only JPA slice (EntityManager, BookRepository) so you can
remove most TestPropertySource entries and the TestConfig; update
imports/annotations on AppBookSpecificationTest (replace `@SpringBootTest` with
`@DataJpaTest` and keep transactional behavior if needed), ensure H2 create-drop
settings remain (or provide them via `@AutoConfigureTestDatabase` or test
properties limited to datasource/h2), and remove mocks/configs that were only
necessary for full context (e.g., TestConfig and any
TaskCronService/OIDC/scheduling overrides).
backend/src/main/java/org/booklore/app/specification/AppBookSpecification.java (1)

675-675: Nit: >= 0 check on bucket 0 is redundant.

fileSizeKb can't be negative in practice, so the lower bound on bucket 0 could be dropped to mirror e.g. withPageCounts bucket 0 (cb.lessThan(count, 50)). Not a blocker.

♻️ Optional simplification
-            case 0 -> cb.and(cb.greaterThanOrEqualTo(size, 0L), cb.lessThan(size, 1024L));
+            case 0 -> cb.lessThan(size, 1024L);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/main/java/org/booklore/app/specification/AppBookSpecification.java`
at line 675, The case 0 bucket in AppBookSpecification.java uses an unnecessary
lower-bound check (cb.greaterThanOrEqualTo(size, 0L)) for fileSizeKb; remove
that redundant condition and simplify the predicate returned in the switch arm
for case 0 (the expression using variable size) to only enforce the upper bound
(cb.lessThan(size, 1024L)), mirroring the simpler pattern used by
withPageCounts's bucket 0.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@backend/src/main/java/org/booklore/app/specification/AppBookSpecification.java`:
- Line 675: The case 0 bucket in AppBookSpecification.java uses an unnecessary
lower-bound check (cb.greaterThanOrEqualTo(size, 0L)) for fileSizeKb; remove
that redundant condition and simplify the predicate returned in the switch arm
for case 0 (the expression using variable size) to only enforce the upper bound
(cb.lessThan(size, 1024L)), mirroring the simpler pattern used by
withPageCounts's bucket 0.

In
`@backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java`:
- Around line 183-210: Rename the misleading fixture and test identifiers so
they reflect that the supplementary file matches but the book-format file does
not: change the BookEntity created by createBook("Only Supplementary Match",
70_000L) and the test method name to something like
supplementaryMatchesButBookFormatDoesNot (or rename the local variable
onlySupplementaryMatch to supplementaryMatchesButBookFormatDoesNot) and update
the twin test similarly; keep the setup using BookFileEntity
(isBookFormat=false) and the assertion against
AppBookSpecification.withFileSizes unchanged.
- Around line 280-293: The generated test fixture filenames currently always use
".epub" even when bookType is set to PDF; update the helper loop that builds
BookFileEntity (in AppBookSpecificationTest) so the fileName extension is
derived from the chosen BookFileType (e.g., use ".epub" when bookType ==
BookFileType.EPUB and ".pdf" when BookFileType.PDF) rather than hardcoding
".epub", ensuring the filename generation logic in the BookFileEntity.builder()
block matches the bookType selection.
- Around line 34-54: The test class AppBookSpecificationTest currently uses
`@SpringBootTest`(classes = BookloreApplication.class) and a large
TestPropertySource/TestConfig to bring up the whole app; change it to use
`@DataJpaTest` to load only JPA slice (EntityManager, BookRepository) so you can
remove most TestPropertySource entries and the TestConfig; update
imports/annotations on AppBookSpecificationTest (replace `@SpringBootTest` with
`@DataJpaTest` and keep transactional behavior if needed), ensure H2 create-drop
settings remain (or provide them via `@AutoConfigureTestDatabase` or test
properties limited to datasource/h2), and remove mocks/configs that were only
necessary for full context (e.g., TestConfig and any
TaskCronService/OIDC/scheduling overrides).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 17eb8994-effb-465b-8e31-4bbe65e74f31

📥 Commits

Reviewing files that changed from the base of the PR and between 03389d7 and ae6bfa8.

📒 Files selected for processing (2)
  • backend/src/main/java/org/booklore/app/specification/AppBookSpecification.java
  • backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Frontend Lint Threshold Check
🧰 Additional context used
📓 Path-based instructions (2)
backend/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

backend/src/**/*.java: Use 4-space indentation and match surrounding Java style in backend code
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection in backend code
Use MapStruct for entity/DTO mapping in backend code
Keep JPA entities on the *Entity suffix in backend code

Files:

  • backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java
  • backend/src/main/java/org/booklore/app/specification/AppBookSpecification.java
backend/src/test/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Prefer focused unit tests; use @SpringBootTest only when the Spring context is required in backend code

Files:

  • backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java
🧠 Learnings (3)
📚 Learning: 2026-04-22T01:56:39.495Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-22T01:56:39.495Z
Learning: Applies to backend/src/test/**/*.java : Prefer focused unit tests; use SpringBootTest only when the Spring context is required in backend code

Applied to files:

  • backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java
📚 Learning: 2026-04-10T08:15:37.436Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 449
File: booklore-api/src/main/java/org/booklore/service/book/BookDownloadService.java:139-145
Timestamp: 2026-04-10T08:15:37.436Z
Learning: When using Spring `ContentDisposition.builder(...).filename(name, StandardCharsets.UTF_8).build()` (i.e., explicitly providing UTF-8), the resulting header value should include both the quoted `filename="=?UTF-8?..."` and the RFC 5987 `filename*=` parameters. In this case, any extra ASCII fallback computation (e.g., deriving an ASCII `fallbackFilename` via `NON_ASCII_PATTERN` and calling `.filename(fallbackFilename)`) is likely redundant—prefer calling only `.filename(fallbackName?, StandardCharsets.UTF_8)` as appropriate and let Spring handle the UTF-8 header parameters. Verify by comparing the emitted header for `filename` and `filename*` before deciding to keep an ASCII fallback.

Applied to files:

  • backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java
  • backend/src/main/java/org/booklore/app/specification/AppBookSpecification.java
📚 Learning: 2026-04-14T12:43:08.698Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 502
File: booklore-api/src/main/java/org/booklore/service/reader/ChapterCacheService.java:0-0
Timestamp: 2026-04-14T12:43:08.698Z
Learning: For this codebase (booklore-api), target Java 25 with `--enable-preview`, so `_` is intentionally used as an unnamed/ignored variable (e.g., lambda parameter or pattern variable) per Java’s preview feature JEP 456. Do not flag `_` in those contexts as an invalid/reserved identifier; only flag it if it’s used in a non-supported position (e.g., where an unnamed variable is not applicable for the Java preview rules).

Applied to files:

  • backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java
  • backend/src/main/java/org/booklore/app/specification/AppBookSpecification.java
🔀 Multi-repo context grimmory-tools/grimmory-docs

Findings for grimmory-tools/grimmory-docs [::grimmory-tools/grimmory-docs::]

  • docs/view-preferences.md references "File Size" filter (label/UX) — may need frontend wording or behavior alignment with backend change. [::grimmory-tools/grimmory-docs::docs/view-preferences.md:1]
  • docs/book-browser/grid.md includes the sidebar-filters image (visual for filters UI) — frontend UI that uses file-size filter; visuals may become stale if filter behavior/labels change. [::grimmory-tools/grimmory-docs::docs/book-browser/grid.md:1]
  • docs/magic-shelf.md documents a "fileSize" field and shows an example JSON filter using fileSize (KB) — a contract example that should match backend bucket semantics. [::grimmory-tools/grimmory-docs::docs/magic-shelf.md:~ lines]
  • DOCS_TODO.md and various docs mention file size limits and related settings (informational only). [::grimmory-tools/grimmory-docs::DOCS_TODO.md, docs/tools/global-preferences.md, docs/tools/custom-fonts.md]

Assessment

  • No code-level consumers of AppBookSpecification or BookFileEntity found in this docs repo. The repo contains user-facing docs and images referencing the file-size filter; those may require doc/UX updates to reflect the backend's corrected AND/OR/NOT semantics or bucket behavior, but there are no direct code changes to coordinate here.
🔇 Additional comments (2)
backend/src/main/java/org/booklore/app/specification/AppBookSpecification.java (1)

638-671: Refactor looks correct and addresses both bugs cleanly.

  • Replacing the MIN(id) correlated subquery with book.id IN (subquery filtered by isBookFormat=true) sidesteps the Hibernate-generated SQL MariaDB rejected, and it now evaluates every book-format file rather than only the lowest-id one.
  • "and" mode builds one subquery per bucket and requires membership in each, which correctly allows different files to satisfy different buckets (per andMode_requiresFilesAcrossAllSelectedRanges).
  • "not" mode now negates the IN-subquery, so books with only non-book-format files (or no files) are included — this is consistent with the new test notMode_includesBooksWithOnlySupplementaryMatches.

One thing worth double-checking: "not" over books that have no book-format files at all (e.g. audiobook-only) will now include them in the result set. If that matches the product intent (as suggested by the tests), no action needed.

backend/src/test/java/org/booklore/app/specification/AppBookSpecificationTest.java (1)

98-260: Nice regression coverage.

Good spread across or, and, not, multi-bucket, and the supplementary-vs-book-format distinction, and andMode_noLongerBehavesLikeOrForMutuallyExclusiveRanges clearly documents the semantic change. As called out in the PR description, backfilling similar coverage for the other specification filters in a follow-up would be worthwhile.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App Book file size filter doesn't work + is only ever treated as an OR

1 participant