Skip to content

Comments

✨Feat: 커뮤니티 추가 API 생성#6

Merged
uni-j-uni merged 3 commits intomainfrom
feature/comment
Dec 13, 2025
Merged

✨Feat: 커뮤니티 추가 API 생성#6
uni-j-uni merged 3 commits intomainfrom
feature/comment

Conversation

@uni-j-uni
Copy link
Contributor

@uni-j-uni uni-j-uni commented Dec 12, 2025

✨ 새로운 기능

  • 댓글에 좋아요 토글(추가/취소) 기능을 추가했습니다.
  • 사용자는 댓글에 대한 공감을 표현할 수 있으며, 좋아요 수와 본인의 좋아요 여부를 즉시 확인할 수 있습니다.

🛠 개발 상세

  • CommentLike 엔티티를 통해 Comment–User 간 좋아요 관계를 관리
  • findByCommentAndUser로 기존 좋아요 존재 여부 확인
    • 존재 시: 삭제(UNLIKE)
    • 미존재 시: 생성(LIKE)
  • 댓글 목록 조회 시: 댓글 ID 목록 기준으로 좋아요 개수 집계

🧪 테스트 방법

  • 좋아요 최초 클릭 시 CommentLike 생성 확인
  • 재클릭 시 삭제 확인
  • 좋아요 수 증감 및 사용자별 좋아요 여부 검증
  • 댓글 목록에서 좋아요 수/상태 정상 반영 확인

🧩 추가 고려사항

  • X

🔗 관련 문서 / 이슈

Summary by CodeRabbit

  • 새로운 기능
    • 댓글 좋아요 토글 기능 추가 (댓글에 대해 좋아요/취소 가능)
    • 댓글 답글(중첩 댓글) 작성 지원 (parentCommentId 포함)
    • 댓글 상세에 작성자 닉네임, 좋아요 수, 사용자 좋아요 여부 및 답글 목록 표시
    • 게시물 조회수 추적 및 게시물 상세에 조회수 표시

✏️ Tip: You can customize this high-level summary in your review settings.

@uni-j-uni uni-j-uni self-assigned this Dec 12, 2025
@uni-j-uni uni-j-uni added ✨ feature 새로운 기능 요청 🚧 in progress 작업 진행 중 labels Dec 12, 2025
@uni-j-uni uni-j-uni linked an issue Dec 12, 2025 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

Walkthrough

댓글 좋아요 토글 API와 CommentLike 엔티티/리포지토리를 추가하고, 댓글의 부모/대댓글 관계 및 DTO 확장, 매퍼/서비스 시그니처와 구현을 변경했으며 게시물 조회수 필드 및 증가 로직을 도입했습니다.

Changes

