diff --git a/open-isle-cli/src/components/SearchDropdown.vue b/open-isle-cli/src/components/SearchDropdown.vue index 6be3e0dac..df88cfef7 100644 --- a/open-isle-cli/src/components/SearchDropdown.vue +++ b/open-isle-cli/src/components/SearchDropdown.vue @@ -74,7 +74,9 @@ export default { const iconMap = { user: 'fas fa-user', post: 'fas fa-file-alt', - comment: 'fas fa-comment' + comment: 'fas fa-comment', + category: 'fas fa-folder', + tag: 'fas fa-hashtag' } watch(selected, val => { @@ -89,6 +91,10 @@ export default { if (opt.postId) { router.push(`/posts/${opt.postId}#comment-${opt.id}`) } + } else if (opt.type === 'category') { + router.push({ path: '/', query: { category: opt.id } }) + } else if (opt.type === 'tag') { + router.push({ path: '/', query: { tags: opt.id } }) } selected.value = null keyword.value = '' diff --git a/src/main/java/com/openisle/repository/CategoryRepository.java b/src/main/java/com/openisle/repository/CategoryRepository.java index 443f08e5f..b813421d8 100644 --- a/src/main/java/com/openisle/repository/CategoryRepository.java +++ b/src/main/java/com/openisle/repository/CategoryRepository.java @@ -3,5 +3,8 @@ package com.openisle.repository; import com.openisle.model.Category; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface CategoryRepository extends JpaRepository { + List findByNameContainingIgnoreCase(String keyword); } diff --git a/src/main/java/com/openisle/service/SearchService.java b/src/main/java/com/openisle/service/SearchService.java index 271678bca..c3b9c72ca 100644 --- a/src/main/java/com/openisle/service/SearchService.java +++ b/src/main/java/com/openisle/service/SearchService.java @@ -4,9 +4,13 @@ import com.openisle.model.Post; import com.openisle.model.PostStatus; import com.openisle.model.Comment; import com.openisle.model.User; +import com.openisle.model.Category; +import com.openisle.model.Tag; import com.openisle.repository.PostRepository; import com.openisle.repository.CommentRepository; import com.openisle.repository.UserRepository; +import com.openisle.repository.CategoryRepository; +import com.openisle.repository.TagRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -21,6 +25,8 @@ public class SearchService { private final UserRepository userRepository; private final PostRepository postRepository; private final CommentRepository commentRepository; + private final CategoryRepository categoryRepository; + private final TagRepository tagRepository; @org.springframework.beans.factory.annotation.Value("${app.snippet-length:50}") private int snippetLength; @@ -48,6 +54,14 @@ public class SearchService { return commentRepository.findByContentContainingIgnoreCase(keyword); } + public List searchCategories(String keyword) { + return categoryRepository.findByNameContainingIgnoreCase(keyword); + } + + public List searchTags(String keyword) { + return tagRepository.findByNameContainingIgnoreCaseAndApprovedTrue(keyword); + } + public List globalSearch(String keyword) { Stream users = searchUsers(keyword).stream() .map(u -> new SearchResult( @@ -59,6 +73,26 @@ public class SearchService { null )); + Stream categories = searchCategories(keyword).stream() + .map(c -> new SearchResult( + "category", + c.getId(), + c.getName(), + null, + c.getDescription(), + null + )); + + Stream tags = searchTags(keyword).stream() + .map(t -> new SearchResult( + "tag", + t.getId(), + t.getName(), + null, + t.getDescription(), + null + )); + // Merge post results while removing duplicates between search by content // and search by title List mergedPosts = Stream.concat( @@ -101,7 +135,8 @@ public class SearchService { c.getPost().getId() )); - return Stream.concat(Stream.concat(users, mergedPosts.stream()), comments) + return Stream.of(users, categories, tags, mergedPosts.stream(), comments) + .flatMap(s -> s) .toList(); } diff --git a/src/test/java/com/openisle/integration/SearchIntegrationTest.java b/src/test/java/com/openisle/integration/SearchIntegrationTest.java index e06849bd3..f67e183ea 100644 --- a/src/test/java/com/openisle/integration/SearchIntegrationTest.java +++ b/src/test/java/com/openisle/integration/SearchIntegrationTest.java @@ -61,10 +61,10 @@ class SearchIntegrationTest { String admin = registerAndLoginAsAdmin("admin1", "a@a.com"); String user = registerAndLogin("bob_nice", "b@b.com"); - ResponseEntity catResp = postJson("/api/categories", Map.of("name", "misc", "description", "d", "icon", "i"), admin); + ResponseEntity catResp = postJson("/api/categories", Map.of("name", "nic-cat", "description", "d", "icon", "i"), admin); Long catId = ((Number)catResp.getBody().get("id")).longValue(); - ResponseEntity tagResp = postJson("/api/tags", Map.of("name", "misc", "description", "d", "icon", "i"), admin); + ResponseEntity tagResp = postJson("/api/tags", Map.of("name", "nic-tag", "description", "d", "icon", "i"), admin); Long tagId = ((Number)tagResp.getBody().get("id")).longValue(); ResponseEntity postResp = postJson("/api/posts", @@ -76,9 +76,11 @@ class SearchIntegrationTest { Map.of("content", "Nice article"), admin); List> results = rest.getForObject("/api/search/global?keyword=nic", List.class); - assertEquals(3, results.size()); + assertEquals(5, results.size()); assertTrue(results.stream().anyMatch(m -> "user".equals(m.get("type")))); assertTrue(results.stream().anyMatch(m -> "post".equals(m.get("type")))); assertTrue(results.stream().anyMatch(m -> "comment".equals(m.get("type")))); + assertTrue(results.stream().anyMatch(m -> "category".equals(m.get("type")))); + assertTrue(results.stream().anyMatch(m -> "tag".equals(m.get("type")))); } }