Skip to content

Commit 9b6dfef

Browse files
authored
Feat: 댓글 조회 API 구현 (#162) (#165)
* Feat: 댓글 목록 조회 API 구현 * Test: 테스트 작성 * Docs: Swagger 문서 작성
1 parent a89df69 commit 9b6dfef

File tree

11 files changed

+498
-4
lines changed

11 files changed

+498
-4
lines changed

src/main/java/com/back/domain/board/controller/CommentController.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.back.domain.board.controller;
22

3+
import com.back.domain.board.dto.CommentListResponse;
34
import com.back.domain.board.dto.CommentRequest;
45
import com.back.domain.board.dto.CommentResponse;
6+
import com.back.domain.board.dto.PageResponse;
57
import com.back.domain.board.service.CommentService;
68
import com.back.global.common.dto.RsData;
79
import com.back.global.security.user.CustomUserDetails;
810
import jakarta.validation.Valid;
911
import lombok.RequiredArgsConstructor;
12+
import org.springframework.data.domain.Pageable;
13+
import org.springframework.data.domain.Sort;
14+
import org.springframework.data.web.PageableDefault;
1015
import org.springframework.http.HttpStatus;
1116
import org.springframework.http.ResponseEntity;
1217
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -34,6 +39,21 @@ public ResponseEntity<RsData<CommentResponse>> createComment(
3439
));
3540
}
3641

42+
// 댓글 다건 조회
43+
@GetMapping
44+
public ResponseEntity<RsData<PageResponse<CommentListResponse>>> getComments(
45+
@PathVariable Long postId,
46+
@PageableDefault(sort = "createdAt", direction = Sort.Direction.ASC) Pageable pageable
47+
) {
48+
PageResponse<CommentListResponse> response = commentService.getComments(postId, pageable);
49+
return ResponseEntity
50+
.status(HttpStatus.OK)
51+
.body(RsData.success(
52+
"댓글 목록이 조회되었습니다.",
53+
response
54+
));
55+
}
56+
3757
// 댓글 수정
3858
@PutMapping("/{commentId}")
3959
public ResponseEntity<RsData<CommentResponse>> updateComment(
@@ -62,8 +82,8 @@ public ResponseEntity<RsData<Void>> deleteComment(
6282
return ResponseEntity
6383
.status(HttpStatus.OK)
6484
.body(RsData.success(
65-
"댓글이 삭제되었습니다.",
66-
null
85+
"댓글이 삭제되었습니다.",
86+
null
6787
));
6888
}
6989
}

src/main/java/com/back/domain/board/controller/CommentControllerDocs.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.back.domain.board.controller;
22

3+
import com.back.domain.board.dto.CommentListResponse;
34
import com.back.domain.board.dto.CommentRequest;
45
import com.back.domain.board.dto.CommentResponse;
6+
import com.back.domain.board.dto.PageResponse;
57
import com.back.global.common.dto.RsData;
68
import com.back.global.security.user.CustomUserDetails;
79
import io.swagger.v3.oas.annotations.Operation;
@@ -10,6 +12,7 @@
1012
import io.swagger.v3.oas.annotations.responses.ApiResponse;
1113
import io.swagger.v3.oas.annotations.responses.ApiResponses;
1214
import io.swagger.v3.oas.annotations.tags.Tag;
15+
import org.springframework.data.domain.Pageable;
1316
import org.springframework.http.ResponseEntity;
1417
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1518
import org.springframework.web.bind.annotation.*;
@@ -142,6 +145,115 @@ ResponseEntity<RsData<CommentResponse>> createComment(
142145
@AuthenticationPrincipal CustomUserDetails user
143146
);
144147

