From 0886520640588dc07dc0668d084eb2c4d91b37d7 Mon Sep 17 00:00:00 2001 From: "jaeho4125@gmail.com" Date: Tue, 17 Feb 2026 01:43:20 +0900 Subject: [PATCH 01/10] =?UTF-8?q?fix:=20comment=20isLiked,=20isMine=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/ChallengeConverter.java | 33 +++++++++++- .../command/ChallengeGetCommentCommand.java | 1 + .../response/ChallengeGetCommentResponse.java | 2 + .../service/ChallengeQueryService.java | 51 ++++++++++++------- .../repository/ChallengeRepository.java | 2 + .../ChallengeRepositoryImpl.java | 5 ++ .../jpa/CommentLikeJpaRepository.java | 3 ++ .../presentation/ChallengeApiController.java | 3 +- .../presentation/docs/ChallengeApiDocs.java | 2 + .../ChallengeQueryServiceTest.java | 2 +- 10 files changed, 82 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/loopon/challenge/application/converter/ChallengeConverter.java b/src/main/java/com/loopon/challenge/application/converter/ChallengeConverter.java index e1e5adf3..0f263ffd 100644 --- a/src/main/java/com/loopon/challenge/application/converter/ChallengeConverter.java +++ b/src/main/java/com/loopon/challenge/application/converter/ChallengeConverter.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; public class ChallengeConverter { @@ -163,21 +164,36 @@ public static ChallengeCommentResponse commentChallenge( public static ChallengeGetCommentCommand getCommentChallenge( Long challengeId, + Long userId, Pageable pageable ) { return ChallengeGetCommentCommand.builder() .challengeId(challengeId) + .userId(userId) .pageable(pageable) .build(); } public static ChallengeGetCommentResponse getCommentChallenge( Comment comment, - List children + List children, + Long userId, + Set likedCommentIds ) { List childResponses = new ArrayList<>(); for (Comment child : children) { + boolean isMine = false; + boolean isLiked = false; + + if (child.getUser().getId().equals(userId)) { + isMine = true; + } + + if (likedCommentIds.contains(child.getId())) { + isLiked = true; + } + childResponses.add(ChallengeGetCommentResponse.builder() .commentId(child.getId()) .content(child.getContent()) @@ -185,9 +201,22 @@ public static ChallengeGetCommentResponse getCommentChallenge( .profileImageUrl(child.getUser().getProfileImageUrl()) .likeCount(child.getLikeCount()) .children(null) + .isMine(isMine) + .isLiked(isLiked) .build()); } + boolean isMine = false; + boolean isLiked = false; + + if (comment.getUser().getId().equals(userId)) { + isMine = true; + } + + if (likedCommentIds.contains(comment.getId())) { + isLiked = true; + } + return ChallengeGetCommentResponse.builder() .commentId(comment.getId()) .content(comment.getContent()) @@ -195,6 +224,8 @@ public static ChallengeGetCommentResponse getCommentChallenge( .profileImageUrl(comment.getUser().getProfileImageUrl()) .likeCount(comment.getLikeCount()) .children(childResponses) + .isMine(isMine) + .isLiked(isLiked) .build(); } diff --git a/src/main/java/com/loopon/challenge/application/dto/command/ChallengeGetCommentCommand.java b/src/main/java/com/loopon/challenge/application/dto/command/ChallengeGetCommentCommand.java index f638724c..e744d9a4 100644 --- a/src/main/java/com/loopon/challenge/application/dto/command/ChallengeGetCommentCommand.java +++ b/src/main/java/com/loopon/challenge/application/dto/command/ChallengeGetCommentCommand.java @@ -6,6 +6,7 @@ @Builder public record ChallengeGetCommentCommand( Long challengeId, + Long userId, Pageable pageable ) { } diff --git a/src/main/java/com/loopon/challenge/application/dto/response/ChallengeGetCommentResponse.java b/src/main/java/com/loopon/challenge/application/dto/response/ChallengeGetCommentResponse.java index 1d073d7b..d93e0904 100644 --- a/src/main/java/com/loopon/challenge/application/dto/response/ChallengeGetCommentResponse.java +++ b/src/main/java/com/loopon/challenge/application/dto/response/ChallengeGetCommentResponse.java @@ -11,6 +11,8 @@ public record ChallengeGetCommentResponse( String profileImageUrl, String content, Integer likeCount, + Boolean isMine, + Boolean isLiked, List children ) { } diff --git a/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java b/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java index 93222f70..6e42ea1f 100644 --- a/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java +++ b/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java @@ -3,10 +3,7 @@ import com.loopon.challenge.application.converter.ChallengeConverter; import com.loopon.challenge.application.dto.command.*; import com.loopon.challenge.application.dto.response.*; -import com.loopon.challenge.domain.Challenge; -import com.loopon.challenge.domain.ChallengeHashtag; -import com.loopon.challenge.domain.ChallengeImage; -import com.loopon.challenge.domain.Comment; +import com.loopon.challenge.domain.*; import com.loopon.challenge.domain.repository.ChallengeRepository; import com.loopon.global.domain.ErrorCode; import com.loopon.global.domain.dto.SliceResponse; @@ -23,13 +20,8 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; import java.util.stream.Stream; @Service @@ -73,29 +65,50 @@ public ChallengeGetResponse getChallenge( @Transactional(readOnly = true) public SliceResponse getCommentChallenge( - ChallengeGetCommentCommand commandDto + ChallengeGetCommentCommand dto ) { - Challenge challenge = challengeRepository.findById(commandDto.challengeId()) + Challenge challenge = challengeRepository.findById(dto.challengeId()) .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND)); - Slice comments = challengeRepository.findCommentsWithUserByChallengeId(challenge.getId(), commandDto.pageable()); - List parentIds = new ArrayList<>(); - for (Comment comment : comments.getContent()) { - parentIds.add(comment.getId()); - } + Slice comments = challengeRepository.findCommentsWithUserByChallengeId(challenge.getId(), dto.pageable()); + + List parentIds = comments.getContent().stream() + .map(Comment::getId) + .toList(); List children = challengeRepository.findAllCommentWithUserByParentIdIn(parentIds); + + List allCommentIds = new ArrayList<>(parentIds); + children.forEach(c -> allCommentIds.add(c.getId())); + + List commentLikes = challengeRepository.findAllCommentLikeByUserIdAndCommentIdIn(dto.userId(), allCommentIds); + + Set likedCommentIds = new HashSet<>(); + + for (CommentLike like : commentLikes) { + Long commentId = like.getComment().getId(); + likedCommentIds.add(commentId); + } + + Map> childrenMap = new HashMap<>(); + for (Comment child : children) { Long parentId = child.getParent().getId(); childrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(child); } + return SliceResponse.from(comments.map(comment -> - ChallengeConverter.getCommentChallenge(comment, childrenMap.getOrDefault(comment.getId(), new ArrayList<>())) + ChallengeConverter.getCommentChallenge( + comment, + childrenMap.getOrDefault(comment.getId(), Collections.emptyList()), + dto.userId(), + likedCommentIds + ) )); } diff --git a/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java b/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java index 3e9ccc13..bbf44b34 100644 --- a/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java +++ b/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java @@ -95,4 +95,6 @@ public interface ChallengeRepository { Boolean existsCommentLikeByCommentIdAndUserId(Long commentId, Long userId); Slice findAllWithJourneyAndUserByUserId(Long userId, Pageable pageable); + + List findAllCommentLikeByUserIdAndCommentIdIn(Long userId, List commentIds); } diff --git a/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java b/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java index 5f3f9239..289000a1 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java +++ b/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java @@ -222,4 +222,9 @@ public Boolean existsCommentLikeByCommentIdAndUserId(Long commentId, Long userId public Slice findAllWithJourneyAndUserByUserId(Long userId, Pageable pageable) { return challengeJpaRepository.findAllWithJourneyAndUserByUserId(userId, pageable); } + + @Override + public List findAllCommentLikeByUserIdAndCommentIdIn(Long userId, List commentIds) { + return commentLikeJpaRepository.findAllByUserIdAndCommentIdIn(userId, commentIds); + } } diff --git a/src/main/java/com/loopon/challenge/infrastructure/jpa/CommentLikeJpaRepository.java b/src/main/java/com/loopon/challenge/infrastructure/jpa/CommentLikeJpaRepository.java index c6d792fd..cccaefc5 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/jpa/CommentLikeJpaRepository.java +++ b/src/main/java/com/loopon/challenge/infrastructure/jpa/CommentLikeJpaRepository.java @@ -3,10 +3,13 @@ import com.loopon.challenge.domain.CommentLike; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface CommentLikeJpaRepository extends JpaRepository { Optional findByCommentIdAndUserId(Long commentId, Long userId); Boolean existsByIdAndUserId(Long commentId, Long userId); + + List findAllByUserIdAndCommentIdIn(Long userId, List commentIds); } diff --git a/src/main/java/com/loopon/challenge/presentation/ChallengeApiController.java b/src/main/java/com/loopon/challenge/presentation/ChallengeApiController.java index c004d05e..0bbaed5f 100644 --- a/src/main/java/com/loopon/challenge/presentation/ChallengeApiController.java +++ b/src/main/java/com/loopon/challenge/presentation/ChallengeApiController.java @@ -99,9 +99,10 @@ public ResponseEntity> commentChallenge @GetMapping("/api/challenges/{challengeId}/comments") public ResponseEntity>> getCommentChallenge( @PathVariable Long challengeId, + @AuthenticationPrincipal PrincipalDetails principalDetails, Pageable pageable ) { - ChallengeGetCommentCommand commandDto = ChallengeConverter.getCommentChallenge(challengeId, pageable); + ChallengeGetCommentCommand commandDto = ChallengeConverter.getCommentChallenge(challengeId, principalDetails.getUserId(), pageable); return ResponseEntity.ok(CommonResponse.onSuccess(challengeQueryService.getCommentChallenge(commandDto))); } diff --git a/src/main/java/com/loopon/challenge/presentation/docs/ChallengeApiDocs.java b/src/main/java/com/loopon/challenge/presentation/docs/ChallengeApiDocs.java index 6c541f65..39748ccb 100644 --- a/src/main/java/com/loopon/challenge/presentation/docs/ChallengeApiDocs.java +++ b/src/main/java/com/loopon/challenge/presentation/docs/ChallengeApiDocs.java @@ -20,6 +20,7 @@ import org.springframework.data.web.PageableDefault; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; @@ -82,6 +83,7 @@ ResponseEntity> commentChallenge( @Operation(summary = "챌린지 댓글 목록 조회") ResponseEntity>> getCommentChallenge( @PathVariable("challengeId") Long challengeId, + @AuthenticationPrincipal PrincipalDetails principalDetails, @PageableDefault Pageable pageable ); diff --git a/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java b/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java index 8112c980..0c3edd80 100644 --- a/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java +++ b/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java @@ -130,7 +130,7 @@ class GetCommentChallengeTest { @DisplayName("성공: 부모 댓글과 그에 달린 대댓글들이 맵핑되어 반환된다.") void success() { // given - ChallengeGetCommentCommand command = new ChallengeGetCommentCommand(1L, PageRequest.of(0, 10)); + ChallengeGetCommentCommand command = new ChallengeGetCommentCommand(1L, 1L, PageRequest.of(0, 10)); Challenge challenge = createTestChallenge(1L); User parentUser = createTestUser(1L, "parent", UserVisibility.PUBLIC); From 38805ee163a16428c751411c25f16e899598843e Mon Sep 17 00:00:00 2001 From: "jaeho4125@gmail.com" Date: Tue, 17 Feb 2026 01:46:46 +0900 Subject: [PATCH 02/10] =?UTF-8?q?fix:=20unused=20import,=20method=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ChallengeQueryService.java | 1 - .../repository/ChallengeRepository.java | 12 ---------- .../ChallengeRepositoryImpl.java | 24 ------------------- .../jpa/ChallengeHashtagJpaRepository.java | 2 -- .../jpa/HashtagJpaRepository.java | 2 -- 5 files changed, 41 deletions(-) diff --git a/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java b/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java index 6e42ea1f..c357b854 100644 --- a/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java +++ b/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java @@ -21,7 +21,6 @@ import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; @Service diff --git a/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java b/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java index bbf44b34..62a04abc 100644 --- a/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java +++ b/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java @@ -1,6 +1,5 @@ package com.loopon.challenge.domain.repository; -import com.loopon.challenge.domain.*; import org.springframework.data.domain.Page; import com.loopon.challenge.application.dto.response.ChallengePreviewResponse; import com.loopon.challenge.domain.Challenge; @@ -13,7 +12,6 @@ import com.loopon.challenge.domain.Hashtag; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; @@ -28,26 +26,18 @@ public interface ChallengeRepository { Long save(Challenge challenge); - ChallengeHashtagId saveChallengeHashtag(ChallengeHashtag challengeHashtag); - Long saveChallengeImage(ChallengeImage challengeImage); Hashtag saveHashtag(Hashtag hashtag); - List findAllChallengeHashtagByChallengeId(Long id); - List findAllChallengeHashtagWithHashtagByChallengeId(Long id); Optional findHashtagByName(String name); - List findAllHashtagByNameIn(List hashtagList); - List findAllImageByChallengeId(Long challengeId); Optional findById(Long challengeId); - void deleteChallengeHashtag(ChallengeHashtag challengeHashtag); - void deleteAllByExpeditionId(Long expeditionId); Slice findAllWithJourneyAndUserByExpeditionId(Long expeditionId, Pageable pageable); @@ -55,8 +45,6 @@ public interface ChallengeRepository { Boolean existsChallengeLikeByIdAndUserId(Long challengeId, Long userId); Page findThumbnailsByUserId(Long userId, Pageable pageable); - - void saveAllHashtags(List hashtagList); Optional findChallengeLikeByUserIdAndId(Long userId, Long challengeId); diff --git a/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java b/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java index 289000a1..d68cc12b 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java +++ b/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java @@ -56,11 +56,6 @@ public Long save(Challenge challenge) { return challengeJpaRepository.save(challenge).getId(); } - @Override - public ChallengeHashtagId saveChallengeHashtag(ChallengeHashtag challengeHashtag) { - return challengeHashtagJpaRepository.save(challengeHashtag).getId(); - } - @Override public Long saveChallengeImage(ChallengeImage challengeImage) { return challengeImageJpaRepository.save(challengeImage).getId(); @@ -77,10 +72,6 @@ public Optional findHashtagByName(String name) { return hashtagJpaRepository.findByName(name); } - @Override - public List findAllChallengeHashtagByChallengeId(Long challengeId) { - return challengeHashtagJpaRepository.findAllByChallengeId(challengeId); - } @Override public List findAllChallengeHashtagWithHashtagByChallengeId(Long challengeId) { @@ -92,11 +83,6 @@ public List findAllImageByChallengeId(Long challengeId) { return challengeImageJpaRepository.findAllByChallengeId(challengeId); } - @Override - public void deleteChallengeHashtag(ChallengeHashtag challengeHashtag) { - challengeHashtagJpaRepository.delete(challengeHashtag); - } - @Override public void deleteAllByExpeditionId(Long expeditionId) { challengeJpaRepository.deleteAllByExpeditionId(expeditionId); @@ -116,16 +102,6 @@ public Boolean existsChallengeLikeByIdAndUserId(Long challengeId, Long userId) { public Page findThumbnailsByUserId(Long userId, Pageable pageable) { return challengeImageJpaRepository.findThumbnailsByUserId(userId, pageable); } - - @Override - public List findAllHashtagByNameIn(List hashtagList) { - return hashtagJpaRepository.findAllByNameIn(hashtagList); - } - - @Override - public void saveAllHashtags(List hashtagList) { - hashtagJpaRepository.saveAll(hashtagList); - } @Override public void deleteChallengeLikeById(Long challengeLikeId) { diff --git a/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeHashtagJpaRepository.java b/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeHashtagJpaRepository.java index faf9c1cc..15274900 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeHashtagJpaRepository.java +++ b/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeHashtagJpaRepository.java @@ -7,7 +7,5 @@ import java.util.List; public interface ChallengeHashtagJpaRepository extends JpaRepository { - List findAllByChallengeId(Long challengeId); - List findAllWithHashtagByChallengeId(Long challengeId); } diff --git a/src/main/java/com/loopon/challenge/infrastructure/jpa/HashtagJpaRepository.java b/src/main/java/com/loopon/challenge/infrastructure/jpa/HashtagJpaRepository.java index 553cea71..926204b9 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/jpa/HashtagJpaRepository.java +++ b/src/main/java/com/loopon/challenge/infrastructure/jpa/HashtagJpaRepository.java @@ -8,6 +8,4 @@ public interface HashtagJpaRepository extends JpaRepository { Optional findByName(String hashtag); - - List findAllByNameIn(List hashtagList); } From ec3891f08699f169b57718ccff4d953d8a1c462f Mon Sep 17 00:00:00 2001 From: "jaeho4125@gmail.com" Date: Tue, 17 Feb 2026 02:02:30 +0900 Subject: [PATCH 03/10] =?UTF-8?q?fix:=20viewChallenge=20trendingChallenges?= =?UTF-8?q?=20=EB=B3=B8=EC=9D=B8=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/application/service/ChallengeQueryService.java | 2 +- .../challenge/domain/repository/ChallengeRepository.java | 2 +- .../challenge/infrastructure/ChallengeRepositoryImpl.java | 4 ++-- .../challenge/infrastructure/jpa/ChallengeJpaRepository.java | 2 ++ .../challenge/application/ChallengeQueryServiceTest.java | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java b/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java index c357b854..834ad420 100644 --- a/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java +++ b/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java @@ -145,7 +145,7 @@ public ChallengeCombinedViewResponse viewChallenge( LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(3); Slice trendingChallenges = challengeRepository.findTrendingChallenges( - threeDaysAgo, commandDto.trendingPage()); + threeDaysAgo, user.getId(), commandDto.trendingPage()); List trendingIds = new ArrayList<>(); diff --git a/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java b/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java index 62a04abc..18e18aa0 100644 --- a/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java +++ b/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java @@ -72,7 +72,7 @@ public interface ChallengeRepository { List findAllCommentWithUserByParentIdIn(List parentIds); - Slice findTrendingChallenges(LocalDateTime threeDaysAgo, Pageable pageable); + Slice findTrendingChallenges(LocalDateTime threeDaysAgo, Long userId, Pageable pageable); Slice findFriendsChallenges(List friendsIds, List trendingIds, Pageable pageable); diff --git a/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java b/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java index d68cc12b..ad95c1cd 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java +++ b/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java @@ -170,8 +170,8 @@ public List findAllCommentWithUserByParentIdIn(List parentIds) { } @Override - public Slice findTrendingChallenges(LocalDateTime threeDaysAgo, Pageable pageable) { - return challengeJpaRepository.findTrendingChallenges(threeDaysAgo, pageable); + public Slice findTrendingChallenges(LocalDateTime threeDaysAgo, Long userId, Pageable pageable) { + return challengeJpaRepository.findTrendingChallenges(threeDaysAgo, userId, pageable); } @Override diff --git a/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeJpaRepository.java b/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeJpaRepository.java index 9b3158e7..f4974559 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeJpaRepository.java +++ b/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeJpaRepository.java @@ -38,9 +38,11 @@ Slice findAllWithJourneyAndUserByExpeditionId( "JOIN FETCH c.journey j " + "WHERE c.createdAt >= :threeDaysAgo " + "AND c.user.visibility = 'PUBLIC' " + + "AND c.user.id != :userId " + "ORDER BY (c.likeCount * 2 + c.commentCount * 5) DESC, c.createdAt DESC") Slice findTrendingChallenges( @Param("threeDaysAgo") LocalDateTime threeDaysAgo, + @Param("userId") Long userId, Pageable pageable ); diff --git a/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java b/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java index 0c3edd80..91959d52 100644 --- a/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java +++ b/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java @@ -234,7 +234,7 @@ void success_combined_view() { when(friend.getId()).thenReturn(200L); given(userRepository.findById(1L)).willReturn(Optional.of(user)); - given(challengeRepository.findTrendingChallenges(any(LocalDateTime.class), any(Pageable.class))) + given(challengeRepository.findTrendingChallenges(any(LocalDateTime.class), 1L, any(Pageable.class))) .willReturn(new SliceImpl<>(List.of(trending))); // 친구 목록 mock From 09fad48171b1d2cfbf016e72995a64f5ae4edb11 Mon Sep 17 00:00:00 2001 From: "jaeho4125@gmail.com" Date: Tue, 17 Feb 2026 02:03:58 +0900 Subject: [PATCH 04/10] =?UTF-8?q?fix:=20unused=20import,=20method=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loopon/challenge/domain/repository/ChallengeRepository.java | 1 - .../loopon/challenge/infrastructure/ChallengeRepositoryImpl.java | 1 - .../challenge/infrastructure/jpa/HashtagJpaRepository.java | 1 - 3 files changed, 3 deletions(-) diff --git a/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java b/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java index 18e18aa0..4cac5b93 100644 --- a/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java +++ b/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java @@ -4,7 +4,6 @@ import com.loopon.challenge.application.dto.response.ChallengePreviewResponse; import com.loopon.challenge.domain.Challenge; import com.loopon.challenge.domain.ChallengeHashtag; -import com.loopon.challenge.domain.ChallengeHashtagId; import com.loopon.challenge.domain.ChallengeImage; import com.loopon.challenge.domain.ChallengeLike; import com.loopon.challenge.domain.Comment; diff --git a/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java b/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java index ad95c1cd..4cf2fbfa 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java +++ b/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java @@ -3,7 +3,6 @@ import com.loopon.challenge.application.dto.response.ChallengePreviewResponse; import com.loopon.challenge.domain.Challenge; import com.loopon.challenge.domain.ChallengeHashtag; -import com.loopon.challenge.domain.ChallengeHashtagId; import com.loopon.challenge.domain.ChallengeImage; import com.loopon.challenge.domain.ChallengeLike; import com.loopon.challenge.domain.Comment; diff --git a/src/main/java/com/loopon/challenge/infrastructure/jpa/HashtagJpaRepository.java b/src/main/java/com/loopon/challenge/infrastructure/jpa/HashtagJpaRepository.java index 926204b9..e94aa63f 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/jpa/HashtagJpaRepository.java +++ b/src/main/java/com/loopon/challenge/infrastructure/jpa/HashtagJpaRepository.java @@ -3,7 +3,6 @@ import com.loopon.challenge.domain.Hashtag; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; import java.util.Optional; public interface HashtagJpaRepository extends JpaRepository { From 8182dc21e0514890231080294a4991609ae4fb4d Mon Sep 17 00:00:00 2001 From: "jaeho4125@gmail.com" Date: Tue, 17 Feb 2026 02:05:06 +0900 Subject: [PATCH 05/10] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loopon/challenge/application/ChallengeQueryServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java b/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java index 91959d52..33291e99 100644 --- a/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java +++ b/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java @@ -234,7 +234,7 @@ void success_combined_view() { when(friend.getId()).thenReturn(200L); given(userRepository.findById(1L)).willReturn(Optional.of(user)); - given(challengeRepository.findTrendingChallenges(any(LocalDateTime.class), 1L, any(Pageable.class))) + given(challengeRepository.findTrendingChallenges(any(LocalDateTime.class), anyLong(), any(Pageable.class))) .willReturn(new SliceImpl<>(List.of(trending))); // 친구 목록 mock From 88bc6aedac49e6a6058a6c8ec4a288183ec60ef4 Mon Sep 17 00:00:00 2001 From: "jaeho4125@gmail.com" Date: Wed, 18 Feb 2026 12:07:50 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=ED=83=80=EC=9D=B8=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84,=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20=EB=AA=A8?= =?UTF-8?q?=EC=95=84=EB=B3=B4=EA=B8=B0=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/application/UserQueryService.java | 22 ++++++++++++ .../response/UserOthersProfileResponse.java | 35 +++++++++++++++++++ .../user/presentation/UserApiController.java | 22 +++++++----- .../user/presentation/docs/UserApiDocs.java | 16 +++++++++ 4 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/loopon/user/application/dto/response/UserOthersProfileResponse.java diff --git a/src/main/java/com/loopon/user/application/UserQueryService.java b/src/main/java/com/loopon/user/application/UserQueryService.java index 1d10112a..410c4491 100644 --- a/src/main/java/com/loopon/user/application/UserQueryService.java +++ b/src/main/java/com/loopon/user/application/UserQueryService.java @@ -7,8 +7,11 @@ import com.loopon.global.domain.dto.PageResponse; import com.loopon.global.exception.BusinessException; import com.loopon.user.application.dto.response.UserDuplicateCheckResponse; +import com.loopon.user.application.dto.response.UserOthersProfileResponse; import com.loopon.user.application.dto.response.UserProfileResponse; +import com.loopon.user.domain.FriendStatus; import com.loopon.user.domain.User; +import com.loopon.user.domain.repository.FriendRepository; import com.loopon.user.domain.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -22,6 +25,7 @@ public class UserQueryService { private final UserRepository userRepository; private final ChallengeRepository challengeRepository; + private final FriendRepository friendRepository; public UserDuplicateCheckResponse isEmailAvailable(String email) { boolean isAvailable = !userRepository.existsByEmail(email); @@ -45,4 +49,22 @@ public UserProfileResponse getUserProfile(Long userId, Pageable pageable) { return UserProfileResponse.of(user, pageResponse); } + + public UserOthersProfileResponse getOthersProfile(Long userId, String nickname, Pageable pageable) { + User me = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + + User target = userRepository.findByNickname(nickname) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + + Boolean isFriend = friendRepository.existsFriendship(me.getId(), target.getId(), FriendStatus.ACCEPTED); + + Page imagePage = challengeRepository.findThumbnailsByUserId(userId, pageable); + + Page dtoPage = imagePage.map(ChallengeThumbnailResponse::from); + + PageResponse pageResponse = PageResponse.from(dtoPage); + + return UserOthersProfileResponse.of(target, isFriend, pageResponse); + } } diff --git a/src/main/java/com/loopon/user/application/dto/response/UserOthersProfileResponse.java b/src/main/java/com/loopon/user/application/dto/response/UserOthersProfileResponse.java new file mode 100644 index 00000000..b559cf5f --- /dev/null +++ b/src/main/java/com/loopon/user/application/dto/response/UserOthersProfileResponse.java @@ -0,0 +1,35 @@ +package com.loopon.user.application.dto.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.loopon.challenge.application.dto.response.ChallengeThumbnailResponse; +import com.loopon.global.domain.dto.PageResponse; +import com.loopon.user.domain.User; + +public record UserOthersProfileResponse( + Long userId, + String nickname, + String bio, + String statusMessage, + String profileImageUrl, + Boolean isFriend, + + @JsonInclude(JsonInclude.Include.NON_NULL) + PageResponse thumbnailResponse +) { + public static UserOthersProfileResponse of( + User user, + Boolean isFriend, + PageResponse challenges + ) { + + return new UserOthersProfileResponse( + user.getId(), + user.getNickname(), + user.getBio(), + user.getStatusMessage(), + user.getProfileImageUrl(), + isFriend, + challenges + ); + } +} diff --git a/src/main/java/com/loopon/user/presentation/UserApiController.java b/src/main/java/com/loopon/user/presentation/UserApiController.java index 6d6aa583..cb773c06 100644 --- a/src/main/java/com/loopon/user/presentation/UserApiController.java +++ b/src/main/java/com/loopon/user/presentation/UserApiController.java @@ -9,6 +9,7 @@ import com.loopon.user.application.dto.request.UpdateProfileRequest; import com.loopon.user.application.dto.request.UserSignUpRequest; import com.loopon.user.application.dto.response.UserDuplicateCheckResponse; +import com.loopon.user.application.dto.response.UserOthersProfileResponse; import com.loopon.user.application.dto.response.UserProfileResponse; import com.loopon.user.application.validator.ImageValidator; import com.loopon.user.presentation.docs.UserApiDocs; @@ -20,14 +21,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @RestController @@ -80,6 +74,18 @@ public ResponseEntity> getUserProfile( return ResponseEntity.ok(CommonResponse.onSuccess(response)); } + @Override + @GetMapping("/{nickname}") + public ResponseEntity> getOthersProfile( + @AuthenticationPrincipal PrincipalDetails principalDetails, + @PathVariable String nickname, + @PageableDefault(size = 20, sort = "id", direction = Sort.Direction.DESC) + Pageable pageable + ) { + UserOthersProfileResponse response = userQueryService.getOthersProfile(principalDetails.getUserId(), nickname, pageable); + return ResponseEntity.ok(CommonResponse.onSuccess(response)); + } + @Override @PatchMapping("/profile") public ResponseEntity> updateProfile( diff --git a/src/main/java/com/loopon/user/presentation/docs/UserApiDocs.java b/src/main/java/com/loopon/user/presentation/docs/UserApiDocs.java index e1d334b9..ab2788d9 100644 --- a/src/main/java/com/loopon/user/presentation/docs/UserApiDocs.java +++ b/src/main/java/com/loopon/user/presentation/docs/UserApiDocs.java @@ -8,6 +8,7 @@ import com.loopon.user.application.dto.request.UpdateProfileRequest; import com.loopon.user.application.dto.request.UserSignUpRequest; import com.loopon.user.application.dto.response.UserDuplicateCheckResponse; +import com.loopon.user.application.dto.response.UserOthersProfileResponse; import com.loopon.user.application.dto.response.UserProfileResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -76,6 +77,21 @@ ResponseEntity> getUserProfile( @Parameter(hidden = true) Pageable pageable ); + @Operation(summary = "타인 프로필 조회", description = "공개/친구 사용자의 프로필 정보(닉네임, 이미지, 한줄 소개 등)를 조회합니다.") + @Parameters({ + @Parameter(name = "page", description = "페이지 번호 (0부터 시작)", in = ParameterIn.QUERY, example = "0"), + @Parameter(name = "size", description = "한 페이지 크기", in = ParameterIn.QUERY, example = "10"), + @Parameter(name = "sort", description = "정렬 기준 (예: createdAt,desc)", in = ParameterIn.QUERY, example = "createdAt,desc") + }) + @ApiResponse(responseCode = "200", description = "조회 성공", useReturnTypeSchema = true) + @CommonBadRequestResponseDocs + @CommonInternalServerErrorResponseDocs + ResponseEntity> getOthersProfile( + @Parameter(hidden = true) PrincipalDetails principalDetails, + @Parameter String nickname, + @Parameter(hidden = true) Pageable pageable + ); + @Operation(summary = "프로필 수정", description = "닉네임, Bio, 상태메시지, 프로필 이미지를 수정합니다.") @ApiResponse(responseCode = "200", description = "수정 성공 (변경된 프로필 정보 반환)", useReturnTypeSchema = true) @CommonBadRequestResponseDocs From eee7fd304c2cf5297e9dc0094fc4b12a687c1379 Mon Sep 17 00:00:00 2001 From: "jaeho4125@gmail.com" Date: Wed, 18 Feb 2026 12:18:12 +0900 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20?= =?UTF-8?q?=EB=AA=A8=EC=95=84=EB=B3=B4=EA=B8=B0=20API=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/ChallengeConverter.java | 23 ---- .../dto/command/ChallengeMyCommand.java | 11 -- .../dto/command/ChallengeOthersCommand.java | 13 --- .../service/ChallengeQueryService.java | 24 ----- .../repository/ChallengeRepository.java | 3 - .../ChallengeRepositoryImpl.java | 6 -- .../jpa/ChallengeJpaRepository.java | 7 -- .../presentation/ChallengeApiController.java | 21 ---- .../presentation/docs/ChallengeApiDocs.java | 13 --- .../ChallengeQueryServiceTest.java | 100 ------------------ 10 files changed, 221 deletions(-) delete mode 100644 src/main/java/com/loopon/challenge/application/dto/command/ChallengeMyCommand.java delete mode 100644 src/main/java/com/loopon/challenge/application/dto/command/ChallengeOthersCommand.java diff --git a/src/main/java/com/loopon/challenge/application/converter/ChallengeConverter.java b/src/main/java/com/loopon/challenge/application/converter/ChallengeConverter.java index 0f263ffd..dd040874 100644 --- a/src/main/java/com/loopon/challenge/application/converter/ChallengeConverter.java +++ b/src/main/java/com/loopon/challenge/application/converter/ChallengeConverter.java @@ -273,29 +273,6 @@ public static ChallengeDeleteCommand deleteChallenge( .build(); } - - public static ChallengeMyCommand myChallenge( - Long userId, - Pageable pageable - ) { - return ChallengeMyCommand.builder() - .userId(userId) - .pageable(pageable) - .build(); - } - - public static ChallengeOthersCommand othersChallenge( - Long userId, - String nickname, - Pageable pageable - ) { - return ChallengeOthersCommand.builder() - .userId(userId) - .nickname(nickname) - .pageable(pageable) - .build(); - } - public static ChallengeViewCommand viewChallenge( Long userId, Pageable trendingPage, diff --git a/src/main/java/com/loopon/challenge/application/dto/command/ChallengeMyCommand.java b/src/main/java/com/loopon/challenge/application/dto/command/ChallengeMyCommand.java deleted file mode 100644 index 72dd28e8..00000000 --- a/src/main/java/com/loopon/challenge/application/dto/command/ChallengeMyCommand.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.loopon.challenge.application.dto.command; - -import lombok.Builder; -import org.springframework.data.domain.Pageable; - -@Builder -public record ChallengeMyCommand( - Long userId, - Pageable pageable -) { -} diff --git a/src/main/java/com/loopon/challenge/application/dto/command/ChallengeOthersCommand.java b/src/main/java/com/loopon/challenge/application/dto/command/ChallengeOthersCommand.java deleted file mode 100644 index 7f7f9d10..00000000 --- a/src/main/java/com/loopon/challenge/application/dto/command/ChallengeOthersCommand.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.loopon.challenge.application.dto.command; - -import lombok.Builder; -import org.springframework.data.domain.Pageable; - - -@Builder -public record ChallengeOthersCommand( - Long userId, - String nickname, - Pageable pageable -) { -} diff --git a/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java b/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java index 834ad420..f07dc4ed 100644 --- a/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java +++ b/src/main/java/com/loopon/challenge/application/service/ChallengeQueryService.java @@ -111,30 +111,6 @@ public SliceResponse getCommentChallenge( )); } - @Transactional(readOnly = true) - public SliceResponse myChallenge( - ChallengeMyCommand commandDto - ) { - User user = userRepository.findById(commandDto.userId()) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND)); - - return SliceResponse.from(challengeRepository.findViewByUserId(user.getId(), commandDto.pageable())); - } - - @Transactional(readOnly = true) - public SliceResponse othersChallenge( - ChallengeOthersCommand commandDto - ) { - User myself = userRepository.findById(commandDto.userId()) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); - User target = userRepository.findByNickname(commandDto.nickname()) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); - - checkAllowed(target, myself); - - return SliceResponse.from(challengeRepository.findViewByUserId(target.getId(), commandDto.pageable())); - } - @Transactional(readOnly = true) public ChallengeCombinedViewResponse viewChallenge( ChallengeViewCommand commandDto diff --git a/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java b/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java index 4cac5b93..74a5460e 100644 --- a/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java +++ b/src/main/java/com/loopon/challenge/domain/repository/ChallengeRepository.java @@ -1,7 +1,6 @@ package com.loopon.challenge.domain.repository; import org.springframework.data.domain.Page; -import com.loopon.challenge.application.dto.response.ChallengePreviewResponse; import com.loopon.challenge.domain.Challenge; import com.loopon.challenge.domain.ChallengeHashtag; import com.loopon.challenge.domain.ChallengeImage; @@ -67,8 +66,6 @@ public interface ChallengeRepository { void delete(Challenge challenge); - Slice findViewByUserId(Long userId, Pageable pageable); - List findAllCommentWithUserByParentIdIn(List parentIds); Slice findTrendingChallenges(LocalDateTime threeDaysAgo, Long userId, Pageable pageable); diff --git a/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java b/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java index 4cf2fbfa..d6b0a51f 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java +++ b/src/main/java/com/loopon/challenge/infrastructure/ChallengeRepositoryImpl.java @@ -1,6 +1,5 @@ package com.loopon.challenge.infrastructure; -import com.loopon.challenge.application.dto.response.ChallengePreviewResponse; import com.loopon.challenge.domain.Challenge; import com.loopon.challenge.domain.ChallengeHashtag; import com.loopon.challenge.domain.ChallengeImage; @@ -158,11 +157,6 @@ public void delete(Challenge challenge) { challengeJpaRepository.delete(challenge); } - @Override - public Slice findViewByUserId(Long userId, Pageable pageable) { - return challengeJpaRepository.findViewByUserId(userId, pageable); - } - @Override public List findAllCommentWithUserByParentIdIn(List parentIds) { return commentJpaRepository.findAllWithUserByParentIdIn(parentIds); diff --git a/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeJpaRepository.java b/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeJpaRepository.java index f4974559..c8f62489 100644 --- a/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeJpaRepository.java +++ b/src/main/java/com/loopon/challenge/infrastructure/jpa/ChallengeJpaRepository.java @@ -1,6 +1,5 @@ package com.loopon.challenge.infrastructure.jpa; -import com.loopon.challenge.application.dto.response.ChallengePreviewResponse; import com.loopon.challenge.domain.Challenge; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -26,12 +25,6 @@ Slice findAllWithJourneyAndUserByExpeditionId( Pageable pageable ); - @Query("SELECT new com.loopon.challenge.application.dto.response.ChallengePreviewResponse(c.id, ci.imageUrl) " + - "FROM Challenge c " + - "JOIN c.challengeImages ci " + - "WHERE c.user.id = :userId " + - "AND ci.displayOrder = 0") - Slice findViewByUserId(@Param("userId") Long userId, Pageable pageable); @Query("SELECT c FROM Challenge c " + "JOIN FETCH c.user u " + diff --git a/src/main/java/com/loopon/challenge/presentation/ChallengeApiController.java b/src/main/java/com/loopon/challenge/presentation/ChallengeApiController.java index 0bbaed5f..f1d99164 100644 --- a/src/main/java/com/loopon/challenge/presentation/ChallengeApiController.java +++ b/src/main/java/com/loopon/challenge/presentation/ChallengeApiController.java @@ -138,27 +138,6 @@ public ResponseEntity> deleteChallenge( return ResponseEntity.ok(CommonResponse.onSuccess(challengeCommandService.deleteChallenge(commandDto))); } - @Override - @GetMapping("/api/challenges/users/me") - public ResponseEntity>> myChallenge( - @AuthenticationPrincipal PrincipalDetails principalDetails, - Pageable pageable - ) { - ChallengeMyCommand commandDto = ChallengeConverter.myChallenge(principalDetails.getUserId(), pageable); - return ResponseEntity.ok(CommonResponse.onSuccess(challengeQueryService.myChallenge(commandDto))); - } - - @Override - @GetMapping("/api/challenges/users/{nickname}") - public ResponseEntity>> othersChallenge( - @PathVariable String nickname, - Pageable pageable, - @AuthenticationPrincipal PrincipalDetails principalDetails - ) { - ChallengeOthersCommand commandDto = ChallengeConverter.othersChallenge(principalDetails.getUserId(), nickname, pageable); - return ResponseEntity.ok(CommonResponse.onSuccess(challengeQueryService.othersChallenge(commandDto))); - } - @Override @GetMapping("/api/challenges") public ResponseEntity> viewChallenge( diff --git a/src/main/java/com/loopon/challenge/presentation/docs/ChallengeApiDocs.java b/src/main/java/com/loopon/challenge/presentation/docs/ChallengeApiDocs.java index 39748ccb..ef693693 100644 --- a/src/main/java/com/loopon/challenge/presentation/docs/ChallengeApiDocs.java +++ b/src/main/java/com/loopon/challenge/presentation/docs/ChallengeApiDocs.java @@ -107,19 +107,6 @@ ResponseEntity> deleteChallenge( PrincipalDetails principalDetails ); - @Operation(summary = "내 챌린지 모아보기") - ResponseEntity>> myChallenge( - PrincipalDetails principalDetails, - @PageableDefault Pageable pageable - ); - - @Operation(summary = "타인의 챌린지 모아보기") - ResponseEntity>> othersChallenge( - @PathVariable("nickname") String nickname, - @PageableDefault Pageable pageable, - PrincipalDetails principalDetails - ); - @Operation(summary = "여정광장 챌린지 조회.", description = "트렌딩 챌린지와 친구 챌린지의 비율은 기본적으로 1:3을 유지합니다.") ResponseEntity> viewChallenge( PrincipalDetails principalDetails, diff --git a/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java b/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java index 33291e99..f213fb39 100644 --- a/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java +++ b/src/test/java/com/loopon/challenge/application/ChallengeQueryServiceTest.java @@ -163,58 +163,6 @@ void success() { } } - @Nested - @DisplayName("타인 챌린지 조회 (othersChallenge)") - class OthersChallengeTest { - - @Test - @DisplayName("성공: 비공개 계정이라도 친구 상태가 ACCEPTED라면 조회가 가능하다.") - void success_private_friend() { - // 1. Given - Long myId = 1L; - Long targetUserId = 2L; - - // 유저 생성 시 ID 확실히 주입 (createTestUser 메서드가 mockUser.getId() -> id 를 리턴하게 되어있어야 함) - User myself = createTestUser(myId, "me", UserVisibility.PUBLIC); - User target = createTestUser(targetUserId, "target", UserVisibility.PRIVATE); - - ChallengeOthersCommand command = new ChallengeOthersCommand(myId, "target", PageRequest.of(0, 10)); - - given(userRepository.findById(myId)).willReturn(Optional.of(myself)); - given(userRepository.findByNickname("target")).willReturn(Optional.of(target)); - - given(friendRepository.existsFriendship(eq(targetUserId), eq(myId), eq(FriendStatus.ACCEPTED))) - .willReturn(true); - - given(challengeRepository.findViewByUserId(anyLong(), any(Pageable.class))) - .willReturn(new SliceImpl<>(List.of())); - - // 2. When - challengeQueryService.othersChallenge(command); - - // 3. Then - verify(challengeRepository).findViewByUserId(eq(targetUserId), any(Pageable.class)); - } - - @Test - @DisplayName("실패: 비공개 계정인데 친구가 아니면 CHALLENGE_FORBIDDEN 예외 발생") - void fail_private_not_friend() { - // given - User myself = createTestUser(1L, "me", UserVisibility.PUBLIC); - User target = createTestUser(2L, "target", UserVisibility.PRIVATE); - ChallengeOthersCommand command = new ChallengeOthersCommand(1L, "target", PageRequest.of(0, 10)); - - given(userRepository.findById(1L)).willReturn(Optional.of(myself)); - given(userRepository.findByNickname("target")).willReturn(Optional.of(target)); - given(friendRepository.existsFriendship(2L, 1L, FriendStatus.ACCEPTED)).willReturn(false); - - // when & then - assertThatThrownBy(() -> challengeQueryService.othersChallenge(command)) - .isInstanceOf(BusinessException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCode.CHALLENGE_FORBIDDEN); - } - } - @Nested @DisplayName("메인 피드 조회 (viewChallenge)") class ViewChallengeTest { @@ -269,54 +217,6 @@ void fail_user_not_found() { } } - @Nested - @DisplayName("내 챌린지 조회 (myChallenge)") - class MyChallengeTest { - - @Test - @DisplayName("성공: 유저가 존재하면 해당 유저의 챌린지 목록을 Slice로 반환한다.") - void success() { - // given - Long userId = 1L; - Pageable pageable = PageRequest.of(0, 10); - ChallengeMyCommand command = new ChallengeMyCommand(userId, pageable); - - User user = createTestUser(userId, "me", UserVisibility.PUBLIC); - - // 가짜 결과물(Slice) 생성 - ChallengePreviewResponse preview = new ChallengePreviewResponse(100L, "thumb.jpg"); // 필드 구성은 DTO에 맞게 조정 - Slice expectedSlice = new SliceImpl<>(List.of(preview), pageable, false); - - given(userRepository.findById(userId)).willReturn(Optional.of(user)); - given(challengeRepository.findViewByUserId(userId, pageable)).willReturn(expectedSlice); - - // when - SliceResponse result = challengeQueryService.myChallenge(command); - - // then - assertThat(result).isNotNull(); - assertThat(result.content()).hasSize(1); - assertThat(result.content().getFirst().challengeId()).isEqualTo(100L); - - // 리포지토리가 정확한 userId로 조회했는지 검증 - verify(challengeRepository).findViewByUserId(eq(userId), eq(pageable)); - } - - @Test - @DisplayName("실패: 존재하지 않는 유저 ID로 요청 시 NOT_FOUND 예외가 발생한다.") - void fail_user_not_found() { - // given - Long userId = 999L; - ChallengeMyCommand command = new ChallengeMyCommand(userId, PageRequest.of(0, 10)); - - given(userRepository.findById(userId)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> challengeQueryService.myChallenge(command)) - .isInstanceOf(BusinessException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCode.NOT_FOUND); - } - } @Test @DisplayName("챌린지 상세 목록 조회 성공 - 연관 엔티티 포함") From a69bcede77f33f43ef4cbc4675d485b54dfd10ec Mon Sep 17 00:00:00 2001 From: Seungwon-Choi Date: Wed, 18 Feb 2026 16:50:15 +0900 Subject: [PATCH 08/10] =?UTF-8?q?chore:=20hikariCP,=20tomcat=20threads=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-dev.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index ee2b6b34..c4a2f52e 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -3,12 +3,24 @@ spring: activate: on-profile: dev + datasource: + hikari: + maximum-pool-size: 15 + connection-timeout: 3000 + idle-timeout: 600000 + max-lifetime: 1800000 + # JPA jpa: hibernate: ddl-auto: validate show-sql: false +server: + tomcat: + threads: + max: 200 + # 로그 레벨 logging: level: From 827e52bb496c38d4bac672b9117d6c4d30c8b321 Mon Sep 17 00:00:00 2001 From: "jaeho4125@gmail.com" Date: Wed, 18 Feb 2026 20:05:08 +0900 Subject: [PATCH 09/10] =?UTF-8?q?fix:=20=ED=83=90=ED=97=98=EB=8C=80=20?= =?UTF-8?q?=EB=82=B4=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/converter/ExpeditionConverter.java | 5 ++++- .../dto/response/ExpeditionChallengesResponse.java | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/loopon/expedition/application/converter/ExpeditionConverter.java b/src/main/java/com/loopon/expedition/application/converter/ExpeditionConverter.java index 7b8adff0..544576f3 100644 --- a/src/main/java/com/loopon/expedition/application/converter/ExpeditionConverter.java +++ b/src/main/java/com/loopon/expedition/application/converter/ExpeditionConverter.java @@ -231,12 +231,15 @@ public static ExpeditionChallengesResponse challengesExpedition( ) { return ExpeditionChallengesResponse.builder() .challengeId(challenge.getId()) - .journeyNumber(challenge.getJourney().getJourneyOrder()) + .journeyNumber(challenge.getJourney().getJourneyOrder()) // 여정 번호 필요! .imageUrls(imageUrls) .content(challenge.getContent()) .hashtags(hashtags) .createdAt(challenge.getCreatedAt()) + .nickname(challenge.getUser().getNickname()) + .profileImageUrl(challenge.getUser().getProfileImageUrl()) .isLiked(isLiked) + .likeCount(challenge.getLikeCount()) .build(); } diff --git a/src/main/java/com/loopon/expedition/application/dto/response/ExpeditionChallengesResponse.java b/src/main/java/com/loopon/expedition/application/dto/response/ExpeditionChallengesResponse.java index 1394158c..bd43350f 100644 --- a/src/main/java/com/loopon/expedition/application/dto/response/ExpeditionChallengesResponse.java +++ b/src/main/java/com/loopon/expedition/application/dto/response/ExpeditionChallengesResponse.java @@ -13,8 +13,9 @@ public record ExpeditionChallengesResponse( String content, List hashtags, LocalDateTime createdAt, - String nickName, + String nickname, String profileImageUrl, - Boolean isLiked + Boolean isLiked, + Integer likeCount ) { -} +} \ No newline at end of file From 4937e7325ba04b82d4e97699d59de00424364337 Mon Sep 17 00:00:00 2001 From: "jaeho4125@gmail.com" Date: Wed, 18 Feb 2026 20:08:54 +0900 Subject: [PATCH 10/10] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expedition/application/ExpeditionQueryServiceTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/com/loopon/expedition/application/ExpeditionQueryServiceTest.java b/src/test/java/com/loopon/expedition/application/ExpeditionQueryServiceTest.java index 87ea960b..4528b771 100644 --- a/src/test/java/com/loopon/expedition/application/ExpeditionQueryServiceTest.java +++ b/src/test/java/com/loopon/expedition/application/ExpeditionQueryServiceTest.java @@ -246,6 +246,7 @@ void success() { Challenge challenge = mock(Challenge.class); lenient().when(challenge.getId()).thenReturn(challengeId); lenient().when(challenge.getJourney()).thenReturn(mock(Journey.class)); + lenient().when(challenge.getUser()).thenReturn(user); // 이미지 모킹 ChallengeImage img = mock(ChallengeImage.class);