Cohort / File(s) 요약
댓글 컨트롤러 변경
src/main/java/com/sku/refit/domain/comment/controller/CommentController.java, src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java
POST /{id}/like 엔드포인트 추가 및 toggleLike(Long id) 컨트롤러/구현 추가
댓글 요청/응답 DTO 확장
src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java, src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java
요청 DTO에 parentCommentId 추가, 응답 DTO에 nickname, parentCommentId, likeCount, isLiked, replies 필드 추가
Comment 엔티티 계층화
src/main/java/com/sku/refit/domain/comment/entity/Comment.java
self-referential 관계 추가: parent (@ManyToOne) 및 replies (@OneToMany, cascade/orphanRemoval)
좋아요 엔티티 및 리포지토리 추가
src/main/java/com/sku/refit/domain/comment/entity/CommentLike.java, src/main/java/com/sku/refit/domain/comment/repository/CommentLikeRepository.java
CommentLike 엔티티 추가(유니크 제약: comment_id, user_id) 및 좋아요 집계/사용자 좋아요 조회용 커스텀 쿼리 포함 리포지토리 추가
매퍼 시그니처 변경
src/main/java/com/sku/refit/domain/comment/mapper/CommentMapper.java
toCommentparent 파라미터 추가, toDetailResponselikeCount, isLiked, replies 파라미터 추가 및 응답 필드 채움
서비스 계층 기능 추가/수정
src/main/java/com/sku/refit/domain/comment/service/CommentService.java, src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java
toggleLike(Long) 인터페이스/구현 추가; create/getAll/update/delete 로직에 부모 댓글 처리, 좋아요 집계 및 대댓글 트리 빌드 반영
게시물 조회수 추가
src/main/java/com/sku/refit/domain/post/entity/Post.java, src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java, src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java, src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java
Post.views 필드 및 increaseViews() 추가, PostDetailResponse에 views 추가 및 getPostById에서 조회수 증가/로깅 변경
리소스 / 서브모듈 참조
src/main/resources
서브모듈 커밋 참조 업데이트
sequenceDiagram
    autonumber
    participant Client
    participant Controller as CommentController
    participant Service as CommentService
    participant RepoComment as CommentRepository
    participant RepoLike as CommentLikeRepository
    participant DB

    Client->>Controller: POST /comments/{id}/like
    Controller->>Service: toggleLike(commentId)
    Service->>RepoComment: findById(commentId)
    RepoComment->>DB: SELECT comment
    DB-->>RepoComment: comment row
    RepoComment-->>Service: Comment
    Service->>RepoLike: findByCommentAndUser(comment, user)
    RepoLike->>DB: SELECT comment_like WHERE comment_id=? AND user_id=?
    DB-->>RepoLike: row or empty
    alt 이미 좋아요 있음
        Service->>RepoLike: delete(commentLike)
        RepoLike->>DB: DELETE
        DB-->>RepoLike: deleted
    else 좋아요 없음
        Service->>RepoLike: save(new CommentLike)
        RepoLike->>DB: INSERT
        DB-->>RepoLike: inserted
    end
    Service-->>Controller: void / 200 OK
    Controller-->>Client: ResponseEntity<BaseResponse<Void>>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • 추가로 집중 검토할 파일/영역:
    • Comment self-relation: cascade, orphanRemoval, fetch 전략 및 기존 데이터 마이그레이션 영향
    • CommentLikeRepository의 JPQL 커스텀 쿼리(집계 쿼리 및 user별 좋아요 조회 정확성/성능)
    • CommentServiceImpl의 toggleLike 구현, 트리 빌드 로직, 동시성/성능 관련 경계 조건
    • 매퍼 시그니처 변경에 따른 호출부(다른 서비스/테스트) 영향

Possibly related PRs

  • Feature/community #2 — 댓글 기능 확장(부모-자식 댓글 구조 및 CommentLike 추가)과 직접적으로 코드 수준에서 관련됨

Suggested labels

🟠 priority: medium

Poem

🐰 폴짝 좋아요, 댓글 숲에 불빛 켜고,
부모 밑에 대댓글 줄줄이 모여요,
하트도 누르고 조회수 쑥쑥 올라가네,
당근 들고 배포 준비 땅! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive PR 제목은 '커뮤니티 추가 API 생성'이지만, 실제 주요 변경사항은 댓글 좋아요 토글 기능 추가입니다. 제목이 구체적 기능을 명시하지 않아 모호합니다. 제목을 '댓글 좋아요 토글 기능 추가' 또는 'Add comment like toggle feature'로 변경하여 실제 주요 변경사항을 명확히 반영하세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed PR 설명이 템플릿의 주요 섹션(새로운 기능, 개발 상세, 테스트 방법)을 대부분 포함하고 있으며, 기능 설명과 구현 방식이 구체적입니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (5)
src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java (1)

38-43: toggleLike 라우팅 등록 여부(인터페이스 매핑 상속)와 응답 스펙을 확인해주세요.
구현체에는 매핑 애너테이션이 없고 인터페이스의 @PostMapping("/{id}/like")에 의존하므로, 현재 Spring 설정에서 해당 방식이 정상 등록되는지 스웨거/통합테스트로 확인이 필요합니다. 또한 응답이 Void라서 토글 직후 likeCount/isLiked를 서버 응답으로 “즉시” 받으려면 DTO 반환으로 바꾸는 편이 API UX가 더 좋습니다.

src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java (1)

24-26: parentCommentId에 기본 입력 검증(@positive 등) 추가를 고려해 주세요.
대댓글일 때만 값이 들어오더라도, 음수/0 방지용 검증이 있으면 방어적입니다.

 import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Positive;

 ...
   @Schema(description = "답글을 작성할 댓글의 식별자", example = "1")
+  @Positive(message = "parentCommentId는 1 이상이어야 합니다.")
   private Long parentCommentId;
src/main/java/com/sku/refit/domain/comment/controller/CommentController.java (1)

38-41: 토글 API 응답을 Void로 둘지(추가 조회 필요) vs 상태 DTO를 줄지 한 번 더 정리하면 좋겠습니다.
“토글 직후 likeCount/isLiked 즉시 제공”을 서버 응답으로 보장하려면 BaseResponse<LikeToggleResponse>(예: likeCount, isLiked) 형태가 더 명확합니다.

