From 93d6e96688417abd277ea93f130e30f0cc426d14 Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:18:01 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Ref:=20comment=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/controller/CommentController.java | 4 +- .../docs/CommentControllerDocs.java | 20 ++- .../docs/CommentLikeControllerDocs.java | 2 +- .../domain/board/comment/entity/Comment.java | 20 ++- .../board/comment/entity/CommentLike.java | 7 + .../comment/service/CommentLikeService.java | 36 ++--- .../board/comment/service/CommentService.java | 150 +++++++++--------- .../comment/service/CommentServiceTest.java | 4 +- 8 files changed, 137 insertions(+), 106 deletions(-) diff --git a/src/main/java/com/back/domain/board/comment/controller/CommentController.java b/src/main/java/com/back/domain/board/comment/controller/CommentController.java index 429f8b1c..99d28e0e 100644 --- a/src/main/java/com/back/domain/board/comment/controller/CommentController.java +++ b/src/main/java/com/back/domain/board/comment/controller/CommentController.java @@ -41,7 +41,7 @@ public ResponseEntity> createComment( )); } - // 댓글 다건 조회 + // 댓글 목록 조회 @GetMapping public ResponseEntity>> getComments( @PathVariable Long postId, @@ -49,7 +49,7 @@ public ResponseEntity>> getComments( @PageableDefault(sort = "createdAt", direction = Sort.Direction.ASC) Pageable pageable ) { Long userId = (user != null) ? user.getUserId() : null; - PageResponse response = commentService.getComments(postId, userId, pageable); + PageResponse response = commentService.getComments(postId, pageable, userId); return ResponseEntity .status(HttpStatus.OK) .body(RsData.success( diff --git a/src/main/java/com/back/domain/board/comment/controller/docs/CommentControllerDocs.java b/src/main/java/com/back/domain/board/comment/controller/docs/CommentControllerDocs.java index 57cedc49..ae8d582e 100644 --- a/src/main/java/com/back/domain/board/comment/controller/docs/CommentControllerDocs.java +++ b/src/main/java/com/back/domain/board/comment/controller/docs/CommentControllerDocs.java @@ -357,10 +357,18 @@ ResponseEntity>> getComments( ), @ApiResponse( responseCode = "404", - description = "존재하지 않는 게시글 또는 댓글", + description = "존재하지 않는 사용자/게시글/댓글", content = @Content( mediaType = "application/json", examples = { + @ExampleObject(name = "존재하지 않는 사용자", value = """ + { + "success": false, + "code": "USER_001", + "message": "존재하지 않는 사용자입니다.", + "data": null + } + """), @ExampleObject(name = "존재하지 않는 게시글", value = """ { "success": false, @@ -473,10 +481,18 @@ ResponseEntity> updateComment( ), @ApiResponse( responseCode = "404", - description = "존재하지 않는 게시글 또는 댓글", + description = "존재하지 않는 사용자/게시글/댓글", content = @Content( mediaType = "application/json", examples = { + @ExampleObject(name = "존재하지 않는 사용자", value = """ + { + "success": false, + "code": "USER_001", + "message": "존재하지 않는 사용자입니다.", + "data": null + } + """), @ExampleObject(name = "존재하지 않는 게시글", value = """ { "success": false, diff --git a/src/main/java/com/back/domain/board/comment/controller/docs/CommentLikeControllerDocs.java b/src/main/java/com/back/domain/board/comment/controller/docs/CommentLikeControllerDocs.java index 3ef9ee92..4f813bcb 100644 --- a/src/main/java/com/back/domain/board/comment/controller/docs/CommentLikeControllerDocs.java +++ b/src/main/java/com/back/domain/board/comment/controller/docs/CommentLikeControllerDocs.java @@ -29,7 +29,7 @@ public interface CommentLikeControllerDocs { examples = @ExampleObject(value = """ { "success": true, - "code": "SUCCESS_201", + "code": "SUCCESS_200", "message": "댓글 좋아요가 등록되었습니다.", "data": { "commentId": 25, diff --git a/src/main/java/com/back/domain/board/comment/entity/Comment.java b/src/main/java/com/back/domain/board/comment/entity/Comment.java index 4119f9d6..74ef1924 100644 --- a/src/main/java/com/back/domain/board/comment/entity/Comment.java +++ b/src/main/java/com/back/domain/board/comment/entity/Comment.java @@ -58,7 +58,7 @@ public static Comment createRoot(Post post, User user, String content) { /** 대댓글 생성 */ public static Comment createChild(Post post, User user, String content, Comment parent) { Comment comment = new Comment(post, user, content, parent); - parent.getChildren().add(comment); + parent.addChildren(comment); return comment; } @@ -71,6 +71,14 @@ public void removeLike(CommentLike like) { this.commentLikes.remove(like); } + public void addChildren(Comment child) { + this.children.add(child); + } + + public void removeChildren(Comment child) { + this.children.remove(child); + } + // -------------------- 비즈니스 메서드 -------------------- /** 댓글 내용 수정 */ public void update(String content) { @@ -88,4 +96,14 @@ public void decreaseLikeCount() { this.likeCount--; } } + + // -------------------- 헬퍼 메서드 -------------------- + /** 댓글 삭제 시 연관관계 정리 */ + public void remove() { + this.post.removeComment(this); + this.user.removeComment(this); + if (this.parent != null) { + this.parent.removeChildren(this); + } + } } diff --git a/src/main/java/com/back/domain/board/comment/entity/CommentLike.java b/src/main/java/com/back/domain/board/comment/entity/CommentLike.java index 4807a331..91c22d07 100644 --- a/src/main/java/com/back/domain/board/comment/entity/CommentLike.java +++ b/src/main/java/com/back/domain/board/comment/entity/CommentLike.java @@ -29,4 +29,11 @@ public CommentLike(Comment comment, User user) { comment.addLike(this); user.addCommentLike(this); } + + // -------------------- 헬퍼 메서드 -------------------- + /** 댓글 좋아요 삭제 시 연관관계 정리 */ + public void remove() { + this.comment.removeLike(this); + this.user.removeCommentLike(this); + } } diff --git a/src/main/java/com/back/domain/board/comment/service/CommentLikeService.java b/src/main/java/com/back/domain/board/comment/service/CommentLikeService.java index cb2946d3..56f87eb6 100644 --- a/src/main/java/com/back/domain/board/comment/service/CommentLikeService.java +++ b/src/main/java/com/back/domain/board/comment/service/CommentLikeService.java @@ -26,11 +26,10 @@ public class CommentLikeService { /** * 댓글 좋아요 서비스 - * 1. User 조회 - * 2. Comment 조회 - * 3. 이미 존재하는 경우 예외 처리 - * 4. CommentLike 저장 및 likeCount 증가 - * 5. 알림 이벤트 발행 (자기 글이 아닐 경우) + * + * @param commentId 댓글 ID + * @param userId 사용자 ID + * @return 댓글 좋아요 응답 DTO */ public CommentLikeResponse likeComment(Long commentId, Long userId) { // User 조회 @@ -41,21 +40,20 @@ public CommentLikeResponse likeComment(Long commentId, Long userId) { Comment comment = commentRepository.findById(commentId) .orElseThrow(() -> new CustomException(ErrorCode.COMMENT_NOT_FOUND)); - // 이미 좋아요를 누른 경우 예외 + // 이미 좋아요한 경우 예외 if (commentLikeRepository.existsByUserIdAndCommentId(userId, commentId)) { throw new CustomException(ErrorCode.COMMENT_ALREADY_LIKED); } - // 좋아요 수 증가 + // CommentLike 생성 및 좋아요 수 증가 처리 comment.increaseLikeCount(); - - // CommentLike 저장 및 응답 반환 commentLikeRepository.save(new CommentLike(comment, user)); + // 댓글 좋아요 이벤트 발행 (자기 댓글이 아닐 때만) if (!comment.getUser().getId().equals(userId)) { eventPublisher.publishEvent( new CommentLikedEvent( - userId, // 좋아요 누른 사람 + userId, // 좋아요한 사용자 comment.getUser().getId(), // 댓글 작성자 comment.getPost().getId(), // 게시글 ID comment.getId(), @@ -69,10 +67,10 @@ public CommentLikeResponse likeComment(Long commentId, Long userId) { /** * 댓글 좋아요 취소 서비스 - * 1. User 조회 - * 2. Comment 조회 - * 3. CommentLike 조회 - * 4. CommentLike 삭제 및 likeCount 감소 + * + * @param commentId 댓글 ID + * @param userId 사용자 ID + * @return 댓글 좋아요 응답 DTO */ public CommentLikeResponse cancelLikeComment(Long commentId, Long userId) { // User 조회 @@ -87,17 +85,11 @@ public CommentLikeResponse cancelLikeComment(Long commentId, Long userId) { CommentLike commentLike = commentLikeRepository.findByUserIdAndCommentId(userId, commentId) .orElseThrow(() -> new CustomException(ErrorCode.COMMENT_LIKE_NOT_FOUND)); - // 연관관계 제거 - comment.removeLike(commentLike); - user.removeCommentLike(commentLike); - - // CommentLike 삭제 + // CommentLike 삭제 및 좋아요 수 감소 처리 + commentLike.remove(); commentLikeRepository.delete(commentLike); - - // 좋아요 수 감소 comment.decreaseLikeCount(); - // 응답 반환 return CommentLikeResponse.from(comment); } } diff --git a/src/main/java/com/back/domain/board/comment/service/CommentService.java b/src/main/java/com/back/domain/board/comment/service/CommentService.java index 43f49eef..e51fd2e9 100644 --- a/src/main/java/com/back/domain/board/comment/service/CommentService.java +++ b/src/main/java/com/back/domain/board/comment/service/CommentService.java @@ -25,7 +25,9 @@ import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; @Service @RequiredArgsConstructor @@ -37,15 +39,13 @@ public class CommentService { private final PostRepository postRepository; private final ApplicationEventPublisher eventPublisher; - // TODO: 연관관계 고려, 메서드 명, 중복 코드 제거, 주석 통일 - // TODO: comment 끝나면 post도 해야 함.. entity > DTO > Repo > Service > Controller > Docs 순으로.. /** * 댓글 생성 서비스 - * 1. User 조회 - * 2. Post 조회 - * 3. Comment 생성 및 저장 - * 4. 댓글 작성 이벤트 발행 - * 5. CommentResponse 반환 + * + * @param postId 대상 게시글 ID + * @param request 댓글 작성 요청 본문 + * @param userId 사용자 ID + * @return 생성된 댓글 응답 DTO */ public CommentResponse createComment(Long postId, CommentRequest request, Long userId) { // User 조회 @@ -56,10 +56,8 @@ public CommentResponse createComment(Long postId, CommentRequest request, Long u Post post = postRepository.findById(postId) .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); - // CommentCount 증가 + // Comment 생성 및 댓글 수 증가 처리 post.increaseCommentCount(); - - // Comment 생성 및 저장 Comment comment = Comment.createRoot(post, user, request.content()); commentRepository.save(comment); @@ -78,67 +76,72 @@ public CommentResponse createComment(Long postId, CommentRequest request, Long u } /** - * 댓글 다건 조회 서비스 - * 1. Post 조회 - * 2. 해당 Post의 댓글 전체 조회 (대댓글 포함, 페이징) - * 3. PageResponse 반환 + * 댓글 목록 조회 서비스 + * + * @param postId 게시글 ID + * @param userId 사용자 ID (선택) + * @param pageable 페이징 정보 + * @return 댓글 목록 페이지 응답 DTO */ @Transactional(readOnly = true) - public PageResponse getComments(Long postId, Pageable pageable) { - // Post 조회 - Post post = postRepository.findById(postId) - .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); + public PageResponse getComments(Long postId, Pageable pageable, Long userId) { + // Post 검증 + if (!postRepository.existsById(postId)) { + throw new CustomException(ErrorCode.POST_NOT_FOUND); + } - // 댓글 목록 조회 + // 댓글 목록 페이지 조회 (대댓글 포함) Page comments = commentRepository.findCommentsByPostId(postId, pageable); + // 로그인 사용자 추가 데이터 설정 (좋아요 여부) + if (userId != null && !comments.isEmpty()) { + applyLikedByUser(comments.getContent(), userId); + } + return PageResponse.from(comments); } - // TODO: 추후 메서드 통합 및 리팩토링 - @Transactional(readOnly = true) - public PageResponse getComments(Long postId, Long userId, Pageable pageable) { - // 기본 댓글 목록 - PageResponse response = getComments(postId, pageable); - - // 로그인 사용자용 로직 - if (userId != null) { - // 댓글 ID 수집 - List commentIds = response.items().stream() - .map(CommentListResponse::getCommentId) - .toList(); - - if (commentIds.isEmpty()) return response; - - // QueryDSL 기반 좋아요 ID 조회 (단일 쿼리) - List likedIds = commentLikeRepository.findLikedCommentIdsIn(userId, commentIds); - Set likedSet = new HashSet<>(likedIds); - - // likedByMe 세팅 - response.items().forEach(c -> c.setLikedByMe(likedSet.contains(c.getCommentId()))); - - // 자식 댓글에도 동일 적용 - response.items().forEach(parent -> { - if (parent.getChildren() != null) { - parent.getChildren().forEach(child -> - child.setLikedByMe(likedSet.contains(child.getCommentId())) - ); - } - }); - } - - return response; + /** + * 댓글 좋아요 여부(likedByMe) 설정 + */ + private void applyLikedByUser(List comments, Long userId) { + // 모든 댓글 ID 수집 + List commentIds = comments.stream() + .flatMap(parent -> { + Stream childIds = Optional.ofNullable(parent.getChildren()) + .orElse(List.of()) + .stream() + .map(CommentListResponse::getCommentId); + return Stream.concat(Stream.of(parent.getCommentId()), childIds); + }) + .toList(); + + // 사용자가 좋아요한 댓글 ID 조회 (단일 쿼리) + Set likedSet = new HashSet<>(commentLikeRepository.findLikedCommentIdsIn(userId, commentIds)); + + // likedByMe 플래그 설정 + comments.forEach(parent -> { + parent.setLikedByMe(likedSet.contains(parent.getCommentId())); + Optional.ofNullable(parent.getChildren()) + .ifPresent(children -> children.forEach(child -> + child.setLikedByMe(likedSet.contains(child.getCommentId())))); + }); } /** * 댓글 수정 서비스 - * 1. Post 조회 - * 2. Comment 조회 - * 3. 작성자 검증 - * 4. Comment 업데이트 (내용) - * 5. CommentResponse 반환 + * + * @param postId 게시글 ID + * @param commentId 댓글 ID + * @param request 댓글 수정 요청 본문 + * @param userId 사용자 ID + * @return 수정된 댓글 응답 DTO */ public CommentResponse updateComment(Long postId, Long commentId, CommentRequest request, Long userId) { + // User 조회 + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + // Post 조회 Post post = postRepository.findById(postId) .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); @@ -154,17 +157,16 @@ public CommentResponse updateComment(Long postId, Long commentId, CommentRequest // Comment 업데이트 comment.update(request.content()); - - // 응답 반환 + return CommentResponse.from(comment); } /** * 댓글 삭제 서비스 - * 1. Post 조회 - * 2. Comment 조회 - * 3. 작성자 검증 - * 4. Comment 삭제 + * + * @param postId 게시글 ID + * @param commentId 댓글 ID + * @param userId 사용자 ID */ public void deleteComment(Long postId, Long commentId, Long userId) { // User 조회 @@ -184,22 +186,20 @@ public void deleteComment(Long postId, Long commentId, Long userId) { throw new CustomException(ErrorCode.COMMENT_NO_PERMISSION); } - // 연관관계 제거 - post.removeComment(comment); - user.removeComment(comment); - - // Comment 삭제 + // Comment 삭제 및 댓글 수 감소 처리 + comment.remove(); commentRepository.delete(comment); + post.decreaseCommentCount(); } /** * 대댓글 생성 서비스 - * 1. User 조회 - * 2. Post 조회 - * 3. 부모 Comment 조회 - * 4. 부모 및 depth 검증 - * 5. 자식 Comment 생성 - * 6. Comment 저장 및 ReplyResponse 반환 + * + * @param postId 게시글 ID + * @param parentCommentId 부모 댓글 ID + * @param request 대댓글 작성 요청 본문 + * @param userId 사용자 ID + * @return 생성된 대댓글 응답 DTO */ public ReplyResponse createReply(Long postId, Long parentCommentId, CommentRequest request, Long userId) { // User 조회 @@ -226,8 +226,6 @@ public ReplyResponse createReply(Long postId, Long parentCommentId, CommentReque // 자식 Comment 생성 Comment reply = new Comment(post, user, request.content(), parent); - - // 저장 및 응답 반환 commentRepository.save(reply); // 대댓글 작성 이벤트 발행 diff --git a/src/test/java/com/back/domain/board/comment/service/CommentServiceTest.java b/src/test/java/com/back/domain/board/comment/service/CommentServiceTest.java index bb2e71a4..90e0ac1f 100644 --- a/src/test/java/com/back/domain/board/comment/service/CommentServiceTest.java +++ b/src/test/java/com/back/domain/board/comment/service/CommentServiceTest.java @@ -139,7 +139,7 @@ void getComments_success() { Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "createdAt")); // when - PageResponse response = commentService.getComments(post.getId(), pageable); + PageResponse response = commentService.getComments(post.getId(), pageable, null); // then assertThat(response.items()).hasSize(1); // 부모만 페이징 결과 @@ -155,7 +155,7 @@ void getComments_fail_postNotFound() { Pageable pageable = PageRequest.of(0, 10); assertThatThrownBy(() -> - commentService.getComments(999L, pageable) + commentService.getComments(999L, pageable, null) ).isInstanceOf(CustomException.class) .hasMessage(ErrorCode.POST_NOT_FOUND.getMessage()); } From 783176182318aa5f42c6f419b40f8eab0437247e Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:20:43 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Ref:=20post=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/post/controller/PostController.java | 5 +- .../docs/PostCategoryControllerDocs.java | 2 +- .../controller/docs/PostControllerDocs.java | 38 ++++++-- .../back/domain/board/post/entity/Post.java | 5 + .../board/post/entity/PostBookmark.java | 7 ++ .../domain/board/post/entity/PostLike.java | 7 ++ .../post/service/PostBookmarkService.java | 33 +++---- .../post/service/PostCategoryService.java | 16 ++-- .../board/post/service/PostLikeService.java | 43 ++++----- .../board/post/service/PostService.java | 94 ++++++++++--------- .../board/post/service/PostServiceTest.java | 4 +- 11 files changed, 137 insertions(+), 117 deletions(-) diff --git a/src/main/java/com/back/domain/board/post/controller/PostController.java b/src/main/java/com/back/domain/board/post/controller/PostController.java index c6662ccd..573d431b 100644 --- a/src/main/java/com/back/domain/board/post/controller/PostController.java +++ b/src/main/java/com/back/domain/board/post/controller/PostController.java @@ -65,9 +65,8 @@ public ResponseEntity> getPost( @PathVariable Long postId, @AuthenticationPrincipal CustomUserDetails user ) { - PostDetailResponse response = (user != null) - ? postService.getPostWithUser(postId, user.getUserId()) - : postService.getPost(postId); + Long userId = (user != null) ? user.getUserId() : null; + PostDetailResponse response = postService.getPost(postId, userId); return ResponseEntity .status(HttpStatus.OK) .body(RsData.success( diff --git a/src/main/java/com/back/domain/board/post/controller/docs/PostCategoryControllerDocs.java b/src/main/java/com/back/domain/board/post/controller/docs/PostCategoryControllerDocs.java index 41baa3b5..0703b240 100644 --- a/src/main/java/com/back/domain/board/post/controller/docs/PostCategoryControllerDocs.java +++ b/src/main/java/com/back/domain/board/post/controller/docs/PostCategoryControllerDocs.java @@ -32,7 +32,7 @@ public interface PostCategoryControllerDocs { examples = @ExampleObject(value = """ { "success": true, - "code": "SUCCESS_201", + "code": "SUCCESS_200", "message": "카테고리가 생성되었습니다.", "data": { "id": 80, diff --git a/src/main/java/com/back/domain/board/post/controller/docs/PostControllerDocs.java b/src/main/java/com/back/domain/board/post/controller/docs/PostControllerDocs.java index e147df34..90acf090 100644 --- a/src/main/java/com/back/domain/board/post/controller/docs/PostControllerDocs.java +++ b/src/main/java/com/back/domain/board/post/controller/docs/PostControllerDocs.java @@ -409,10 +409,18 @@ ResponseEntity> getPost( ), @ApiResponse( responseCode = "404", - description = "존재하지 않는 게시글 또는 카테고리", + description = "존재하지 않는 사용자/게시글/카테고리", content = @Content( mediaType = "application/json", examples = { + @ExampleObject(name = "존재하지 않는 사용자", value = """ + { + "success": false, + "code": "USER_001", + "message": "존재하지 않는 사용자입니다.", + "data": null + } + """), @ExampleObject(name = "존재하지 않는 게시글", value = """ { "success": false, @@ -539,17 +547,27 @@ ResponseEntity> updatePost( ), @ApiResponse( responseCode = "404", - description = "존재하지 않는 게시글", + description = "존재하지 않는 사용자 또는 게시글", content = @Content( mediaType = "application/json", - examples = @ExampleObject(value = """ - { - "success": false, - "code": "POST_001", - "message": "존재하지 않는 게시글입니다.", - "data": null - } - """) + examples = { + @ExampleObject(name = "존재하지 않는 사용자", value = """ + { + "success": false, + "code": "USER_001", + "message": "존재하지 않는 사용자입니다.", + "data": null + } + """), + @ExampleObject(name = "존재하지 않는 게시글", value = """ + { + "success": false, + "code": "POST_001", + "message": "존재하지 않는 게시글입니다.", + "data": null + } + """) + } ) ), @ApiResponse( diff --git a/src/main/java/com/back/domain/board/post/entity/Post.java b/src/main/java/com/back/domain/board/post/entity/Post.java index 863e21ce..f3efa1c8 100644 --- a/src/main/java/com/back/domain/board/post/entity/Post.java +++ b/src/main/java/com/back/domain/board/post/entity/Post.java @@ -159,4 +159,9 @@ public List getCategories() { .map(PostCategoryMapping::getCategory) .toList(); } + + /** 게시글 삭제 시 연관관계 정리 */ + public void remove() { + this.user.removePost(this); + } } diff --git a/src/main/java/com/back/domain/board/post/entity/PostBookmark.java b/src/main/java/com/back/domain/board/post/entity/PostBookmark.java index 6f8bdbe7..fc0ccdae 100644 --- a/src/main/java/com/back/domain/board/post/entity/PostBookmark.java +++ b/src/main/java/com/back/domain/board/post/entity/PostBookmark.java @@ -29,4 +29,11 @@ public PostBookmark(Post post, User user) { post.addBookmark(this); user.addPostBookmark(this); } + + // -------------------- 헬퍼 메서드 -------------------- + /** 게시글 북마크 삭제 시 연관관계 정리 */ + public void remove() { + this.post.removeBookmark(this); + this.user.removePostBookmark(this); + } } diff --git a/src/main/java/com/back/domain/board/post/entity/PostLike.java b/src/main/java/com/back/domain/board/post/entity/PostLike.java index 8c0d5712..2f42850e 100644 --- a/src/main/java/com/back/domain/board/post/entity/PostLike.java +++ b/src/main/java/com/back/domain/board/post/entity/PostLike.java @@ -29,4 +29,11 @@ public PostLike(Post post, User user) { post.addLike(this); user.addPostLike(this); } + + // -------------------- 헬퍼 메서드 -------------------- + /** 게시글 좋아요 삭제 시 연관관계 정리 */ + public void remove() { + this.post.removeLike(this); + this.user.removePostLike(this); + } } diff --git a/src/main/java/com/back/domain/board/post/service/PostBookmarkService.java b/src/main/java/com/back/domain/board/post/service/PostBookmarkService.java index 54a44f18..cc3c3d2b 100644 --- a/src/main/java/com/back/domain/board/post/service/PostBookmarkService.java +++ b/src/main/java/com/back/domain/board/post/service/PostBookmarkService.java @@ -23,11 +23,10 @@ public class PostBookmarkService { /** * 게시글 북마크 서비스 - * 1. User 조회 - * 2. Post 조회 - * 3. 이미 존재하는 경우 예외 처리 - * 4. PostBookmark 저장 및 bookmarkCount 증가 - * 5. PostBookmarkResponse 반환 + * + * @param postId 게시글 ID + * @param userId 사용자 ID + * @return 게시글 북마크 응답 DTO */ public PostBookmarkResponse bookmarkPost(Long postId, Long userId) { // User 조회 @@ -43,21 +42,19 @@ public PostBookmarkResponse bookmarkPost(Long postId, Long userId) { throw new CustomException(ErrorCode.BOOKMARK_ALREADY_EXISTS); } - // 북마크 수 증가 + // PostBookmark 저장 및 북마크 수 증가 처리 post.increaseBookmarkCount(); - - // PostBookmark 저장 및 응답 반환 postBookmarkRepository.save(new PostBookmark(post, user)); + return PostBookmarkResponse.from(post); } /** * 게시글 북마크 취소 서비스 - * 1. User 조회 - * 2. Post 조회 - * 3. PostBookmark 조회 - * 4. PostBookmark 삭제 및 bookmarkCount 감소 - * 5. PostBookmarkResponse 반환 + * + * @param postId 게시글 ID + * @param userId 사용자 ID + * @return 게시글 북마크 응답 DTO */ public PostBookmarkResponse cancelBookmarkPost(Long postId, Long userId) { // User 조회 @@ -72,17 +69,11 @@ public PostBookmarkResponse cancelBookmarkPost(Long postId, Long userId) { PostBookmark postBookmark = postBookmarkRepository.findByUserIdAndPostId(userId, postId) .orElseThrow(() -> new CustomException(ErrorCode.BOOKMARK_NOT_FOUND)); - // 연관관계 제거 - post.removeBookmark(postBookmark); - user.removePostBookmark(postBookmark); - - // PostBookmark 삭제 + // PostBookmark 삭제 및 북마크 수 감소 처리 + postBookmark.remove(); postBookmarkRepository.delete(postBookmark); - - // 북마크 수 감소 post.decreaseBookmarkCount(); - // 응답 반환 return PostBookmarkResponse.from(post); } } diff --git a/src/main/java/com/back/domain/board/post/service/PostCategoryService.java b/src/main/java/com/back/domain/board/post/service/PostCategoryService.java index 0f93c18b..a55d548c 100644 --- a/src/main/java/com/back/domain/board/post/service/PostCategoryService.java +++ b/src/main/java/com/back/domain/board/post/service/PostCategoryService.java @@ -23,13 +23,12 @@ public class PostCategoryService { /** * 카테고리 생성 서비스 - * 1. User 조회 - * 2. 이미 존재하는 경우 예외 - * 3. PostCategory 생성 - * 4. PostCategory 저장 및 CategoryResponse 반환 + * + * @param request 카테고리 생성 요청 본문 + * @param userId 사용자 ID + * @return 생성된 카테고리 응답 DTO */ public CategoryResponse createCategory(CategoryRequest request, Long userId) { - // User 조회 User user = userRepository.findById(userId) .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); @@ -41,16 +40,15 @@ public CategoryResponse createCategory(CategoryRequest request, Long userId) { // PostCategory 생성 PostCategory category = new PostCategory(request.name(), request.type()); - - // PostCategory 저장 및 응답 반환 PostCategory saved = postCategoryRepository.save(category); + return CategoryResponse.from(saved); } /** * 카테고리 전체 조회 서비스 - * 1. PostCategory 전체 조회 - * 2. List 반환 + * + * @return 카테고리 응답 DTO 리스트 */ @Transactional(readOnly = true) public List getAllCategories() { diff --git a/src/main/java/com/back/domain/board/post/service/PostLikeService.java b/src/main/java/com/back/domain/board/post/service/PostLikeService.java index ae3c5028..5be34854 100644 --- a/src/main/java/com/back/domain/board/post/service/PostLikeService.java +++ b/src/main/java/com/back/domain/board/post/service/PostLikeService.java @@ -25,13 +25,11 @@ public class PostLikeService { private final ApplicationEventPublisher eventPublisher; /** - * 게시글 좋아요 서비스 - * 1. User 조회 - * 2. Post 조회 - * 3. 이미 존재하는 경우 예외 처리 - * 4. PostLike 저장 및 likeCount 증가 - * 5. 알림 이벤트 발행 (자기 글이 아닐 경우) - * 6. PostLikeResponse 반환 + * 게시글 좋아요 생성 서비스 + * + * @param postId 게시글 ID + * @param userId 사용자 ID + * @return 게시글 좋아요 응답 DTO */ public PostLikeResponse likePost(Long postId, Long userId) { // User 조회 @@ -42,22 +40,20 @@ public PostLikeResponse likePost(Long postId, Long userId) { Post post = postRepository.findById(postId) .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); - // 이미 좋아요를 누른 경우 예외 + // 이미 좋아요한 경우 예외 if (postLikeRepository.existsByUserIdAndPostId(userId, postId)) { throw new CustomException(ErrorCode.POST_ALREADY_LIKED); } - // 좋아요 수 증가 + // PostLike 저장 및 좋아요 수 증가 처리 post.increaseLikeCount(); - - // PostLike 저장 및 응답 반환 postLikeRepository.save(new PostLike(post, user)); - // 알림 이벤트 발행 (자기 자신의 글이 아닐 때만) + // 게시글 좋아요 이벤트 발행 (자기 자신의 글이 아닐 때만) if (!post.getUser().getId().equals(userId)) { eventPublisher.publishEvent( new PostLikedEvent( - userId, // 좋아요 누른 사람 + userId, // 좋아요한 사용자 post.getUser().getId(), // 게시글 작성자 post.getId(), post.getTitle() @@ -69,12 +65,11 @@ public PostLikeResponse likePost(Long postId, Long userId) { } /** - * 게시글 좋아요 취소 서비스 - * 1. User 조회 - * 2. Post 조회 - * 3. PostLike 조회 - * 4. PostLike 삭제 및 likeCount 감소 - * 5. PostLikeResponse 반환 + * 게시글 좋아요 삭제 서비스 + * + * @param postId 게시글 ID + * @param userId 사용자 ID + * @return 게시글 좋아요 응답 DTO */ public PostLikeResponse cancelLikePost(Long postId, Long userId) { // User 조회 @@ -89,17 +84,11 @@ public PostLikeResponse cancelLikePost(Long postId, Long userId) { PostLike postLike = postLikeRepository.findByUserIdAndPostId(userId, postId) .orElseThrow(() -> new CustomException(ErrorCode.POST_LIKE_NOT_FOUND)); - // 연관관계 제거 - post.removeLike(postLike); - user.removePostLike(postLike); - - // PostLike 삭제 + // PostLike 삭제 및 좋아요 수 감소 처리 + postLike.remove(); postLikeRepository.delete(postLike); - - // 좋아요 수 감소 post.decreaseLikeCount(); - // 응답 반환 return PostLikeResponse.from(post); } } diff --git a/src/main/java/com/back/domain/board/post/service/PostService.java b/src/main/java/com/back/domain/board/post/service/PostService.java index d290d819..7249d850 100644 --- a/src/main/java/com/back/domain/board/post/service/PostService.java +++ b/src/main/java/com/back/domain/board/post/service/PostService.java @@ -35,29 +35,23 @@ public class PostService { /** * 게시글 생성 서비스 - * 1. User 조회 - * 2. Post 생성 - * 3. Category 매핑 - * 4. Post 저장 및 PostResponse 반환 + * + * @param request 게시글 작성 요청 본문 + * @param userId 사용자 ID + * @return 생성된 게시글 응답 DTO */ public PostResponse createPost(PostRequest request, Long userId) { - // User 조회 User user = userRepository.findById(userId) .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); // Post 생성 Post post = new Post(user, request.title(), request.content(), request.thumbnailUrl()); - - // Post 저장 Post saved = postRepository.save(post); // Category 매핑 if (request.categoryIds() != null) { - List categories = postCategoryRepository.findAllById(request.categoryIds()); - if (categories.size() != request.categoryIds().size()) { - throw new CustomException(ErrorCode.CATEGORY_NOT_FOUND); - } + List categories = validateAndFindCategories(request.categoryIds()); saved.updateCategories(categories); } @@ -66,8 +60,12 @@ public PostResponse createPost(PostRequest request, Long userId) { /** * 게시글 다건 조회 서비스 - * 1. Post 검색 (키워드, 검색타입, 카테고리, 페이징) - * 2. PageResponse 반환 + * + * @param keyword 검색어 + * @param searchType 검색 타입 + * @param categoryIds 카테고리 ID 목록 + * @param pageable 페이징 정보 + * @return 게시글 목록 페이지 응답 DTO */ @Transactional(readOnly = true) public PageResponse getPosts(String keyword, String searchType, List categoryIds, Pageable pageable) { @@ -77,39 +75,41 @@ public PageResponse getPosts(String keyword, String searchType /** * 게시글 단건 조회 서비스 - * 1. Post 조회 - * 2. PostResponse 반환 + * + * @param postId 게시글 ID + * @param userId 사용자 ID + * @return 게시글 상세 응답 DTO */ @Transactional(readOnly = true) - public PostDetailResponse getPost(Long postId) { + public PostDetailResponse getPost(Long postId, Long userId) { // Post 조회 Post post = postRepository.findById(postId) .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); - // 응답 반환 - return PostDetailResponse.from(post); - } - - // TODO: 로그인 회원용 게시글 단건 조회 서비스, 추후 리팩토링 필요 - @Transactional(readOnly = true) - public PostDetailResponse getPostWithUser(Long postId, Long userId) { - Post post = postRepository.findById(postId) - .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); - - boolean likedByMe = postLikeRepository.existsByUserIdAndPostId(userId, postId); - boolean bookmarkedByMe = postBookmarkRepository.existsByUserIdAndPostId(userId, postId); + // 로그인 사용자 추가 데이터 설정 (좋아요, 북마크 여부) + if (userId != null) { + boolean likedByMe = postLikeRepository.existsByUserIdAndPostId(userId, post.getId()); + boolean bookmarkedByMe = postBookmarkRepository.existsByUserIdAndPostId(userId, post.getId()); + return PostDetailResponse.from(post, likedByMe, bookmarkedByMe); + } - return PostDetailResponse.from(post, likedByMe, bookmarkedByMe); + // 비로그인 사용자는 기본 응답 반환 + return PostDetailResponse.from(post); } /** * 게시글 수정 서비스 - * 1. Post 조회 - * 2. 작성자 검증 - * 3. Post 업데이트 (제목, 내용, 카테고리) - * 4. PostResponse 반환 + * + * @param postId 게시글 ID + * @param request 게시글 수정 요청 본문 + * @param userId 사용자 ID + * @return 수정된 게시글 응답 DTO */ public PostResponse updatePost(Long postId, PostRequest request, Long userId) { + // User 조회 + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + // Post 조회 Post post = postRepository.findById(postId) .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); @@ -123,21 +123,17 @@ public PostResponse updatePost(Long postId, PostRequest request, Long userId) { post.update(request.title(), request.content()); // Category 매핑 업데이트 - List categories = postCategoryRepository.findAllById(request.categoryIds()); - if (categories.size() != request.categoryIds().size()) { - throw new CustomException(ErrorCode.CATEGORY_NOT_FOUND); - } + List categories = validateAndFindCategories(request.categoryIds()); post.updateCategories(categories); - // 응답 반환 return PostResponse.from(post); } /** * 게시글 삭제 서비스 - * 1. Post 조회 - * 2. 작성자 검증 - * 3. Post 삭제 + * + * @param postId 게시글 ID + * @param userId 사용자 ID */ public void deletePost(Long postId, Long userId) { // User 조회 @@ -153,10 +149,20 @@ public void deletePost(Long postId, Long userId) { throw new CustomException(ErrorCode.POST_NO_PERMISSION); } - // 연관관계 제거 - user.removePost(post); - // Post 삭제 + post.remove(); postRepository.delete(post); } + + /** + * 카테고리 ID 유효성 검증 및 조회 + */ + private List validateAndFindCategories(List categoryIds) { + List categories = postCategoryRepository.findAllById(categoryIds); + + if (categories.size() != categoryIds.size()) { + throw new CustomException(ErrorCode.CATEGORY_NOT_FOUND); + } + return categories; + } } \ No newline at end of file diff --git a/src/test/java/com/back/domain/board/post/service/PostServiceTest.java b/src/test/java/com/back/domain/board/post/service/PostServiceTest.java index 634c799d..a7fc63ab 100644 --- a/src/test/java/com/back/domain/board/post/service/PostServiceTest.java +++ b/src/test/java/com/back/domain/board/post/service/PostServiceTest.java @@ -156,7 +156,7 @@ void getPost_success() { postRepository.save(post); // when - PostDetailResponse response = postService.getPost(post.getId()); + PostDetailResponse response = postService.getPost(post.getId(), null); // then assertThat(response.postId()).isEqualTo(post.getId()); @@ -173,7 +173,7 @@ void getPost_success() { @DisplayName("게시글 단건 조회 실패 - 존재하지 않는 게시글") void getPost_fail_postNotFound() { // when & then - assertThatThrownBy(() -> postService.getPost(999L)) + assertThatThrownBy(() -> postService.getPost(999L, null)) .isInstanceOf(CustomException.class) .hasMessage(ErrorCode.POST_NOT_FOUND.getMessage()); }