-
Notifications
You must be signed in to change notification settings - Fork 0
[FEATURE #22]: comment 도메인 기본 구현 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
d111807
feature: post 기본 구현
seoyeon2001 bfede9c
fix: 빌드 오류
seoyeon2001 b314e0d
refactor: gemini 리뷰 반영
seoyeon2001 cf775e9
refactor: 공통 에러 반환
seoyeon2001 19c4093
refactor: 공통 응답 반환
seoyeon2001 3426453
fix: todo task 제거
seoyeon2001 e466606
feature: comment 기본 구현
seoyeon2001 b75fb7d
refactor: 게시글 작성 엔드포인트 수정
seoyeon2001 8671681
conflict: 충돌 해결
seoyeon2001 072c801
conflict: 충돌 해결
seoyeon2001 a9a3d2d
fix: TODO task 등록
seoyeon2001 85dd997
feature: 게시글 삭제 시 댓글 삭제
seoyeon2001 397e29f
refactor: 커서 기반 무한 스크롤 - Slice
seoyeon2001 7443ad2
refactor: post 별 댓글 조회
seoyeon2001 1f75da0
refactor: 리뷰 반영
seoyeon2001 be31ce7
refactor: 댓글 작성 및 조회 엔드포인트 수정
seoyeon2001 20c8f7d
Merge branch 'develop' of https://github.com/payper-devs/payper-serve…
seoyeon2001 211579f
refactor: 하드 코딩 user ID -> @AuthenticationPrincipal
seoyeon2001 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
78 changes: 78 additions & 0 deletions
78
src/main/java/com/payper/server/comment/controller/CommentController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| package com.payper.server.comment.controller; | ||
|
|
||
| import com.payper.server.comment.dto.CommentRequest; | ||
| import com.payper.server.comment.dto.CommentResponse; | ||
| import com.payper.server.comment.service.CommentService; | ||
| import com.payper.server.global.response.ApiResponse; | ||
| import com.payper.server.security.CustomUserDetails; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/v1/comments") | ||
| @RequiredArgsConstructor | ||
| public class CommentController { | ||
| private final CommentService commentService; | ||
|
|
||
| /** | ||
| * 댓글 수정 | ||
| * 작성자만 수정 가능 | ||
| */ | ||
| @PutMapping("/{commentId}") | ||
| public ResponseEntity<ApiResponse<Void>> updateComment( | ||
| @AuthenticationPrincipal CustomUserDetails user, | ||
| @PathVariable Long commentId, | ||
| @RequestBody @Valid CommentRequest.UpdateComment request | ||
| ) { | ||
| commentService.updateComment(user.getId(), commentId, request); | ||
| return ResponseEntity.ok(ApiResponse.ok()); | ||
| } | ||
|
|
||
| /** | ||
| * 댓글 삭제 | ||
| * 작성자만 삭제 가능 | ||
| * | ||
| * 자식 댓글은 삭제하지 않음 | ||
| */ | ||
| @DeleteMapping("/{commentId}") | ||
| public ResponseEntity<ApiResponse<Void>> deleteComment( | ||
| @AuthenticationPrincipal CustomUserDetails user, | ||
| @PathVariable Long commentId) { | ||
|
|
||
| commentService.deleteComment(user.getId(), commentId); | ||
| return ResponseEntity.ok(ApiResponse.ok()); | ||
| } | ||
|
|
||
| /** | ||
| * 내가 쓴 댓글 조회 | ||
| * | ||
| * 무한 스크롤 방식 | ||
| * | ||
| * 정렬: 최신 순 | ||
| */ | ||
| @GetMapping("/me") | ||
| public ResponseEntity<ApiResponse<CommentResponse.MyCommentList>> getMyComments( | ||
| @AuthenticationPrincipal CustomUserDetails user, | ||
| @RequestParam(required = false) Long cursorId, | ||
| @RequestParam(defaultValue = "20") int size | ||
| ) { | ||
| CommentResponse.MyCommentList response = commentService.getMyComments(user.getId(), cursorId, size); | ||
| return ResponseEntity.ok(ApiResponse.ok(response)); | ||
| } | ||
|
|
||
| /** | ||
| * 자식 댓글 조회 | ||
| */ | ||
| @GetMapping("/{parentId}/replies") | ||
| public ResponseEntity<ApiResponse<CommentResponse.CommentList>> getReplies( | ||
| @PathVariable Long parentId, | ||
| @RequestParam(required = false) Long cursorId, | ||
| @RequestParam(defaultValue = "20") int size | ||
| ) { | ||
| CommentResponse.CommentList response = commentService.getReplies(parentId, cursorId, size); | ||
| return ResponseEntity.ok(ApiResponse.ok(response)); | ||
| } | ||
| } | ||
29 changes: 29 additions & 0 deletions
29
src/main/java/com/payper/server/comment/dto/CommentRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.payper.server.comment.dto; | ||
|
|
||
| import jakarta.annotation.Nullable; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.Size; | ||
|
|
||
| public class CommentRequest { | ||
|
|
||
| /** | ||
| * 댓글 작성 DTO | ||
| */ | ||
| public record CreateComment( | ||
| @NotBlank(message = "댓글을 적어주세요.") | ||
| @Size(max = 21800, message = "댓글은 21,800자 이하여야 합니다.") | ||
| String content, | ||
|
|
||
| @Nullable | ||
| Long parentCommentId | ||
| ) {} | ||
|
|
||
| /** | ||
| * 댓글 수정 DTO | ||
| */ | ||
| public record UpdateComment( | ||
| @NotBlank(message = "댓글을 적어주세요.") | ||
| @Size(max = 21800, message = "댓글은 21,800자 이하여야 합니다.") | ||
| String content | ||
| ) {} | ||
| } |
91 changes: 91 additions & 0 deletions
91
src/main/java/com/payper/server/comment/dto/CommentResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| package com.payper.server.comment.dto; | ||
|
|
||
| import com.payper.server.comment.entity.Comment; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
|
|
||
| public class CommentResponse { | ||
|
|
||
| /** | ||
| * Post에 달린 Comment 리스트 | ||
| */ | ||
| public record CommentList( | ||
| List<CommentResponse.CommentItem> comments, | ||
| Long nextCursor, | ||
| boolean hasNext | ||
| ) { | ||
| public static CommentList from(List<Comment> comments, Long nextCursor, boolean hasNext) { | ||
| return new CommentList( | ||
| comments.stream() | ||
| .map(CommentResponse.CommentItem::from) | ||
| .toList(), | ||
| nextCursor, | ||
| hasNext | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Comment Item | ||
| */ | ||
| public record CommentItem( | ||
| Long id, | ||
| String userName, | ||
| Long parentCommentId, | ||
| String content, | ||
| LocalDateTime createdAt, | ||
| LocalDateTime updatedAt | ||
| ) { | ||
| public static CommentItem from(Comment comment) { | ||
| return new CommentItem( | ||
| comment.getId(), | ||
| comment.getUser().getName(), | ||
| comment.getParentComment() != null ? comment.getParentComment().getId() : null, | ||
| comment.isDeleted() ? "[삭제된 댓글입니다]" : comment.getContent(), | ||
| comment.getCreatedAt(), | ||
| comment.getUpdatedAt() | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 내가 작성한 Comment 리스트 | ||
| */ | ||
| public record MyCommentList( | ||
| List<CommentResponse.MyCommentItem> comments, | ||
| Long nextCursor, | ||
| boolean hasNext | ||
| ) { | ||
| public static MyCommentList from(List<Comment> comments, Long nextCursor, boolean hasNext) { | ||
| return new MyCommentList( | ||
| comments.stream() | ||
| .map(CommentResponse.MyCommentItem::from) | ||
| .toList(), | ||
| nextCursor, | ||
| hasNext | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 내가 작성한 Comment Item | ||
| */ | ||
| public record MyCommentItem( | ||
| Long id, | ||
| Long postId, | ||
| String content, | ||
| LocalDateTime createdAt, | ||
| LocalDateTime updatedAt | ||
| ) { | ||
| public static MyCommentItem from(Comment comment) { | ||
| return new MyCommentItem( | ||
| comment.getId(), | ||
| comment.getPost().getId(), | ||
| comment.getContent(), | ||
| comment.getCreatedAt(), | ||
| comment.getUpdatedAt() | ||
| ); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
src/main/java/com/payper/server/comment/repository/CommentRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| package com.payper.server.comment.repository; | ||
|
|
||
| import com.payper.server.comment.entity.Comment; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.data.domain.Slice; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Modifying; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.Optional; | ||
|
|
||
| @Repository | ||
| public interface CommentRepository extends JpaRepository<Comment, Long> { | ||
|
|
||
| Optional<Comment> findByIdAndIsDeletedFalse(Long id); | ||
|
|
||
| @Query(""" | ||
| select c from Comment c | ||
| join fetch c.user | ||
| where c.post.id = :postId | ||
| and c.parentComment is null | ||
| and ( | ||
| c.isDeleted = false | ||
| or exists ( | ||
| select 1 from Comment child | ||
| where child.parentComment = c | ||
| and child.isDeleted = false | ||
| ) | ||
| ) | ||
| order by c.createdAt asc, c.id asc | ||
| """) | ||
| Slice<Comment> findParent(@Param("postId") Long postId, Pageable pageable); | ||
|
|
||
| @Query(""" | ||
| select c from Comment c | ||
| join fetch c.user | ||
| where c.post.id = :postId | ||
| and c.parentComment is null | ||
| and ( | ||
| c.isDeleted = false | ||
| or exists ( | ||
| select 1 from Comment child | ||
| where child.parentComment = c | ||
| and child.isDeleted = false | ||
| ) | ||
| ) | ||
| and ( | ||
| c.createdAt > :createdAt | ||
| or (c.createdAt = :createdAt and c.id > :cursorId) | ||
| ) | ||
| order by c.createdAt asc, c.id asc | ||
| """) | ||
| Slice<Comment> findParentNext( | ||
| @Param("postId") Long postId, | ||
| @Param("cursorId") Long cursorId, | ||
| @Param("createdAt") LocalDateTime createdAt, | ||
| Pageable pageable | ||
| ); | ||
|
|
||
| @Query(""" | ||
| select c from Comment c | ||
| join fetch c.user | ||
| where c.parentComment.id = :parentId | ||
| and c.isDeleted = false | ||
| order by c.createdAt asc, c.id asc | ||
| """) | ||
|
seoyeon2001 marked this conversation as resolved.
|
||
| Slice<Comment> findReply(@Param("parentId") Long parentId, Pageable pageable); | ||
|
|
||
| @Query(""" | ||
| select c from Comment c | ||
| join fetch c.user | ||
| where c.parentComment.id = :parentId | ||
| and c.isDeleted = false | ||
| and ( | ||
| c.createdAt > :createdAt | ||
| or (c.createdAt = :createdAt and c.id > :cursorId) | ||
| ) | ||
| order by c.createdAt asc, c.id asc | ||
| """) | ||
| Slice<Comment> findReplyNext( | ||
| @Param("parentId") Long parentId, | ||
| @Param("cursorId") Long cursorId, | ||
| @Param("createdAt") LocalDateTime createdAt, | ||
| Pageable pageable | ||
| ); | ||
|
|
||
| @Query(""" | ||
| select c from Comment c | ||
| where c.user.id = :userId | ||
| and c.isDeleted = false | ||
| order by c.createdAt desc, c.id desc | ||
| """) | ||
| Slice<Comment> findFirstMyCommentPage(@Param("userId") Long userId, Pageable pageable); | ||
|
|
||
| @Query(""" | ||
| select c from Comment c | ||
| where c.user.id = :userId | ||
| and c.isDeleted = false | ||
| and ( | ||
| c.createdAt < :createdAt | ||
| or (c.createdAt = :createdAt and c.id < :cursorId) | ||
| ) | ||
| order by c.createdAt desc, c.id desc | ||
| """) | ||
| Slice<Comment> findNextMyCommentPage( | ||
| @Param("userId") Long userId, | ||
| @Param("cursorId") Long cursorId, | ||
| @Param("createdAt") LocalDateTime createdAt, | ||
| Pageable pageable | ||
| ); | ||
|
|
||
| @Modifying | ||
| @Query(""" | ||
| update Comment c | ||
| set c.isDeleted = true, | ||
| c.deletedAt = :now | ||
| where c.post.id = :postId | ||
| and c.isDeleted = false | ||
| """) | ||
| long softDeleteByPostId(@Param("postId") Long postId, @Param("now") LocalDateTime now); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.