src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1)

174-177: updateComment 응답에서 replies가 항상 빈 리스트로 반환됨

댓글 수정 후 응답에서 replies가 항상 빈 리스트로 반환됩니다. 실제 대댓글이 있는 경우 클라이언트에서 불일치가 발생할 수 있으니, 클라이언트 요구사항에 따라 실제 replies를 조회하여 반환하는 것을 고려해 주세요.

src/main/java/com/sku/refit/domain/comment/entity/CommentLike.java (1)

38-42: @joincolumn 어노테이션 추가 권장

commentuser 필드에 @JoinColumn을 명시적으로 추가하면 컬럼명을 명확히 하고 nullable = false 제약 조건을 DB 레벨에서 적용할 수 있습니다.

  @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "comment_id", nullable = false)
  private Comment comment;

  @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id", nullable = false)
  private User user;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69fc9bc and 33f2cc0.

📒 Files selected for processing (13)
  • src/main/java/com/sku/refit/domain/comment/controller/CommentController.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/controller/CommentControllerImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/dto/request/CommentRequest.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (2 hunks)
  • src/main/java/com/sku/refit/domain/comment/entity/Comment.java (3 hunks)
  • src/main/java/com/sku/refit/domain/comment/entity/CommentLike.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/mapper/CommentMapper.java (2 hunks)
  • src/main/java/com/sku/refit/domain/comment/repository/CommentLikeRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/service/CommentService.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (5 hunks)
  • src/main/java/com/sku/refit/domain/post/entity/Post.java (2 hunks)
  • src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (1 hunks)
  • src/main/resources (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/sku/refit/domain/comment/entity/CommentLike.java (3)
src/main/java/com/sku/refit/domain/comment/entity/Comment.java (1)
  • Entity (31-64)
src/main/java/com/sku/refit/domain/post/entity/Post.java (1)
  • Entity (33-86)
src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (1)
  • Getter (13-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (7)
src/main/resources (1)

1-1: 서브모듈 참조만 제공되어 있습니다. 도메인 레이어 코드 검토 필요합니다.

제공된 파일은 서브모듈 커밋 참조만 포함하고 있습니다. PR 설명에 따르면 CommentLike 엔티티, 리포지토리, 토글 엔드포인트, 좋아요 기능이 구현되었으나, 실제 코드 변경 사항이 검토 대상에 포함되어 있지 않습니다.

포괄적인 코드 리뷰를 진행하려면 다음 파일들의 검토가 필요합니다:

  • CommentLike 엔티티 및 리포지토리 구현
  • Comment 엔티티의 변경 사항
  • CommentService의 좋아요 토글 로직
  • CommentController의 새로운 API 엔드포인트
  • DTO 변경 사항 (CommentDetailResponse 등)
src/main/java/com/sku/refit/domain/comment/mapper/CommentMapper.java (2)

19-26: LGTM!

toComment 메서드가 parent 댓글을 포함하여 Comment 엔티티를 올바르게 빌드하고 있습니다.


28-46: LGTM!

toDetailResponse 메서드가 새로운 필드들(nickname, parentCommentId, likeCount, isLiked, replies)을 올바르게 매핑하고 있으며, parent null 체크도 적절히 처리되어 있습니다.

src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (2)

47-69: LGTM!

createComment 메서드가 parentCommentId를 통해 대댓글 기능을 올바르게 구현하고 있습니다.


71-101: LGTM!

toggleLike 메서드가 ifPresentOrElse를 사용하여 좋아요 토글 로직을 깔끔하게 구현하고 있습니다. 로깅도 적절히 포함되어 있습니다.

src/main/java/com/sku/refit/domain/comment/dto/response/CommentDetailResponse.java (1)

24-43: LGTM!

새로운 필드들(nickname, parentCommentId, likeCount, isLiked, replies)이 적절한 OpenAPI 스키마 어노테이션과 함께 추가되었습니다. 대댓글을 위한 자기 참조 구조도 올바르게 설계되었습니다.

src/main/java/com/sku/refit/domain/comment/repository/CommentLikeRepository.java (1)

29-46: LGTM!

JPQL 쿼리가 올바르게 작성되었습니다. countByCommentIds는 댓글별 좋아요 수를 효율적으로 집계하고, findLikedCommentIds는 사용자가 좋아요한 댓글 ID를 일괄 조회합니다.

Comment on lines +117 to +118
post.increaseViews();

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

@Transactional(readOnly = true) 안에서 post.increaseViews()는 조회수 저장이 안 될 가능성이 큽니다(기능 불발).
현재 메서드는 readOnly인데 엔티티를 변경하고 있어 flush가 생략되어 views가 DB에 반영되지 않을 수 있습니다. 최소한 readOnly를 제거하고, 가능하면 update post set views = views + 1 where id = ? 같은 원자적 증가로 lost update도 막는 편이 안전합니다.

-  @Transactional(readOnly = true)
+  @Transactional
   public PostDetailResponse getPostById(Long id) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
post.increaseViews();
@Transactional
public PostDetailResponse getPostById(Long id) {
post.increaseViews();
🤖 Prompt for AI Agents
In src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java around
lines 117-118, the call to post.increaseViews() is inside a
@Transactional(readOnly = true) so the entity change may not be flushed and
views won't persist; change the transaction semantics and/or use an atomic DB
update. Remove readOnly=true (or move the increment into a separate
@Transactional(propagation = Propagation.REQUIRES_NEW) method) and replace the
in-memory increment with a repository-level atomic update like update post set
views = views + 1 where id = ? (or call such a custom repository method), and
ensure the transaction commits so the increment is persisted and lost-updates
are avoided.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1)

198-220: CommentLike FK 제약으로 인한 댓글 삭제 실패 위험

현재 코드에서 commentRepository.delete(comment) 시 CommentLike 레코드가 존재하면 FK 제약 위반으로 삭제가 실패합니다. Comment 엔티티에 CommentLike에 대한 cascade 설정이 없고, CommentLikeRepository에 deleteAllByComment 메서드도 없기 때문입니다.

다음 중 하나를 적용해야 합니다:

  1. 삭제 전 관련 좋아요 레코드 먼저 제거:
   public void deleteComment(Long id) {
     ...
+    commentLikeRepository.deleteAllByComment(comment);
     commentRepository.delete(comment);
   }

(CommentLikeRepository에 void deleteAllByComment(Comment comment); 메서드 추가 필요)

  1. 또는 Comment 엔티티에 cascade 설정 추가:
-  @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
+  @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
+  private List<CommentLike> commentLikes = new ArrayList<>();
♻️ Duplicate comments (1)
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1)

120-169: 댓글 목록 좋아요 집계/계층화 구성은 방향이 좋습니다.

  • 빈 리스트 early-return 처리도 좋습니다.
  • 부모 응답이 없을 때(고아 대댓글) NPE 방어 로직이 들어가 안전합니다.
🧹 Nitpick comments (4)
src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java (1)

30-41: post.getViews()는 null-safe 매핑을 권장합니다.
Post.views가 null일 여지가 있으면 NPE는 아니더라도 API 계약이 흔들립니다(응답 null). 아래처럼 보정 권장:

-        .views(post.getViews())
+        .views(post.getViews() == null ? 0L : post.getViews())
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1)

