diff --git a/src/main/java/com/dodo/backend/board/controller/BoardController.java b/src/main/java/com/dodo/backend/board/controller/BoardController.java index f2c4de7..55ce059 100644 --- a/src/main/java/com/dodo/backend/board/controller/BoardController.java +++ b/src/main/java/com/dodo/backend/board/controller/BoardController.java @@ -74,6 +74,38 @@ public ResponseEntity getBoardList( return ResponseEntity.ok(boardService.getBoardList(page, size)); } + + /** + * 요청 사용자가 작성한 게시글 목록을 조회합니다. + * + * @param page 페이지 번호 + * @param size 페이지 크기 + * @param userDetails 인증된 사용자 정보 + * @return 내가 쓴 게시글 목록 조회 응답 + */ + @Operation(summary = "내가 쓴 게시글 목록 조회", description = "인증된 사용자가 작성한 게시글 목록을 페이지 단위로 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "내가 쓴 게시글 목록을 성공적으로 조회했습니다.", + content = @Content(schema = @Schema(implementation = BoardListResponse.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode = "401", description = "로그인이 필요한 기능입니다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode = "500", description = "서버 내부 오류가 발생했습니다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + }) + @GetMapping("/me") + public ResponseEntity getMyBoards( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @AuthenticationPrincipal UserDetails userDetails + ) { + UUID userId = UUID.fromString(userDetails.getUsername()); + log.info("내가 쓴 게시글 목록 조회 요청 수신 - User: {}, Page: {}, Size: {}", userId, page, size); + + return ResponseEntity.ok(boardService.getMyBoards(userId, page, size)); + } + /** * 새 게시글을 작성합니다. * diff --git a/src/main/java/com/dodo/backend/board/mapper/BoardMapper.java b/src/main/java/com/dodo/backend/board/mapper/BoardMapper.java index a97ac8d..a0c8606 100644 --- a/src/main/java/com/dodo/backend/board/mapper/BoardMapper.java +++ b/src/main/java/com/dodo/backend/board/mapper/BoardMapper.java @@ -6,6 +6,7 @@ import org.apache.ibatis.annotations.Param; import java.util.List; +import java.util.UUID; /** * 게시글(Board) 도메인의 동적 수정 쿼리를 담당하는 MyBatis Mapper 인터페이스입니다. @@ -32,6 +33,28 @@ List findBoardList( */ long countPublishedBoards(); + /** + * 특정 사용자가 작성한 게시글 목록을 페이지 단위로 조회합니다. + * + * @param userId 조회할 사용자 ID + * @param offset 조회 시작 위치 + * @param size 조회 개수 + * @return 내가 쓴 게시글 목록 조회 결과 + */ + List findMyBoardList( + @Param("userId") UUID userId, + @Param("offset") int offset, + @Param("size") int size + ); + + /** + * 특정 사용자가 작성한 게시글 전체 개수를 조회합니다. + * + * @param userId 조회할 사용자 ID + * @return 내가 쓴 게시글 전체 개수 + */ + long countMyBoards(@Param("userId") UUID userId); + /** * 게시글 제목과 내용을 선택적으로 수정합니다. * diff --git a/src/main/java/com/dodo/backend/board/service/BoardService.java b/src/main/java/com/dodo/backend/board/service/BoardService.java index 9056f15..8eae232 100644 --- a/src/main/java/com/dodo/backend/board/service/BoardService.java +++ b/src/main/java/com/dodo/backend/board/service/BoardService.java @@ -36,6 +36,16 @@ public interface BoardService { */ BoardListResponse getBoardList(int page, int size); + /** + * 요청 사용자가 작성한 게시글 목록을 조회합니다. + * + * @param userId 요청 사용자 ID + * @param page 조회할 페이지 번호 + * @param size 페이지 크기 + * @return 내가 쓴 게시글 목록 조회 응답 DTO + */ + BoardListResponse getMyBoards(UUID userId, int page, int size); + /** * 새 게시글을 생성하고 이미지 URL 목록을 게시글에 연결합니다. * diff --git a/src/main/java/com/dodo/backend/board/service/BoardServiceImpl.java b/src/main/java/com/dodo/backend/board/service/BoardServiceImpl.java index 73cc6cf..d7cf8ff 100644 --- a/src/main/java/com/dodo/backend/board/service/BoardServiceImpl.java +++ b/src/main/java/com/dodo/backend/board/service/BoardServiceImpl.java @@ -47,41 +47,13 @@ @RequiredArgsConstructor public class BoardServiceImpl implements BoardService { - /** - * Redis에 임시 저장 게시글 데이터를 저장할 때 사용하는 키 접두사입니다. - */ private static final String TEMP_SAVE_KEY_PREFIX = "board:temp-save:"; - - /** - * 임시 저장 데이터의 Redis 유지 기간입니다. - */ private static final long TEMP_SAVE_TTL_DAYS = 7L; - private static final int MAX_BOARD_LIST_SIZE = 100; - - /** - * 게시글 저장 및 단건 조회를 처리하는 JPA Repository입니다. - */ private final BoardRepository boardRepository; - - /** - * 사용자 엔티티 조회를 처리하는 서비스입니다. - */ private final UserService userService; - - /** - * 게시글 이미지 URL 저장, 조회, 교체, 삭제를 처리하는 서비스입니다. - */ private final ImageFileService imageFileService; - - /** - * 게시글 수정, 삭제, 조회수 증가처럼 동적 SQL이 필요한 작업을 처리하는 MyBatis Mapper입니다. - */ private final BoardMapper boardMapper; - - /** - * 게시글 임시 저장 데이터를 Redis에 저장하고 조회하기 위한 Template입니다. - */ private final RedisTemplate redisTemplate; /** @@ -124,6 +96,35 @@ public BoardListResponse getBoardList(int page, int size) { ); } + /** + * 요청 사용자가 작성한 게시글 목록을 조회합니다. + * + * @param userId 요청 사용자 ID + * @param page 조회할 페이지 번호 + * @param size 페이지 크기 + * @return 내가 쓴 게시글 목록 조회 응답 DTO + */ + @Override + @Transactional(readOnly = true) + public BoardListResponse getMyBoards(UUID userId, int page, int size) { + if (userId == null) { + throw new BoardException(INVALID_REQUEST); + } + validateBoardListRequest(page, size); + + int offset = page * size; + List queryResponses = boardMapper.findMyBoardList(userId, offset, size); + long totalElements = boardMapper.countMyBoards(userId); + + return BoardListResponse.toDto( + queryResponses, + totalElements, + page, + size, + "내가 쓴 게시글 목록을 성공적으로 조회했습니다." + ); + } + /** * 새 게시글을 생성하고 요청에 포함된 이미지 URL 목록을 게시글에 연결합니다. * diff --git a/src/main/java/com/dodo/backend/comment/controller/CommentController.java b/src/main/java/com/dodo/backend/comment/controller/CommentController.java index 661af80..d85da27 100644 --- a/src/main/java/com/dodo/backend/comment/controller/CommentController.java +++ b/src/main/java/com/dodo/backend/comment/controller/CommentController.java @@ -5,6 +5,7 @@ import com.dodo.backend.comment.dto.response.CommentResponse.CommentCreateResponse; import com.dodo.backend.comment.dto.response.CommentResponse.CommentListResponse; import com.dodo.backend.comment.dto.response.CommentResponse.CommentSimpleResponse; +import com.dodo.backend.comment.dto.response.CommentResponse.MyCommentListResponse; import com.dodo.backend.comment.service.CommentService; import com.dodo.backend.common.exception.ErrorResponse; import io.swagger.v3.oas.annotations.Operation; @@ -74,6 +75,37 @@ public ResponseEntity createComment( return ResponseEntity.ok(commentService.createComment(userId, request)); } + /** + * 요청 사용자가 작성한 댓글 목록을 조회합니다. + * + * @param page 페이지 번호 + * @param size 페이지 크기 + * @param userDetails 인증된 사용자 정보 + * @return 내가 쓴 댓글 목록 조회 응답 + */ + @Operation(summary = "내가 쓴 댓글 목록 조회", description = "인증된 사용자가 작성한 댓글 목록을 페이지 단위로 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "내가 쓴 댓글 목록을 성공적으로 조회했습니다.", + content = @Content(schema = @Schema(implementation = MyCommentListResponse.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode = "401", description = "로그인이 필요한 기능입니다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode = "500", description = "서버 내부 오류가 발생했습니다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + }) + @GetMapping("/me") + public ResponseEntity getMyComments( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @AuthenticationPrincipal UserDetails userDetails + ) { + UUID userId = UUID.fromString(userDetails.getUsername()); + log.info("내가 쓴 댓글 목록 조회 요청 수신 - User: {}, Page: {}, Size: {}", userId, page, size); + + return ResponseEntity.ok(commentService.getMyComments(userId, page, size)); + } + /** * 특정 게시글의 댓글 목록을 조회합니다. * diff --git a/src/main/java/com/dodo/backend/comment/dto/response/CommentResponse.java b/src/main/java/com/dodo/backend/comment/dto/response/CommentResponse.java index c68110c..73b1c33 100644 --- a/src/main/java/com/dodo/backend/comment/dto/response/CommentResponse.java +++ b/src/main/java/com/dodo/backend/comment/dto/response/CommentResponse.java @@ -1,7 +1,6 @@ package com.dodo.backend.comment.dto.response; import com.dodo.backend.comment.entity.Comment; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -14,7 +13,6 @@ /** * 댓글 API에서 사용하는 응답 DTO를 모아 둔 클래스입니다. */ -@Schema(description = "댓글 응답 DTO 그룹") public class CommentResponse { /** @@ -24,22 +22,31 @@ public class CommentResponse { @Builder @AllArgsConstructor @NoArgsConstructor - @Schema(description = "댓글 작성 응답") public static class CommentCreateResponse { - @Schema(description = "응답 메시지", example = "댓글이 성공적으로 작성되었습니다.") + /** + * 응답 메시지입니다. + */ private String message; - @Schema(description = "댓글 ID", example = "123") + /** + * 생성된 댓글 ID입니다. + */ private Long commentId; - @Schema(description = "댓글 내용", example = "좋은 정보 감사합니다!") + /** + * 생성된 댓글 내용입니다. + */ private String commentContent; - @Schema(description = "작성자 ID", example = "uuid-user-1") + /** + * 댓글 작성자 ID입니다. + */ private String userId; - @Schema(description = "작성자 닉네임", example = "멍멍이집사") + /** + * 댓글 작성자 닉네임입니다. + */ private String nickname; /** @@ -67,16 +74,21 @@ public static CommentCreateResponse toDto(Comment comment, String message) { @Builder @AllArgsConstructor @NoArgsConstructor - @Schema(description = "댓글 목록 조회 응답") public static class CommentListResponse { - @Schema(description = "응답 메시지", example = "댓글 목록을 성공적으로 조회했습니다.") + /** + * 응답 메시지입니다. + */ private String message; - @Schema(description = "페이지 정보") + /** + * 페이지 정보입니다. + */ private PageInfoResponse pageInfo; - @Schema(description = "댓글 목록") + /** + * 댓글 목록입니다. + */ private List data; /** @@ -117,19 +129,26 @@ public static CommentListResponse toDto( @Builder @AllArgsConstructor @NoArgsConstructor - @Schema(description = "페이지 정보 응답") public static class PageInfoResponse { - @Schema(description = "현재 페이지 번호", example = "0") + /** + * 현재 페이지 번호입니다. + */ private int page; - @Schema(description = "페이지 크기", example = "10") + /** + * 페이지 크기입니다. + */ private int size; - @Schema(description = "전체 요소 수", example = "48") + /** + * 전체 요소 수입니다. + */ private long totalElements; - @Schema(description = "전체 페이지 수", example = "5") + /** + * 전체 페이지 수입니다. + */ private int totalPages; /** @@ -161,22 +180,31 @@ public static PageInfoResponse toDto(int page, int size, long totalElements) { @Builder @AllArgsConstructor @NoArgsConstructor - @Schema(description = "댓글 목록 아이템 응답") public static class CommentItemResponse { - @Schema(description = "댓글 ID", example = "102") + /** + * 댓글 ID입니다. + */ private Long commentId; - @Schema(description = "부모 댓글 ID", example = "null") + /** + * 부모 댓글 ID입니다. + */ private Long parentCommentId; - @Schema(description = "댓글 내용", example = "두 번째 댓글입니다.") + /** + * 댓글 내용입니다. + */ private String commentContent; - @Schema(description = "작성자 정보") + /** + * 댓글 작성자 정보입니다. + */ private CommentAuthorResponse author; - @Schema(description = "댓글 작성 일시", example = "2025-10-14T14:30:00") + /** + * 댓글 작성 일시입니다. + */ private LocalDateTime createdAt; /** @@ -203,13 +231,16 @@ public static CommentItemResponse toDto(CommentListQueryResponse queryResponse) @Builder @AllArgsConstructor @NoArgsConstructor - @Schema(description = "댓글 작성자 응답") public static class CommentAuthorResponse { - @Schema(description = "작성자 ID", example = "uuid-user-1") + /** + * 작성자 ID입니다. + */ private String userId; - @Schema(description = "작성자 닉네임", example = "행복한강아지") + /** + * 작성자 닉네임입니다. + */ private String nickname; /** @@ -275,10 +306,11 @@ public static class CommentListQueryResponse { @Builder @AllArgsConstructor @NoArgsConstructor - @Schema(description = "댓글 단순 처리 응답") public static class CommentSimpleResponse { - @Schema(description = "응답 메시지", example = "댓글이 성공적으로 수정되었습니다.") + /** + * 응답 메시지입니다. + */ private String message; /** @@ -293,4 +325,157 @@ public static CommentSimpleResponse toDto(String message) { .build(); } } + + /** + * 내가 쓴 댓글 목록 조회 응답 DTO입니다. + */ + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class MyCommentListResponse { + + /** + * 응답 메시지입니다. + */ + private String message; + + /** + * 페이지 정보입니다. + */ + private PageInfoResponse pageInfo; + + /** + * 내가 쓴 댓글 목록입니다. + */ + private List data; + + /** + * MyBatis 조회 결과를 내가 쓴 댓글 목록 조회 응답 DTO로 변환합니다. + * + * @param queryResponses 내가 쓴 댓글 목록 조회 결과 + * @param page 현재 페이지 번호 + * @param size 페이지 크기 + * @param totalElements 전체 댓글 수 + * @param message 응답 메시지 + * @return 내가 쓴 댓글 목록 조회 응답 DTO + */ + public static MyCommentListResponse toDto( + List queryResponses, + int page, + int size, + long totalElements, + String message + ) { + List data = queryResponses == null + ? List.of() + : queryResponses.stream() + .map(MyCommentItemResponse::toDto) + .toList(); + + return MyCommentListResponse.builder() + .message(message) + .pageInfo(PageInfoResponse.toDto(page, size, totalElements)) + .data(data) + .build(); + } + } + + /** + * 내가 쓴 댓글 목록 아이템 응답 DTO입니다. + */ + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class MyCommentItemResponse { + + /** + * 댓글 ID입니다. + */ + private Long commentId; + + /** + * 댓글이 작성된 게시글 ID입니다. + */ + private Long boardId; + + /** + * 댓글이 작성된 게시글 제목입니다. + */ + private String boardTitle; + + /** + * 부모 댓글 ID입니다. + */ + private Long parentCommentId; + + /** + * 댓글 내용입니다. + */ + private String commentContent; + + /** + * 댓글 작성 일시입니다. + */ + private LocalDateTime createdAt; + + /** + * 내가 쓴 댓글 목록 조회 결과를 응답 아이템 DTO로 변환합니다. + * + * @param queryResponse 내가 쓴 댓글 목록 조회 결과 + * @return 내가 쓴 댓글 목록 아이템 응답 DTO + */ + public static MyCommentItemResponse toDto(MyCommentListQueryResponse queryResponse) { + return MyCommentItemResponse.builder() + .commentId(queryResponse.getCommentId()) + .boardId(queryResponse.getBoardId()) + .boardTitle(queryResponse.getBoardTitle()) + .parentCommentId(queryResponse.getParentCommentId()) + .commentContent(queryResponse.getCommentContent()) + .createdAt(queryResponse.getCreatedAt()) + .build(); + } + } + + /** + * 내가 쓴 댓글 목록 조회 MyBatis 결과 DTO입니다. + */ + @Getter + @Setter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class MyCommentListQueryResponse { + + /** + * 댓글 ID입니다. + */ + private Long commentId; + + /** + * 게시글 ID입니다. + */ + private Long boardId; + + /** + * 게시글 제목입니다. + */ + private String boardTitle; + + /** + * 부모 댓글 ID입니다. + */ + private Long parentCommentId; + + /** + * 댓글 내용입니다. + */ + private String commentContent; + + /** + * 댓글 작성 일시입니다. + */ + private LocalDateTime createdAt; + } } diff --git a/src/main/java/com/dodo/backend/comment/mapper/CommentMapper.java b/src/main/java/com/dodo/backend/comment/mapper/CommentMapper.java index b890dbe..97cfa51 100644 --- a/src/main/java/com/dodo/backend/comment/mapper/CommentMapper.java +++ b/src/main/java/com/dodo/backend/comment/mapper/CommentMapper.java @@ -1,10 +1,11 @@ package com.dodo.backend.comment.mapper; import com.dodo.backend.comment.dto.response.CommentResponse.CommentListQueryResponse; +import com.dodo.backend.comment.dto.response.CommentResponse.MyCommentListQueryResponse; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; - import java.util.List; +import java.util.UUID; /** * 댓글(Comment) 도메인의 목록 조회 및 동적 수정 쿼리를 담당하는 MyBatis Mapper 인터페이스입니다. @@ -34,6 +35,28 @@ List findCommentsByBoardId( */ long countCommentsByBoardId(@Param("boardId") Long boardId); + /** + * 특정 사용자가 작성한 댓글 목록을 페이지 단위로 조회합니다. + * + * @param userId 조회할 사용자 ID + * @param offset 조회 시작 위치 + * @param size 조회 개수 + * @return 내가 쓴 댓글 목록 조회 결과 + */ + List findMyComments( + @Param("userId") UUID userId, + @Param("offset") int offset, + @Param("size") int size + ); + + /** + * 특정 사용자가 작성한 댓글 전체 개수를 조회합니다. + * + * @param userId 조회할 사용자 ID + * @return 내가 쓴 댓글 전체 개수 + */ + long countMyComments(@Param("userId") UUID userId); + /** * 댓글 내용을 수정합니다. * diff --git a/src/main/java/com/dodo/backend/comment/service/CommentService.java b/src/main/java/com/dodo/backend/comment/service/CommentService.java index 5f01e58..fcd2423 100644 --- a/src/main/java/com/dodo/backend/comment/service/CommentService.java +++ b/src/main/java/com/dodo/backend/comment/service/CommentService.java @@ -5,6 +5,7 @@ import com.dodo.backend.comment.dto.response.CommentResponse.CommentCreateResponse; import com.dodo.backend.comment.dto.response.CommentResponse.CommentListResponse; import com.dodo.backend.comment.dto.response.CommentResponse.CommentSimpleResponse; +import com.dodo.backend.comment.dto.response.CommentResponse.MyCommentListResponse; import com.dodo.backend.comment.exception.CommentException; import java.util.UUID; @@ -34,6 +35,16 @@ public interface CommentService { */ CommentListResponse getComments(Long boardId, int page, int size); + /** + * 요청 사용자가 작성한 댓글 목록을 조회합니다. + * + * @param userId 요청 사용자 ID + * @param page 페이지 번호 + * @param size 페이지 크기 + * @return 내가 쓴 댓글 목록 조회 응답 DTO + */ + MyCommentListResponse getMyComments(UUID userId, int page, int size); + /** * 특정 댓글을 수정합니다. * diff --git a/src/main/java/com/dodo/backend/comment/service/CommentServiceImpl.java b/src/main/java/com/dodo/backend/comment/service/CommentServiceImpl.java index e4bb7b6..48b378b 100644 --- a/src/main/java/com/dodo/backend/comment/service/CommentServiceImpl.java +++ b/src/main/java/com/dodo/backend/comment/service/CommentServiceImpl.java @@ -9,6 +9,8 @@ import com.dodo.backend.comment.dto.response.CommentResponse.CommentListQueryResponse; import com.dodo.backend.comment.dto.response.CommentResponse.CommentListResponse; import com.dodo.backend.comment.dto.response.CommentResponse.CommentSimpleResponse; +import com.dodo.backend.comment.dto.response.CommentResponse.MyCommentListQueryResponse; +import com.dodo.backend.comment.dto.response.CommentResponse.MyCommentListResponse; import com.dodo.backend.comment.entity.Comment; import com.dodo.backend.comment.exception.CommentErrorCode; import com.dodo.backend.comment.exception.CommentException; @@ -55,8 +57,8 @@ public class CommentServiceImpl implements CommentService { * @return 댓글 작성 응답 DTO * @throws CommentException 잘못된 요청 또는 부모 댓글이 없는 경우 */ - @Override @Transactional + @Override public CommentCreateResponse createComment(UUID userId, CommentCreateRequest request) { if (userId == null || request == null || isBlank(request.getCommentContent())) { throw new CommentException(INVALID_REQUEST); @@ -82,8 +84,8 @@ public CommentCreateResponse createComment(UUID userId, CommentCreateRequest req * @param size 페이지 크기 * @return 댓글 목록 조회 응답 DTO */ - @Override @Transactional(readOnly = true) + @Override public CommentListResponse getComments(Long boardId, int page, int size) { validateCommentListRequest(boardId, page, size); @@ -103,6 +105,35 @@ public CommentListResponse getComments(Long boardId, int page, int size) { ); } + /** + * 요청 사용자가 작성한 댓글 목록을 조회합니다. + * + * @param userId 요청 사용자 ID + * @param page 페이지 번호 + * @param size 페이지 크기 + * @return 내가 쓴 댓글 목록 조회 응답 DTO + */ + @Transactional(readOnly = true) + @Override + public MyCommentListResponse getMyComments(UUID userId, int page, int size) { + if (userId == null) { + throw new CommentException(INVALID_REQUEST); + } + validateMyCommentListRequest(page, size); + + int offset = page * size; + List queryResponses = commentMapper.findMyComments(userId, offset, size); + long totalElements = commentMapper.countMyComments(userId); + + return MyCommentListResponse.toDto( + queryResponses, + page, + size, + totalElements, + "내가 쓴 댓글 목록을 성공적으로 조회했습니다." + ); + } + /** * 특정 댓글을 수정합니다. * @@ -112,8 +143,8 @@ public CommentListResponse getComments(Long boardId, int page, int size) { * @return 댓글 수정 응답 DTO * @throws CommentException 잘못된 요청, 댓글 없음, 수정 권한 없음인 경우 */ - @Override @Transactional + @Override public CommentSimpleResponse updateComment(UUID userId, Long commentId, CommentUpdateRequest request) { if (userId == null || request == null || isBlank(request.getCommentContent())) { throw new CommentException(INVALID_REQUEST); @@ -137,8 +168,8 @@ public CommentSimpleResponse updateComment(UUID userId, Long commentId, CommentU * @return 댓글 삭제 응답 DTO * @throws CommentException 잘못된 요청, 댓글 없음, 삭제 권한 없음인 경우 */ - @Override @Transactional + @Override public CommentSimpleResponse deleteComment(UUID userId, Long commentId) { if (userId == null) { throw new CommentException(INVALID_REQUEST); @@ -211,6 +242,18 @@ private void validateCommentListRequest(Long boardId, int page, int size) { } } + /** + * 내가 쓴 댓글 목록 조회 요청 값을 검증합니다. + * + * @param page 페이지 번호 + * @param size 페이지 크기 + */ + private void validateMyCommentListRequest(int page, int size) { + if (page < 0 || size <= 0 || size > MAX_COMMENT_LIST_SIZE || page > Integer.MAX_VALUE / size) { + throw new CommentException(INVALID_REQUEST); + } + } + /** * 댓글 작성자인지 검증합니다. * diff --git a/src/main/resources/com/dodo/backend/board/mapper/BoardMapper.xml b/src/main/resources/com/dodo/backend/board/mapper/BoardMapper.xml index bfce201..6176c37 100644 --- a/src/main/resources/com/dodo/backend/board/mapper/BoardMapper.xml +++ b/src/main/resources/com/dodo/backend/board/mapper/BoardMapper.xml @@ -3,54 +3,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - - - UPDATE board - - board_title = #{request.boardTitle}, - board_content = #{request.boardContent}, - modified_at = NOW() - - WHERE board_id = #{boardId} - - - - - UPDATE board - SET view_count = view_count + 1 - WHERE board_id = #{boardId} - - - - - UPDATE board - SET board_status = #{status}, - board_status_updated_at = NOW() - WHERE board_id = #{boardId} - - - - - + + + + + + + UPDATE board + + board_title = #{request.boardTitle}, + board_content = #{request.boardContent}, + modified_at = NOW() + + WHERE board_id = #{boardId} + + + + UPDATE board + SET view_count = view_count + 1 + WHERE board_id = #{boardId} + + + + UPDATE board + SET board_status = #{status}, + board_status_updated_at = NOW() + WHERE board_id = #{boardId} + diff --git a/src/main/resources/com/dodo/backend/comment/mapper/CommentMapper.xml b/src/main/resources/com/dodo/backend/comment/mapper/CommentMapper.xml new file mode 100644 index 0000000..9c9367c --- /dev/null +++ b/src/main/resources/com/dodo/backend/comment/mapper/CommentMapper.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + UPDATE comment + SET comment_content = #{commentContent}, + comment_modified_at = NOW() + WHERE comment_id = #{commentId} + + diff --git a/src/test/java/com/dodo/backend/board/controller/BoardMyBoardControllerTest.java b/src/test/java/com/dodo/backend/board/controller/BoardMyBoardControllerTest.java new file mode 100644 index 0000000..dc25c71 --- /dev/null +++ b/src/test/java/com/dodo/backend/board/controller/BoardMyBoardControllerTest.java @@ -0,0 +1,88 @@ +package com.dodo.backend.board.controller; + +import com.dodo.backend.board.dto.response.BoardResponse.BoardListItemResponse; +import com.dodo.backend.board.dto.response.BoardResponse.BoardListResponse; +import com.dodo.backend.board.service.BoardService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +/** + * 내가 쓴 게시글 목록 조회 컨트롤러 로직을 검증하는 테스트 클래스입니다. + */ +@ExtendWith(MockitoExtension.class) +class BoardMyBoardControllerTest { + + @Mock + private BoardService boardService; + + /** + * 내가 쓴 게시글 목록 조회 요청 시 200 상태 코드와 목록 응답을 반환하는지 검증합니다. + */ + @Test + @DisplayName("내가 쓴 게시글 목록 조회 성공: 200 상태 코드와 목록을 반환한다.") + void getMyBoards_Success() { + BoardController boardController = new BoardController(boardService); + UUID userId = UUID.randomUUID(); + UserDetails userDetails = createUserDetails(userId); + BoardListResponse serviceResponse = createBoardListResponse(); + + given(boardService.getMyBoards(userId, 0, 10)).willReturn(serviceResponse); + + ResponseEntity response = boardController.getMyBoards(0, 10, userDetails); + + assertNotNull(response); + assertEquals(200, response.getStatusCode().value()); + assertNotNull(response.getBody()); + assertEquals(1, response.getBody().getBoards().size()); + assertEquals(1L, response.getBody().getBoards().get(0).getBoardId()); + + verify(boardService).getMyBoards(userId, 0, 10); + } + + private UserDetails createUserDetails(UUID userId) { + return User.withUsername(userId.toString()) + .password("password") + .authorities(List.of()) + .build(); + } + + private BoardListResponse createBoardListResponse() { + BoardListItemResponse itemResponse = BoardListItemResponse.builder() + .boardId(1L) + .boardTitle("우리 강아지 자랑합니다") + .boardContentPreview("오늘 산책하다가 찍은 사진이에요. 너") + .thumbnailImageUrl("https://example.com/images/bori_1.jpg") + .nickname("자유로운산책") + .viewCount(51) + .commentCount(3L) + .likeCount(12L) + .dislikeCount(1L) + .createdAt(LocalDateTime.of(2026, 1, 31, 13, 52, 32)) + .modifiedAt(LocalDateTime.of(2026, 1, 31, 14, 10, 12)) + .build(); + + return BoardListResponse.builder() + .message("내가 쓴 게시글 목록을 성공적으로 조회했습니다.") + .boards(List.of(itemResponse)) + .totalPages(1) + .totalElements(1L) + .currentPage(0) + .pageSize(10) + .build(); + } +} diff --git a/src/test/java/com/dodo/backend/board/service/BoardMyBoardServiceTest.java b/src/test/java/com/dodo/backend/board/service/BoardMyBoardServiceTest.java new file mode 100644 index 0000000..f2eea78 --- /dev/null +++ b/src/test/java/com/dodo/backend/board/service/BoardMyBoardServiceTest.java @@ -0,0 +1,103 @@ +package com.dodo.backend.board.service; + +import com.dodo.backend.board.dto.response.BoardResponse.BoardListQueryResponse; +import com.dodo.backend.board.dto.response.BoardResponse.BoardListResponse; +import com.dodo.backend.board.exception.BoardErrorCode; +import com.dodo.backend.board.exception.BoardException; +import com.dodo.backend.board.mapper.BoardMapper; +import com.dodo.backend.board.repository.BoardRepository; +import com.dodo.backend.imagefile.service.ImageFileService; +import com.dodo.backend.user.service.UserService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.redis.core.RedisTemplate; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * 내가 쓴 게시글 목록 조회 비즈니스 로직을 검증하는 테스트 클래스입니다. + */ +@ExtendWith(MockitoExtension.class) +class BoardMyBoardServiceTest { + + @InjectMocks + private BoardServiceImpl boardService; + + @Mock + private BoardRepository boardRepository; + + @Mock + private UserService userService; + + @Mock + private ImageFileService imageFileService; + + @Mock + private BoardMapper boardMapper; + + @Mock + private RedisTemplate redisTemplate; + + /** + * 내가 쓴 게시글 목록 조회 시 페이지 정보와 게시글 목록을 반환하는지 검증합니다. + */ + @Test + @DisplayName("내가 쓴 게시글 목록 조회 성공: 게시글 목록과 페이지 정보를 반환한다.") + void getMyBoards_Success() { + UUID userId = UUID.randomUUID(); + BoardListQueryResponse queryResponse = BoardListQueryResponse.builder() + .boardId(1L) + .boardTitle("우리 강아지 자랑합니다") + .boardContent("오늘 산책하다가 찍은 사진이에요. 너무 귀엽죠?") + .thumbnailImageUrl("https://example.com/images/bori_1.jpg") + .nickname("자유로운산책") + .viewCount(51) + .commentCount(3L) + .likeCount(12L) + .dislikeCount(1L) + .createdAt(LocalDateTime.of(2026, 1, 31, 13, 52, 32)) + .modifiedAt(LocalDateTime.of(2026, 1, 31, 14, 10, 12)) + .build(); + + given(boardMapper.findMyBoardList(userId, 0, 10)).willReturn(List.of(queryResponse)); + given(boardMapper.countMyBoards(userId)).willReturn(1L); + + BoardListResponse response = boardService.getMyBoards(userId, 0, 10); + + assertNotNull(response); + assertEquals("내가 쓴 게시글 목록을 성공적으로 조회했습니다.", response.getMessage()); + assertEquals(1, response.getBoards().size()); + assertEquals(1L, response.getBoards().get(0).getBoardId()); + assertEquals(1L, response.getTotalElements()); + assertEquals(1, response.getTotalPages()); + + verify(boardMapper).findMyBoardList(userId, 0, 10); + verify(boardMapper).countMyBoards(userId); + } + + /** + * 사용자 ID가 없으면 잘못된 요청 예외가 발생하는지 검증합니다. + */ + @Test + @DisplayName("내가 쓴 게시글 목록 조회 실패: 사용자 ID가 없으면 예외가 발생한다.") + void getMyBoards_Fail_UserIdNull() { + BoardException exception = assertThrows(BoardException.class, + () -> boardService.getMyBoards(null, 0, 10)); + + assertEquals(BoardErrorCode.INVALID_REQUEST, exception.getErrorCode()); + verify(boardMapper, never()).findMyBoardList(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.anyInt(), org.mockito.ArgumentMatchers.anyInt()); + } +} diff --git a/src/test/java/com/dodo/backend/comment/controller/CommentMyCommentControllerTest.java b/src/test/java/com/dodo/backend/comment/controller/CommentMyCommentControllerTest.java new file mode 100644 index 0000000..30f3691 --- /dev/null +++ b/src/test/java/com/dodo/backend/comment/controller/CommentMyCommentControllerTest.java @@ -0,0 +1,82 @@ +package com.dodo.backend.comment.controller; + +import com.dodo.backend.comment.dto.response.CommentResponse.MyCommentItemResponse; +import com.dodo.backend.comment.dto.response.CommentResponse.MyCommentListResponse; +import com.dodo.backend.comment.dto.response.CommentResponse.PageInfoResponse; +import com.dodo.backend.comment.service.CommentService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +/** + * 내가 쓴 댓글 목록 조회 컨트롤러 로직을 검증하는 테스트 클래스입니다. + */ +@ExtendWith(MockitoExtension.class) +class CommentMyCommentControllerTest { + + @Mock + private CommentService commentService; + + /** + * 내가 쓴 댓글 목록 조회 요청 시 200 상태 코드와 목록 응답을 반환하는지 검증합니다. + */ + @Test + @DisplayName("내가 쓴 댓글 목록 조회 성공: 200 상태 코드와 목록을 반환한다.") + void getMyComments_Success() { + CommentController commentController = new CommentController(commentService); + UUID userId = UUID.randomUUID(); + UserDetails userDetails = createUserDetails(userId); + MyCommentListResponse serviceResponse = createMyCommentListResponse(); + + given(commentService.getMyComments(userId, 0, 10)).willReturn(serviceResponse); + + ResponseEntity response = commentController.getMyComments(0, 10, userDetails); + + assertNotNull(response); + assertEquals(200, response.getStatusCode().value()); + assertNotNull(response.getBody()); + assertEquals(1, response.getBody().getData().size()); + assertEquals(10L, response.getBody().getData().get(0).getCommentId()); + assertEquals(1L, response.getBody().getData().get(0).getBoardId()); + + verify(commentService).getMyComments(userId, 0, 10); + } + + private UserDetails createUserDetails(UUID userId) { + return User.withUsername(userId.toString()) + .password("password") + .authorities(List.of()) + .build(); + } + + private MyCommentListResponse createMyCommentListResponse() { + MyCommentItemResponse itemResponse = MyCommentItemResponse.builder() + .commentId(10L) + .boardId(1L) + .boardTitle("우리 강아지 자랑합니다") + .parentCommentId(null) + .commentContent("좋은 정보 감사합니다.") + .createdAt(LocalDateTime.of(2026, 1, 31, 13, 52, 32)) + .build(); + + return MyCommentListResponse.builder() + .message("내가 쓴 댓글 목록을 성공적으로 조회했습니다.") + .pageInfo(PageInfoResponse.toDto(0, 10, 1L)) + .data(List.of(itemResponse)) + .build(); + } +} diff --git a/src/test/java/com/dodo/backend/comment/service/CommentMyCommentServiceTest.java b/src/test/java/com/dodo/backend/comment/service/CommentMyCommentServiceTest.java new file mode 100644 index 0000000..ba38f0a --- /dev/null +++ b/src/test/java/com/dodo/backend/comment/service/CommentMyCommentServiceTest.java @@ -0,0 +1,94 @@ +package com.dodo.backend.comment.service; + +import com.dodo.backend.board.service.BoardService; +import com.dodo.backend.comment.dto.response.CommentResponse.MyCommentListQueryResponse; +import com.dodo.backend.comment.dto.response.CommentResponse.MyCommentListResponse; +import com.dodo.backend.comment.exception.CommentErrorCode; +import com.dodo.backend.comment.exception.CommentException; +import com.dodo.backend.comment.mapper.CommentMapper; +import com.dodo.backend.comment.repository.CommentRepository; +import com.dodo.backend.user.service.UserService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * 내가 쓴 댓글 목록 조회 비즈니스 로직을 검증하는 테스트 클래스입니다. + */ +@ExtendWith(MockitoExtension.class) +class CommentMyCommentServiceTest { + + @InjectMocks + private CommentServiceImpl commentService; + + @Mock + private CommentRepository commentRepository; + + @Mock + private CommentMapper commentMapper; + + @Mock + private BoardService boardService; + + @Mock + private UserService userService; + + /** + * 내가 쓴 댓글 목록 조회 시 게시글 정보가 포함된 댓글 목록을 반환하는지 검증합니다. + */ + @Test + @DisplayName("내가 쓴 댓글 목록 조회 성공: 게시글 정보가 포함된 댓글 목록을 반환한다.") + void getMyComments_Success() { + UUID userId = UUID.randomUUID(); + MyCommentListQueryResponse queryResponse = MyCommentListQueryResponse.builder() + .commentId(10L) + .boardId(1L) + .boardTitle("우리 강아지 자랑합니다") + .parentCommentId(null) + .commentContent("좋은 정보 감사합니다.") + .createdAt(LocalDateTime.of(2026, 1, 31, 13, 52, 32)) + .build(); + + given(commentMapper.findMyComments(userId, 0, 10)).willReturn(List.of(queryResponse)); + given(commentMapper.countMyComments(userId)).willReturn(1L); + + MyCommentListResponse response = commentService.getMyComments(userId, 0, 10); + + assertNotNull(response); + assertEquals("내가 쓴 댓글 목록을 성공적으로 조회했습니다.", response.getMessage()); + assertEquals(1, response.getData().size()); + assertEquals(10L, response.getData().get(0).getCommentId()); + assertEquals(1L, response.getData().get(0).getBoardId()); + assertEquals("우리 강아지 자랑합니다", response.getData().get(0).getBoardTitle()); + + verify(commentMapper).findMyComments(userId, 0, 10); + verify(commentMapper).countMyComments(userId); + } + + /** + * 사용자 ID가 없으면 잘못된 요청 예외가 발생하는지 검증합니다. + */ + @Test + @DisplayName("내가 쓴 댓글 목록 조회 실패: 사용자 ID가 없으면 예외가 발생한다.") + void getMyComments_Fail_UserIdNull() { + CommentException exception = assertThrows(CommentException.class, + () -> commentService.getMyComments(null, 0, 10)); + + assertEquals(CommentErrorCode.INVALID_REQUEST, exception.getErrorCode()); + verify(commentMapper, never()).findMyComments(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.anyInt(), org.mockito.ArgumentMatchers.anyInt()); + } +}