Skip to content

81 포스트 일기 상세조회 dto 수정#82

Merged
xoruddl merged 3 commits into
developfrom
81-포스트-일기-상세조회-dto-수정
Aug 24, 2025

Hidden character warning

The head ref may contain hidden characters: "81-\ud3ec\uc2a4\ud2b8-\uc77c\uae30-\uc0c1\uc138\uc870\ud68c-dto-\uc218\uc815"
Merged

81 포스트 일기 상세조회 dto 수정#82
xoruddl merged 3 commits into
developfrom
81-포스트-일기-상세조회-dto-수정

Conversation

@xoruddl
Copy link
Copy Markdown
Member

@xoruddl xoruddl commented Aug 24, 2025

📝 개요
이번 PR의 핵심 내용을 한 줄로 요약해 주세요.

💻 작업 내용
이번 PR에서 작업한 내용을 상세히 설명해 주세요.

작업 내용 1
작업 내용 2
...

✅ PR 체크리스트
PR을 보내기 전에 아래 체크리스트를 확인해 주세요.

커밋 메시지는 포맷에 맞게 작성했나요?
스스로 코드를 다시 한번 검토했나요?
관련 이슈를 연결했나요?
빌드 및 테스트가 로컬에서 성공했나요?

🔗 관련 이슈
이번 PR과 관련된 이슈 번호를 기재해 주세요. 예: Closes #81

스크린샷 (선택)
UI 변경 사항이 있다면 스크린샷을 첨부해 주세요.

Summary by CodeRabbit

  • New Features

    • 다이어리 상세 응답 추가: 댓글 목록, 작성자 정보, 이미지, 공개 여부, 좋아요 여부/수, 댓글 수, 생성·수정 시각 포함 제공.
    • 아바타 게시글 상세 조회 개선: 작성자·댓글·댓글 작성자 정보를 한 번에 반환.
  • Refactor

    • 연관 데이터 일괄 로딩으로 성능 개선 및 N+1 완화.
    • 비공개 다이어리는 작성자만 열람 가능하도록 접근 제한 강화.
    • 게시글/댓글 응답 구조 일부 필드명(작성자/댓글 리스트, 공개 여부 표기) 변경.
  • Chores

    • 내부 패키지 경로·임포트 정리.

@xoruddl xoruddl linked an issue Aug 24, 2025 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 24, 2025

Walkthrough

포스트와 일기 상세 조회 DTO가 레코드로 재작성되고 서비스/컨트롤러에서 해당 DTO를 사용하도록 변경되었습니다. DiaryRepository가 패키지로 이동·확장(상세 조회 쿼리 추가)되었고 여러 서비스의 import 경로가 정정되었습니다. 일부 리포지토리에 메서드가 추가되었습니다.

Changes

Cohort / File(s) Summary
DiaryRepository 이동·확장
src/main/java/.../diary/domain/Repository/DiaryRepository.javasrc/main/java/.../diary/domain/repository/DiaryRepository.java
패키지 이동 및 메서드 추가: findByIsPublicIsTrue(Pageable), findByUserOrderByCreatedAtDesc(User), findByIdWithDetails(@Param("diaryId") Long)(left join fetch).
AvatarPostRepository: 상세 조회 확장
src/main/java/.../avatarpost/domain/repository/AvatarPostRepository.java
findByIdWithCommentsfindByIdWithDetails(@Param("postId") Long postId)로 교체, 작성자·댓글·댓글 작성자까지 페치 조인(SELECT DISTINCT).
PostInfoResponse 리팩터링
src/main/java/.../avatarpost/dto/PostInfoResponse.java
Lombok 기반 POJO → Java record로 전환, 필드명/구조 변경(예: title→writerId, comment→comments), from(AvatarPost, boolean) 팩토리 및 CommentResponseDTO record 추가.
AvatarPostService 변경
src/main/java/.../avatarpost/service/AvatarPostService.java
findByIdWithDetails 사용, 좋아요 체크에 existsByUserIdAndTargetIdAndTargetType(..., "AVATAR_POST") 사용, DTO 조립을 PostInfoResponse.from으로 대체, 클래스에 @Transactional(readOnly = true).
DiaryInfoResponse 신규 DTO 및 적용
src/main/java/.../diary/dto/response/DiaryInfoResponse.java, src/main/java/.../diary/service/DiaryService.java, src/main/java/.../diary/presentation/DiaryController.java
DiaryInfoResponse record 추가(from(Diary, boolean) 포함). 서비스 getDiaryInfo 반환 타입을 DiaryInfoResponse로 변경, findByIdWithDetails 사용, 비공개 접근 제어 추가. 컨트롤러 제네릭도 DiaryInfoResponse로 변경.
Import 경로 정정(여러 파일)
src/main/java/.../reports/service/ReportService.java, .../comment/service/CommentService.java, .../feed/service/FeedService.java, .../like/service/LikeService.java, .../global/listener/NotificationEventListener.java
...diary.domain.Repository.DiaryRepository...diary.domain.repository.DiaryRepository로 import 경로 수정(기능 변화 없음).
LikeRepository 메서드 추가(중복 위험)
src/main/java/.../like/domain/repository/LikeRepository.java
boolean existsByUserIdAndTargetIdAndTargetType(Long, Long, String post) 추가 — 기존 시그니처와 유사해 중복·컴파일 오류 위험.
NotificationEventListener 로거 위치 변경(비기능)
src/main/java/.../global/listener/NotificationEventListener.java
Logger 필드 선언 순서 변경(기능적 영향 없음).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as 사용자
  participant C as DiaryController
  participant S as DiaryService
  participant R as DiaryRepository
  participant L as LikeRepository

  U->>C: GET /diaries/{id}
  C->>S: getDiaryInfo(diaryId, currentUser)
  S->>R: findByIdWithDetails(diaryId)
  R-->>S: Diary(+user,+image,+comments+writers)
  alt 공개이거나 작성자
    S->>L: existsByUserAndTargetIdAndTargetType(currentUser, diaryId, "DIARY")
    L-->>S: isLiked
    S-->>C: DiaryInfoResponse.from(diary, isLiked)
    C-->>U: 200 OK + ApiResponse<DiaryInfoResponse>
  else 접근불가
    S-->>C: throw AccessDeniedException
    C-->>U: 403
  end
Loading
sequenceDiagram
  autonumber
  actor U as 사용자
  participant S as AvatarPostService
  participant R as AvatarPostRepository
  participant L as LikeRepository

  U->>S: getAvatarPostInfo(postId, currentUser)
  S->>R: findByIdWithDetails(postId)
  R-->>S: AvatarPost(+user,+comments+writers)
  S->>L: existsByUserIdAndTargetIdAndTargetType(currentUserId, postId, "AVATAR_POST")
  L-->>S: isLiked
  S-->>U: PostInfoResponse.from(post, isLiked)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
