Skip to content

Commit ccc703e

Browse files
committed
feat : 댓글, 대댓글 목록 조회 시 추가 데이터 포함하도록 구현
- 댓글은 정렬을 항상 최신순(id 기준 오름차순)으로 사용하므로, 쿼리를 분리하지 않고 JOIN + GROUP BY 만으로 구현함
1 parent 774a3b4 commit ccc703e

File tree

12 files changed

+309
-52
lines changed

12 files changed

+309
-52
lines changed

src/main/java/org/ezcode/codetest/application/community/dto/response/ReplyResponse.java

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22

33
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.*;
44

5+
import java.time.LocalDateTime;
6+
57
import org.ezcode.codetest.application.usermanagement.user.dto.response.SimpleUserInfoResponse;
8+
import org.ezcode.codetest.domain.community.dto.ReplyQueryResult;
69
import org.ezcode.codetest.domain.community.model.entity.Reply;
10+
import org.ezcode.codetest.domain.community.model.enums.VoteType;
711

812
import io.swagger.v3.oas.annotations.media.Schema;
913

1014
@Schema(name = "ReplyResponse", description = "Reply 조회 응답 DTO")
1115
public record ReplyResponse(
16+
1217
@Schema(description = "Reply 고유 ID", example = "456", requiredMode = REQUIRED)
1318
Long replyId,
1419

1520
@Schema(description = "부모 Reply ID (없으면 null)", example = "123", nullable = true, requiredMode = NOT_REQUIRED)
16-
Long parentId,
21+
Long parentReplyId,
1722

1823
@Schema(description = "소속 Discussion ID", example = "123", requiredMode = REQUIRED)
1924
Long discussionId,
@@ -22,19 +27,58 @@ public record ReplyResponse(
2227
SimpleUserInfoResponse userInfo,
2328

2429
@Schema(description = "댓글 내용", example = "동의합니다!", requiredMode = REQUIRED)
25-
String content
30+
String content,
31+
32+
@Schema(description = "생성 일시", example = "2025-06-25T14:30:00", requiredMode = REQUIRED)
33+
LocalDateTime createdAt,
34+
35+
@Schema(description = "총 추천 수 (upvote)", example = "10")
36+
Long upvoteCount,
37+
38+
@Schema(description = "총 비추천 수 (downvote)", example = "2")
39+
Long downvoteCount,
40+
41+
@Schema(description = "총 댓글 수", example = "5")
42+
Long childReplyCount,
43+
44+
@Schema(description = "현재 사용자의 추천 상태 (UP, DOWN, NONE)", example = "UP")
45+
VoteType voteStatus
46+
2647
) {
2748

2849
public static ReplyResponse fromEntity(Reply reply) {
50+
2951
Long parentId = (reply.getParent() != null)
3052
? reply.getParent().getId()
3153
: null;
54+
3255
return new ReplyResponse(
3356
reply.getId(),
3457
parentId,
3558
reply.getDiscussion().getId(),
3659
SimpleUserInfoResponse.fromEntity(reply.getUser()),
37-
reply.getContent()
60+
reply.getContent(),
61+
reply.getCreatedAt(),
62+
null,
63+
null,
64+
null,
65+
null
66+
);
67+
}
68+
69+
public static ReplyResponse from(ReplyQueryResult result) {
70+
71+
return new ReplyResponse(
72+
result.getReplyId(),
73+
result.getParentReplyId(),
74+
result.getDiscussionId(),
75+
result.getUserInfo(),
76+
result.getContent(),
77+
result.getCreatedAt(),
78+
result.getUpvoteCount(),
79+
result.getDownvoteCount(),
80+
result.getChildReplyCount(),
81+
result.getVoteStatus()
3882
);
3983
}
4084
}

src/main/java/org/ezcode/codetest/application/community/service/ReplyService.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.ezcode.codetest.application.community.dto.response.ReplyResponse;
88
import org.ezcode.codetest.application.notification.event.NotificationCreateEvent;
99
import org.ezcode.codetest.application.notification.port.NotificationEventService;
10+
import org.ezcode.codetest.domain.community.dto.ReplyQueryResult;
1011
import org.ezcode.codetest.domain.community.model.entity.Discussion;
1112
import org.ezcode.codetest.domain.community.model.entity.Reply;
1213
import org.ezcode.codetest.domain.community.service.DiscussionDomainService;
@@ -58,28 +59,30 @@ public ReplyResponse createReply(
5859
}
5960

6061
@Transactional(readOnly = true)
61-
public Page<ReplyResponse> getReplies(Long problemId, Long discussionId, Pageable pageable) {
62+
public Page<ReplyResponse> getReplies(Long problemId, Long discussionId, Long currentUserId, Pageable pageable) {
6263

6364
Discussion discussion = discussionDomainService.getDiscussionForProblem(discussionId, problemId);
6465

65-
Page<Reply> replies = replyDomainService.getRepliesByDiscussionId(discussion, pageable);
66+
Page<ReplyQueryResult> replies = replyDomainService.getRepliesByDiscussionId(discussion, currentUserId, pageable);
6667

67-
return replies.map(ReplyResponse::fromEntity);
68+
return replies.map(ReplyResponse::from);
6869
}
6970

7071
@Transactional(readOnly = true)
7172
public Page<ReplyResponse> getChildReplies(
7273
Long problemId,
7374
Long discussionId,
7475
Long parentReplyId,
76+
Long currentUserId,
7577
Pageable pageable
7678
) {
7779

7880
Discussion discussion = discussionDomainService.getDiscussionForProblem(discussionId, problemId);
7981

80-
Page<Reply> replies = replyDomainService.getRepliesByParentReplyId(parentReplyId, discussion, pageable);
82+
Page<ReplyQueryResult> childReplies =
83+
replyDomainService.getRepliesByParentReplyId(parentReplyId, discussion, currentUserId, pageable);
8184

82-
return replies.map(ReplyResponse::fromEntity);
85+
return childReplies.map(ReplyResponse::from);
8386
}
8487

8588
@Transactional

src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public SigninResponse signin(@Valid SigninRequest signinRequest) {
157157
throw new AuthException(AuthExceptionCode.AUTH_TYPE_MISMATCH);
158158
}
159159

160-
// userDomainService.userPasswordCheck(signinRequest.getEmail(), signinRequest.getPassword());
160+
userDomainService.userPasswordCheck(signinRequest.getEmail(), signinRequest.getPassword());
161161

162162
String accessToken = createAccessToken(loginUser);
163163

src/main/java/org/ezcode/codetest/domain/community/dto/DiscussionQueryResult.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,18 @@ public class DiscussionQueryResult {
3131
private final VoteType voteStatus;
3232

3333
@QueryProjection
34-
public DiscussionQueryResult(Long discussionId, SimpleUserInfoResponse userInfo, Long problemId, String content,
35-
LocalDateTime createdAt, Long upvoteCount, Long downvoteCount, Long replyCount, VoteType voteType) {
34+
public DiscussionQueryResult(
35+
Long discussionId,
36+
SimpleUserInfoResponse userInfo,
37+
Long problemId,
38+
String content,
39+
LocalDateTime createdAt,
40+
Long upvoteCount,
41+
Long downvoteCount,
42+
Long replyCount,
43+
VoteType voteType
44+
) {
45+
3646
this.discussionId = discussionId;
3747
this.userInfo = userInfo;
3848
this.problemId = problemId;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.ezcode.codetest.domain.community.dto;
2+
3+
import java.time.LocalDateTime;
4+
5+
import org.ezcode.codetest.application.usermanagement.user.dto.response.SimpleUserInfoResponse;
6+
import org.ezcode.codetest.domain.community.model.enums.VoteType;
7+
8+
import com.querydsl.core.annotations.QueryProjection;
9+
10+
import lombok.Getter;
11+
12+
@Getter
13+
public class ReplyQueryResult {
14+
15+
private final Long replyId;
16+
17+
private final SimpleUserInfoResponse userInfo;
18+
19+
private final Long parentReplyId;
20+
21+
private final Long discussionId;
22+
23+
private final String content;
24+
25+
private final LocalDateTime createdAt;
26+
27+
private final Long upvoteCount;
28+
29+
private final Long downvoteCount;
30+
31+
private final Long childReplyCount;
32+
33+
private final VoteType voteStatus;
34+
35+
@QueryProjection
36+
public ReplyQueryResult(
37+
Long replyId,
38+
SimpleUserInfoResponse userInfo,
39+
Long parentReplyId,
40+
Long discussionId,
41+
String content,
42+
LocalDateTime createdAt,
43+
Long upvoteCount,
44+
Long downvoteCount,
45+
Long childReplyCount,
46+
VoteType voteType
47+
) {
48+
49+
this.replyId = replyId;
50+
this.userInfo = userInfo;
51+
this.parentReplyId = parentReplyId;
52+
this.discussionId = discussionId;
53+
this.content = content;
54+
this.createdAt = createdAt;
55+
this.upvoteCount = upvoteCount;
56+
this.downvoteCount = downvoteCount;
57+
this.childReplyCount = childReplyCount;
58+
59+
if (voteType == null) {
60+
this.voteStatus = VoteType.NONE;
61+
} else if (voteType == VoteType.UP) {
62+
this.voteStatus = VoteType.UP;
63+
} else {
64+
this.voteStatus = VoteType.DOWN;
65+
}
66+
}
67+
}

src/main/java/org/ezcode/codetest/domain/community/repository/ReplyRepository.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.Optional;
44

5+
import org.ezcode.codetest.domain.community.dto.ReplyQueryResult;
56
import org.ezcode.codetest.domain.community.model.entity.Reply;
67
import org.springframework.data.domain.Page;
78
import org.springframework.data.domain.Pageable;
@@ -12,9 +13,9 @@ public interface ReplyRepository {
1213

1314
Optional<Reply> findReplyById(Long replyId);
1415

15-
Page<Reply> findAllRepliesByDiscussionId(Long discussionId, Pageable pageable);
16+
Page<ReplyQueryResult> findAllRepliesByDiscussionId(Long discussionId, Long currentUserId, Pageable pageable);
1617

17-
Page<Reply> findAllChildRepliesByParentReplyId(Long parentReplyId, Pageable pageable);
18+
Page<ReplyQueryResult> findAllChildRepliesByParentReplyId(Long parentReplyId, Long currentUserId, Pageable pageable);
1819

1920
void updateReply(Reply reply, String content);
2021

src/main/java/org/ezcode/codetest/domain/community/service/ReplyDomainService.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.ezcode.codetest.application.notification.enums.NotificationType;
44
import org.ezcode.codetest.application.notification.event.NotificationCreateEvent;
55
import org.ezcode.codetest.application.notification.event.payload.ReplyCreatePayload;
6+
import org.ezcode.codetest.domain.community.dto.ReplyQueryResult;
67
import org.ezcode.codetest.domain.community.exception.CommunityException;
78
import org.ezcode.codetest.domain.community.exception.CommunityExceptionCode;
89
import org.ezcode.codetest.domain.community.model.entity.Discussion;
@@ -54,17 +55,17 @@ public Reply getReplyForDiscussion(Long replyId, Discussion discussion) {
5455
return reply;
5556
}
5657

57-
public Page<Reply> getRepliesByDiscussionId(Discussion discussion, Pageable pageable) {
58+
public Page<ReplyQueryResult> getRepliesByDiscussionId(Discussion discussion, Long currentUserId, Pageable pageable) {
5859

59-
return replyRepository.findAllRepliesByDiscussionId(discussion.getId(), pageable);
60+
return replyRepository.findAllRepliesByDiscussionId(discussion.getId(), currentUserId, pageable);
6061
}
6162

62-
public Page<Reply> getRepliesByParentReplyId(Long parentReplyId, Discussion discussion, Pageable pageable) {
63+
public Page<ReplyQueryResult> getRepliesByParentReplyId(Long parentReplyId, Discussion discussion, Long currentUserId, Pageable pageable) {
6364

6465
Reply parentReply = getReplyById(parentReplyId);
6566
validateDiscussionMatches(parentReply, discussion);
6667

67-
return replyRepository.findAllChildRepliesByParentReplyId(parentReply.getId(), pageable);
68+
return replyRepository.findAllChildRepliesByParentReplyId(parentReply.getId(), currentUserId, pageable);
6869
}
6970

7071
public Reply modify(Long replyId, Discussion discussion, Long userId, String content) {
Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,7 @@
11
package org.ezcode.codetest.infrastructure.persistence.repository.community.reply;
22

33
import org.ezcode.codetest.domain.community.model.entity.Reply;
4-
import org.springframework.data.domain.Page;
5-
import org.springframework.data.domain.Pageable;
6-
import org.springframework.data.jpa.repository.EntityGraph;
74
import org.springframework.data.jpa.repository.JpaRepository;
8-
import org.springframework.data.jpa.repository.Query;
9-
10-
public interface ReplyJpaRepository extends JpaRepository<Reply, Long> {
11-
12-
@EntityGraph(attributePaths = { "user" })
13-
@Query("""
14-
SELECT r
15-
FROM Reply r
16-
WHERE r.discussion.id = :discussionId
17-
AND r.isDeleted = false
18-
AND r.parent IS NULL
19-
ORDER BY r.createdAt DESC
20-
""")
21-
Page<Reply> findAllByDiscussionId(Long discussionId, Pageable pageable);
22-
23-
@EntityGraph(attributePaths = { "user" })
24-
@Query("""
25-
SELECT r
26-
FROM Reply r
27-
WHERE r.parent.id = :parentReplyId
28-
AND r.isDeleted = false
29-
ORDER BY r.createdAt ASC
30-
""")
31-
Page<Reply> findAllByParentReplyId(Long parentReplyId, Pageable pageable);
325

6+
public interface ReplyJpaRepository extends JpaRepository<Reply, Long>, ReplyQueryRepository {
337
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.ezcode.codetest.infrastructure.persistence.repository.community.reply;
2+
3+
import org.ezcode.codetest.domain.community.dto.ReplyQueryResult;
4+
import org.springframework.data.domain.Page;
5+
import org.springframework.data.domain.Pageable;
6+
7+
public interface ReplyQueryRepository {
8+
9+
// 댓글 목록 조회
10+
Page<ReplyQueryResult> findRepliesByDiscussionId(Long discussionId, Long currentUserId, Pageable pageable);
11+
12+
// 대댓글 목록 조회
13+
Page<ReplyQueryResult> findRepliesByParentId(Long parentId, Long currentUserId, Pageable pageable);
14+
15+
Long countByDiscussionId(Long discussionId);
16+
17+
Long countByParentId(Long parentId);
18+
19+
}

0 commit comments

Comments
 (0)