From 6f478ab97a8e319b416b6b70ae01bdc24a01155c Mon Sep 17 00:00:00 2001 From: Stefan Hardegger Date: Fri, 25 Jul 2025 07:49:07 +0200 Subject: [PATCH] Fix Tag Filtering --- .../storycove/controller/StoryController.java | 2 ++ .../storycove/service/TypesenseService.java | 23 +++++++++++++++++-- frontend/src/components/stories/TagFilter.tsx | 20 ++++++++-------- frontend/src/lib/api.ts | 22 +++++++++++++++++- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/com/storycove/controller/StoryController.java b/backend/src/main/java/com/storycove/controller/StoryController.java index fe00ed9..0193b04 100644 --- a/backend/src/main/java/com/storycove/controller/StoryController.java +++ b/backend/src/main/java/com/storycove/controller/StoryController.java @@ -206,6 +206,8 @@ public class StoryController { @RequestParam(required = false) String sortBy, @RequestParam(required = false) String sortDir) { + logger.info("CONTROLLER DEBUG: Search request - query='{}', tags={}, authors={}", query, tags, authors); + if (typesenseService != null) { SearchResultDto results = typesenseService.searchStories( query, page, size, authors, tags, minRating, maxRating, sortBy, sortDir); diff --git a/backend/src/main/java/com/storycove/service/TypesenseService.java b/backend/src/main/java/com/storycove/service/TypesenseService.java index 09129d7..2c4079c 100644 --- a/backend/src/main/java/com/storycove/service/TypesenseService.java +++ b/backend/src/main/java/com/storycove/service/TypesenseService.java @@ -177,6 +177,9 @@ public class TypesenseService { try { long startTime = System.currentTimeMillis(); + logger.info("SEARCH DEBUG: searchStories called with query='{}', tagFilters={}, authorFilters={}", + query, tagFilters, authorFilters); + // Convert 0-based page (frontend/backend) to 1-based page (Typesense) int typesensePage = page + 1; @@ -207,9 +210,16 @@ public class TypesenseService { } if (tagFilters != null && !tagFilters.isEmpty()) { + logger.info("SEARCH DEBUG: Processing {} tag filters: {}", tagFilters.size(), tagFilters); String tagFilter = tagFilters.stream() - .map(tag -> "tagNames:=" + escapeTypesenseValue(tag)) + .map(tag -> { + String escaped = escapeTypesenseValue(tag); + String condition = "tagNames:=" + escaped; + logger.info("SEARCH DEBUG: Tag '{}' -> escaped '{}' -> condition '{}'", tag, escaped, condition); + return condition; + }) .collect(Collectors.joining(" || ")); + logger.info("SEARCH DEBUG: Final tag filter condition: '{}'", tagFilter); filterConditions.add("(" + tagFilter + ")"); } @@ -222,13 +232,19 @@ public class TypesenseService { } if (!filterConditions.isEmpty()) { - searchParameters.filterBy(String.join(" && ", filterConditions)); + String finalFilter = String.join(" && ", filterConditions); + logger.info("SEARCH DEBUG: Final filter condition: '{}'", finalFilter); + searchParameters.filterBy(finalFilter); + } else { + logger.info("SEARCH DEBUG: No filter conditions applied"); } SearchResult searchResult = typesenseClient.collections(STORIES_COLLECTION) .documents() .search(searchParameters); + logger.info("SEARCH DEBUG: Typesense returned {} results", searchResult.getFound()); + List results = convertSearchResult(searchResult); long searchTime = System.currentTimeMillis() - startTime; @@ -338,7 +354,10 @@ public class TypesenseService { List tagNames = story.getTags().stream() .map(tag -> tag.getName()) .collect(Collectors.toList()); + logger.debug("INDEXING DEBUG: Story '{}' has tags: {}", story.getTitle(), tagNames); document.put("tagNames", tagNames); + } else { + logger.debug("INDEXING DEBUG: Story '{}' has no tags", story.getTitle()); } document.put("rating", story.getRating() != null ? story.getRating() : 0); diff --git a/frontend/src/components/stories/TagFilter.tsx b/frontend/src/components/stories/TagFilter.tsx index fb4ff98..cec4cc9 100644 --- a/frontend/src/components/stories/TagFilter.tsx +++ b/frontend/src/components/stories/TagFilter.tsx @@ -11,15 +11,17 @@ interface TagFilterProps { export default function TagFilter({ tags, selectedTags, onTagToggle }: TagFilterProps) { if (!Array.isArray(tags) || tags.length === 0) return null; - // Sort tags by usage count (descending) and then alphabetically - const sortedTags = [...tags].sort((a, b) => { - const aCount = a.storyCount || 0; - const bCount = b.storyCount || 0; - if (bCount !== aCount) { - return bCount - aCount; - } - return a.name.localeCompare(b.name); - }); + // Filter out tags with no stories, then sort by usage count (descending) and then alphabetically + const sortedTags = [...tags] + .filter(tag => (tag.storyCount || 0) > 0) + .sort((a, b) => { + const aCount = a.storyCount || 0; + const bCount = b.storyCount || 0; + if (bCount !== aCount) { + return bCount - aCount; + } + return a.name.localeCompare(b.name); + }); return (
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index cc668cf..d386d10 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -268,7 +268,27 @@ export const searchApi = { sortBy?: string; sortDir?: string; }): Promise => { - const response = await api.get('/stories/search', { params }); + // Create URLSearchParams to properly handle array parameters + const searchParams = new URLSearchParams(); + + // Add basic parameters + searchParams.append('query', params.query); + if (params.page !== undefined) searchParams.append('page', params.page.toString()); + if (params.size !== undefined) searchParams.append('size', params.size.toString()); + if (params.minRating !== undefined) searchParams.append('minRating', params.minRating.toString()); + if (params.maxRating !== undefined) searchParams.append('maxRating', params.maxRating.toString()); + if (params.sortBy) searchParams.append('sortBy', params.sortBy); + if (params.sortDir) searchParams.append('sortDir', params.sortDir); + + // Add array parameters - each element gets its own parameter + if (params.authors && params.authors.length > 0) { + params.authors.forEach(author => searchParams.append('authors', author)); + } + if (params.tags && params.tags.length > 0) { + params.tags.forEach(tag => searchParams.append('tags', tag)); + } + + const response = await api.get(`/stories/search?${searchParams.toString()}`); return response.data; }, };