188-194: updateComment에서 likeCount/isLiked 조회는 OK(다만 쿼리 2번).
핫패스면 단일 쿼리/집계로 줄이는 것도 고려해볼 만합니다.

src/main/java/com/sku/refit/domain/comment/entity/Comment.java (1)

54-61: 부모/대댓글 매핑 및 @Builder.Default 적용은 적절합니다.
replies가 builder 경로에서 null 되지 않게 막은 점 좋아요. 다만 user, postFetchType.EAGER라 댓글 목록 조회 시 로딩 비용이 커질 수 있어, 필요 시 LAZY 전환 + fetch join으로 제어하는 것도 검토해볼 만합니다.

src/main/java/com/sku/refit/domain/comment/repository/CommentLikeRepository.java (1)

1-46: @Param import가 Spring Data 것으로 들어가 있어 정상입니다(이전 이슈 해결).
추가로, List<Object[]>는 캐스팅 실수 여지가 있으니 Tuple/프로젝션 인터페이스로 바꾸면 더 안전합니다. 또한 (comment_id, user_id) 유니크 + 인덱스가 실제로 잡혀있는지 확인을 권장합니다(토글/집계 성능).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33f2cc0 and 0a91147.

📒 Files selected for processing (7)
  • src/main/java/com/sku/refit/domain/comment/entity/Comment.java (3 hunks)
  • src/main/java/com/sku/refit/domain/comment/repository/CommentLikeRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (6 hunks)
  • src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/entity/Post.java (2 hunks)
  • src/main/java/com/sku/refit/domain/post/mapper/PostMapper.java (1 hunks)
  • src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/sku/refit/domain/post/entity/Post.java
  • src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java

