feat: structured boolean search (Phase 5) #9
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/structured-search"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 5: Structured Boolean Search
Implements
POST /api/doujins/searchwith a flat, AND-combined filter model per ADR-0008.Query shape
All filters AND-combined. Languages within
anyLanguageCodeare OR-combined. Include tags are AND (must have all). Exclude tags are NOT-ANY (must not have any).Supported filters
textanyLanguageCodeincludeTagsexcludeTagspersonIds+personRolecircleIdsminRating/maxRatingunratedvariantKindpage/pageSizeResults include
matchedVariants(only variants matching language/kind filters) andmatchedTags.Architecture
Server-side
IQueryable<Doujin>with progressive filters — no client-side evaluation.Tests (47 new, 220 total)
Comprehensive integration tests with 4 seeded doujins covering every filter individually and in combination:
Summary
Summary
Coverage
DoujinManager.ApplicationCore - 84.8%
DoujinManager.Infrastructure - 91.8%
pshot
DoujinManager.RestAdapter - 83.3%
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
DoujinManager.Server - 18.5%
🤖 Hermes automated review: minor comments
Reviewed the full diff (1065 additions, 9 files) for the structured boolean search feature. This is a well-structured, high-quality implementation — clean architecture (DTO → UseCase → Service → server-side
IQueryable), no client-side evaluation, comprehensive test coverage (47 new tests, 220 total). No blocking issues found. A few minor non-blocking notes below.Minor (non-blocking) observations
1. Text search does not escape LIKE wildcards in user input —
backend/src/DoujinManager.Infrastructure/Services/SearchService.cs:~52The
%and_characters in a user's search text act as SQL wildcards, so searching for e.g.100%ora_bwould match more than intended. This is not a SQL injection issue —EF.Functions.Likeis parameterized — just a correctness/UX edge case. Suggested fix:Low severity; titles/descriptions rarely contain these chars.
2.
matchedTagsreturns the doujin's full tag set, not only the matched tags —backend/src/DoujinManager.RestAdapter/Endpoints/SearchEndpoints.cs:~95The field is named
matchedTags, but this projects all of the doujin's tags regardless of theincludeTagsfilter. The testSearch_MatchedTags_Populatedconfirms A returns bothactionandromancewhen filtered only byaction. If the intent is to return all tags, consider renaming totagsfor clarity; if the intent is only the matched ones, intersect againstincludeTags.3.
unrated: falseis an intentional no-op — confirmed bySearch_By_Unrated_False_Does_Not_Filterand theif (query.Unrated is { } unrated && unrated)guard. This tri-state design (null/true/false) is fine and tested; just noting it's worth a one-line note in theSearchDoujinsDtoXML doc so callers knowfalse≡ unset.What looks good
Where(with the loop-variable capture handled correctly).OrderByDescending(UpdatedAt).ThenBy(Id)gives deterministic pagination with a tiebreaker. ✅Math.Max(1, page),Math.Clamp(pageSize, 1, 100)) andtotalPagesmath are correct, including thetotalItems == 0 → 0 pagesedge.Guid.Emptyfiltering) done once before query construction.d.Rating != null && ...) — tested viaSearch_By_MinRating_Excludes_Unrated.Program.cs(MapSearchEndpoints) andUseCaseRegistrationHelperfor both service and use case.Verification
db4c1f33generated successfully (line 83.9%,SearchService100%/100%,SearchEndpoints100%/50%). This implies build + 220 tests passed. Local build/test skipped per CI-evidence policy.SearchEndpointsreflects the enum-parse null/empty branches — the error paths themselves are covered by theUnknown_*_Returns_400tests.Automated daily review. I never merge PRs.
Addressed all 3 minor findings in commit
fd39ae5. 220 tests pass.LIKE wildcard escaping —
SearchServicenow escapes%,_, and\in user search text and passes\as the escape char toEF.Functions.Like. Searching for100%ora_bnow matches literally.matchedTags→tags— Renamed totagsinSearchResultDtoand JSON output, since it returns the doujin's full tag set. Updated endpoint and test accordingly.unrateddoc — Added XML doc: "If true, restricts to doujins with no rating (Rating is null). Null or false = no filter."🤖 Hermes automated review: no blocking issues found
Follow-up review of the fix commit
fd39ae5("escape LIKE wildcards, rename matchedTags→tags, document unrated"), which addresses all three minor findings from the prior review. The changes are correct, minimal, and well-scoped. No blocking issues.Findings from prior review — all resolved ✅
1. LIKE wildcard escaping —
backend/src/DoujinManager.Infrastructure/Services/SearchService.cs:55-62Now escapes
\, then%, then_before building the pattern, and passes\as the escape char toEF.Functions.Like(..., @"\"). Order is correct (backslash first, so escaping itself doesn't re-introduce wildcards). Searching for100%ora_bnow matches literally. ✅2.
matchedTags→tags—backend/src/DoujinManager.RestAdapter/Dtos/SearchDtos.cs:66-67,SearchEndpoints.cs:100-104Renamed the DTO field and JSON property (
matchedTags→tags) and the local variable. Since the field returns the doujin's full tag set,tagsis accurate. TestSearch_MatchedTags_Populatedrenamed toSearch_Tags_Populatedand assertions updated toa.Tags. ✅3.
unrateddoc —SearchDtos.cs:43Added XML doc: "If true, restricts to doujins with no rating (Rating is null). Null or false = no filter." Clearly documents the tri-state behavior. ✅
Verification
db4c1f33(line 83.9%,SearchService100%/100%), but the head has since moved tofd39ae57. The coverage comment was last updated at 21:27 but its "Coverage date" field still reads 19:26:46 — i.e., it predates the fix commit. Treating CI as stale for the current head; ran tests locally per policy.dotnet test→ Passed: 92, Failed: 0 (net10.0, 33s). Includes the fullSearchIntegrationTestssuite (47 search tests) with the renamedSearch_Tags_Populatedtest. Only pre-existing nullable-warning diagnostics, no errors.Automated daily review. I never merge PRs.