포스트 상세조회 DTO 수정 (#81)
일기 상세조회 DTO 수정 (#81)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
LikeRepository에 중복 가능 메서드 추가 (existsByUserIdAndTargetIdAndTargetType(..., String post)) (src/main/java/.../like/domain/repository/LikeRepository.java) 이슈 #81은 DTO 수정이 목적임. 신규 메서드는 범위를 벗어나며 기존 동일 메서드와 중복되어 컴파일 오류를 유발할 가능성이 있음.
Logger 필드 선언 순서 변경 (src/main/java/.../global/listener/NotificationEventListener.java) DTO/서비스 변경 이슈와 무관한 비기능적 정렬 변경으로, 연결된 이슈의 요구사항과 관계없음.

Possibly related PRs

Suggested reviewers

  • c5ln
  • lejuho

Poem

"당근 들고 달려온 토끼예요 🥕
레코드로 변신한 포스트와 일기, 반짝 반짝!
조인 한 번으로 댓글 쭉- 끌어오고,
좋아요는 타입 맞춰 또각- 체크하죠.
커밋에 폴짝, 배포로 폴짝!"

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 81-포스트-일기-상세조회-dto-수정

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@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 (3)
src/main/java/com/example/cp_main_be/domain/social/like/service/LikeService.java (1)

95-125: 버그: removeLike에 FEED 분기 누락

addLike에는 "feed" 분기가 있으나, removeLike에는 FEED에 대한 해제 처리가 없습니다. FEED 좋아요가 존재하는 경우 해제 호출 시 사이드이펙트 없이 0을 반환하더라도 분기 자체는 대칭적으로 존재해야 합니다(추후 로직 확장 대비).

다음 패치를 추가해 최소 대응하세요(집계 변경이 없다면 0 반환 유지):

     } else if ("avatar_post".equalsIgnoreCase(targetType)) {
       AvatarPost avatarPost =
           avatarPostRepository
               .findById(targetId)
               .orElseThrow(() -> new IllegalArgumentException("포스트를 찾을 수 없습니다."));

       avatarPost.decreaseLikeCount();
       return avatarPost.getLikeCount();
+    } else if ("FEED".equalsIgnoreCase(targetType)) {
+      // 현재 FEED는 좋아요 수를 별도 집계하지 않는 것으로 보임
+      // (addLike에서도 0을 반환). 대칭 처리만 보장.
+      return 0;
     }
     return 0;
src/main/java/com/example/cp_main_be/global/listener/NotificationEventListener.java (2)

87-93: Optional.get() 무방비 사용 및 targetType 분기 불명확

  • findById(...).get()은 데이터가 없을 때 NoSuchElementException을 유발합니다. 안전하게 orElseThrow로 바꾸세요.
  • targetType"DIARY"가 아닌 모든 경우를 아바타 포스트로 간주하고 있습니다. 새로운 타입이 추가되면 잘못된 분기/알림이 발생합니다. 명시적으로 분기하고 알 수 없는 타입은 경고 후 리턴하세요.

아래처럼 수정 권장:

-    if (Objects.equals(targetType, "DIARY")) {
-      receiver = diaryRepository.findById(targetId).get().getUser();
-      url = "/api/v1/diaries/" + targetId;
-    } else {
-      receiver = avatarPostRepository.findById(targetId).get().getUser();
-      url = "/api/v1/avatar-posts/" + targetId;
-    }
+    if (Objects.equals(targetType, "DIARY")) {
+      var diary = diaryRepository.findById(targetId)
+          .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 일기입니다: id=" + targetId));
+      receiver = diary.getUser();
+      url = "/api/v1/diaries/" + targetId;
+    } else if (Objects.equals(targetType, "AVATAR_POST")) {
+      var post = avatarPostRepository.findById(targetId)
+          .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 아바타 포스트입니다: id=" + targetId));
+      receiver = post.getUser();
+      url = "/api/v1/avatar-posts/" + targetId;
+    } else {
+      log.warn("지원하지 않는 targetType입니다: {} (targetId={})", targetType, targetId);
+      return;
+    }

36-38: 이벤트 알림은 커밋 이후 전송 권장: @TransactionalEventListener(phase = AFTER_COMMIT)로 전환

현재 @EventListener + @Transactional 조합은 트랜잭션 롤백 시에도 알림이 전송될 수 있습니다(이벤트가 커밋 전에 발행되는 경우). 알림은 사이드 이펙트이므로 커밋 이후에 전송되도록 @TransactionalEventListener(AFTER_COMMIT)로 바꾸세요.

예시(패턴만 제시, 모든 핸들러에 동일 적용 권장):

-  @EventListener
-  @Transactional
-  public void handleCommentCreatedEvent(CommentCreatedEvent event) {
+  @org.springframework.transaction.event.TransactionalEventListener(
+      phase = org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT)
+  public void handleCommentCreatedEvent(CommentCreatedEvent event) {

같은 변경을 Like/Guestbook/Follow/Watering 핸들러에도 적용해 주세요.

Also applies to: 78-80, 100-102, 124-126

🧹 Nitpick comments (21)
src/main/java/com/example/cp_main_be/domain/social/comment/service/CommentService.java (1)

45-64: targetType 문자열 분기를 enum/상수 기반으로 일원화하세요

현재 "DIARY", "AVATAR_POST" 문자열 비교에 의존합니다. 오타·케이스 불일치에 취약하고 전역 일관성 유지가 어렵습니다. 최소한 상수로 묶거나, 가능하면 TargetType enum을 도입해 DTO와 서비스 전반에서 타입 안정성을 보장하세요.

적용 예(선호: enum 사용):

public enum TargetType { DIARY, AVATAR_POST }

선 최소 조치(본 메서드 내 문자열 상수화) — 선택 라인 범위 내 치환:

-    if ("DIARY".equalsIgnoreCase(targetType)) {
+    if (TARGET_DIARY.equalsIgnoreCase(targetType)) {
...
-    } else if ("AVATAR_POST".equalsIgnoreCase(targetType)) {
+    } else if (TARGET_AVATAR_POST.equalsIgnoreCase(targetType)) {

클래스 상단(선택 범위 외) 보조 추가:

private static final String TARGET_DIARY = "DIARY";
private static final String TARGET_AVATAR_POST = "AVATAR_POST";
src/main/java/com/example/cp_main_be/domain/social/like/domain/repository/LikeRepository.java (1)

15-16: 파라미터 이름 정정 제안: 'post' → 'targetType'

Spring Data JPA 파생 쿼리는 파라미터 “이름”에 의존하진 않지만, 가독성과 오해 방지를 위해 메서드 시그니처의 세 번째 파라미터 이름을 의미에 맞게 바꾸는 것이 좋습니다. 또한 13라인의 postId도 대상이 포스트로 한정되지 않는다면 targetId로 정리하는 것을 권장합니다.

권장 변경:

-  boolean existsByUserIdAndTargetIdAndTargetType(Long id, Long postId, String post);
+  boolean existsByUserIdAndTargetIdAndTargetType(Long id, Long targetId, String targetType);
src/main/java/com/example/cp_main_be/domain/social/feed/service/FeedService.java (1)

83-97: 인메모리 병합/정렬 비용 최적화 고려

현재 두 소스를 (page+1)*size로 넉넉히 가져와 인메모리 정렬/슬라이싱합니다. 데이터가 커지면 GC/힙 압박 및 O(N log N) 정렬 비용이 증가합니다. 향후 스케일 고려 시:

  • 커서 기반(keyset) 페이지네이션
  • 각 소스에서 동일한 createdAt 상한/하한 커서 조건으로 조합 후보 축소
  • DB 레벨에서 union all + 정렬(가능한 구조라면)로 오프셋/리밋 처리
    를 검토하세요.
src/main/java/com/example/cp_main_be/domain/social/like/service/LikeService.java (1)

33-41: targetType 문자열 상수/enum으로 일원화

이 파일 내에서도 "feed"/"FEED", "DIARY"/"diary", "AVATAR_POST"/"avatar_post"가 혼재합니다. equalsIgnoreCase로 동작은 하지만, 상수/enum으로 통일해 가독성과 안전성을 높이세요. 최소치로 상수 도입을 권장합니다.

부분 치환(선택 라인 범위 내):

-    if ("feed".equalsIgnoreCase(targetType)) {
+    if (TARGET_FEED.equalsIgnoreCase(targetType)) {
...
-    } else if ("DIARY".equalsIgnoreCase(targetType)) {
+    } else if (TARGET_DIARY.equalsIgnoreCase(targetType)) {
...
-    } else if ("AVATAR_POST".equalsIgnoreCase(targetType)) {
+    } else if (TARGET_AVATAR_POST.equalsIgnoreCase(targetType)) {
...
-    if ("diary".equalsIgnoreCase(targetType)) {
+    if (TARGET_DIARY.equalsIgnoreCase(targetType)) {
...
-    } else if ("avatar_post".equalsIgnoreCase(targetType)) {
+    } else if (TARGET_AVATAR_POST.equalsIgnoreCase(targetType)) {

클래스 상단(선택 범위 외) 보조 추가:

private static final String TARGET_FEED = "FEED";
private static final String TARGET_DIARY = "DIARY";
private static final String TARGET_AVATAR_POST = "AVATAR_POST";

또는 enum 도입(권장) 후 DTO/컨트롤러 계층까지 적용을 검토하세요.

Also applies to: 50-66, 72-79, 85-91, 107-115, 115-123

src/main/java/com/example/cp_main_be/global/listener/NotificationEventListener.java (1)

50-52: 알림 리다이렉트 URL 규칙 불일치: UI 경로(/diaries/…) vs API 경로(/api/v1/… ) 혼재

댓글의 경우 /diaries/{id}, 좋아요는 /api/v1/diaries/{id}, 방명록은 /garden/{ownerId}, 물주기 알림은 /garden 등 서로 다른 규칙이 섞여 있어 클라이언트 라우팅/딥링크가 불안정해질 수 있습니다. 한 가지 규칙(UI 라우팅용 경로 권장)으로 통일하고, URL 빌더 헬퍼로 중복 생성을 제거하세요.

예시(헬퍼 메서드 도입):

private String buildRedirectUrl(String targetType, Long targetId) {
  return switch (targetType) {
    case "DIARY" -> "/diaries/" + targetId;         // UI 라우팅 기준
    case "AVATAR_POST" -> "/avatar-posts/" + targetId;
    case "GUESTBOOK" -> "/garden/" + targetId;      // 필요 시 의미에 맞게 조정
    case "WATERING" -> "/garden";
    default -> "/";
  };
}

그리고 각 이벤트 핸들러에서는 buildRedirectUrl(...)만 호출하도록 맞추면 유지보수가 수월합니다.

Also applies to: 58-60, 89-93, 106-107, 129-131

src/main/java/com/example/cp_main_be/domain/social/diary/presentation/DiaryController.java (1)

50-50: TODO는 서비스에서 이미 처리됨 — 주석 정리 권장

비공개 접근 제어는 서비스(DiaryService.getDiaryInfo)에서 수행하고 있으므로 컨트롤러의 TODO는 제거하거나 "서비스에서 처리"로 업데이트하세요.

-    // TODO: 비공개 글일 경우 작성자만 볼 수 있도록 하는 로직 추가 필요
+    // 비공개 접근 제어는 DiaryService.getDiaryInfo에서 수행됨
src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java (3)

87-89: 좋아요 조회는 userId 기반 시그니처가 더 효율적

현재 existsByUserAndTargetIdAndTargetType(User, Long, String)를 사용하고 있습니다. 가능하다면 existsByUser_IdAndTargetIdAndTargetType(Long, Long, String) 형태로 바꿔 조인 비용을 줄이세요. 레포 메서드가 없다면 추가를 검토해 주세요.


66-68: 리소스 미존재 예외 매핑 재검토(404 권장)

IllegalArgumentException("일기를 찾을 수 없습니다.")는 보통 400으로 매핑될 수 있습니다. REST语의 관점에서는 404가 더 적절합니다. ResponseStatusException(HttpStatus.NOT_FOUND, "...") 또는 전용 NotFoundException + @ControllerAdvice 매핑을 제안합니다.


80-83: SecurityException 대신 Spring 표준 예외 사용 제안

보안 컨텍스트와의 일관성을 위해 SecurityException 대신 AccessDeniedException(spring-security)을 사용하면 전역 예외 처리/필터 체인과의 통합이 수월합니다. 위 주요 코멘트의 diff에 반영했습니다.

src/main/java/com/example/cp_main_be/domain/social/avatarpost/service/AvatarPostService.java (4)

24-24: 예외 타입을 도메인 의미에 맞게 구체화 제안

IllegalArgumentException 대신 404 매핑이 되는 EntityNotFoundException(혹은 커스텀 NotFoundException) 사용을 고려해 주세요. 컨트롤러 어드바이스에서 404로 일괄 변환하기도 수월합니다.
적용 예시:

- .orElseThrow(() -> new IllegalArgumentException("해당 포스트를 찾을 수 없습니다."));
+ .orElseThrow(() -> new EntityNotFoundException("AvatarPost not found: id=" + postId));

29-30: 타겟 타입 문자열 리터럴 제거 제안

하드코딩 문자열 "AVATAR_POST"는 오타·변경에 취약합니다. TargetType.AVATAR_POST와 같은 enum을 도입하고, 리포지토리 시그니처도 enum을 받도록 정리하는 것을 권장합니다.

예시(호출부):

- likeRepository.existsByUserIdAndTargetIdAndTargetType(currentUser.getId(), postId, "AVATAR_POST");
+ likeRepository.existsByUserIdAndTargetIdAndTargetType(currentUser.getId(), postId, TargetType.AVATAR_POST);

21-22: var 사용은 JDK 버전 의존적 — 프로젝트 소스/타깃 호환성 확인

var는 JDK 10+에서만 지원됩니다. 빌드 설정(maven-compiler-pluginmaven.compiler.release 또는 Gradle의 sourceCompatibility/targetCompatibility)이 이를 보장하는지 확인 바랍니다. 팀 컨벤션에 따라 명시적 타입 사용을 선호한다면 교체를 고려해 주세요.


29-30: LikeRepository 메서드 시그니처(User vs. userId) 통일 및 하드코딩된 타입 문자열 enum 대체 권장

  • LikeRepository 인터페이스(src/main/java/com/example/cp_main_be/domain/social/like/domain/repository/LikeRepository.java)에는
    • boolean existsByUserAndTargetIdAndTargetType(User user, Long targetId, String targetType)
    • boolean existsByUserIdAndTargetIdAndTargetType(Long id, Long postId, String postType)
    두 가지 오버로드된 메서드가 공존하고 있습니다.
  • AvatarPostService(src/main/java/com/example/cp_main_be/domain/social/avatarpost/service/AvatarPostService.java:29–30)
    → existsByUserIdAndTargetIdAndTargetType(currentUser.getId(), postId, "AVATAR_POST") 사용
  • DiaryService(src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java:88–89)
    → existsByUserAndTargetIdAndTargetType(currentUser, diaryId, "DIARY") 사용

위와 같은 혼재는 유지보수성과 가독성을 저해하므로, 다음과 같이 리팩터링을 권장드립니다.

  1. LikeRepository 내 메서드를 하나의 시그니처로 통일 (User 객체 또는 userId 중 하나 선택)
  2. 내부에 공통 로직(private 메서드)으로 분리해 중복 제거
  3. "AVATAR_POST", "DIARY" 등의 하드코딩된 문자열을 enum 타입(e.g. TargetType.AVATAR_POST, TargetType.DIARY)으로 대체
src/main/java/com/example/cp_main_be/domain/social/diary/dto/response/DiaryInfoResponse.java (4)

15-15: boolean 직렬화 키 일관성 확보 제안 (isLiked)

isPublic에는 @JsonProperty를 부여했지만 isLiked에는 미부여입니다. Jackson이 record 구성요소 이름을 기본 키로 쓰긴 하나, 팀/클라이언트 측에서 “isLiked” 키를 계약으로 고정하려면 명시를 권장합니다.

-    boolean isLiked,
+    @JsonProperty("isLiked") boolean isLiked,

24-27: 댓글 정렬 보장 필요 가능성 (UI 일관성)

현재 diary.getComments().stream() 순서는 DB/컬렉션 기본 순서에 의존합니다. 작성일 ASC(또는 DESC) 요구가 있으면 여기서 명시적으로 정렬해 주세요.

-    List<CommentResponseDTO> commentDTOs =
-        diary.getComments().stream().map(CommentResponseDTO::from).toList();
+    List<CommentResponseDTO> commentDTOs =
+        diary.getComments().stream()
+            .sorted(java.util.Comparator.comparing(c -> c.getCreatedAt()))
+            .map(CommentResponseDTO::from)
+            .toList();

45-51: null-안전성 확인(작성자/프로필 이미지)

comment.getWriter() 또는 getProfileImageUrl()가 null일 가능성이 도메인 제약상 완전히 배제되는지 확인해 주세요. 만약 탈퇴 사용자/이미지 미등록 케이스가 존재하면 NPE를 방지하는 null 안전 매핑이 필요합니다.

예시:

-      return new CommentResponseDTO(
-          comment.getId(),
-          comment.getWriter().getProfileImageUrl(),
-          comment.getWriter().getNickname(),
-          comment.getContent());
+      var writer = comment.getWriter();
+      return new CommentResponseDTO(
+          comment.getId(),
+          writer != null ? writer.getProfileImageUrl() : null,
+          writer != null ? writer.getNickname() : "알 수 없음",
+          comment.getContent());

9-53: 중복 DTO 구조의 공통화 제안 (댓글 응답 구조)

PostInfoResponse.CommentResponseDTO와 동일 구조가 반복됩니다. 공용 패키지에 재사용 가능한 CommentResponseDTO를 두거나, 매퍼 유틸(예: MapStruct)로 중복 제거를 고려해 주세요.

src/main/java/com/example/cp_main_be/domain/social/avatarpost/dto/PostInfoResponse.java (4)

22-25: 댓글 리스트 생성 시 정렬 고려

Diary와 동일하게 댓글 표시 순서 요구가 있다면 여기서 정렬을 명시하세요.

-    List<CommentResponseDTO> commentDTOs =
-        post.getComments().stream().map(CommentResponseDTO::from).toList();
+    List<CommentResponseDTO> commentDTOs =
+        post.getComments().stream()
+            .sorted(java.util.Comparator.comparing(c -> c.getCreatedAt()))
+            .map(CommentResponseDTO::from)
+            .toList();

14-15: isLiked 직렬화 키 고정 일관성

DiaryInfoResponse와 동일하게 @JsonProperty("isLiked")를 부여하여 직렬화 키를 계약으로 고정하는 것을 권장합니다.

-    boolean isLiked,
+    @JsonProperty("isLiked") boolean isLiked,

41-49: 댓글 DTO 매핑 로직 일관성 좋음 — 공통화 고려

다이어리와 동일 구조의 매핑을 사용하고 있어 클라이언트 일관성에 유리합니다. 중복 제거는 별도 공통 DTO/매퍼로의 추출로 해결 가능합니다.


26-38: AvatarPost에 가시성 필드가 없으므로 true 하드코딩은 현재 타당합니다.
다만, 추후 공개/비공개 기능이 도입될 가능성을 대비해 아래를 적용해 주세요.

  • PostInfoResponse.from 메서드의 하드코딩 부분에 TODO 주석 추가
  •    true // AvatarPost는 항상 public이라고 가정
    
  •    true // AvatarPost는 항상 public (추후 가시성 필드 추가 시 매핑 필요) // TODO
    
  • PR 설명에 “AvatarPost 도메인에는 가시성 필드가 없어 현재 모든 포스트를 공개로 처리함”을 명시
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 37861bd and 58c0d9d.

📒 Files selected for processing (13)
  • src/main/java/com/example/cp_main_be/domain/reports/service/ReportService.java (1 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/avatarpost/domain/repository/AvatarPostRepository.java (1 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/avatarpost/dto/PostInfoResponse.java (1 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/avatarpost/service/AvatarPostService.java (1 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/comment/service/CommentService.java (1 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/diary/domain/Repository/DiaryRepository.java (2 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/diary/dto/response/DiaryInfoResponse.java (1 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/diary/presentation/DiaryController.java (2 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java (2 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/feed/service/FeedService.java (1 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/like/domain/repository/LikeRepository.java (1 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/like/service/LikeService.java (1 hunks)
  • src/main/java/com/example/cp_main_be/global/listener/NotificationEventListener.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/example/cp_main_be/domain/social/avatarpost/service/AvatarPostService.java (2)
src/main/java/com/example/cp_main_be/domain/social/comment/service/CommentService.java (1)
  • Service (21-121)
src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java (1)
  • Service (24-213)
🔇 Additional comments (18)
src/main/java/com/example/cp_main_be/domain/social/comment/service/CommentService.java (1)

13-13: DiaryRepository import 경로 정정: 문제 없음

패키지 구조 변경에 따른 import 수정으로 보이며, 동작 영향은 없습니다.

src/main/java/com/example/cp_main_be/domain/social/like/domain/repository/LikeRepository.java (1)

15-16: existsByUserAndTargetIdAndTargetType 오버로드 사용처 검토 결과

  • existsByUserAndTargetIdAndTargetType(User, Long, String)
    • LikeService.java (line 39)
    • DiaryService.java (line 88)
  • existsByUserIdAndTargetIdAndTargetType(Long, Long, String)
    • AvatarPostService.java (line 29)

위 두 가지 오버로드는 각각 엔티티 기반 호출과 ID 기반 호출이라는 분명한 용도가 있고, 실제로 모두 사용되고 있으므로 중복이나 미사용 메서드가 아닙니다. 불필요한 API 제거는 필요하지 않습니다.

src/main/java/com/example/cp_main_be/domain/social/feed/service/FeedService.java (1)

7-7: DiaryRepository import 경로 정정: 문제 없음

패키지 이동에 따른 import 수정이며, 동작 영향은 없습니다.

src/main/java/com/example/cp_main_be/domain/reports/service/ReportService.java (1)

14-14: DiaryRepository import 경로 정정: 문제 없음

패키지 이동에 따른 import 수정으로 보이며, 동작 영향은 없습니다.

src/main/java/com/example/cp_main_be/domain/social/like/service/LikeService.java (2)

10-10: DiaryRepository import 경로 정정: 문제 없음

패키지 이동에 따른 import 수정이며, 로직 영향은 없습니다.


39-49: 중복 좋아요 유니크 제약 확인 완료

  • src/main/java/com/example/cp_main_be/domain/social/like/domain/Like.java@Table 애노테이션에
    uniqueConstraints = @UniqueConstraint(name = "uk_likes_user_target", columnNames = {"user_id", "target_id", "target_type"})가 정의되어 있어
    user_id + target_id + target_type 조합에 대한 유니크 제약이 존재합니다.

이로써 경쟁 상태에서도 중복 삽입을 방지할 수 있는 DB 레벨 유니크 제약이 확인되었습니다.

src/main/java/com/example/cp_main_be/global/listener/NotificationEventListener.java (2)

10-10: DiaryRepository import 경로 정정 LGTM

패키지 이동(...domain.repository) 반영이 정확합니다. 추가 작업 없이 컴포넌트 스캔에도 영향 없어 보입니다.


29-29: 로거 필드 위치 변경 무관하지만 유지보수 관점에서 OK

로거를 상단으로 올린 건 가독성에 도움 됩니다. 기능 영향은 없습니다.

src/main/java/com/example/cp_main_be/domain/social/diary/presentation/DiaryController.java (2)

47-52: DTO 교체(DiaryInfoResponse) 및 서비스 시그니처 연동 LGTM

상세 조회가 DiaryInfoResponse로 일원화되면서 컨트롤러-서비스 간 계약이 명확해졌습니다. 반환 타입 변경도 적절합니다.


47-52: API 응답 스키마 변경 영향도 확인 필요 (DiaryInfoResponse 적용)
rg -nP -C2 --type=java '\bPostInfoResponse\b|ApiResponse<\s*PostInfoResponse\s*>' 명령을 실행한 결과, PostInfoResponseavatarpost 관련 코드에만 남아있고 diary 패키지에는 잔존 사용처가 없음을 확인했습니다.
다음 항목들도 반드시 점검 부탁드립니다:

  • 모바일/Web 클라이언트
    · getDiaryDetail 엔드포인트 호출부가 DiaryInfoResponse 반환 스키마로 업데이트되었는지
  • API 문서 (Swagger)
    · OpenAPI 정의에 DiaryInfoResponse가 정상 반영되었는지
  • 스냅샷 테스트
    · 변경된 스키마에 맞춰 테스트 스냅샷이 갱신되었는지
src/main/java/com/example/cp_main_be/domain/social/diary/domain/Repository/DiaryRepository.java (1)

1-1: 패키지 이동(repository 소문자) 반영 LGTM

패키지 컨벤션(repository)으로 정리된 점 좋습니다. 관련 import 업데이트도 일관적입니다.

src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java (1)

88-89: AI 요약과 구현이 상이: existsByUserId… vs existsByUser…

AI 요약에는 existsByUserIdAndTargetIdAndTargetType 사용으로 기재되어 있으나 실제 코드는 existsByUserAndTargetIdAndTargetType입니다. 문서/PR 설명을 코드와 일치하도록 수정하거나, 필요 시 구현을 userId 버전으로 교체하세요.

src/main/java/com/example/cp_main_be/domain/social/avatarpost/service/AvatarPostService.java (3)

12-12: 클래스 레벨 read-only 트랜잭션 적용 좋습니다

조회 전용 서비스에 @Transactional(readOnly = true)를 부여해 JPA 플러시·더티체킹 비용을 줄입니다.


21-24: N+1 회피 목적의 fetch 메서드 사용은 적절합니다만, 자식 연관(댓글 작성자 등)까지 실제 fetch 되는지 확인 필요

findByIdWithDetails(postId)가 댓글과 댓글 작성자까지 fetch join 혹은 배치 사이즈로 로딩하는지 확인해 주세요. 컬렉션 fetch join이 다수일 경우 카티전 폭발 위험도 있으므로 필요 컬렉션만 선택적으로 fetch 하거나 배치 사이즈를 권장합니다.
원하시면 쿼리 힌트와 함께 리포지토리 JPQL을 점검해 드리겠습니다.


33-33: DTO 팩토리 메서드로의 위임이 가독성과 응집도 면에서 적절합니다

서비스 단의 매핑 로직을 DTO 정적 팩토리로 위임한 방향성 좋습니다. 동일 패턴을 다른 응답 DTO에도 일관되게 적용하면 유지보수성이 높아집니다.

src/main/java/com/example/cp_main_be/domain/social/diary/dto/response/DiaryInfoResponse.java (2)

9-21: Record 기반 응답 DTO 전환 좋습니다

불변·명세 중심의 응답 모델로 명확하며 필드 구성이 직관적입니다. @JsonProperty("isPublic")로 boolean 직렬화 키를 고정한 점도 👍


28-41: 카운트 계산은 DTO 리스트 크기 활용 — 일관성 OK

commentCountcommentDTOs.size()로 채우는 선택은 일관성이 있습니다. 성능 최적화가 필요하면 도메인에 집계 필드/프로젝션을 도입하는 대안도 가능합니다.

src/main/java/com/example/cp_main_be/domain/social/avatarpost/dto/PostInfoResponse.java (1)

9-21: Record 기반 전환 및 필드 구성 적절

불변 DTO와 명시적 필드로 응답 스키마가 명확합니다. @JsonProperty("isPublic") 지정도 좋습니다.

Comment on lines +15 to +21
@Query(
"SELECT ap FROM AvatarPost ap "
+ "LEFT JOIN FETCH ap.user u " // 포스트 작성자 fetch
+ "LEFT JOIN FETCH ap.comments c " // 댓글 목록 fetch
+ "LEFT JOIN FETCH c.writer cw " // 댓글 작성자 fetch
+ "WHERE ap.id = :postId")
Optional<AvatarPost> findByIdWithDetails(@Param("postId") Long postId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

컬렉션 fetch join 시 DISTINCT 누락 — 중복 행으로 단일 결과 쿼리 실패 위험

LEFT JOIN FETCH ap.comments c가 있으므로 동일 ap.id에 대해 다중 행이 발생합니다. Spring Data가 단건/Optional 매핑을 수행할 때 IncorrectResultSizeDataAccessException이 날 수 있어 DISTINCT를 추가해야 합니다.

-  @Query(
-      "SELECT ap FROM AvatarPost ap "
+  @Query(
+      "SELECT DISTINCT ap FROM AvatarPost ap "
       + "LEFT JOIN FETCH ap.user u "
       + "LEFT JOIN FETCH ap.comments c "
       + "LEFT JOIN FETCH c.writer cw "
       + "WHERE ap.id = :postId")

추가로, 댓글 정렬이 필요하다면 쿼리 ORDER BY c.createdAt 대신, 엔티티의 @OneToMany(mappedBy="...") 쪽에 @OrderBy("createdAt ASC")를 부여하는 접근을 권장합니다(페치 조인과 정렬 충돌 회피).

📝 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
@Query(
"SELECT ap FROM AvatarPost ap "
+ "LEFT JOIN FETCH ap.user u " // 포스트 작성자 fetch
+ "LEFT JOIN FETCH ap.comments c " // 댓글 목록 fetch
+ "LEFT JOIN FETCH c.writer cw " // 댓글 작성자 fetch
+ "WHERE ap.id = :postId")
Optional<AvatarPost> findByIdWithDetails(@Param("postId") Long postId);
@Query(
"SELECT DISTINCT ap FROM AvatarPost ap "
"LEFT JOIN FETCH ap.user u " // 포스트 작성자 fetch
"LEFT JOIN FETCH ap.comments c " // 댓글 목록 fetch
"LEFT JOIN FETCH c.writer cw " // 댓글 작성자 fetch
"WHERE ap.id = :postId")
Optional<AvatarPost> findByIdWithDetails(@Param("postId") Long postId);
🤖 Prompt for AI Agents
In
src/main/java/com/example/cp_main_be/domain/social/avatarpost/domain/repository/AvatarPostRepository.java
around lines 15 to 21, the JPQL should use DISTINCT to avoid duplicate rows when
fetch-joining the comments collection; change the query to select DISTINCT ap
(e.g., "SELECT DISTINCT ap FROM AvatarPost ap ...") so Optional mapping won't
fail. Also, if you need comments ordered, do not rely on ORDER BY in the
fetch-join; instead add @OrderBy("createdAt ASC") on the AvatarPost.comments
collection in the AvatarPost entity to enforce stable ordering while avoiding
fetch-join sort issues.

Copy link
Copy Markdown

@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: 1

♻️ Duplicate comments (2)
src/main/java/com/example/cp_main_be/domain/social/avatarpost/domain/repository/AvatarPostRepository.java (1)

15-21: DISTINCT 추가로 컬렉션 fetch join 중복 방지 — 이전 코멘트 반영 OK

LEFT JOIN FETCH ap.comments c가 있는 단건 조회에서 SELECT DISTINCT ap로 루트 엔티티 중복을 제거해 Optional 매핑 오류 위험을 줄였습니다. 지난 리뷰의 지적 사항이 정확히 반영되었습니다.

src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java (1)

80-86: 비공개 접근 제어와 null 사용자 처리 — 이전 리뷰 이슈가 정확히 해소되었습니다

currentUser null 안전성 및 비공개 글의 접근 제어가 올바르게 반영되었습니다. 403을 기대하는 클라이언트와의 계약에도 부합합니다.

🧹 Nitpick comments (7)
src/main/java/com/example/cp_main_be/domain/social/avatarpost/domain/repository/AvatarPostRepository.java (4)

15-21: 댓글 정렬 일관성이 필요하다면 엔티티 측 @orderby 권장

fetch join에 ORDER BY를 섞으면 예기치 않은 동작/경고가 날 수 있습니다. 댓글 표시 순서를 보장해야 한다면 AvatarPost 엔티티의 컬렉션에 정렬을 선언하세요.

  • 권장: @OrderBy("createdAt ASC") (또는 필요한 정렬 키)

엔티티 측 변경 예시(참고용):

// AvatarPost.java
@OneToMany(mappedBy = "post")
@OrderBy("createdAt ASC")
private List<Comment> comments;

15-21: 불필요한 별칭 정리로 JPQL 가독성 향상 (경미)

u, cw 별칭을 추가로 사용하지 않으므로 제거하면 가독성이 조금 좋아집니다. 기능 변화는 없습니다.

-      "SELECT DISTINCT ap FROM AvatarPost ap "
-          + "LEFT JOIN FETCH ap.user u " // 포스트 작성자 fetch
-          + "LEFT JOIN FETCH ap.comments c " // 댓글 목록 fetch
-          + "LEFT JOIN FETCH c.writer cw " // 댓글 작성자 fetch
+      "SELECT DISTINCT ap FROM AvatarPost ap "
+          + "LEFT JOIN FETCH ap.user "
+          + "LEFT JOIN FETCH ap.comments c "
+          + "LEFT JOIN FETCH c.writer "
           + "WHERE ap.id = :postId")

15-21: 대용량 댓글이 예상된다면 카티전 곱으로 인한 row 폭증 가능성 점검

단건 조회라도 댓글 수가 매우 많으면 ap × comments × writer 조인으로 ResultSet이 커질 수 있습니다. 제품 특성상 댓글 수가 커질 가능성이 있으면 아래 대안을 검토해주세요.

  • 대안 A: 댓글은 별도 API 또는 지연 로딩 + @BatchSize/hibernate.default_batch_fetch_size로 N+1 완화
  • 대안 B: @EntityGraph(attributePaths = {"user"}) + 댓글은 페이지네이션 조회

필요 시 성능 추적 포인트를 추가해 평균/최대 댓글 수를 계측하는 것도 권장합니다.


15-21: Java 텍스트 블록으로 JPQL 가독성 개선 (JDK 15+ 환경일 때만)

멀티라인 문자열을 텍스트 블록으로 바꾸면 주석 없이도 읽기 쉬워집니다. 기능 변화는 없습니다.

-  @Query(
-      "SELECT DISTINCT ap FROM AvatarPost ap "
-          + "LEFT JOIN FETCH ap.user "
-          + "LEFT JOIN FETCH ap.comments c "
-          + "LEFT JOIN FETCH c.writer "
-          + "WHERE ap.id = :postId")
+  @Query("""
+      SELECT DISTINCT ap FROM AvatarPost ap
+      LEFT JOIN FETCH ap.user
+      LEFT JOIN FETCH ap.comments c
+      LEFT JOIN FETCH c.writer
+      WHERE ap.id = :postId
+      """)
src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java (3)

87-91: 좋아요 exists 조회: 엔티티 파라미터/매직 스트링 → ID 기반 + Enum 전환 제안

  • 현재 구현은 엔티티 파라미터(User)와 매직 스트링("DIARY")에 의존합니다. ID 기반 existsBy... 시그니처와 Enum을 사용하면 JPA 바인딩/평가가 단순해지고 오타 리스크가 줄어듭니다.

적용 예시(레포지토리 시그니처를 추가/변경하는 경우):

-      isLiked = likeRepository.existsByUserAndTargetIdAndTargetType(currentUser, diaryId, "DIARY");
+      isLiked = likeRepository.existsByUserIdAndTargetIdAndTargetType(
+          currentUser.getId(), diaryId, LikeTargetType.DIARY);

레포지토리/타입 정의(파일 외 보조 변경):

// LikeRepository
boolean existsByUserIdAndTargetIdAndTargetType(Long userId, Long targetId, LikeTargetType targetType);

// LikeTargetType
public enum LikeTargetType { DIARY /*, POST, ...*/ }

운영 성능 팁: likes 테이블에 (user_id, target_type, target_id) 복합 인덱스가 없다면 추가를 고려해 주세요.


73-95: 핵심 경계 조건 테스트 케이스 보강 제안

이 메서드의 리그레션 방지를 위해 아래 테스트를 권장합니다.

  • 공개 글 + 비로그인: 200 OK, isLiked=false
  • 비공개 글 + 비로그인: 403
  • 비공개 글 + 작성자 본인: 200 OK
  • 공개/비공개 글 + 로그인 타 사용자: 403(비공개), 200(공개)
  • 로그인 사용자 + 좋아요 존재/부재: isLiked true/false 분기 검증

원하시면 컨트롤러 통합 테스트(WebMvcTest/RestDocs 포함)와 서비스 단위 테스트 샘플을 생성해 드리겠습니다.


77-78: findByIdWithDetails 페치 조인 쿼리 최적화 권장

  • DiaryRepository.findByIdWithDetails 메서드(@Param("diaryId") Long diaryId)에는
    @Query("SELECT d FROM Diary d " +
           "LEFT JOIN FETCH d.comments c " +
           "LEFT JOIN FETCH c.writer cw " +
           "WHERE d.id = :diaryId")
    와 같이 작성되어 있어, 페치 조인만으로 연관 엔티티를 로딩하고 있습니다. 이 경우 댓글이 여러 개일 때 SQL 결과에 같은 Diary 엔티티가 중복으로 노출될 수 있으므로, 아래 중 하나를 적용해 중복 제거를 권장드립니다.
    • JPQL에 DISTINCT 추가
      @Query("SELECT DISTINCT d FROM Diary d " +
             "LEFT JOIN FETCH d.comments c " +
             "LEFT JOIN FETCH c.writer cw " +
             "WHERE d.id = :diaryId")
    • @EntityGraph(@NamedEntityGraph) 적용
      @EntityGraph(attributePaths = {"comments", "comments.writer"})
      Optional<Diary> findByIdWithDetails(@Param("diaryId") Long diaryId);
  • Diary 작성자(d.user)도 조회 시점에 자주 사용된다면, 추가 페치 조인 대상에 포함하거나 트랜잭션(readOnly = true) 내에서 안전히 lazy 로드되는지 재확인해 주세요 (현재 getDiaryInfo는 @transactional(readOnly = true)로 감싸져 있어, 같은 트랜잭션 내에서 lazy 로드가 가능합니다).

위 개선을 통해 중복 로우에 의한 장애를 예방하고, 필요한 연관만 효율적으로 로딩할 수 있습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 58c0d9d and c1d57c4.

📒 Files selected for processing (3)
  • src/main/java/com/example/cp_main_be/domain/social/avatarpost/domain/repository/AvatarPostRepository.java (1 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/diary/domain/Repository/DiaryRepository.java (2 hunks)
  • src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/example/cp_main_be/domain/social/diary/domain/Repository/DiaryRepository.java
🔇 Additional comments (4)
src/main/java/com/example/cp_main_be/domain/social/avatarpost/domain/repository/AvatarPostRepository.java (2)

10-10: 굳 — @Param 추가로 이름 기반 바인딩 안정화

@Query의 네임드 파라미터(:postId)와 일치하도록 @Param("postId")를 사용한 점 좋습니다. 리플렉션 파라미터 이름 보존 여부와 무관하게 안정적으로 동작합니다.


21-21: 메서드명 변경 완료 확인

  • findByIdWithComments 호출은 더 이상 코드베이스 내에서 발견되지 않습니다.
  • findByIdWithDetails 호출이 DiaryService, AvatarPostService, 각 Repository 파일에서 정상적으로 반영된 것을 확인했습니다.

추가 조치가 필요 없으므로 이 검토 요청을 닫습니다.

src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java (2)

73-79: DTO 반환 시그니처 변경 + fetch-join 기반 조회로 N+1 완화 — 좋습니다

메서드 시그니처를 DiaryInfoResponse로 명확히 하고, findByIdWithDetails로 연관 로딩을 한 번에 처리한 점이 합리적입니다.


93-95: DTO 팩토리 위임은 적절합니다

지연 로딩 컬렉션을 순회하지 않는 선에서 필요한 필드만 접근하면 성능 안정성이 확보됩니다. from(...) 내부에서 추가 쿼리 유발 여부만 유의해 주세요.

import com.example.cp_main_be.domain.social.avatarpost.dto.PostInfoResponse;
import com.example.cp_main_be.domain.social.diary.domain.Diary;
import com.example.cp_main_be.domain.social.diary.domain.Repository.DiaryRepository;
import com.example.cp_main_be.domain.social.diary.domain.repository.DiaryRepository;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

AccessDeniedException 도입 및 Repository 경로 정정은 적절합니다 — 예외 타입 일관화만 추가 제안

  • AccessDeniedException 추가로 보안语义가 명확해졌습니다.
  • 동일 파일 내 update/delete 경로(예: Line 107, 142)의 SecurityException도 AccessDeniedException으로 통일 시 HTTP 403 매핑 일관성이 좋아집니다. 글로벌 예외 핸들러가 403으로 매핑되는지도 함께 확인 부탁드립니다.

예외 매핑 및 레포지토리 선언을 점검하려면 아래 스크립트를 실행하세요.

Also applies to: 10-10, 19-19


🏁 Script executed:

#!/bin/bash
# AccessDeniedException -> 403 매핑 여부 확인
rg -nC3 '@ControllerAdvice|@RestControllerAdvice'
rg -nC3 '@ExceptionHandler\s*\(\s*AccessDeniedException\.class\s*\)'

# SecurityException 사용 위치 확인(교체 후보)
rg -nC2 'throw new SecurityException\(' src/main/java

# DiaryRepository 패키지/선언 확인
rg -nC2 'interface\s+DiaryRepository\b' src/main/java

Length of output: 2888


아래 스크립트를 실행해 GlobalExceptionHandler에서 AccessDeniedException 핸들러 매핑이 있는지, 그리고 DiaryRepository 파일의 패키지 선언(디렉토리명 대소문자 일치 여부)을 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# 1. GlobalExceptionHandler에서 AccessDeniedException 매핑 여부 확인
grep -n '@ExceptionHandler.*AccessDeniedException' src/main/java/com/example/cp_main_be/global/GlobalExceptionHandler.java || echo "매핑 없음"

# 2. DiaryRepository 패키지 선언 및 디렉토리명 확인
head -n 10 src/main/java/com/example/cp_main_be/domain/social/diary/domain/Repository/DiaryRepository.java

Length of output: 722


GlobalExceptionHandler에서 AccessDeniedExceptionSecurityException 매핑을 확인하기 위해 아래 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
# GlobalExceptionHandler 내 ExceptionHandler 어노테이션 확인
rg -n '@ExceptionHandler' -C2 src/main/java/com/example/cp_main_be/global/GlobalExceptionHandler.java

Length of output: 1148


GlobalExceptionHandler에 AccessDeniedException 매핑 및 Repository 디렉토리명 수정 필요

아래 작업을 통해 보안 예외 일관성과 패키지 경로 일치를 확보해주세요.

  • GlobalExceptionHandler.java에 AccessDeniedException 전용 핸들러 추가
    • 파일: src/main/java/com/example/cp_main_be/global/GlobalExceptionHandler.java
    • 예시:
      @ExceptionHandler(AccessDeniedException.class)
      public ResponseEntity<ErrorResponse> handleAccessDenied(AccessDeniedException e) {
        ErrorResponse response = ErrorResponse.of(HttpStatus.FORBIDDEN, e.getMessage());
        return new ResponseEntity<>(response, HttpStatus.FORBIDDEN);
      }
  • DiaryService의 SecurityException → AccessDeniedException 교체
    • 파일: src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java
      • Line 107: throw new SecurityException("일기를 수정할 권한이 없습니다.");
      • Line 142: throw new SecurityException("일기를 삭제할 권한이 없습니다.");
    • 수정안 예시:
      throw new AccessDeniedException("일기를 수정할 권한이 없습니다.");
  • DiaryRepository 디렉토리명 소문자화
    • 경로 수정 전: src/main/java/com/example/cp_main_be/domain/social/diary/domain/Repository/DiaryRepository.java
    • 수정 후: src/main/java/com/example/cp_main_be/domain/social/diary/domain/repository/DiaryRepository.java

위 세 가지 이슈를 반영하신 뒤, 글로벌 핸들러가 의도한 대로 403 응답을 반환하는지 통합 테스트를 통해 확인해 주세요.

🤖 Prompt for AI Agents
In src/main/java/com/example/cp_main_be/global/GlobalExceptionHandler.java (add
new handler) and
src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java
(lines ~107 and ~142) and the repository path, add an @ExceptionHandler for
AccessDeniedException that returns ErrorResponse with HttpStatus.FORBIDDEN;
replace the two throws of SecurityException in DiaryService with throws of
AccessDeniedException using the same messages; and rename/move the
DiaryRepository file from .../domain/Repository/DiaryRepository.java to
.../domain/repository/DiaryRepository.java and update any imports to the new
package so the project compiles and the global handler returns 403 as intended.

@xoruddl xoruddl merged commit 17753f3 into develop Aug 24, 2025
6 checks passed
@lejuho lejuho deleted the 81-포스트-일기-상세조회-dto-수정 branch August 25, 2025 08:13
@coderabbitai coderabbitai Bot mentioned this pull request Aug 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

포스트, 일기 상세조회 dto 수정

1 participant