Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import stackpot.stackpot.feed.entity.enums.Category;
import stackpot.stackpot.feed.service.FeedQueryService;
import stackpot.stackpot.feed.service.FeedCommandService;
import stackpot.stackpot.user.dto.response.UserMyPageResponseDto;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

미사용 import 제거 권장

UserMyPageResponseDto를 본 컨트롤러에서 사용하지 않습니다. 코드 정리 및 정적 분석 경고 방지를 위해 import를 제거하세요.

-import stackpot.stackpot.user.dto.response.UserMyPageResponseDto;

🏁 Script executed:

#!/bin/bash
# 해당 타입이 파일 내에서 사용되는지 재확인
rg -n --no-heading 'UserMyPageResponseDto' src/main/java/stackpot/stackpot/feed/controller/FeedController.java

Length of output: 179


미사용 import 제거 필요
다음 컨트롤러에서 UserMyPageResponseDto가 전혀 참조되지 않습니다. 코드 정리와 정적 분석 경고 방지를 위해 해당 import를 제거하세요.

• 위치: src/main/java/stackpot/stackpot/feed/controller/FeedController.java (19번 라인)

-import stackpot.stackpot.user.dto.response.UserMyPageResponseDto;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import stackpot.stackpot.user.dto.response.UserMyPageResponseDto;
🤖 Prompt for AI Agents
In src/main/java/stackpot/stackpot/feed/controller/FeedController.java at line
19, the import statement for UserMyPageResponseDto is unused. Remove this import
line to clean up the code and prevent static analysis warnings.


import java.util.Map;

Expand Down Expand Up @@ -137,37 +138,7 @@ public ResponseEntity<ApiResponse<Map>> toggleLike(@PathVariable Long feedId) {
"message", isLiked ? "좋아요를 눌렀습니다." : "좋아요를 취소했습니다."
)));
}
@GetMapping("/{userId}")
@Operation(
summary = "사용자별 Feed 조회 API",
description = "사용자의 feed를 조회합니다."
)
public ResponseEntity<ApiResponse<FeedResponseDto.FeedPreviewList>> getFeedsByUserId(
@Parameter(description = "사용자 ID", example = "1")
@PathVariable("userId") Long userId,

@Parameter(description = "커서", example = "100", required = false)
@RequestParam(value = "cursor", required = false) Long cursor,

@Parameter(description = "페이지 크기", example = "10")
@RequestParam(value = "size", defaultValue = "10") int size
) {
FeedResponseDto.FeedPreviewList feedPreviewList = feedQueryService.getFeedsByUserId(userId, cursor, size);
return ResponseEntity.ok(ApiResponse.onSuccess(feedPreviewList));
}

@Operation(summary = "나의 Feed 조회 API")
@GetMapping("/my-feeds")
@ApiErrorCodeExamples({
ErrorStatus.USER_NOT_FOUND
})
public ResponseEntity<ApiResponse<FeedResponseDto.FeedPreviewList>> getFeeds(
@RequestParam(name = "cursor", required = false) Long cursor,
@RequestParam(name = "size", defaultValue = "10") int size) {

FeedResponseDto.FeedPreviewList feedPreviewList = feedQueryService.getFeeds(cursor, size);
return ResponseEntity.ok(ApiResponse.onSuccess(feedPreviewList));
}

