diff --git a/src/main/java/com/devpick/domain/content/repository/ContentRepository.java b/src/main/java/com/devpick/domain/content/repository/ContentRepository.java index 77375f3..96b4e50 100644 --- a/src/main/java/com/devpick/domain/content/repository/ContentRepository.java +++ b/src/main/java/com/devpick/domain/content/repository/ContentRepository.java @@ -21,6 +21,17 @@ public interface ContentRepository extends JpaRepository { @Query("SELECT DISTINCT c FROM Content c JOIN c.contentTags ct WHERE ct.tag.id IN :tagIds AND c.isAvailable = true AND c.source.name <> 'YouTube' ORDER BY c.publishedAt DESC") Page findByTagIdsAndIsAvailableTrue(@Param("tagIds") List tagIds, Pageable pageable); + @Query(""" + SELECT c FROM Content c + WHERE c.isAvailable = true AND c.source.name <> 'YouTube' + ORDER BY + CASE WHEN EXISTS ( + SELECT ct FROM ContentTag ct WHERE ct.content = c AND ct.tag.id IN :tagIds + ) THEN 0 ELSE 1 END, + c.publishedAt DESC + """) + Page findAllRankedByTagIds(@Param("tagIds") List tagIds, Pageable pageable); + @Query("SELECT DISTINCT c FROM Content c LEFT JOIN c.contentTags ct LEFT JOIN ct.tag t " + "WHERE c.isAvailable = true " + "AND c.source.name <> 'YouTube' " + diff --git a/src/main/java/com/devpick/domain/content/service/ContentService.java b/src/main/java/com/devpick/domain/content/service/ContentService.java index c0793f7..9c927bc 100644 --- a/src/main/java/com/devpick/domain/content/service/ContentService.java +++ b/src/main/java/com/devpick/domain/content/service/ContentService.java @@ -72,10 +72,7 @@ public ContentListResponse getFeed(UUID userId, Pageable pageable) { if (tagIds.isEmpty()) { page = contentRepository.findByIsAvailableTrueOrderByPublishedAtDesc(pageable); } else { - page = contentRepository.findByTagIdsAndIsAvailableTrue(tagIds, pageable); - if (page.getTotalElements() == 0) { - page = contentRepository.findByIsAvailableTrueOrderByPublishedAtDesc(pageable); - } + page = contentRepository.findAllRankedByTagIds(tagIds, pageable); } List contents = page.getContent().stream() diff --git a/src/test/java/com/devpick/domain/content/service/ContentServiceTest.java b/src/test/java/com/devpick/domain/content/service/ContentServiceTest.java index 10c4e9f..441be2c 100644 --- a/src/test/java/com/devpick/domain/content/service/ContentServiceTest.java +++ b/src/test/java/com/devpick/domain/content/service/ContentServiceTest.java @@ -135,14 +135,14 @@ void getFeed_anonymous_skipsUserScopedQueries() { } @Test - @DisplayName("getFeed — 태그 있으면 태그 필터링된 콘텐츠 반환") - void getFeed_withUserTags_returnsFilteredFeed() { + @DisplayName("getFeed — 태그 있으면 랭킹 방식으로 전체 콘텐츠 반환 (관심 태그 우선)") + void getFeed_withUserTags_returnsRankedFeed() { UserTag userTag = UserTag.builder() .user(user) .tag(Tag.builder().name("Spring").build()) .build(); given(userTagRepository.findByUser_Id(userId)).willReturn(List.of(userTag)); - given(contentRepository.findByTagIdsAndIsAvailableTrue(any(), any())) + given(contentRepository.findAllRankedByTagIds(any(), any())) .willReturn(new PageImpl<>(List.of(content))); given(scrapRepository.existsByUser_IdAndContent_Id(any(), any())).willReturn(false); given(likeRepository.existsByUser_IdAndContent_Id(any(), any())).willReturn(false); @@ -151,21 +151,19 @@ void getFeed_withUserTags_returnsFilteredFeed() { ContentListResponse response = contentService.getFeed(userId, PageRequest.of(0, 20)); assertThat(response.contents()).hasSize(1); - verify(contentRepository).findByTagIdsAndIsAvailableTrue(any(), any()); + verify(contentRepository).findAllRankedByTagIds(any(), any()); verify(contentRepository, never()).findByIsAvailableTrueOrderByPublishedAtDesc(any()); } @Test - @DisplayName("getFeed — 태그 필터 결과가 비면 전체 공개 글로 폴백") - void getFeed_tagFilterEmpty_fallsBackToAllAvailable() { + @DisplayName("getFeed — 관심 태그와 일치하는 글 없어도 전체 글 반환 (랭킹 방식)") + void getFeed_withUserTags_noTagMatch_stillReturnsAllContents() { UserTag userTag = UserTag.builder() .user(user) .tag(Tag.builder().name("Rust").build()) .build(); given(userTagRepository.findByUser_Id(userId)).willReturn(List.of(userTag)); - given(contentRepository.findByTagIdsAndIsAvailableTrue(any(), any())) - .willReturn(new PageImpl<>(List.of())); - given(contentRepository.findByIsAvailableTrueOrderByPublishedAtDesc(any())) + given(contentRepository.findAllRankedByTagIds(any(), any())) .willReturn(new PageImpl<>(List.of(content))); given(scrapRepository.existsByUser_IdAndContent_Id(any(), any())).willReturn(false); given(likeRepository.existsByUser_IdAndContent_Id(any(), any())).willReturn(false); @@ -174,8 +172,8 @@ void getFeed_tagFilterEmpty_fallsBackToAllAvailable() { ContentListResponse response = contentService.getFeed(userId, PageRequest.of(0, 20)); assertThat(response.contents()).hasSize(1); - verify(contentRepository).findByTagIdsAndIsAvailableTrue(any(), any()); - verify(contentRepository).findByIsAvailableTrueOrderByPublishedAtDesc(any()); + verify(contentRepository).findAllRankedByTagIds(any(), any()); + verify(contentRepository, never()).findByIsAvailableTrueOrderByPublishedAtDesc(any()); } @Test