Comment on lines +30 to 34
@Schema(description = "게시글 조회수", example = "100")
private Long views;

@Schema(description = "게시글 작성 시간", example = "2025-12-03T14:37:17")
private LocalDateTime createdAt;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the Post entity class
find . -type f -name "Post.java" | grep -E "domain|entity"

Repository: refit-lab/refit-be

Length of output: 120


🏁 Script executed:

# Search for views field definition in Post entity
rg "class Post" -A 30 --type java | head -100

Repository: refit-lab/refit-be

Length of output: 9085


🏁 Script executed:

# Look for migration files
find . -type f \( -name "*.sql" -o -name "*.xml" \) | grep -i migration | head -20

Repository: refit-lab/refit-be

Length of output: 44


🏁 Script executed:

# Check for PostDetailResponse mapper/converter
rg "PostDetailResponse" -l --type java | head -10

Repository: refit-lab/refit-be

Length of output: 481


🏁 Script executed:

# Check if there's a mapper that converts Post to PostDetailResponse
rg "views" -B 2 -A 2 --type java | grep -E "(mapper|convert|views)" | head -20

Repository: refit-lab/refit-be

Length of output: 941


🏁 Script executed:

# Look for schema creation files
find . -type f \( -name "schema.sql" -o -name "schema.ddl" \) | head -10

Repository: refit-lab/refit-be

Length of output: 44


🏁 Script executed:

# Check for db migration directory (Flyway convention)
find . -path "*/db/migration*" -type f | head -20

Repository: refit-lab/refit-be

Length of output: 44


🏁 Script executed:

# Check application properties for data initialization
rg "spring.jpa.hibernate.ddl-auto|spring.sql.init" --type properties --type yaml | head -20

Repository: refit-lab/refit-be

Length of output: 96


🏁 Script executed:

# Look at the complete Post entity definition
cat -n ./src/main/java/com/sku/refit/domain/post/entity/Post.java | head -80

Repository: refit-lab/refit-be

Length of output: 2852


🏁 Script executed:

# Check BaseTimeEntity to understand createdAt and updatedAt handling
find . -type f -name "BaseTimeEntity.java" -exec cat {} \;

Repository: refit-lab/refit-be

Length of output: 694


🏁 Script executed:

# Final check: Look for any null-handling utilities or Optional usage in PostDetailResponse
cat -n ./src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java

Repository: refit-lab/refit-be

Length of output: 1527


🏁 Script executed:

# Check if there's a columnDefinition with DEFAULT in any entity
rg "@Column.*columnDefinition.*DEFAULT" --type java

Repository: refit-lab/refit-be

Length of output: 44


views 필드의 레거시 데이터 NULL 가능성에 대한 대비가 필요합니다.

Post 엔티티에 @Column(nullable = false)@Builder.Default로 views가 선언되어 있으나, 이는 새로 생성되는 게시글에만 보호를 제공합니다. 해당 제약이 추가되기 전에 저장된 레거시 row는 여전히 NULL 값을 가질 수 있으며, Hibernate가 DB에서 로드할 때 실제 NULL 값을 그대로 반영합니다.

현재 PostMapper의 toDetailResponse() 메서드는 post.getViews()를 직접 매핑하므로, 레거시 데이터의 NULL이 그대로 응답에 포함될 수 있습니다. DB 마이그레이션으로 기존 행에 DEFAULT 0을 보장하거나, 매퍼에서 post.getViews() != null ? post.getViews() : 0L로 null-safe 처리하세요.

🤖 Prompt for AI Agents
In src/main/java/com/sku/refit/domain/post/dto/response/PostDetailResponse.java
around lines 30 to 34, the views field can be NULL for legacy DB rows and should
be guarded against; update the mapping logic (PostMapper.toDetailResponse) to
use a null-safe default (e.g., post.getViews() != null ? post.getViews() : 0L)
when populating this response field so responses never carry a null views value
(alternatively ensure a DB migration sets DEFAULT 0 for existing rows).

Copy link

@coderabbitai coderabbitai bot left a 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 (1)
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1)

81-111: 동시성 이슈가 여전히 해결되지 않았습니다.

이전 리뷰에서 지적된 대로, findByCommentAndUsersave 패턴은 동시 요청 시 레이스 컨디션이 발생할 수 있습니다. 두 요청이 동시에 "좋아요 없음"을 조회하고 동시에 insert를 시도하면 DataIntegrityViolationException이 발생하여 500 에러가 반환됩니다.