@PostMapping("/series")
@Operation(summary = "시리즈 생성/삭제 동기화 API",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public FeedResponseDto.FeedDto feedDto(Feed feed, Boolean isOwner, Boolean isLik
return FeedResponseDto.FeedDto.builder()
.feedId(feed.getFeedId())
.writerId(feed.getUser().getId())
.writer(feed.getUser().getNickname() + " 씨앗")
.writer(feed.getUser().getNickname() + " 새싹")
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

닉네임 접미사(" 새싹") 하드코딩 반복 → 상수/헬퍼로 일원화하고, 캐시와의 출력 일관성도 맞춰주세요

  • 변경 자체(씨앗→새싹)는 적절합니다. 다만 동일한 문자열 결합이 여러 군데 반복되어 유지보수와 국제화에 취약합니다.
  • 캐시 경로 불일치: toFeedDtoFromCache()는 캐시의 writer 그대로 쓰고(toFeedCacheDto는 현재 접미사를 붙이지 않음), DB→DTO 경로는 접미사를 붙여 반환합니다. 동일 피드라도 캐시 여부에 따라 writer 표기가 달라질 수 있습니다.

권장:

  • 접미사 로직을 private 헬퍼로 일원화하고, 캐시에도 동일 규칙을 반영하거나 읽기 시점에만 일관되게 붙이도록 하세요.

아래와 같이 변경 라인에서는 헬퍼를 호출하도록 제안합니다.

-                .writer(feed.getUser().getNickname() + " 새싹")
+                .writer(displayName(feed.getUser()))
-                .writer(feed.getUser().getNickname()+ " 새싹")
+                .writer(displayName(feed.getUser()))
-                .creatorNickname(feed.getUser().getNickname()+" 새싹")
+                .creatorNickname(displayName(feed.getUser()))
-                .writer(feed.getUser().getNickname()+" 새싹")
+                .writer(displayName(feed.getUser()))

헬퍼 메서드 예시(클래스 내부에 추가):

private String displayName(User user) {
    String nickname = (user != null && user.getNickname() != null) ? user.getNickname() : "익명";
    return nickname + " 새싹";
}

캐시 일관성 방안 중 하나:

  • toFeedCacheDto()에서도 writer를 displayName(user)로 저장하거나,
  • 캐시 읽기 경로(toFeedDtoFromCache)에서 접미사를 붙이도록 통일.

추가 점검(선택):

  • 레포 전역에 남아있는 "씨앗" 문자열을 일괄 점검해 드릴 수 있습니다.

Also applies to: 64-64, 94-94, 113-113


🏁 Script executed:

#!/bin/bash
# "씨앗" 잔존 문자열/리소스 점검
rg -n --hidden -S '씨앗' -g '!**/node_modules/**' -g '!**/build/**' -g '!**/dist/**'

Length of output: 107


캐시 변환 로직 및 모든 닉네임 접미사 사용 위치를 한 번에 확인하기 위해 아래 스크립트를 실행해주세요.


🏁 Script executed:

#!/bin/bash
set -e

# 1. 캐시 저장 시 DTO 생성 메서드 위치 확인
rg -n "toFeedCacheDto" -g '*.java'

# 2. 캐시 조회 후 DTO 변환 시 메서드 위치 확인
rg -n "toFeedDtoFromCache" -g '*.java'

# 3. writer()에 직접 접미사 하드코딩된 모든 위치 확인
rg -n "writer(feed.getUser().getNickname" -g '*.java'

# 4. creatorNickname()에 직접 접미사 하드코딩된 모든 위치 확인
rg -n "creatorNickname(feed.getUser().getNickname" -g '*.java'

Length of output: 949


아래 스크립트로 새싹 접미사 사용 위치와 캐시 변환 메서드 동작을 한 번에 조회해주세요.


🏁 Script executed:

#!/bin/bash
set -e

# 1. FeedConverter.java: 캐시 저장/조회 메서드 주변 코드 확인
rg -n -B2 -A2 "toFeedCacheDto" src/main/java/stackpot/stackpot/feed/converter/FeedConverter.java
rg -n -B2 -A2 "toFeedDtoFromCache" src/main/java/stackpot/stackpot/feed/converter/FeedConverter.java

# 2. 전체 Java 파일에서 "새싹" 접미사 하드코딩 위치 확인
rg -n -B2 -A2 "새싹" -g '*.java'

Length of output: 9001


닉네임 접미사(" 새싹") 중복 호출 → 헬퍼로 일원화 & 캐시 처리 일관화 필요

FeedConverter 내 4곳뿐 아니라 PotConverter, PotDetailConverter, PotApplicationConverter, UserConverter, UserCommandServiceImpl 등에도 동일 패턴이 반복되고, 캐시 변환 경로(toFeedCacheDto/toFeedDtoFromCache)에서는 접미사 처리/미처리가 달라 일관성이 깨질 위험이 있습니다.

  • 모든 접미사 처리 위치에서 공통 헬퍼 메서드 사용
    • src/main/java/stackpot/stackpot/feed/converter/FeedConverter.java (36, 64, 94, 113)
    • src/main/java/stackpot/stackpot/pot/converter/PotDetailConverter.java (43)
    • src/main/java/stackpot/stackpot/pot/converter/PotConverter.java (75)
    • src/main/java/stackpot/stackpot/pot/converter/PotApplicationConverter.java (48)
    • src/main/java/stackpot/stackpot/user/converter/UserConverter.java (39, 64)
    • src/main/java/stackpot/stackpot/user/service/UserCommandServiceImpl.java (246)
  • 캐시 저장(toFeedCacheDto)에는 순수 닉네임(raw)만, 조회 변환(toFeedDtoFromCache)에는 헬퍼 호출로 접미사 붙이도록 통일하거나, 반대로 저장 시에도 헬퍼 적용
  • 헬퍼 예시
    public class DisplayNameUtil {
        public static String appendSeedSuffix(String nickname) {
            String name = (nickname != null && !nickname.isEmpty()) ? nickname : "익명";
            return name + " 새싹";
        }
        public static String displayName(User user) {
            return appendSeedSuffix(user != null ? user.getNickname() : null);
        }
    }
  • 적용 예: FeedConverter.java
    -                .writer(feed.getUser().getNickname() + " 새싹")
    +                .writer(DisplayNameUtil.displayName(feed.getUser()))
  • 캐시→DTO 변환(toFeedDtoFromCache)에도 동일 헬퍼 사용
    -                .writer(feed.getWriter())
    +                .writer(DisplayNameUtil.appendSeedSuffix(feed.getWriter()))
🤖 Prompt for AI Agents
In src/main/java/stackpot/stackpot/feed/converter/FeedConverter.java around line
36 and other specified lines, the nickname suffix " 새싹" is appended directly in
multiple places causing duplication and inconsistency with cache conversion.
Refactor by creating a common helper method (e.g., in a DisplayNameUtil class)
that appends the suffix consistently. Replace all direct suffix concatenations
in FeedConverter and related converters/services with calls to this helper.
Ensure cache storage uses raw nicknames and suffix is appended only during DTO
conversion, or apply the helper uniformly during both caching and conversion for
consistency.

.writerRole(feed.getUser().getRole())
.title(feed.getTitle())
.content(feed.getContent())
Expand Down Expand Up @@ -61,7 +61,7 @@ public FeedResponseDto.CreatedFeedDto createFeedDto(Feed feed) {
.title(feed.getTitle())
.content(feed.getContent())
.writerId(feed.getUser().getId())
.writer(feed.getUser().getNickname()+ " 씨앗")
.writer(feed.getUser().getNickname()+ " 새싹")
.writerRole(feed.getUser().getRole())
.categories(feed.getCategories().stream()
.map(Enum::name)
Expand Down Expand Up @@ -91,7 +91,7 @@ public FeedSearchResponseDto toSearchDto(Feed feed) {
.feedId(feed.getFeedId())
.title(feed.getTitle())
.content(feed.getContent())
.creatorNickname(feed.getUser().getNickname()+" 씨앗")
.creatorNickname(feed.getUser().getNickname()+" 새싹")
.creatorRole(mapRoleName(String.valueOf(feed.getUser().getRole())))
.createdAt(DateFormatter.koreanFormatter(feed.getCreatedAt()))
.likeCount(feed.getLikeCount()) // 좋아요 개수 포함
Expand All @@ -110,7 +110,7 @@ public FeedResponseDto.AuthorizedFeedDto toAuthorizedFeedDto(Feed feed, boolean
FeedResponseDto.CreatedFeedDto createdDto = FeedResponseDto.CreatedFeedDto.builder()
.feedId(feed.getFeedId())
.writerId(feed.getUser().getId())
.writer(feed.getUser().getNickname()+" 씨앗")
.writer(feed.getUser().getNickname()+" 새싹")
.writerRole(feed.getUser().getRole())
.title(feed.getTitle())
.content(feed.getContent())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

import stackpot.stackpot.feed.entity.Feed;
import stackpot.stackpot.feed.dto.FeedResponseDto;
import stackpot.stackpot.user.dto.response.UserMyPageResponseDto;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

반환 타입 변경(UserMyPageResponseDto) OK. 호출부 영향 점검 + 코드 스타일(들여쓰기) 정리 권장

  • getFeedsByUserId/getFeeds의 반환 타입 변경은 현재 리팩터링 방향과 일치합니다. 다만 기존에 FeedPreviewList를 기대하던 호출부가 있다면 컴파일 타임에 깨질 수 있으니 점검이 필요합니다.
  • 스타일: Line 14 들여쓰기가 깨져 있습니다. 정렬해 주세요.

들여쓰기 보정(diff):

-UserMyPageResponseDto getFeeds(Long nextCursor, int pageSize);
+     UserMyPageResponseDto getFeeds(Long nextCursor, int pageSize);

또한, 신규 DTO로의 변경 과정에서 nextCursor가 응답에 포함되지 않는 구조라면, 프런트의 커서 기반 페이지네이션 요구사항과 괴리가 없는지 확인이 필요합니다.

Also applies to: 12-12, 14-14


🏁 Script executed:

#!/bin/bash
# 1) 이전 타입(FeedPreviewList) 기대 호출부 탐색
rg -n --no-heading -S 'getFeedsByUserId\\(|getFeeds\\(' -A 2 -B 2

# 2) nextCursor를 여전히 사용하는지 컨트롤러/서비스 레이어에서 확인
rg -n --no-heading -S 'nextCursor' -A 2 -B 2

Length of output: 24877


FeedQueryService 변경사항: 호출부 영향 없음 + 들여쓰기 정리 + 페이징 커서 확인

  • getFeedsByUserId/getFeeds 인터페이스 선언부를 참조하는 호출부가 없어 컴파일 문제는 없습니다.
  • 인터페이스 선언부(Line 12, 14)의 메서드 들여쓰기를 다른 메서드 선언과 동일하게 4칸 들여쓰기 해주세요.
  • UserMyPageResponseDto에 nextCursor 필드가 정의되어 프런트의 커서 기반 페이지네이션 요구사항을 충족하는지 확인이 필요합니다.

들여쓰기 보정(diff):

-UserMyPageResponseDto getFeedsByUserId(Long userId, Long nextCursor, int pageSize);
-UserMyPageResponseDto getFeeds(Long nextCursor, int pageSize);
+    UserMyPageResponseDto getFeedsByUserId(Long userId, Long nextCursor, int pageSize);
+    UserMyPageResponseDto getFeeds(Long nextCursor, int pageSize);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import stackpot.stackpot.user.dto.response.UserMyPageResponseDto;
public interface FeedQueryService {
// … other imports and methods …
UserMyPageResponseDto getFeedsByUserId(Long userId, Long nextCursor, int pageSize);
UserMyPageResponseDto getFeeds(Long nextCursor, int pageSize);
// … remaining methods …
}
🤖 Prompt for AI Agents
In src/main/java/stackpot/stackpot/feed/service/FeedQueryService.java at line 5,
adjust the indentation of the method declarations getFeedsByUserId and getFeeds
at lines 12 and 14 to use 4 spaces, matching the indentation style of other
methods in the interface. Also, verify that the UserMyPageResponseDto class
includes a nextCursor field to support cursor-based pagination as required by
the frontend.


import java.util.Map;

public interface FeedQueryService {
FeedResponseDto.FeedPreviewList getPreViewFeeds(String category, String sort, Long cursor, int limit);
FeedResponseDto.AuthorizedFeedDto getFeed(Long feedId);
FeedResponseDto.FeedPreviewList getFeedsByUserId(Long userId, Long nextCursor, int pageSize);
UserMyPageResponseDto getFeedsByUserId(Long userId, Long nextCursor, int pageSize);
// FeedResponseDto.FeedPreviewList searchByUserIdByKeyword(Long userId, Long nextCursor, int pageSize);
FeedResponseDto.FeedPreviewList getFeeds(Long nextCursor, int pageSize);
UserMyPageResponseDto getFeeds(Long nextCursor, int pageSize);
Map<Long, String> getMySeries();
Long getLikeCount(Long feedId);
Feed getFeedByFeedId(Long feedId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import stackpot.stackpot.feed.repository.SeriesRepository;
import stackpot.stackpot.notification.service.NotificationCommandService;
import stackpot.stackpot.save.repository.FeedSaveRepository;
import stackpot.stackpot.user.dto.response.UserMyPageResponseDto;
import stackpot.stackpot.user.entity.User;
import stackpot.stackpot.user.repository.UserRepository;

Expand Down Expand Up @@ -179,7 +180,7 @@ public FeedResponseDto.AuthorizedFeedDto getFeed(Long feedId) {
}

@Transactional
public FeedResponseDto.FeedPreviewList getFeedsByUserId(Long userId, Long nextCursor, int pageSize) {
public UserMyPageResponseDto getFeedsByUserId(Long userId, Long nextCursor, int pageSize) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean isAuthenticated = authentication != null
&& !(authentication instanceof AnonymousAuthenticationToken)
Expand All @@ -201,13 +202,9 @@ public FeedResponseDto.FeedPreviewList getFeedsByUserId(Long userId, Long nextCu
: List.of();

Pageable pageable = PageRequest.of(0, pageSize, Sort.by(Sort.Direction.DESC, "feedId"));
List<Feed> feeds;

if (nextCursor == null) {
feeds = feedRepository.findByUser_Id(userId, pageable);
} else {
feeds = feedRepository.findByUserIdAndFeedIdBefore(userId, nextCursor, pageable);
}
List<Feed> feeds = (nextCursor == null)
? feedRepository.findByUser_Id(userId, pageable)
: feedRepository.findByUserIdAndFeedIdBefore(userId, nextCursor, pageable);

List<FeedResponseDto.FeedDto> feedDtos = feeds.stream()
.map(feed -> {
Expand All @@ -220,18 +217,23 @@ public FeedResponseDto.FeedPreviewList getFeedsByUserId(Long userId, Long nextCu
})
.collect(Collectors.toList());

Long nextCursorResult = (!feeds.isEmpty() && feeds.size() >= pageSize)
? feeds.get(feeds.size() - 1).getFeedId()
: null;
// 조회 대상 유저 정보
User targetUser = userRepository.findById(userId)
.orElseThrow(() -> new UserHandler(ErrorStatus.USER_NOT_FOUND));

return FeedResponseDto.FeedPreviewList.builder()
List<String> seriesComments = targetUser.getSeriesList().stream()
.map(Series::getComment)
.collect(Collectors.toList());

return UserMyPageResponseDto.builder()
.id(targetUser.getId())
.seriesComments(seriesComments)
.feeds(feedDtos)
.nextCursor(nextCursorResult)
.build();
}

@Override
public FeedResponseDto.FeedPreviewList getFeeds(Long nextCursor, int pageSize) {
public UserMyPageResponseDto getFeeds(Long nextCursor, int pageSize) {
User user = authService.getCurrentUser();

Pageable pageable = PageRequest.of(0, pageSize, Sort.by(Sort.Direction.DESC, "feedId"));
Expand All @@ -253,13 +255,14 @@ public FeedResponseDto.FeedPreviewList getFeeds(Long nextCursor, int pageSize) {
})
.collect(Collectors.toList());

Long nextCursorResult = (!feeds.isEmpty() && feeds.size() >= pageSize)
? feeds.get(feeds.size() - 1).getFeedId()
: null;
List<String> seriesComments = user.getSeriesList().stream()
.map(Series::getComment)
.collect(Collectors.toList());

return FeedResponseDto.FeedPreviewList.builder()
return UserMyPageResponseDto.builder()
.id(user.getId())
.seriesComments(seriesComments)
.feeds(feedDtos)
.nextCursor(nextCursorResult)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public PotApplicationResponseDto toDto(PotApplication entity) {
.applicationId(entity.getApplicationId())
.potRole(roleInfo)
.userId(entity.getUser().getId())
.userNickname(entity.getUser().getNickname() + " 씨앗")
.userNickname(entity.getUser().getNickname() + " 새싹")
.build();

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public PotPreviewResponseDto toPrviewDto(User user, Pot pot, List<String> recrui
return PotPreviewResponseDto.builder()
.userId(user.getId())
.userRole(user.getRole().name())
.userNickname(user.getNickname() + " 씨앗")
.userNickname(user.getNickname() + " 새싹")
.potId(pot.getPotId())
.potName(pot.getPotName())
.potContent(pot.getPotContent())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public PotDetailResponseDto toPotDetailResponseDto(User user, Pot pot, String re
return PotDetailResponseDto.builder()
.userId(user.getId())
.userRole(user.getRole().name())
.userNickname(user.getNickname() + " 씨앗")
.userNickname(user.getNickname() + " 새싹")
.isOwner(isOwner)
.potId(pot.getPotId())
.potName(pot.getPotName())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
import stackpot.stackpot.feed.entity.mapping.FeedSave;
import stackpot.stackpot.user.entity.User;

import java.util.List;
import java.util.Optional;
import java.util.*;

@Repository
public interface FeedSaveRepository extends JpaRepository<FeedSave, Long> {
Expand All @@ -30,6 +29,23 @@ public interface FeedSaveRepository extends JpaRepository<FeedSave, Long> {

@Query("SELECT COUNT(fs) FROM FeedSave fs WHERE fs.feed.feedId = :feedId")
long countByFeedId(@Param("feedId") Long feedId);
@Query("""
SELECT fs.feed.feedId, COUNT(fs)
FROM FeedSave fs
WHERE fs.feed.feedId IN :feedIds
GROUP BY fs.feed.feedId
""")
List<Object[]> countByFeedIdsRaw(@Param("feedIds") List<Long> feedIds);

default Map<Long, Long> countByFeedIds(List<Long> feedIds) {
if (feedIds == null || feedIds.isEmpty()) return Collections.emptyMap();
Map<Long, Long> map = new HashMap<>();
for (Object[] row : countByFeedIdsRaw(feedIds)) {
map.put((Long) row[0], (Long) row[1]);
}
return map;
}


}

Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,22 @@ public Page<PotPreviewResponseDto> searchPots(String keyword, Pageable pageable)
.map(Pot::getPotId)
.collect(Collectors.toList());

User user = null;
Long userId = null;
try {
user = authService.getCurrentUser();
User user = authService.getCurrentUser();
userId = user.getId();
} catch (Exception e) {
// 비로그인
}
} catch (Exception ignored) { /* 비로그인 */ }

// 배치 집계(이미 배치 쿼리 사용 중이라 OK)
Map<Long, Integer> potSaveCountMap = potSaveRepository.countSavesByPotIds(potIds);

// List -> Set (contains O(1))
Set<Long> savedPotIds = (userId != null)
? potSaveRepository.findPotIdsByUserIdAndPotIds(userId, potIds)
? new HashSet<>(potSaveRepository.findPotIdsByUserIdAndPotIds(userId, potIds))
: Collections.emptySet();

// 참여 여부 isMember 확인
Set<Long> memberPotIds = (userId != null)
? potMemberRepository.findPotIdsByUserIdAndPotIds(userId, potIds)
? new HashSet<>(potMemberRepository.findPotIdsByUserIdAndPotIds(userId, potIds))
: Collections.emptySet();

return pots.map(pot -> {
Expand All @@ -86,47 +85,50 @@ public Page<PotPreviewResponseDto> searchPots(String keyword, Pageable pageable)
});
}


@Override
@Transactional(readOnly = true)
public Page<FeedResponseDto.FeedDto> searchFeeds(String keyword, Pageable pageable) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean isAuthenticated = authentication != null &&
!(authentication instanceof AnonymousAuthenticationToken) &&
authentication.isAuthenticated();
var authentication = SecurityContextHolder.getContext().getAuthentication();
boolean isAuthenticated = authentication != null
&& !(authentication instanceof AnonymousAuthenticationToken)
&& authentication.isAuthenticated();

User user = isAuthenticated ? authService.getCurrentUser() : null;
Long userId = (user != null) ? user.getId() : null;

User user;
Long userId = null;
if (isAuthenticated) {
user = authService.getCurrentUser();
userId = user.getId();
} else {
user = null;
}

// 키워드 기반 검색
Page<Feed> feeds = feedRepository.findByTitleContainingOrContentContainingOrderByCreatedAtDesc(
keyword, keyword, pageable
);

// 좋아요/저장 정보 조회
List<Long> likedFeedIds = (userId != null)
? feedLikeRepository.findFeedIdsByUserId(userId)
: Collections.emptyList();
// === N+1 제거: 한 번에 집계/조회 ===
List<Long> feedIds = feeds.getContent().stream()
.map(Feed::getFeedId)
.collect(Collectors.toList());

List<Long> savedFeedIds = (userId != null)
? feedSaveRepository.findFeedIdsByUserId(userId)
: Collections.emptyList();
// 저장 수 배치 집계
Map<Long, Long> saveCountMap = feedSaveRepository.countByFeedIds(feedIds);

// List -> Set (contains O(1))
Set<Long> likedFeedIds = (userId != null)
? new HashSet<>(feedLikeRepository.findFeedIdsByUserId(userId))
: Collections.emptySet();

Set<Long> savedFeedIds = (userId != null)
? new HashSet<>(feedSaveRepository.findFeedIdsByUserId(userId))
: Collections.emptySet();
Comment on lines +114 to +120
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

불필요한 대용량 조회

findFeedIdsByUserId(userId)는 사용자가 좋아요/저장한 모든 피드 ID를 한꺼번에 가져옵니다.
검색 결과(feedIds) 집합으로 범위를 제한하면 메모리·네트워크 비용을 크게 줄일 수 있습니다.

제안: findFeedIdsByUserIdAndFeedIds(userId, feedIds) 형태의 배치 쿼리 추가 후 사용.

🤖 Prompt for AI Agents
In src/main/java/stackpot/stackpot/search/service/SearchServiceImpl.java around
lines 114 to 120, the current code fetches all liked and saved feed IDs for a
user without filtering by the relevant feedIds, causing unnecessary large data
retrieval. To fix this, add new repository methods like
findFeedIdsByUserIdAndFeedIds(userId, feedIds) that accept the userId and the
subset of feedIds to limit the query scope. Then replace the current calls with
these new batch queries passing the filtered feedIds to reduce memory and
network usage.


// 각 Feed를 FeedDto로 변환
return feeds.map(feed -> {
boolean isOwner = user != null && Objects.equals(user.getId(), feed.getUser().getId());
Boolean isLiked = likedFeedIds.contains(feed.getFeedId());
Boolean isSaved = savedFeedIds.contains(feed.getFeedId());
int saveCount = feedSaveRepository.countByFeed(feed);
Boolean isLiked = (userId != null) ? likedFeedIds.contains(feed.getFeedId()) : null;
Boolean isSaved = (userId != null) ? savedFeedIds.contains(feed.getFeedId()) : null;
int saveCount = saveCountMap.getOrDefault(feed.getFeedId(), 0L).intValue();

return feedConverter.feedDto(feed, isOwner, isLiked, isSaved, saveCount);
});
}


}

Loading