refactor/get-diary-dto#71
Conversation
WalkthroughGET 다이어리 상세 조회가 인증 사용자 정보를 요구하도록 변경되었고, 반환 DTO가 DiaryResponse에서 PostInfoResponse로 교체되었습니다. Controller는 User를 주입받아 Service의 getDiaryInfo를 호출하며, Service는 좋아요 여부와 댓글 정보를 포함한 상세 응답을 조합해 반환합니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant Controller as DiaryController
participant Service as DiaryService
participant DiaryRepo as DiaryRepository
participant LikeRepo as LikeRepository
participant Mapper as Comment Mapper
Client->>Controller: GET /diaries/{id} (Auth Principal: User)
Controller->>Service: getDiaryInfo(diaryId, user)
Service->>DiaryRepo: findById(diaryId)
DiaryRepo-->>Service: Diary
Service->>LikeRepo: existsByUserIdAndTargetId(user.id, diaryId)
LikeRepo-->>Service: isLiked (boolean)
Service->>Mapper: map Diary.comments -> CommentResponseDTO[]
Mapper-->>Service: comments DTO list
Service-->>Controller: PostInfoResponse
Controller-->>Client: ApiResponse<PostInfoResponse>
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/main/java/com/example/cp_main_be/domain/social/diary/presentation/DiaryController.java (1)
47-52: 비공개 접근 제어 TODO는 서비스 계층에서 강제하고, 엔드포인트는 인증 필요를 명시하세요
- 현재 TODO로 남긴 비공개 접근 제어는 서비스의
getDiaryInfo에서 일관되게 검사하는 편이 안전합니다(컨트롤러에서 누락될 수 있음).- 인증이 반드시 필요하다면 Security 설정과 스웨거 문서에도 명시하세요. 컨트롤러 수준에서는 아래와 같이 보완할 수 있습니다.
추가 적용(선택, 메서드 시그니처 바깥 영역 변경 예시):
// import io.swagger.v3.oas.annotations.security.SecurityRequirement; @Operation(summary = "특정 일기 조회", description = "특정 id로 일기를 조회합니다") @SecurityRequirement(name = "bearerAuth") // import org.springframework.security.access.prepost.PreAuthorize; @PreAuthorize("isAuthenticated()") @GetMapping("/{diaryId}") public ResponseEntity<ApiResponse<PostInfoResponse>> getDiaryDetail( @PathVariable Long diaryId, @AuthenticationPrincipal User user) { PostInfoResponse postInfo = diaryService.getDiaryInfo(diaryId, user); return ResponseEntity.ok(ApiResponse.success(postInfo)); }참고:
- 익명 접근이 완전히 차단되지 않았다면
user가 null일 수 있어 서비스에서 NPE가 발생합니다. 보안 설정에서 익명 차단 여부를 확인해 주세요.
📜 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.
📒 Files selected for processing (2)
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(3 hunks)
🔇 Additional comments (1)
src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java (1)
62-68: 읽기 전용 주석/속성 일치, 깔끔합니다
@Transactional(readOnly = true)와 주석이 일치하며, 예외 메시지도 명확합니다.
| package com.example.cp_main_be.domain.social.diary.presentation; | ||
|
|
||
| import com.example.cp_main_be.domain.member.user.domain.User; | ||
| import com.example.cp_main_be.domain.social.avatarpost.dto.PostInfoResponse; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
PostInfoResponse import 경로 불일치 및 도메인 결합 가능성
현재 Diary 영역에서 avatarpost DTO(com.example.cp_main_be.domain.social.avatarpost.dto.PostInfoResponse)를 직접 참조하고 있습니다. PR 요약(및 일반적인 모듈링 가이드)과 상충하며, 도메인 간 결합을 키울 수 있습니다. PostInfoResponse의 실제 위치와 의도된 소유 도메인을 확인하고, 필요 시 공용(post) 계층으로 추출하거나 diary 전용 DTO로 이동하는 것을 권장합니다.
다음 스크립트로 PostInfoResponse의 실제 위치와 필드명을 확인해 주세요:
🏁 Script executed:
#!/bin/bash
# PostInfoResponse 위치와 핵심 필드 탐색
fd -a 'PostInfoResponse.java' src
# 클래스/빌더 필드 훑어보기
for f in $(fd -a 'PostInfoResponse.java' src); do
echo "==== $f ===="
rg -nC2 -e 'class\s+PostInfoResponse' -e '@Builder' -e '\bcomments?\b' -e '\bimageUrl\b' -e '\bisPublic\b' "$f"
doneLength of output: 1251
Diary 도메인에 AvatarPost DTO 직접 참조 제거 필요
– PostInfoResponse 정의 위치가 avatarpost 전용 패키지임이 확인되었습니다.
- src/main/java/com/example/cp_main_be/domain/social/avatarpost/dto/PostInfoResponse.java
– DiaryController 에서 해당 클래스를 직접 import 하고 있어 도메인 간 결합이 발생합니다.
- src/main/java/com/example/cp_main_be/domain/social/diary/presentation/DiaryController.java:4
제안 사항
- PostInfoResponse 를 ‘post’ 공용 계층(예:
com.example.cp_main_be.domain.social.post.dto)으로 옮겨 모든 게시물 유형에서 재사용할 수 있도록 하기 - 또는 Diary 전용 DTO를 새로 생성해, Diary에서 실제로 필요한 필드만 노출하도록 분리하기
위 두 가지 중 하나로 리팩토링하여 도메인 결합도를 낮춰 주세요.
🤖 Prompt for AI Agents
In
src/main/java/com/example/cp_main_be/domain/social/diary/presentation/DiaryController.java
around line 4, DiaryController currently imports PostInfoResponse from the
avatarpost DTO package which creates unwanted coupling between diary and
avatarpost domains; remove that direct import and either (a) move
PostInfoResponse to a shared post DTO package (e.g.
com.example.cp_main_be.domain.social.post.dto) and update all references/imports
accordingly so all post types reuse it, or (b) create a new Diary-specific DTO
in the diary.dto package that contains only the fields Diary needs, map
avatarpost.PostInfoResponse to the new DTO in the service layer, and update
DiaryController to use the new diary DTO; ensure package names, imports,
constructor/mapping code and tests are updated to reflect the refactor.
| import com.example.cp_main_be.domain.avatar.image.ImageUploader; | ||
| import com.example.cp_main_be.domain.member.user.domain.User; | ||
| import com.example.cp_main_be.domain.member.user.domain.repository.UserRepository; | ||
| import com.example.cp_main_be.domain.social.avatarpost.dto.PostInfoResponse; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
컴파일 오류 가능성이 높은 import 경로 및 도메인 결합 이슈
- Line 8:
com.example.cp_main_be.domain.social.diary.domain.Repository.DiaryRepository— 패키지 세그먼트Repository의 대문자 사용은 관례상 잘못되었고, 실제 경로도 소문자repository일 가능성이 큽니다. 컴파일 오류 위험이 큽니다. - Line 6:
PostInfoResponse를 avatarpost DTO에서 가져오고 있는데, Diary 서비스가 다른 도메인의 DTO에 의존하면 순환/불필요 결합이 생깁니다. 의도된 소유 위치를 재검토하세요. - Line 14:
LikeRepository경로는 자연스러우나, 좋아요 대상 타입 구분(예: DIARY vs. AVATAR_POST)이 없다면 혼선 가능성이 있습니다(아래 메서드 코멘트 참고).
가능한 수정안(Repository 경로만 우선 정정):
-import com.example.cp_main_be.domain.social.diary.domain.Repository.DiaryRepository;
+import com.example.cp_main_be.domain.social.diary.domain.repository.DiaryRepository;PostInfoResponse의 소유 도메인 재배치가 필요하다면 공용(post) 계층(예: domain.social.post.dto)으로 이동을 고려해 주세요.
Also applies to: 8-8, 14-14
🏁 Script executed:
#!/bin/bash
# Search for DiaryRepository file path
echo "=== Searching for DiaryRepository file path ==="
fd --type f DiaryRepository.java
# Search for DiaryRepository class and its package declaration
echo "--- Class declaration ---"
rg -nP "class\s+DiaryRepository" -n src/main/java
echo "--- Package declaration for repository packages ---"
rg -nP "package\s+com\.example\.cp_main_be\.domain\.social\.diary\.domain\.repository" -n src/main/java
# Search for PostInfoResponse file path
echo "=== Searching for PostInfoResponse file path ==="
fd --type f PostInfoResponse.java
# Search for PostInfoResponse class and its package declaration
echo "--- Class declaration ---"
rg -nP "class\s+PostInfoResponse" -n src/main/java
echo "--- Package declaration for avatarpost ---"
rg -nP "package\s+com\.example\.cp_main_be\.domain\.social\.avatarpost\.dto" -n src/main/javaLength of output: 1707
🏁 Script executed:
#!/bin/bash
# DiaryService.java의 패키지 선언 및 import 구문 확인
echo "=== DiaryService.java 패키지 및 import (1~30행) ==="
sed -n '1,30p' src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java
# LikeRepository import 위치 확인
echo "=== LikeRepository import 확인 ==="
grep -n "LikeRepository" src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.javaLength of output: 2025
🏁 Script executed:
#!/bin/bash
# DiaryRepository 패키지 선언 확인
echo "=== DiaryRepository package declaration ==="
sed -n '1,5p' src/main/java/com/example/cp_main_be/domain/social/diary/domain/Repository/DiaryRepository.java
# LikeRepository 인터페이스 정의 확인
echo "=== LikeRepository definition ==="
sed -n '1,30p' src/main/java/com/example/cp_main_be/domain/social/like/domain/repository/LikeRepository.java
# DiaryService 내 PostInfoResponse 사용 위치 확인
echo "=== Usage of PostInfoResponse in DiaryService ==="
grep -R -n "PostInfoResponse" src/main/java/com/example/cp_main_be/domain/social/diary/serviceLength of output: 2292
패키지 명명 컨벤션 및 도메인 결합 수정 필요
– DiaryRepository 패키지 경로
• src/main/java/com/example/cp_main_be/domain/social/diary/domain/Repository/DiaryRepository.java
└ package 선언이 ...domain.Repository(대문자)로 되어 있어, Java 패키지 네이밍 컨벤션을 위반합니다.
→ 디렉터리명과 package 선언을 모두 repository(소문자)로 변경하고, 관련 import 구문도 아래처럼 수정해주세요.
```diff
- package com.example.cp_main_be.domain.social.diary.domain.Repository;
+ package com.example.cp_main_be.domain.social.diary.domain.repository;
```
```diff
- import com.example.cp_main_be.domain.social.diary.domain.Repository.DiaryRepository;
+ import com.example.cp_main_be.domain.social.diary.domain.repository.DiaryRepository;
```
– DTO 의존성 분리
• DiaryService에서 사용 중인 com.example.cp_main_be.domain.social.avatarpost.dto.PostInfoResponse는 avatarpost 전용 DTO로, diary 도메인에 직접 의존하면 불필요한 결합 및 순환 참조 위험이 있습니다.
→ 아래 중 하나를 선택해 주세요.
1. 공통 ‘post’ 계층(domain.social.post.dto)을 만들어 PostInfoResponse를 이동
2. Diary 전용 DTO(DiaryInfoResponse)를 새로 정의
– LikeRepository 사용 시 targetType 관리
• LikeRepository.findByUserAndTargetIdAndTargetType(…) 호출 시, DIARY/AVATAR_POST 구분을 문자열(String)로 직접 넘기면 오타·하드코딩 위험이 큽니다.
→ enum 또는 상수로 targetType을 정의해 사용하도록 변경하는 것을 권장합니다.
위 세 가지 변경을 통해 컴파일 에러 방지 및 아키텍처상 도메인 경계를 명확히 유지할 수 있습니다.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java
around line 6, update imports and domain coupling per the review: change any
imports referencing the wrongly-cased package path (domain.Repository) to the
corrected lowercase package path (domain.repository) and ensure the filesystem
package folder is renamed accordingly; remove direct dependency on
com.example.cp_main_be.domain.social.avatarpost.dto.PostInfoResponse by either
moving that DTO to a shared post package (domain.social.post.dto) or creating a
diary-specific DTO (e.g., DiaryInfoResponse) and adjust usages/imports in
DiaryService; and replace any literal targetType String passed to
LikeRepository.findByUserAndTargetIdAndTargetType(...) with a strongly-typed
enum or constant (e.g., TargetType.DIARY) and update calls and repository
signatures as needed.
| // 일기 상세 조회 (읽기 전용) | ||
| @Transactional(readOnly = true) | ||
| public PostInfoResponse getDiaryInfo(Long diaryId, User currentUser) { | ||
| Diary postById = | ||
| diaryRepository | ||
| .findById(diaryId) | ||
| .orElseThrow(() -> new IllegalArgumentException("일기를 찾을 수 없습니다.")); | ||
|
|
||
| // 좋아요 여부 확인 | ||
| boolean isLiked = likeRepository.existsByUserIdAndTargetId(currentUser.getId(), diaryId); | ||
|
|
||
| // 응답 형식에 맞게 comment DTO 생성 | ||
| List<PostInfoResponse.CommentResponseDTO> comments = | ||
| postById.getComments().stream() | ||
| .map( | ||
| comment -> | ||
| PostInfoResponse.CommentResponseDTO.builder() | ||
| .commentId(comment.getId()) | ||
| .content(comment.getContent()) | ||
| .profileImageUrl(comment.getWriter().getProfileImageUrl()) | ||
| .writer(comment.getWriter().getNickname()) | ||
| .build()) | ||
| .toList(); | ||
|
|
||
| return PostInfoResponse.builder() | ||
| .id(postById.getId()) | ||
| .title(postById.getUser().getNickname()) | ||
| .content(postById.getContent()) | ||
| .imageUrl(postById.getDiaryImage().getImageUrl()) | ||
| .isLiked(isLiked) | ||
| .isPublic(true) | ||
| .commentCount(comments.size()) | ||
| .createdAt(postById.getCreatedAt()) | ||
| .updatedAt(postById.getUpdatedAt()) | ||
| .comment(comments) | ||
| .build(); | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
여러 기능/안전성 문제: 제목 필드 오매핑, 공개여부 하드코딩, 이미지/인증자 NPE, 권한 체크 누락, DTO 필드명 가능 불일치
발견 사항
- Line 96: 제목이
postById.getUser().getNickname()으로 매핑되어 있습니다. 일기 제목(getTitle())이어야 할 가능성이 큽니다. - Line 98:
postById.getDiaryImage().getImageUrl()은 이미지가 없는 일기에서 NPE가 발생합니다. - Line 100:
isPublic(true)로 하드코딩되어 비공개 일기가 공개로 노출됩니다. - 접근 제어: 비공개 글 접근 제한 로직이 없음. 컨트롤러 TODO에 의존하면 누락 위험.
- 인증자:
currentUser가 null일 때currentUser.getId()에서 NPE. - DTO 빌더:
comment(comments)vscomments(...)필드명이 실제 DTO와 다를 수 있습니다(컴파일 에러 가능).
아래와 같이 방어 로직 및 매핑을 보완해 주세요.
- @Transactional(readOnly = true)
- public PostInfoResponse getDiaryInfo(Long diaryId, User currentUser) {
- Diary postById =
- diaryRepository
- .findById(diaryId)
- .orElseThrow(() -> new IllegalArgumentException("일기를 찾을 수 없습니다."));
-
- // 좋아요 여부 확인
- boolean isLiked = likeRepository.existsByUserIdAndTargetId(currentUser.getId(), diaryId);
-
- // 응답 형식에 맞게 comment DTO 생성
- List<PostInfoResponse.CommentResponseDTO> comments =
- postById.getComments().stream()
- .map(
- comment ->
- PostInfoResponse.CommentResponseDTO.builder()
- .commentId(comment.getId())
- .content(comment.getContent())
- .profileImageUrl(comment.getWriter().getProfileImageUrl())
- .writer(comment.getWriter().getNickname())
- .build())
- .toList();
-
- return PostInfoResponse.builder()
- .id(postById.getId())
- .title(postById.getUser().getNickname())
- .content(postById.getContent())
- .imageUrl(postById.getDiaryImage().getImageUrl())
- .isLiked(isLiked)
- .isPublic(true)
- .commentCount(comments.size())
- .createdAt(postById.getCreatedAt())
- .updatedAt(postById.getUpdatedAt())
- .comment(comments)
- .build();
- }
+ @Transactional(readOnly = true)
+ public PostInfoResponse getDiaryInfo(Long diaryId, User currentUser) {
+ Diary diary = diaryRepository
+ .findById(diaryId)
+ .orElseThrow(() -> new IllegalArgumentException("일기를 찾을 수 없습니다."));
+
+ // 접근 제어: 비공개면 작성자만 접근 허용
+ final boolean isOwner =
+ currentUser != null && Objects.equals(diary.getUser().getId(), currentUser.getId());
+ final boolean isPublic = Boolean.TRUE.equals(diary.getIsPublic());
+ if (!isPublic && !isOwner) {
+ throw new SecurityException("비공개 일기에 접근할 권한이 없습니다.");
+ }
+
+ // 좋아요 여부 (비인증 사용자를 대비)
+ final boolean isLiked = (currentUser != null)
+ && likeRepository.existsByUserIdAndTargetId(currentUser.getId(), diaryId);
+
+ // 댓글 DTO
+ List<PostInfoResponse.CommentResponseDTO> comments =
+ diary.getComments().stream()
+ .map(comment ->
+ PostInfoResponse.CommentResponseDTO.builder()
+ .commentId(comment.getId())
+ .content(comment.getContent())
+ .profileImageUrl(comment.getWriter().getProfileImageUrl())
+ .writer(comment.getWriter().getNickname())
+ .build())
+ .toList();
+
+ // 이미지 NPE 방어
+ final String imageUrl = (diary.getDiaryImage() != null)
+ ? diary.getDiaryImage().getImageUrl()
+ : null;
+
+ return PostInfoResponse.builder()
+ .id(diary.getId())
+ .title(diary.getTitle())
+ .content(diary.getContent())
+ .imageUrl(imageUrl)
+ .isLiked(isLiked)
+ .isPublic(isPublic)
+ .commentCount(comments.size())
+ .createdAt(diary.getCreatedAt())
+ .updatedAt(diary.getUpdatedAt())
+ .comments(comments) // DTO 필드명이 comments인지 확인 필요
+ .build();
+ }추가 확인(필수)
가능하다면 findById 시 댓글/작성자/이미지 fetch join 또는 전용 조회 DTO로 N+1을 방지하세요.
🏁 Script executed:
#!/bin/bash
# PostInfoResponse의 빌더 필드명이 comments인지, imageUrl/isPublic이 존재하는지 확인
F=$(fd -a 'PostInfoResponse.java' src | head -n1)
echo "Target: $F"
rg -n 'class\s+PostInfoResponse|@Builder' -n -C2 "$F"
rg -n '\bcomments\b|\bcomment\b' -n -C2 "$F"
rg -n '\bimageUrl\b|\bisPublic\b|\btitle\b|\bcontent\b' -n -C2 "$F"
# LikeRepository에 대상 타입 구분이 있는지 확인
fd -a 'LikeRepository.java' src
rg -n 'existsByUserIdAndTargetId|DIARY|targetType' -n -C2 $(fd -a 'LikeRepository.java' src)Length of output: 2440
다이어리 상세 조회 서비스: 제목 매핑·NPE 방어·권한 검사·DTO 필드명·좋아요 타입 필터 필수 수정
– 대상 위치
- src/main/java/com/example/cp_main_be/domain/social/diary/service/DiaryService.java ▶ getDiaryInfo 메서드 수정
- src/main/java/com/example/cp_main_be/domain/social/avatarpost/dto/PostInfoResponse.java ▶ Builder 필드명 확인
- src/main/java/com/example/cp_main_be/domain/social/like/domain/repository/LikeRepository.java ▶ 타입별 좋아요 조회 메서드 사용 검토
@Transactional(readOnly = true)
public PostInfoResponse getDiaryInfo(Long diaryId, User currentUser) {
Diary diary = diaryRepository
.findById(diaryId)
.orElseThrow(() -> new IllegalArgumentException("일기를 찾을 수 없습니다."));
+ // 1) 접근 제어: 비공개 글은 작성자만 조회 허용
+ final boolean isPublic = Boolean.TRUE.equals(diary.getIsPublic());
+ final boolean isOwner = currentUser != null
+ && Objects.equals(diary.getUser().getId(), currentUser.getId());
+ if (!isPublic && !isOwner) {
+ throw new SecurityException("비공개 일기에 접근할 권한이 없습니다.");
+ }
+ // 2) 좋아요 여부 (비인증·다른 타입 필터링 방지)
+ final boolean isLiked = currentUser != null
+ && likeRepository.existsByUserAndTargetIdAndTargetType(
+ currentUser, diaryId, TargetType.DIARY.name()
+ );
// 3) 댓글 DTO 변환
List<PostInfoResponse.CommentResponseDTO> comments = diary.getComments().stream()
.map(comment ->
PostInfoResponse.CommentResponseDTO.builder()
.commentId(comment.getId())
.content(comment.getContent())
.profileImageUrl(comment.getWriter().getProfileImageUrl())
.writer(comment.getWriter().getNickname())
.build())
.toList();
// 4) 이미지 NPE 방어
final String imageUrl = diary.getDiaryImage() != null
? diary.getDiaryImage().getImageUrl()
: null;
- return PostInfoResponse.builder()
+ return PostInfoResponse.builder()
.id(diary.getId())
- .title(diary.getUser().getNickname())
+ .title(diary.getTitle())
.content(diary.getContent())
.imageUrl(imageUrl)
.isLiked(isLiked)
.isPublic(isPublic)
.commentCount(comments.size())
- .comments(comments)
+ .comment(comments)
.createdAt(diary.getCreatedAt())
.updatedAt(diary.getUpdatedAt())
.build();
}추가 안내
- PostInfoResponseBuilder의 실제 필드명은
comment(singular)로 확인됨. - LikeRepository에
existsByUserAndTargetIdAndTargetType(User, Long, String)메서드가 존재하므로, 타입 필터(TargetType.DIARY)를 함께 사용해 주세요. - 가능하다면 댓글·작성자·이미지 fetch join 또는 전용 조회 DTO로 N+1 문제를 방지하세요.
Committable suggestion skipped: line range outside the PR's diff.
다이어리 상세 조회 응답 구조 수정
Summary by CodeRabbit