권장 해결 방안:

  1. DataIntegrityViolationException을 catch하여 비즈니스 로직으로 처리
  2. 또는 @Lock(LockModeType.PESSIMISTIC_WRITE) 사용
   @Override
   @Transactional
   public void toggleLike(Long commentId) {
 
     User user = userService.getCurrentUser();
 
     Comment comment =
         commentRepository
             .findById(commentId)
             .orElseThrow(() -> new CustomException(CommentErrorCode.COMMENT_NOT_FOUND));
 
-    commentLikeRepository
-        .findByCommentAndUser(comment, user)
-        .ifPresentOrElse(
-            like -> {
-              commentLikeRepository.delete(like);
-              log.info(
-                  "[COMMENT LIKE] UNLIKE - commentId={}, userId={}", comment.getId(), user.getId());
-            },
-            () -> {
-              CommentLike saved =
-                  commentLikeRepository.save(
-                      CommentLike.builder().comment(comment).user(user).build());
- 
-              log.info(
-                  "[COMMENT LIKE] LIKE - commentId={}, userId={}, likeId={}",
-                  comment.getId(),
-                  user.getId(),
-                  saved.getId());
-            });
+    try {
+      commentLikeRepository
+          .findByCommentAndUser(comment, user)
+          .ifPresentOrElse(
+              like -> {
+                commentLikeRepository.delete(like);
+                log.info(
+                    "[COMMENT LIKE] UNLIKE - commentId={}, userId={}", comment.getId(), user.getId());
+              },
+              () -> {
+                CommentLike saved =
+                    commentLikeRepository.save(
+                        CommentLike.builder().comment(comment).user(user).build());
+ 
+                log.info(
+                    "[COMMENT LIKE] LIKE - commentId={}, userId={}, likeId={}",
+                    comment.getId(),
+                    user.getId(),
+                    saved.getId());
+              });
+    } catch (org.springframework.dao.DataIntegrityViolationException e) {
+      // 동시 요청으로 인한 중복 삽입 시도 - 이미 좋아요 상태이므로 UNLIKE 처리
+      commentLikeRepository.findByCommentAndUser(comment, user)
+          .ifPresent(like -> {
+            commentLikeRepository.delete(like);
+            log.info("[COMMENT LIKE] UNLIKE (conflict resolved) - commentId={}, userId={}", 
+                comment.getId(), user.getId());
+          });
+    }
   }
🧹 Nitpick comments (1)
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1)

191-192: 좋아요 데이터 조회 시 쿼리 최적화를 고려해 보세요.

countByCommentexistsByCommentAndUser가 별도로 호출되어 2개의 쿼리가 발생합니다. 단일 댓글 업데이트에서는 큰 문제가 아니지만, 필요시 단일 쿼리로 통합하는 방법을 고려할 수 있습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a91147 and bae3675.

📒 Files selected for processing (1)
  • src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (1)
src/main/java/com/sku/refit/domain/post/service/PostServiceImpl.java (1)
  • Service (33-213)
🔇 Additional comments (4)
src/main/java/com/sku/refit/domain/comment/service/CommentServiceImpl.java (4)

6-43: LGTM!

좋아요 기능을 위한 CommentLikeRepository 의존성과 컬렉션 관련 임포트가 적절하게 추가되었습니다.


56-65: 부모 댓글 검증 로직이 잘 추가되었습니다.

부모 댓글이 동일한 게시글에 속하는지 검증하여 크로스 포스트 부모 할당을 방지합니다. 이전 리뷰 코멘트가 잘 반영되었습니다.


123-172: 댓글 목록 조회 로직이 잘 구현되었습니다.

좋아요 카운트와 사용자의 좋아요 여부를 배치 쿼리로 조회하여 N+1 문제를 방지했습니다. 또한 부모 댓글이 responseMap에 없는 경우의 NPE 이슈가 적절히 처리되었습니다 (lines 161-166).


199-223: LGTM!

삭제 로직이 올바르게 구현되었고, 로깅 패턴이 다른 메서드들과 일관성 있게 적용되었습니다.

@uni-j-uni uni-j-uni merged commit 5657094 into main Dec 13, 2025
3 checks passed
@uni-j-uni uni-j-uni deleted the feature/comment branch December 15, 2025 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feature 새로운 기능 요청 🚧 in progress 작업 진행 중

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Feat: 커뮤니티 추가 기능 개발

1 participant