-
Notifications
You must be signed in to change notification settings - Fork 1
[LNK-39] 피드 댓글 기능 구현 #79
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
The head ref may contain hidden characters: "LNK-39-Leenk-\uD53C\uB4DC-\uB313\uAE00-\uC791\uC131-\uAE30\uB2A5-\uAD6C\uD604"
Conversation
Walkthrough피드에 댓글 기능을 추가합니다. Comment 엔티티·리포지토리·서비스·매퍼·DTO·예외·응답코드가 추가되고, FeedUsecase와 FeedController에 댓글 작성/삭제 및 상세/네비게이션 응답에 댓글 포함 기능이 통합됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant C as Client
participant Ctrl as FeedController
participant UC as FeedUsecase
participant Map as CommentMapper
participant Save as CommentSaveService
participant Get as CommentGetService
participant Repo as CommentRepository
participant Ent as Comment
rect rgba(76,175,80,0.12)
Note over C,Ent: 댓글 작성
C->>Ctrl: POST /feeds/{feedId}/comments (request)
Ctrl->>UC: writeComment(userId, feedId, request)
UC->>Map: toComment(user, feed, request)
Map->>Ent: build Comment entity
UC->>Save: saveComment(comment)
Save->>Repo: save(comment)
Repo-->>Save: persisted
Save-->>UC: saved
UC-->>Ctrl: 완료
Ctrl-->>C: 200 OK (WRITE_COMMENT)
end
rect rgba(244,67,54,0.08)
Note over C,Ent: 댓글 삭제
C->>Ctrl: DELETE /feeds/comments/{commentId}
Ctrl->>UC: deleteComment(userId, commentId)
UC->>Get: findCommentByIdNotDeleted(commentId)
Get->>Repo: findByCommentIdAndDeletedAtIsNull(commentId)
Repo-->>Get: Comment
UC->>Ent: 권한 검증
UC->>Save: deleteComment(comment)
Save->>Ent: comment.deleteComment()
Save->>Repo: save(comment)
UC-->>Ctrl: 완료
Ctrl-->>C: 200 OK (DELETE_COMMENT)
end
rect rgba(33,150,243,0.08)
Note over C,Ctrl: 피드 상세 조회 (댓글 포함)
C->>Ctrl: GET /feeds/{feedId}
Ctrl->>UC: getFeedDetail(feedId)
UC->>Get: findAllByFeed(feed)
Get->>Repo: findAllByFeedAndDeletedAtIsNullOrderByCreateDateDesc(feed)
Repo-->>Get: List<Comment>
UC->>UC: 구성한 댓글 목록 전달
UC->>Map: toFeedDetailResponse(feed, medias, linkedUsers, comments)
Map->>Map: toGetCommentsResponses(comments)
Map-->>UC: FeedDetailResponse(with comments)
UC-->>Ctrl: 응답
Ctrl-->>C: 200 OK (FeedDetailResponse)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (4)
src/main/java/leets/leenk/domain/feed/presentation/FeedController.java (1)
8-8: 명시적 import 사용을 고려해 보세요.와일드카드 import는 어떤 클래스가 실제로 사용되는지 명확하지 않아 가독성이 떨어질 수 있습니다. 하지만 기능상 문제는 없으므로 선택적으로 개선하시면 됩니다.
src/main/java/leets/leenk/domain/feed/domain/service/CommentDeleteService.java (1)
8-14: 사용되지 않는@RequiredArgsConstructor어노테이션 정리 고려현재 주입받는 의존성이 없어
@RequiredArgsConstructor가 불필요합니다. 향후 의존성 추가 계획이 없다면 제거를 고려해주세요.전체적인 서비스 계층 분리 패턴은 프로젝트 구조와 일관성이 있습니다.
@Service -@RequiredArgsConstructor public class CommentDeleteService {src/main/java/leets/leenk/domain/feed/domain/entity/Comment.java (1)
24-25:comment필드 유효성 검증 누락댓글 내용(
comment)에 대한 길이 제한이나 null/빈 문자열 검증이 없습니다. 요청 DTO나 엔티티 레벨에서 검증 추가를 고려해주세요.- @Column(name = "comments") + @Column(name = "comments", nullable = false) private String comment;src/main/java/leets/leenk/domain/feed/domain/service/CommentSaveService.java (1)
13-15: 저장된 Comment 엔티티를 반환하는 것을 고려하세요.현재 메서드가 void를 반환하는데, 저장된 Comment 엔티티(생성된 ID 포함)를 반환하면 호출자가 추가 조회 없이 결과를 활용할 수 있습니다. 향후 댓글 작성 후 알림 전송이나 응답 반환이 필요할 경우 유용합니다.
다음과 같이 수정할 수 있습니다:
- public void saveComment(Comment comment) { - commentRepository.save(comment); + public Comment saveComment(Comment comment) { + return commentRepository.save(comment); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
src/main/java/leets/leenk/domain/feed/application/dto/request/CommentWriteRequest.java(1 hunks)src/main/java/leets/leenk/domain/feed/application/dto/response/FeedDetailResponse.java(1 hunks)src/main/java/leets/leenk/domain/feed/application/dto/response/GetCommentsResponse.java(1 hunks)src/main/java/leets/leenk/domain/feed/application/exception/CommentDeleteNotAllowedException.java(1 hunks)src/main/java/leets/leenk/domain/feed/application/exception/CommentNotFoundException.java(1 hunks)src/main/java/leets/leenk/domain/feed/application/exception/ErrorCode.java(1 hunks)src/main/java/leets/leenk/domain/feed/application/mapper/CommentMapper.java(1 hunks)src/main/java/leets/leenk/domain/feed/application/mapper/FeedMapper.java(4 hunks)src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java(6 hunks)src/main/java/leets/leenk/domain/feed/domain/entity/Comment.java(1 hunks)src/main/java/leets/leenk/domain/feed/domain/repository/CommentRepository.java(1 hunks)src/main/java/leets/leenk/domain/feed/domain/service/CommentDeleteService.java(1 hunks)src/main/java/leets/leenk/domain/feed/domain/service/CommentGetService.java(1 hunks)src/main/java/leets/leenk/domain/feed/domain/service/CommentSaveService.java(1 hunks)src/main/java/leets/leenk/domain/feed/presentation/FeedController.java(3 hunks)src/main/java/leets/leenk/domain/feed/presentation/ResponseCode.java(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-12T14:17:56.674Z
Learnt from: hyxklee
Repo: Leets-Makers/Leenk-BE PR: 54
File: src/main/java/leets/leenk/domain/feed/domain/service/LinkedUserDeleteService.java:18-18
Timestamp: 2025-08-12T14:17:56.674Z
Learning: The LinkedUser entity in the Leenk-BE project has a unique constraint on the combination of feed_id and user_id columns (UniqueConstraint(columnNames = {"feed_id", "user_id"})), which requires explicit flush() calls after delete operations to prevent constraint violations during the delete-and-recreate pattern used in feed updates.
Applied to files:
src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.javasrc/main/java/leets/leenk/domain/feed/domain/entity/Comment.java
📚 Learning: 2025-08-12T14:17:56.674Z
Learnt from: hyxklee
Repo: Leets-Makers/Leenk-BE PR: 54
File: src/main/java/leets/leenk/domain/feed/domain/service/LinkedUserDeleteService.java:18-18
Timestamp: 2025-08-12T14:17:56.674Z
Learning: In the Leenk-BE project, the LinkedUser entity has DB unique constraints that require explicit flush() calls after deleteAllByFeed operations to prevent constraint violations when recreating the relationships immediately afterward.
Applied to files:
src/main/java/leets/leenk/domain/feed/domain/entity/Comment.java
🧬 Code graph analysis (3)
src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java (4)
src/main/java/leets/leenk/domain/feed/application/exception/CommentDeleteNotAllowedException.java (1)
CommentDeleteNotAllowedException(5-9)src/main/java/leets/leenk/domain/feed/application/exception/FeedDeleteNotAllowedException.java (1)
FeedDeleteNotAllowedException(5-9)src/main/java/leets/leenk/domain/feed/application/exception/FeedUpdateNotAllowedException.java (1)
FeedUpdateNotAllowedException(5-9)src/main/java/leets/leenk/domain/feed/application/exception/SelfReactionNotAllowedException.java (1)
SelfReactionNotAllowedException(5-9)
src/main/java/leets/leenk/domain/feed/domain/service/CommentSaveService.java (1)
src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java (1)
Service(41-387)
src/main/java/leets/leenk/domain/feed/domain/service/CommentGetService.java (1)
src/main/java/leets/leenk/domain/feed/application/exception/CommentNotFoundException.java (1)
CommentNotFoundException(5-9)
🔇 Additional comments (17)
src/main/java/leets/leenk/domain/feed/application/exception/ErrorCode.java (1)
16-18: LGTM!댓글 관련 에러 코드가 올바르게 추가되었습니다. 에러 코드 번호가 순차적이고, HTTP 상태 코드도 적절합니다.
src/main/java/leets/leenk/domain/feed/application/exception/CommentDeleteNotAllowedException.java (1)
1-9: LGTM!예외 클래스가 기존 패턴을 따라 올바르게 구현되었습니다.
src/main/java/leets/leenk/domain/feed/application/dto/request/CommentWriteRequest.java (1)
1-9: LGTM!댓글 작성 요청 DTO가 적절한 검증과 함께 올바르게 구현되었습니다.
src/main/java/leets/leenk/domain/feed/application/dto/response/GetCommentsResponse.java (1)
1-27: LGTM!댓글 응답 DTO가 적절한 문서화와 함께 올바르게 구현되었습니다. Swagger 스키마 설명도 명확합니다.
src/main/java/leets/leenk/domain/feed/application/dto/response/FeedDetailResponse.java (1)
36-39: LGTM!피드 상세 응답에 댓글 목록이 깔끔하게 추가되었습니다.
src/main/java/leets/leenk/domain/feed/presentation/FeedController.java (1)
86-94: LGTM!댓글 작성 및 삭제 엔드포인트가 기존 패턴을 따라 올바르게 구현되었습니다. 인증과 검증이 적절하게 적용되었습니다.
Also applies to: 198-205
src/main/java/leets/leenk/domain/feed/application/exception/CommentNotFoundException.java (1)
1-9: LGTM!예외 클래스가 기존 패턴을 따라 올바르게 구현되었습니다.
src/main/java/leets/leenk/domain/feed/domain/repository/CommentRepository.java (1)
10-10: No action needed. The query methodOrderByCreateDateDescis valid—thecreateDatefield is inherited fromBaseEntityand exists in the Comment entity. The naming differences between DTOs (createDateinGetCommentsResponsevs.createdAtinFeedDetailResponse) are intentional and handled correctly by the mappers.Likely an incorrect or invalid review comment.
src/main/java/leets/leenk/domain/feed/presentation/ResponseCode.java (1)
28-31: LGTM!새로운 댓글 관련 응답 코드가 기존 패턴과 일관성 있게 추가되었습니다. 코드 번호도 순차적으로 잘 할당되었습니다.
src/main/java/leets/leenk/domain/feed/domain/entity/Comment.java (1)
38-40: LGTM - Soft delete 구현
deletedAt을 현재 시간으로 설정하는 soft delete 패턴이 잘 구현되었습니다.src/main/java/leets/leenk/domain/feed/application/mapper/CommentMapper.java (1)
9-17: LGTM!깔끔한 매퍼 구현입니다. 빌더 패턴을 적절히 활용하여 Comment 엔티티를 생성하고 있습니다.
src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java (3)
69-71: LGTM!댓글 관련 서비스 및 매퍼 의존성이 올바르게 주입되었습니다.
Also applies to: 79-79
103-105: LGTM!피드 상세 조회 시 댓글을 함께 조회하는 로직이 올바르게 구현되었습니다.
351-361: LGTM!댓글 삭제 로직이 올바르게 구현되었습니다. 작성자 검증 및 예외 처리가 기존
deleteFeed메서드(lines 339-349)와 일관된 패턴을 따르고 있습니다.src/main/java/leets/leenk/domain/feed/application/mapper/FeedMapper.java (3)
80-92: LGTM!피드 상세 응답에 댓글 정보를 포함하도록 메서드 시그니처와 응답 구성이 올바르게 업데이트되었습니다.
115-125: LGTM!댓글 엔티티를 응답 DTO로 변환하는 헬퍼 메서드가 깔끔하게 구현되었습니다. 필요한 작성자 정보(ID, 이름)와 댓글 데이터가 모두 포함되어 있습니다.
127-169: LGTM!피드 네비게이션 응답에 댓글 맵을 포함하도록 메서드 시그니처와 모든 호출 지점이 일관되게 업데이트되었습니다.
| Map<Long, List<Comment>> commentsMap = new HashMap<>(); | ||
| for (Feed feed : allFeeds) { | ||
| List<Comment> comments = commentGetService.findAllByFeed(feed); | ||
| commentsMap.put(feed.getId(), comments); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, find the CommentGetService file
fd CommentGetService --type fRepository: Leets-Makers/Leenk-BE
Length of output: 140
🏁 Script executed:
# Search for batch methods in CommentGetService
rg -A 10 'findAllByFeeds|findByFeedIn|findAllByFeedIn' --type=javaRepository: Leets-Makers/Leenk-BE
Length of output: 7861
🏁 Script executed:
# Also search for the current method being used (findAllByFeed)
rg -B 5 -A 10 'findAllByFeed' --type=java -g '*CommentGetService*'Repository: Leets-Makers/Leenk-BE
Length of output: 1075
🏁 Script executed:
# Check the FeedUsecase to understand the full context around lines 155-159
rg -B 10 -A 15 'commentsMap.*HashMap' src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.javaRepository: Leets-Makers/Leenk-BE
Length of output: 1067
Implement batch query method in CommentGetService to avoid N+1 query issue
The concern is valid. CommentGetService only provides findAllByFeed(Feed feed) for single-feed queries, while the code loops through all feeds to fetch comments individually (lines 155-159). This creates an N+1 query problem.
The codebase already demonstrates the correct pattern: MediaGetService implements findAllByFeeds(List<Feed> feeds) and is used elsewhere in FeedUsecase. Apply the same pattern to CommentGetService by adding a batch method (e.g., findAllByFeeds(List<Feed> feeds)) that queries all comments in a single database call, then update lines 155-159 to use this new method.
🤖 Prompt for AI Agents
In src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java
around lines 155-159, the current loop calls
commentGetService.findAllByFeed(feed) per feed causing an N+1 query; add a batch
method to CommentGetService (e.g., findAllByFeeds(List<Feed> feeds) or
findAllByFeedIds(List<Long> feedIds)) that performs a single DB query for all
comments for the given feeds, implement it in the service/repository to query by
feed IDs and return either a Map<Long, List<Comment>> or a flat List<Comment>
which you then group by feedId, and replace the loop with a single call to this
new batch method and use the returned grouped results to populate commentsMap.
| @Transactional | ||
| public void writeComment(long userId, long feedId, CommentWriteRequest request) { | ||
| User user = userGetService.findById(userId); | ||
| Feed feed = feedGetService.findById(feedId); | ||
|
|
||
| Comment comment = commentMapper.toComment(user, feed, request); | ||
|
|
||
| commentSaveService.saveComment(comment); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
댓글 작성 후 알림 기능 추가를 고려하세요.
현재 댓글 작성 기능은 구현되었지만, 피드 작성자에게 새 댓글 알림을 전송하는 로직이 누락되었습니다. reactToFeed 메서드(lines 214-233)에서 리액션 알림을 전송하는 것처럼, 댓글 작성 시에도 피드 작성자에게 알림을 보내는 것이 사용자 경험 측면에서 중요합니다.
다음과 같이 알림 로직을 추가할 수 있습니다:
@Transactional
public void writeComment(long userId, long feedId, CommentWriteRequest request) {
User user = userGetService.findById(userId);
Feed feed = feedGetService.findById(feedId);
Comment comment = commentMapper.toComment(user, feed, request);
commentSaveService.saveComment(comment);
+
+ // 피드 작성자에게 댓글 알림 전송 (자기 자신은 제외)
+ if (!feed.getUser().equals(user)) {
+ feedNotificationUsecase.saveCommentNotification(feed, comment, user);
+ }
}FeedNotificationUsecase에 saveCommentNotification 메서드 구현이 필요한 경우 도움을 드릴 수 있습니다. 구현을 원하시나요?
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java
around lines 235-243, the writeComment method currently saves a comment but does
not send a notification to the feed author; add a call after
commentSaveService.saveComment(comment) to invoke
FeedNotificationUsecase.saveCommentNotification(feed.getAuthor(), user, feed,
comment) (or equivalent) to create/send the notification, ensuring you avoid
notifying the author if they are the commenter and handle any checked
exceptions; if FeedNotificationUsecase lacks saveCommentNotification, implement
that method to persist and/or dispatch the notification with necessary DTOs.
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class Comment extends BaseEntity { | ||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @Column(name = "comment_id") | ||
| private Long commentId; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find CommentGetService file
fd -t f "CommentGetService" --extension javaRepository: Leets-Makers/Leenk-BE
Length of output: 140
🏁 Script executed:
# Also search for findCommentById usage and implementation
rg "findCommentById" -t java -A 5Repository: Leets-Makers/Leenk-BE
Length of output: 1388
🏁 Script executed:
# Check the Comment entity file to understand the full structure
cat -n src/main/java/leets/leenk/domain/feed/domain/entity/Comment.javaRepository: Leets-Makers/Leenk-BE
Length of output: 1487
🏁 Script executed:
# Look for BaseEntity to understand soft delete handling pattern
fd -t f "BaseEntity" --extension java | head -5Repository: Leets-Makers/Leenk-BE
Length of output: 127
🏁 Script executed:
cat -n src/main/java/leets/leenk/global/common/entity/BaseEntity.javaRepository: Leets-Makers/Leenk-BE
Length of output: 1040
🏁 Script executed:
# Find CommentRepository
fd -t f "CommentRepository" --extension javaRepository: Leets-Makers/Leenk-BE
Length of output: 143
🏁 Script executed:
# Check the repository interface
cat -n src/main/java/leets/leenk/domain/feed/domain/repository/CommentRepository.javaRepository: Leets-Makers/Leenk-BE
Length of output: 544
🏁 Script executed:
# Check all methods in CommentGetService
cat -n src/main/java/leets/leenk/domain/feed/domain/service/CommentGetService.javaRepository: Leets-Makers/Leenk-BE
Length of output: 1101
findCommentById()에서 soft delete된 댓글 반환 가능성
CommentGetService.findCommentById()는 deletedAt 필드를 확인하지 않아 soft delete된 댓글도 반환합니다. 같은 서비스의 findAllByFeed() 메서드는 findAllByFeedAndDeletedAtIsNullOrderByCreateDateDesc()를 사용하여 삭제된 댓글을 필터링하므로, findCommentById()도 동일하게 처리되어야 합니다.
삭제된 댓글 조회 시도 시 404 예외를 발생시켜야 한다면, repository에 findByIdAndDeletedAtIsNull() 메서드 추가를 권장합니다.
🤖 Prompt for AI Agents
In src/main/java/leets/leenk/domain/feed/domain/entity/Comment.java around lines
17-23, the service method findCommentById() may return soft-deleted comments
because it doesn't filter by deletedAt; update the logic to exclude deleted
comments by adding a repository method like findByIdAndDeletedAtIsNull(Long id)
(or use existing findById(...) and explicitly check deletedAt == null) and
change findCommentById() to call that repository method and throw a 404 (e.g.,
EntityNotFoundException) when no non-deleted comment is found.
| public Comment findCommentById(long commentId) { | ||
| return commentRepository.findById(commentId) | ||
| .orElseThrow(CommentNotFoundException::new); | ||
| } | ||
|
|
||
| public List<Comment> findAllByFeed(Feed feed) { | ||
| return commentRepository.findAllByFeedAndDeletedAtIsNullOrderByCreateDateDesc(feed); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n "findCommentById" --type java -A 3 -B 1Repository: Leets-Makers/Leenk-BE
Length of output: 186
🏁 Script executed:
# Also check the repository interface to understand the pattern
rg -n "findAllByFeedAndDeletedAtIsNullOrderByCreateDateDesc" --type java -B 2 -A 2Repository: Leets-Makers/Leenk-BE
Length of output: 1102
🏁 Script executed:
# Check for other methods in CommentGetService and related services
cat -n src/main/java/leets/leenk/domain/feed/domain/service/CommentGetService.javaRepository: Leets-Makers/Leenk-BE
Length of output: 1101
🏁 Script executed:
rg -n "findCommentById" --type java -B 2 -A 5Repository: Leets-Makers/Leenk-BE
Length of output: 1916
🏁 Script executed:
# Also check for delete/update operations in CommentGetService or related services
find . -name "*Comment*Service.java" -type fRepository: Leets-Makers/Leenk-BE
Length of output: 302
🏁 Script executed:
# Search for soft-delete handling patterns in other domain services
rg -n "deletedAt IS NULL\|DeletedAtIsNull" --type java -lRepository: Leets-Makers/Leenk-BE
Length of output: 47
🏁 Script executed:
rg -n "findCommentById" --type javaRepository: Leets-Makers/Leenk-BE
Length of output: 347
🏁 Script executed:
# Check CommentDeleteService implementation
cat -n src/main/java/leets/leenk/domain/feed/domain/service/CommentDeleteService.javaRepository: Leets-Makers/Leenk-BE
Length of output: 521
🏁 Script executed:
# Check the Comment entity for soft-delete field
rg -n "deletedAt\|@SoftDelete" --type java src/main/java/leets/leenk/domain/feed/domain/entity/Comment.javaRepository: Leets-Makers/Leenk-BE
Length of output: 47
🏁 Script executed:
# Check Comment entity to understand soft-delete implementation
cat -n src/main/java/leets/leenk/domain/feed/domain/entity/Comment.java | head -50Repository: Leets-Makers/Leenk-BE
Length of output: 1487
🏁 Script executed:
# Check if there's a pattern in other Get Services for soft-delete handling
rg -n "deletedAtIsNull\|DeletedAtIsNull\|deletedAt" --type java src/main/java/leets/leenk/domain/feed/domain/Repository: Leets-Makers/Leenk-BE
Length of output: 47
findCommentById() must filter soft-deleted comments
findCommentById() retrieves comments without checking deletedAt, while findAllByFeed() explicitly filters deletedAt IS NULL. This inconsistency allows soft-deleted comments to be retrieved and operated on.
Currently, findCommentById() is used only in the delete operation flow. Adding a deletedAt IS NULL filter ensures consistent soft-delete handling across the service and prevents operations on already-deleted comments.
Modify findCommentById() to use a repository method that filters deletedAt IS NULL, similar to the pattern in findAllByFeed().
🤖 Prompt for AI Agents
In src/main/java/leets/leenk/domain/feed/domain/service/CommentGetService.java
around lines 17-24, findCommentById currently returns comments regardless of
deletedAt and must exclude soft-deleted rows; change it to call a repository
method that filters deletedAt IS NULL (e.g., findByIdAndDeletedAtIsNull or
create one in CommentRepository), throw CommentNotFoundException when not
present, and ensure callers (delete flow) operate only on non-deleted comments.
jj0526
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨어요!
| @Schema(description = "작성자 id", example = "1") | ||
| long authorId, | ||
|
|
||
| @Schema(description = "작성자 이름", example = "한승현") | ||
| String authorName, | ||
|
|
||
| @Schema(description = "댓글", example = "오 좋은데??") | ||
| String comment, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
작성자 정보는 UserProfileResponse를 사용해야 유지보수하기 쉬울거같아요! 추가적으로 생일 여부도 확인할수있고요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아 그렇네요!! 감사합니다!! 수정했습니다!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/main/java/leets/leenk/domain/feed/application/mapper/FeedMapper.java (2)
115-124:toGetCommentsResponses에서 null 리스트 방어 코드 추가 제안현재는
comments.stream()을 바로 호출하고 있어, 혹시라도 다른 호출부에서comments에 null을 넘기면 NPE가 발생할 수 있습니다. 현재 사용 경로에서는 빈 리스트를 넘기는 패턴이지만, 재사용 가능성을 고려해 방어 로직을 추가하는 것을 추천합니다.- private List<GetCommentsResponse> toGetCommentsResponses(List<Comment> comments) { - return comments.stream() + private List<GetCommentsResponse> toGetCommentsResponses(List<Comment> comments) { + List<Comment> safeComments = comments == null ? List.of() : comments; + return safeComments.stream() .map(comment -> GetCommentsResponse.builder() ... ) .toList(); }이렇게 하면 호출 측 계약이 조금 느슨해져도 안전하게 동작합니다.
126-158: FeedNavigationResponse에서 중복 매핑 패턴 경량 리팩터 제안
current,prevFeeds,nextFeeds모두에서
mediaMap.getOrDefault(feedId, List.of())linkedUserMap.getOrDefault(feedId, List.of())commentsMap.getOrDefault(feedId, List.of())를 동일 패턴으로 호출하면서
toFeedDetailResponse로 넘기는 코드가 반복되고 있습니다. 기능상 문제는 없지만, 가독성과 유지보수를 위해 아래처럼 작은 헬퍼로 추출하는 것도 고려해볼 만합니다.private FeedDetailResponse toFeedDetailResponse( Feed feed, Map<Long, List<Media>> mediaMap, Map<Long, List<LinkedUser>> linkedUserMap, Map<Long, List<Comment>> commentsMap ) { Long feedId = feed.getId(); return toFeedDetailResponse( feed, mediaMap.getOrDefault(feedId, List.of()), linkedUserMap.getOrDefault(feedId, List.of()), commentsMap.getOrDefault(feedId, List.of()) ); }이렇게 두면 navigation 메서드 쪽은
feed만 넘기면 돼 로직이 더 읽기 쉬워집니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/main/java/leets/leenk/domain/feed/application/dto/response/GetCommentsResponse.java(1 hunks)src/main/java/leets/leenk/domain/feed/application/mapper/FeedMapper.java(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/leets/leenk/domain/feed/application/dto/response/GetCommentsResponse.java
🔇 Additional comments (2)
src/main/java/leets/leenk/domain/feed/application/mapper/FeedMapper.java (2)
4-4: Comment 엔티티 import 적절성새로 추가된
Comment타입이 메서드 시그니처 및commentsMap등에 실제로 사용되고 있어, 불필요한 의존성 없이 필요한 import 추가로 보입니다.
80-91: FeedDetailResponse에 댓글 포함 방식 타당
toFeedDetailResponse에List<Comment> comments를 인자로 받아 같은 계층(Mapper)에서 DTO(GetCommentsResponse)로 변환하는 구조라 응답 스펙을 한 곳에서 관리할 수 있어 좋습니다.commentsMap.getOrDefault(..., List.of())와 함께 사용하면 null 이 아닌 빈 리스트를 넘기는 패턴도 잘 맞습니다.
soo0711
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨습니다! 댓글 생성, 조회, 삭제까지 잘 구현된 것 같아요!!
구현 순서대로 커밋해 주셔서 덕분에 보기 편했습니다 👍
hyxklee
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구현 고생하셨습니다!!
comments 확인 부탁드릴게여!
|
|
||
| @Builder | ||
| @JsonInclude(JsonInclude.Include.NON_NULL) | ||
| public record GetCommentsResponse( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FeedCommentResponse 정도의 네이밍이 좋지 않을까 싶습니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵!! 수정했습니다!!
| String comment, | ||
|
|
||
| @Schema(description = "댓글 작성 시간", example = "2025-06-30T00:00:00") | ||
| LocalDateTime createDate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
타 도메인과 필드명 통일을 위해서 createdAt으로 반환해주세요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정했습니다!!
| SELF_REACTION(2202, HttpStatus.FORBIDDEN, "자신의 피드에 공감할 수 없습니다."), | ||
| FEED_DELETE_NOT_ALLOWED(2203, HttpStatus.FORBIDDEN, "피드 삭제는 작성자만 가능합니다."), | ||
| FEED_UPDATE_NOT_ALLOWED(2204, HttpStatus.FORBIDDEN, "피드 수정은 작성자만 가능합니다."); | ||
| FEED_UPDATE_NOT_ALLOWED(2204, HttpStatus.FORBIDDEN, "피드 수정은 작성자만 가능합니다."), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
노션 명세 추가도 부탁드립니당
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
추가해두었습니다!!
| public class CommentGetService { | ||
| private final CommentRepository commentRepository; | ||
|
|
||
| public Comment findCommentById(long commentId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
개별 조회도 삭제되지 않은 댓글을 필터링해서 반환해줘야할 것 같네염
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아 현재 해당 메서드의 경우 댓글 삭제 기능에서만 사용됩니다. 따라서 굳이 deletedAt 조건을 추가하지 않더라도 댓글을 삭제하는 로직에는 전혀 문제가 되지 않는다고 판단하여 필터링을 하지 않았습니다.
혹시 어떻게 생각하시나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getService는 보통 공용으로 사용하게 되는 경우가 많습니당
현재는 삭제를 위한 선조회의 목적으로만 사용되나, 후에는 다른 목적으로 호출되는 경우가 생길 수도 있습니다
그렇다면 해당 메서드를 삭제만을 위한 특수 메서드로 분리할 수도 있겠으나, 해당 방식보다는 common한 조회 메서드로 사용하고, soft delete를 필터링하는 일관성을 부여하는 것이 유지보수 관점에서 좋다는게 제 의견입니다😀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 알겠습니다!! 삭제되지 않은 댓글만 조회되도록 반영했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/main/java/leets/leenk/domain/feed/application/mapper/FeedMapper.java (1)
115-124: 메서드 명명 규칙을 개선하세요.메서드명
toGetCommentsResponses에서 "Get"이 불필요합니다. 다른 매퍼 메서드들(toFeedMediaResponses,toLinkedUserResponses)과의 일관성을 위해toFeedCommentResponses또는toCommentsResponses로 변경하는 것을 권장합니다.- private List<FeedCommentResponse> toGetCommentsResponses(List<Comment> comments) { + private List<FeedCommentResponse> toFeedCommentResponses(List<Comment> comments) { return comments.stream() .map(comment -> FeedCommentResponse.builder() .commentId(comment.getCommentId())
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/main/java/leets/leenk/domain/feed/application/dto/response/FeedCommentResponse.java(1 hunks)src/main/java/leets/leenk/domain/feed/application/dto/response/FeedDetailResponse.java(1 hunks)src/main/java/leets/leenk/domain/feed/application/mapper/FeedMapper.java(4 hunks)
🔇 Additional comments (3)
src/main/java/leets/leenk/domain/feed/application/dto/response/FeedDetailResponse.java (1)
36-39: 변경사항이 적절합니다.피드 상세 응답에 댓글 목록 필드가 올바르게 추가되었습니다. 기존 필드들과 일관된 스타일을 유지하고 있습니다.
src/main/java/leets/leenk/domain/feed/application/mapper/FeedMapper.java (2)
80-92: 메서드 시그니처 변경이 적절합니다.
toFeedDetailResponse메서드에 댓글 파라미터가 올바르게 추가되었으며, 기존 파라미터들과 일관된 패턴을 따릅니다. 호출부에서getOrDefault(feedId, List.of())를 사용하여 null 안전성이 보장되고 있습니다.
126-168: 네비게이션 응답 매핑이 올바르게 구현되었습니다.
toFeedNavigationResponse메서드에 댓글 맵이 적절하게 통합되었습니다.commentsMap.getOrDefault(feedId, List.of())를 사용하여 null 안전성을 보장하고 있으며, 기존mediaMap및linkedUserMap과 동일한 패턴을 따라 일관성이 유지됩니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java (2)
235-243: 댓글 작성 시 피드 작성자 알림 발송을 함께 고려해 보세요.현재
writeComment는 댓글 생성·저장까지만 처리하고 있어, 피드 작성자가 새 댓글을 인지하기 어렵습니다.reactToFeed에서 리액션 알림을 보내는 것처럼, 댓글 작성 시에도 (자기 자신 제외) 피드 작성자에게 알림을 보내면 UX가 좋아질 것 같습니다.예시:
Comment comment = commentMapper.toComment(user, feed, request); commentSaveService.saveComment(comment); + + // 피드 작성자에게 댓글 알림 (본인 댓글은 제외) + if (!feed.getUser().equals(user)) { + feedNotificationUsecase.saveCommentNotification(feed, comment, user); + }구체적인 메서드명/파라미터는 기존
FeedNotificationUsecase설계에 맞춰 조정하시면 될 것 같습니다.
155-159: getFeedNavigation에서 댓글 조회 N+1 쿼리 문제 여전히 존재합니다.
allFeeds에 대해commentGetService.findAllByFeed(feed)를 반복 호출하면서 피드 수만큼 댓글 조회 쿼리가 나가는 N+1 패턴입니다. 피드 네비게이션이 자주 호출되거나 한 번에 여러 피드를 포함할 경우 DB 부하가 커질 수 있습니다. 이전 리뷰에서도 동일 내용이 지적되었으므로, 이번에 함께 정리하는 것이 좋아 보입니다.가능한 개선 방향 예시는 다음과 같습니다.
- Map<Long, List<Comment>> commentsMap = new HashMap<>(); - for (Feed feed : allFeeds) { - List<Comment> comments = commentGetService.findAllByFeed(feed); - commentsMap.put(feed.getId(), comments); - } + // 예시: CommentGetService에 배치 조회 메서드를 추가 + List<Comment> allComments = commentGetService.findAllByFeeds(allFeeds); + Map<Long, List<Comment>> commentsMap = allComments.stream() + .collect(Collectors.groupingBy(comment -> comment.getFeed().getId()));실제 시그니처나 반환 타입은
MediaGetService.findAllByFeeds패턴을 참고해서 맞추면 될 것 같습니다.또한 차단 사용자(blockedUsers) 정책에 따라, 네비게이션에서 댓글도 차단 대상 유저의 댓글을 숨겨야 한다면 이 지점에서 함께 필터링하는 로직을 고려할 수 있습니다(요구사항에 따라 선택).
Also applies to: 167-167
🧹 Nitpick comments (1)
src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java (1)
351-361: 댓글 삭제 권한 정책은 명확하나, 피드 작성자 권한 필요 여부는 한 번 더 확인이 필요합니다.현재 구현은 “댓글 작성자 본인만 삭제 가능”이라는 정책을 명확히 반영하고 있고,
findCommentByIdNotDeleted+ soft delete 조합도 일관성 있게 잘 설계되어 있습니다.다만 서비스 요구사항에 따라 피드 작성자(혹은 관리자)가 다른 사용자의 댓글도 삭제할 수 있어야 한다면, 여기서
feed를 함께 로딩해 피드 작성자/관리자 권한까지 허용할지 여부를 검토해 볼 필요가 있습니다. (요구사항이 “작성자만 삭제”라면 현재 구현으로 충분합니다.)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java(6 hunks)src/main/java/leets/leenk/domain/feed/domain/repository/CommentRepository.java(1 hunks)src/main/java/leets/leenk/domain/feed/domain/service/CommentGetService.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/leets/leenk/domain/feed/domain/service/CommentGetService.java
- src/main/java/leets/leenk/domain/feed/domain/repository/CommentRepository.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-12T14:17:56.674Z
Learnt from: hyxklee
Repo: Leets-Makers/Leenk-BE PR: 54
File: src/main/java/leets/leenk/domain/feed/domain/service/LinkedUserDeleteService.java:18-18
Timestamp: 2025-08-12T14:17:56.674Z
Learning: The LinkedUser entity in the Leenk-BE project has a unique constraint on the combination of feed_id and user_id columns (UniqueConstraint(columnNames = {"feed_id", "user_id"})), which requires explicit flush() calls after delete operations to prevent constraint violations during the delete-and-recreate pattern used in feed updates.
Applied to files:
src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java
🧬 Code graph analysis (1)
src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java (4)
src/main/java/leets/leenk/domain/feed/application/exception/CommentDeleteNotAllowedException.java (1)
CommentDeleteNotAllowedException(5-9)src/main/java/leets/leenk/domain/feed/application/exception/FeedDeleteNotAllowedException.java (1)
FeedDeleteNotAllowedException(5-9)src/main/java/leets/leenk/domain/feed/application/exception/FeedUpdateNotAllowedException.java (1)
FeedUpdateNotAllowedException(5-9)src/main/java/leets/leenk/domain/feed/application/exception/SelfReactionNotAllowedException.java (1)
SelfReactionNotAllowedException(5-9)
🔇 Additional comments (2)
src/main/java/leets/leenk/domain/feed/application/usecase/FeedUsecase.java (2)
3-13: Comment 관련 의존성·필드 주입 구조는 기존 패턴과 잘 맞습니다.Comment 엔티티/서비스/매퍼를 FeedUsecase에 주입한 방식이 기존 Feed/Media/Reaction 구조와 일관되고, 책임 분리도 명확해 보여 별도 이슈는 없어 보입니다.
Also applies to: 69-71, 79-79
103-106: 피드 상세에 댓글 포함 처리 자연스럽습니다.
commentGetService.findAllByFeed(feed)결과를feedMapper.toFeedDetailResponse에 그대로 넘기는 구조가 간단하고, Media/LinkedUser와 동일한 패턴을 따라가서 유지보수 측면에서도 무난해 보입니다.
Related issue 🛠
작업 내용 💻
스크린샷 📷
댓글 삭제 기능이 동작하면 다음과 같이 해당 피드 조회 시 삭제된 댓글은 조회되지 않는 것을 확인할 수 있습니다.

같이 얘기해보고 싶은 내용이 있다면 작성 📢
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.