diff --git a/src/main/java/balancetalk/global/config/SecurityConfig.java b/src/main/java/balancetalk/global/config/SecurityConfig.java index c006ffe8d..e2588616a 100644 --- a/src/main/java/balancetalk/global/config/SecurityConfig.java +++ b/src/main/java/balancetalk/global/config/SecurityConfig.java @@ -32,7 +32,7 @@ public class SecurityConfig { // swagger "/swagger-ui/**", "/v3/api-docs/**", "/members/duplicate", - "/posts", "/posts/{postId}", "/posts/{postId}/vote", "/posts/{postId}/comments", + "/posts", "/posts/{postId}", "/posts/{postId}/vote", "/posts/{postId}/comments/**", "/notices", "/notices/{noticeId}" }; diff --git a/src/main/java/balancetalk/module/comment/application/CommentService.java b/src/main/java/balancetalk/module/comment/application/CommentService.java index 6505ef8d2..bb82ab685 100644 --- a/src/main/java/balancetalk/module/comment/application/CommentService.java +++ b/src/main/java/balancetalk/module/comment/application/CommentService.java @@ -16,9 +16,12 @@ import balancetalk.module.post.domain.PostRepository; import balancetalk.module.vote.domain.Vote; import balancetalk.module.vote.domain.VoteRepository; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,6 +35,9 @@ @RequiredArgsConstructor public class CommentService { + private static final int BEST_COMMENTS_SIZE = 3; + private static final int MIN_COUNT_FOR_BEST_COMMENT = 15; + private final CommentRepository commentRepository; private final MemberRepository memberRepository; private final PostRepository postRepository; @@ -53,7 +59,7 @@ public Comment createComment(CommentRequest request, Long postId) { } @Transactional(readOnly = true) - public Page findAllComments(Long postId, Pageable pageable) { + public Page findAllComments(Long postId, String token, Pageable pageable) { validatePostId(postId); Page comments = commentRepository.findAllByPostId(postId, pageable); @@ -65,7 +71,12 @@ public Page findAllComments(Long postId, Pageable pageable) { Long balanceOptionId = voteForComment.map(Vote::getBalanceOption).map(BalanceOption::getId) .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_BALANCE_OPTION)); - return CommentResponse.fromEntity(comment, balanceOptionId); + if (token == null) { + return CommentResponse.fromEntity(comment, balanceOptionId, false); + } else { + Member member = getCurrentMember(memberRepository); + return CommentResponse.fromEntity(comment, balanceOptionId, member.hasLikedComment(comment)); + } }); } @@ -180,4 +191,32 @@ public void cancelLikeComment(Long commentId) { commentLikeRepository.deleteByMemberAndComment(member, comment); } + + @Transactional(readOnly = true) + public List findBestComments(Long postId, String token) { + Post post = validatePostId(postId); + List options = post.getOptions(); + + List responses = new ArrayList<>(); + for (BalanceOption option : options) { + List memberIdsBySelectedOptionId = + memberRepository.findMemberIdsBySelectedOptionId(option.getId()); + + List bestComments = commentRepository.findBestCommentsByPostId(postId, + memberIdsBySelectedOptionId, MIN_COUNT_FOR_BEST_COMMENT, PageRequest.of(0, BEST_COMMENTS_SIZE)); + + if (token == null) { + responses.addAll(bestComments.stream() + .map(comment -> CommentResponse.fromEntity(comment, option.getId(), false)).toList()); + } else { + Member member = getCurrentMember(memberRepository); + responses.addAll(bestComments.stream() + .map(comment -> + CommentResponse.fromEntity(comment, option.getId(), member.hasLikedComment(comment))) + .toList()); + } + } + + return responses; + } } diff --git a/src/main/java/balancetalk/module/comment/domain/CommentLike.java b/src/main/java/balancetalk/module/comment/domain/CommentLike.java index b926218b9..a6aecabd2 100644 --- a/src/main/java/balancetalk/module/comment/domain/CommentLike.java +++ b/src/main/java/balancetalk/module/comment/domain/CommentLike.java @@ -5,9 +5,11 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.Getter; import lombok.NoArgsConstructor; @Entity +@Getter @Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) diff --git a/src/main/java/balancetalk/module/comment/domain/CommentRepository.java b/src/main/java/balancetalk/module/comment/domain/CommentRepository.java index 51d0fba38..eeb27e070 100644 --- a/src/main/java/balancetalk/module/comment/domain/CommentRepository.java +++ b/src/main/java/balancetalk/module/comment/domain/CommentRepository.java @@ -1,9 +1,22 @@ package balancetalk.module.comment.domain; +import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface CommentRepository extends JpaRepository { Page findAllByPostId(Long postId, Pageable pageable); + + @Query("select c from Comment c left join c.likes l " + + "where c.post.id = :postId and c.member.id in :memberIds " + + "group by c.id " + + "having count(l) >= :minCountForBest " + + "order by count(l) desc") + List findBestCommentsByPostId(@Param("postId") Long postId, + @Param("memberIds") List memberIds, + @Param("minCountForBest") int minCountForBest, + Pageable pageable); } diff --git a/src/main/java/balancetalk/module/comment/dto/CommentResponse.java b/src/main/java/balancetalk/module/comment/dto/CommentResponse.java index ef19e2d2f..d3174fb91 100644 --- a/src/main/java/balancetalk/module/comment/dto/CommentResponse.java +++ b/src/main/java/balancetalk/module/comment/dto/CommentResponse.java @@ -31,13 +31,16 @@ public class CommentResponse { @Schema(description = "댓글 추천 수", example = "24") private int likesCount; + @Schema(description = "추천 여부", example = "true") + private boolean myLike; + @Schema(description = "댓글 생성 날짜") private LocalDateTime createdAt; @Schema(description = "댓글 수정 날짜") private LocalDateTime lastModifiedAt; - public static CommentResponse fromEntity(Comment comment, Long balanceOptionId) { + public static CommentResponse fromEntity(Comment comment, Long balanceOptionId, boolean myLike) { return CommentResponse.builder() .id(comment.getId()) .content(comment.getContent()) @@ -45,6 +48,7 @@ public static CommentResponse fromEntity(Comment comment, Long balanceOptionId) .postId(comment.getPost().getId()) .selectedOptionId(balanceOptionId) .likesCount(comment.getLikes().size()) + .myLike(myLike) .createdAt(comment.getCreatedAt()) .lastModifiedAt(comment.getLastModifiedAt()) .build(); diff --git a/src/main/java/balancetalk/module/comment/presentation/CommentController.java b/src/main/java/balancetalk/module/comment/presentation/CommentController.java index e02813f86..736993cf4 100644 --- a/src/main/java/balancetalk/module/comment/presentation/CommentController.java +++ b/src/main/java/balancetalk/module/comment/presentation/CommentController.java @@ -6,6 +6,7 @@ import balancetalk.module.comment.dto.ReplyCreateRequest; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -31,8 +32,17 @@ public String createComment(@PathVariable Long postId, @RequestBody CommentReque @ResponseStatus(HttpStatus.OK) @GetMapping @Operation(summary = "댓글 목록 조회", description = "post-id에 해당하는 게시글에 있는 모든 댓글을 조회한다.") - public Page findAllCommentsByPostId(@PathVariable Long postId, Pageable pageable) { - return commentService.findAllComments(postId, pageable); + public Page findAllCommentsByPostId(@PathVariable Long postId, Pageable pageable, + @RequestHeader(value = "Authorization", required = false) String token) { + return commentService.findAllComments(postId, token, pageable); + } + + @ResponseStatus(HttpStatus.OK) + @GetMapping("/best") + @Operation(summary = "인기 댓글 조회", description = "추천 수가 가장 많은 댓글을 각 선택지별로 3개씩 조회한다.") + public List findBestComments(@PathVariable Long postId, + @RequestHeader(value = "Authorization", required = false) String token) { + return commentService.findBestComments(postId, token); } @ResponseStatus(HttpStatus.OK) diff --git a/src/main/java/balancetalk/module/member/domain/Member.java b/src/main/java/balancetalk/module/member/domain/Member.java index 1c8162277..3f6b61e75 100644 --- a/src/main/java/balancetalk/module/member/domain/Member.java +++ b/src/main/java/balancetalk/module/member/domain/Member.java @@ -140,4 +140,9 @@ public boolean hasLiked(Post post) { return postLikes.stream() .anyMatch(like -> like.getPost().equals(post)); } + + public boolean hasLikedComment(Comment comment) { + return commentLikes.stream() + .anyMatch(like -> like.getComment().equals(comment)); + } } diff --git a/src/main/java/balancetalk/module/member/domain/MemberRepository.java b/src/main/java/balancetalk/module/member/domain/MemberRepository.java index adc3ed057..619204fd4 100644 --- a/src/main/java/balancetalk/module/member/domain/MemberRepository.java +++ b/src/main/java/balancetalk/module/member/domain/MemberRepository.java @@ -1,10 +1,16 @@ package balancetalk.module.member.domain; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; +import org.springframework.data.jpa.repository.Query; + public interface MemberRepository extends JpaRepository { Optional findByEmail(String username); boolean existsByNickname(String nickname); boolean existsByEmail(String email); void deleteByEmail(String email); + + @Query("select m.id from Member m JOIN m.votes v WHERE v.balanceOption.id = :balanceOptionId") + List findMemberIdsBySelectedOptionId(Long balanceOptionId); }