148+
@Operation(
149+
summary = "댓글 목록 조회",
150+
description = "특정 게시글에 달린 댓글 목록을 조회합니다. " +
151+
"부모 댓글 기준으로 페이징되며, 각 댓글의 대댓글(children) 목록이 함께 포함됩니다."
152+
)
153+
@ApiResponses({
154+
@ApiResponse(
155+
responseCode = "200",
156+
description = "댓글 목록 조회 성공",
157+
content = @Content(
158+
mediaType = "application/json",
159+
examples = @ExampleObject(value = """
160+
{
161+
"success": true,
162+
"code": "SUCCESS_200",
163+
"message": "댓글 목록이 조회되었습니다.",
164+
"data": {
165+
"content": [
166+
{
167+
"commentId": 1,
168+
"postId": 101,
169+
"parentId": null,
170+
"author": {
171+
"id": 5,
172+
"nickname": "홍길동"
173+
},
174+
"content": "부모 댓글",
175+
"likeCount": 2,
176+
"createdAt": "2025-09-22T11:30:00",
177+
"updatedAt": "2025-09-22T11:30:00",
178+
"children": [
179+
{
180+
"commentId": 2,
181+
"postId": 101,
182+
"parentId": 1,
183+
"author": {
184+
"id": 5,
185+
"nickname": "홍길동"
186+
},
187+
"content": "자식 댓글",
188+
"likeCount": 0,
189+
"createdAt": "2025-09-22T11:35:00",
190+
"updatedAt": "2025-09-22T11:35:00",
191+
"children": []
192+
}
193+
]
194+
}
195+
],
196+
"pageNumber": 0,
197+
"pageSize": 10,
198+
"totalElements": 1,
199+
"totalPages": 1,
200+
"last": true
201+
}
202+
}
203+
""")
204+
)
205+
),
206+
@ApiResponse(
207+
responseCode = "400",
208+
description = "잘못된 요청 (파라미터 오류)",
209+
content = @Content(
210+
mediaType = "application/json",
211+
examples = @ExampleObject(value = """
212+
{
213+
"success": false,
214+
"code": "COMMON_400",
215+
"message": "잘못된 요청입니다.",
216+
"data": null
217+
}
218+
""")
219+
)
220+
),
221+
@ApiResponse(
222+
responseCode = "404",
223+
description = "존재하지 않는 게시글",
224+
content = @Content(
225+
mediaType = "application/json",
226+
examples = @ExampleObject(value = """
227+
{
228+
"success": false,
229+
"code": "POST_001",
230+
"message": "존재하지 않는 게시글입니다.",
231+
"data": null
232+
}
233+
""")
234+
)
235+
),
236+
@ApiResponse(
237+
responseCode = "500",
238+
description = "서버 내부 오류",
239+
content = @Content(
240+
mediaType = "application/json",
241+
examples = @ExampleObject(value = """
242+
{
243+
"success": false,
244+
"code": "COMMON_500",
245+
"message": "서버 오류가 발생했습니다.",
246+
"data": null
247+
}
248+
""")
249+
)
250+
)
251+
})
252+
ResponseEntity<RsData<PageResponse<CommentListResponse>>> getComments(
253+
@PathVariable Long postId,
254+
Pageable pageable
255+
);
256+
145257
@Operation(
146258
summary = "댓글 수정",
147259
description = "로그인한 사용자가 자신이 작성한 댓글을 수정합니다."

src/main/java/com/back/domain/board/dto/AuthorResponse.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.back.domain.board.dto;
22

33
import com.back.domain.user.entity.User;
4+
import com.querydsl.core.annotations.QueryProjection;
45

56
/**
67
* 작성자 응답 DTO
@@ -12,6 +13,9 @@ public record AuthorResponse(
1213
Long id,
1314
String nickname
1415
) {
16+
@QueryProjection
17+
public AuthorResponse {}
18+
1519
public static AuthorResponse from(User user) {
1620
return new AuthorResponse(
1721
user.getId(),
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.back.domain.board.dto;
2+
3+
import com.querydsl.core.annotations.QueryProjection;
4+
import lombok.Getter;
5+
import lombok.Setter;
6+
7+
import java.time.LocalDateTime;
8+
import java.util.List;
9+
10+
/**
11+
* 댓글 목록 응답 DTO
12+
*/
13+
@Getter
14+
public class CommentListResponse {
15+
private final Long commentId;
16+
private final Long postId;
17+
private final Long parentId;
18+
private final AuthorResponse author;
19+
private final String content;
20+
21+
@Setter
22+
private long likeCount;
23+
24+
private final LocalDateTime createdAt;
25+
private final LocalDateTime updatedAt;
26+
27+
@Setter
28+
private List<CommentListResponse> children;
29+
30+
@QueryProjection
31+
public CommentListResponse(Long commentId,
32+
Long postId,
33+
Long parentId,
34+
AuthorResponse author,
35+
String content,
36+
long likeCount,
37+
LocalDateTime createdAt,
38+
LocalDateTime updatedAt,
39+
List<CommentListResponse> children) {
40+
this.commentId = commentId;
41+
this.postId = postId;
42+
this.parentId = parentId;
43+
this.author = author;
44+
this.content = content;
45+
this.likeCount = likeCount;
46+
this.createdAt = createdAt;
47+
this.updatedAt = updatedAt;
48+
this.children = children;
49+
}
50+
}

src/main/java/com/back/domain/board/entity/Comment.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ public Comment(Post post, User user, String content) {
4141
this.user = user;
4242
this.content = content;
4343
}
44+
45+
public Comment(Post post, User user, String content, Comment parent) {
46+
this.post = post;
47+
this.user = user;
48+
this.content = content;
49+
this.parent = parent;
50+
}
4451

4552
// -------------------- 비즈니스 메서드 --------------------
4653
// 댓글 업데이트

src/main/java/com/back/domain/board/repository/CommentRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
import org.springframework.stereotype.Repository;
66

77
@Repository
8-
public interface CommentRepository extends JpaRepository<Comment, Long> {
8+
public interface CommentRepository extends JpaRepository<Comment, Long>, CommentRepositoryCustom {
99
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.back.domain.board.repository;
2+
3+
import com.back.domain.board.dto.CommentListResponse;
4+
import org.springframework.data.domain.Page;
5+
import org.springframework.data.domain.Pageable;
6+
7+
public interface CommentRepositoryCustom {
8+
Page<CommentListResponse> getCommentsByPostId(Long postId, Pageable pageable);
9+
}

0 commit comments

Comments
 (0)