[hotfix] 조회 시 isWriter response 전체 추가#223
Conversation
Walkthrough댓글과 피드 조회 전반에 isWriter 필드를 응답에 추가. 이를 위해 댓글/피드 응답 DTO에 필드 추가, MapStruct 매퍼에 @context userId 전파 및 isWriter 매핑 로직 도입, 각 서비스에서 매퍼 호출부 인자 확장. 일부 피드 조회 관련 persistence 계층 메서드의 파라미터명(userId→feedOwnerId) 변경. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Controller as Feed Controller
participant Service as Feed Show Services
participant Mapper as FeedQueryMapper
participant Repo as FeedQueryPort/Repositories
User->>Controller: GET /feeds
Controller->>Service: showAllFeeds(userId, cursor)
Service->>Repo: find... (cursor)
Repo-->>Service: List<FeedQueryDto>
Service->>Mapper: toFeedShowAllResponse(dto, savedIds, likedIds, userId)
Mapper-->>Service: FeedDto (isWriter computed)
Service-->>Controller: Response DTOs
Controller-->>User: 200 OK (isWriter 포함)
sequenceDiagram
actor User
participant Controller as Comment Controller
participant Service as CommentShowAllService
participant Mapper as CommentQueryMapper
participant Repo as CommentQueryPort
User->>Controller: GET /posts/{id}/comments
Controller->>Service: showAllCommentsOfPost(query with userId)
Service->>Repo: load root/child comments + likedIds
Repo-->>Service: CommentQueryDtos, likedIds
Service->>Mapper: toRootCommentResponseWithChildren(root, children, likedIds, userId)
Mapper-->>Service: RootCommentDto/ReplyDto (isWriter 포함)
Service-->>Controller: Response
Controller-->>User: 200 OK (isWriter 포함)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested labels
Suggested reviewers
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 (
|
Test Results393 tests 393 ✅ 31s ⏱️ Results for commit 0b134e2. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Actionable comments posted: 5
🔭 Outside diff range comments (5)
src/main/java/konkuk/thip/comment/application/service/CommentShowAllService.java (1)
42-45: 좋아요 집계 누락 버그: 자식이 없는 루트 댓글의 ID가 allCommentIds에서 빠질 수 있음현재
parseAllCommentIds(childrenMap)은childrenMap.keySet()으로 루트 댓글 ID를 수집합니다. 구현체가 “자식이 없는 루트”에 대해 빈 리스트 엔트리를 넣지 않는 경우, 해당 루트 댓글의 ID가allCommentIds에 포함되지 않아, 그 루트 댓글의 좋아요 상태가 항상 false로 처리될 위험이 있습니다.루트 댓글 리스트(rootsInOrder) 기반으로 모든 루트 ID를 먼저 포함시키고, 이후 모든 자식 댓글 ID를 추가하도록 수정해 주세요.
적용 diff:
- Set<Long> allCommentIds = parseAllCommentIds(childrenMap); + Set<Long> allCommentIds = parseAllCommentIds(rootsInOrder, childrenMap);그리고 메서드 구현 수정:
- private Set<Long> parseAllCommentIds(Map<Long, List<CommentQueryDto>> childrenMap) { - Set<Long> allCommentIds = new HashSet<>(childrenMap.keySet()); // 루트 댓글들 - for (Long rootCommentId : childrenMap.keySet()) { - childrenMap.get(rootCommentId).stream() - .map(CommentQueryDto::commentId) - .forEach(allCommentIds::add); - } - return allCommentIds; - } + private Set<Long> parseAllCommentIds(List<CommentQueryDto> roots, + Map<Long, List<CommentQueryDto>> childrenMap) { + // 1) 모든 루트 댓글 ID 선반영 + Set<Long> allCommentIds = roots.stream() + .map(CommentQueryDto::commentId) + .collect(HashSet::new, Set::add, Set::addAll); + // 2) 모든 자식 댓글 ID 추가 + for (List<CommentQueryDto> children : childrenMap.values()) { + for (CommentQueryDto child : children) { + allCommentIds.add(child.commentId()); + } + } + return allCommentIds; + }Also applies to: 56-64
src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java (1)
200-215: 특정 유저 피드 조회 시 본인 요청일 경우 비공개까지 포함하도록 분기 필요현재
findSpecificUserFeedsByCreatedAt는 항상 공개글만 조회합니다. 요청자(userId)와 대상(feedOwnerId)이 동일한 경우, 비공개 글까지 포함되어야 UX가 자연스럽습니다. 간단히 기존의 "내 피드" 쿼리를 재사용하는 분기로 해결 가능합니다.적용 제안:
- public List<FeedQueryDto> findSpecificUserFeedsByCreatedAt(Long userId, Long feedOwnerId, LocalDateTime lastCreatedAt, int size) { - // 1. 특정 유저 피드 ID 목록만 최신순 페이징 조회 - List<Long> feedIds = fetchSpecificUserFeedIdsByCreatedAt(feedOwnerId, lastCreatedAt, size); + public List<FeedQueryDto> findSpecificUserFeedsByCreatedAt(Long userId, Long feedOwnerId, LocalDateTime lastCreatedAt, int size) { + // 1. 특정 유저 피드 ID 목록만 최신순 페이징 조회 + List<Long> feedIds = + (userId != null && userId.equals(feedOwnerId)) + ? fetchMyFeedIdsByCreatedAt(feedOwnerId, lastCreatedAt, size) // 본인 요청: 비공개 포함 + : fetchSpecificUserFeedIdsByCreatedAt(feedOwnerId, lastCreatedAt, size); // 타인 요청: 공개만src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java (3)
32-47: userId null 시 QueryDSL Projection에 null Expression 전달 위험 (런타임 오류 가능성)
commentCreator.userId.eq(userId)는userId == null일 때 null Expression을 생성하여.select(proj)에서 런타임 오류가 날 수 있습니다. null이면 항상 false로 간주하도록 안전 가드를 넣어주세요.- QCommentQueryDto proj = new QCommentQueryDto( + QCommentQueryDto proj = new QCommentQueryDto( comment.commentId, commentCreator.userId, aliasOfCommentCreator.imageUrl, commentCreator.nickname, aliasOfCommentCreator.value, aliasOfCommentCreator.color, comment.createdAt, comment.content, comment.likeCount, - comment.status.eq(StatusType.INACTIVE), // 루트 댓글이 삭제된 상태인지 아닌지, - commentCreator.userId.eq(userId) + comment.status.eq(StatusType.INACTIVE), // 루트 댓글이 삭제된 상태인지 아닌지, + (userId != null) ? commentCreator.userId.eq(userId) : Expressions.FALSE );
70-93: userId null 가드 + 파라미터 공백 컨벤션자식 댓글 프로젝션도 동일 이슈입니다. 또한 메서드 시그니처의 공백 컨벤션을 정리하면 좋습니다.
- public List<CommentQueryDto> findAllActiveChildCommentsByCreatedAtAsc(Long rootCommentId,Long userId) { + public List<CommentQueryDto> findAllActiveChildCommentsByCreatedAtAsc(Long rootCommentId, Long userId) {- QCommentQueryDto childProj = new QCommentQueryDto( + QCommentQueryDto childProj = new QCommentQueryDto( comment.commentId, comment.parent.commentId, parentCommentCreator.nickname, commentCreator.userId, aliasOfCommentCreator.imageUrl, commentCreator.nickname, aliasOfCommentCreator.value, aliasOfCommentCreator.color, comment.createdAt, comment.content, comment.likeCount, - comment.status.eq(StatusType.INACTIVE), - commentCreator.userId.eq(userId) + comment.status.eq(StatusType.INACTIVE), + (userId != null) ? commentCreator.userId.eq(userId) : Expressions.FALSE );
124-156: 여러 루트 ID 대상 자식 댓글 프로젝션도 동일한 null 가드 필요 + 공백 컨벤션다중 루트 조회용 프로젝션에서도 동일한 null 전달 위험이 있습니다. 시그니처 공백도 함께 정리하세요.
- public Map<Long, List<CommentQueryDto>> findAllActiveChildCommentsByCreatedAtAsc(Set<Long> rootCommentIds,Long userId) { + public Map<Long, List<CommentQueryDto>> findAllActiveChildCommentsByCreatedAtAsc(Set<Long> rootCommentIds, Long userId) {- QCommentQueryDto childProj = new QCommentQueryDto( + QCommentQueryDto childProj = new QCommentQueryDto( comment.commentId, comment.parent.commentId, parentCommentCreator.nickname, commentCreator.userId, aliasOfCommentCreator.imageUrl, commentCreator.nickname, aliasOfCommentCreator.value, aliasOfCommentCreator.color, comment.createdAt, comment.content, comment.likeCount, - comment.status.eq(StatusType.INACTIVE), - commentCreator.userId.eq(userId) + comment.status.eq(StatusType.INACTIVE), + (userId != null) ? commentCreator.userId.eq(userId) : Expressions.FALSE );
🧹 Nitpick comments (8)
src/main/java/konkuk/thip/feed/application/port/out/dto/FeedQueryDto.java (1)
24-26: 비로그인(anonymous) 또는 userId 미전달 시 isWriter 의미를 명시하세요현재 타입이 primitive boolean이라 null 표현이 불가합니다. 미인증/미전달 시 false로 일괄 설정하는 정책이라면, 주석/Javadoc 혹은 API 스펙(Swagger)에 이를 명시해 클라이언트 혼선을 줄이는 것을 권장합니다.
src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowByUserResponse.java (1)
31-33: OpenAPI 문서 보강(선택): isWriter 의미를 스키마에 명시하세요Swagger 스키마에 isWriter 의미를 넣어두면 클라이언트 혼선을 줄일 수 있습니다. 아래처럼 컴포넌트에 주석을 추가하는 것을 고려해 주세요.
boolean isLiked, - boolean isWriter + @Schema(description = "요청자와 작성자가 동일한 경우 true") + boolean isWritersrc/main/java/konkuk/thip/comment/application/port/out/dto/CommentQueryDto.java (1)
20-22: isWriter Boolean 사용에 따른 null-안전성 점검 필요record 컴포넌트가
Boolean(wrapper)로 선언되어 있어, DB/프로젝션에서 null이 들어오면 이후 계층에서 unboxing 시 NPE가 발생할 수 있습니다. FE 요구사항상 항상 boolean 응답이 필요하다면, 최소한 API 응답 DTO에서는 primitiveboolean으로 보장되도록 매핑하거나, 여기서도 primitive로 통일하는 방안을 검토해 주세요. 현재isDeleted도 Boolean이므로 일관된 처리가 필요합니다.다음 사항을 확인해 주세요:
- 레포지토리/프로젝션에서
isWriter가 항상 non-null로 계산/리턴되는지- API 응답 DTO에서
isWriter가 primitive boolean으로 보장되는지 (null -> false 기본값)필요하다면, 매퍼에서 null-safe 기본값 처리를 추가하도록 도와드릴 수 있습니다.
src/main/java/konkuk/thip/feed/application/service/FeedShowAllOfUserService.java (1)
52-53: 호출부 시그니처 반영 — LGTM. 다만 계산 위치 단순화 가능특정 유저의 공개 피드 목록에서는 모든 아이템의 작성자가
feedOwnerId이므로isWriter는userId.equals(feedOwnerId)로 상수값(전부 true/false)입니다. 성능/복잡도 관점에서 이 케이스만큼은 DB/포트 계층으로 전달하지 않고 서비스/매퍼에서 계산하는 방안도 고려해볼 수 있습니다. 현재는 핫픽스 목적이므로 추후 리팩터로 제안드립니다.src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java (1)
13-17: userId null 계약 명시 필요 + 파라미터 스타일 정리
- 현재 하위 레이어에서
userId가 null일 때eq(userId)평가 중 NPE/Invalid expression 위험이 있습니다(아래 Repository 구현 코멘트 참조). 포트 시그니처에서 null 불허를 계약으로 못 박거나, null 허용 시의 동작(예: isWriter 항상 false)을 명확히 해 주세요.- 사소하지만, 쉼표 뒤 공백(
Long rootCommentId, Long userId) 유지로 일관성 개선 권장.src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java (1)
82-82: 사소: 공백 컨벤션 정리쉼표 뒤 공백 누락으로 코드 스타일 일관성이 깨집니다.
- .map(e -> toDto(e, priorityMap.get(e.getPostId()),userId)) + .map(e -> toDto(e, priorityMap.get(e.getPostId()), userId))- .map(e -> toDto(e, null, userId)) + .map(e -> toDto(e, null, userId))- .map(e -> toDto(e, null, userId)) + .map(e -> toDto(e, null, userId))- .map(e -> toDto(e, null, userId)) + .map(e -> toDto(e, null, userId))Also applies to: 104-104, 195-195, 214-214
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java (1)
38-45: 사소: 파라미터 서식 정리(Long rootCommentId, Long userId / Set rootCommentIds, Long userId)쉼표 뒤 공백 누락을 정리하면 가독성이 좋아집니다.
- public List<CommentQueryDto> findAllActiveChildCommentsOldestFirst(Long rootCommentId,Long userId) { + public List<CommentQueryDto> findAllActiveChildCommentsOldestFirst(Long rootCommentId, Long userId) {- public Map<Long, List<CommentQueryDto>> findAllActiveChildCommentsOldestFirst(Set<Long> rootCommentIds,Long userId) { + public Map<Long, List<CommentQueryDto>> findAllActiveChildCommentsOldestFirst(Set<Long> rootCommentIds, Long userId) {src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java (1)
94-121: 계층 자식 조회 반복 루프의 성능 고려깊은 트리나 팬아웃이 큰 경우 while 루프의 단계별 조회가 다수의 쿼리를 유발합니다. 현재 요구사항상 기능적으로 문제없지만, 성능 이슈가 관측되면 다음을 검토해 주세요:
- 동일 루프 내에서 parentIds를 가능한 한 크게 묶어 배치 질의 유지(이미 그렇게 되어 있음).
- 필요 시 한 번의 쿼리로 전체 서브트리를 가져와 메모리에서 그룹핑/정렬(데이터 규모에 따라 trade-off).
- 페이징 요구가 생기면 BFS/DFS 순회 기준으로 커서 도입.
Also applies to: 157-191
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentForSinglePostResponse.java(3 hunks)src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java(2 hunks)src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepository.java(1 hunks)src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java(6 hunks)src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java(1 hunks)src/main/java/konkuk/thip/comment/application/port/out/dto/CommentQueryDto.java(2 hunks)src/main/java/konkuk/thip/comment/application/service/CommentShowAllService.java(1 hunks)src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowAllResponse.java(1 hunks)src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowByUserResponse.java(1 hunks)src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowMineResponse.java(1 hunks)src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowSingleResponse.java(1 hunks)src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepository.java(1 hunks)src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java(6 hunks)src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java(1 hunks)src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java(1 hunks)src/main/java/konkuk/thip/feed/application/port/out/dto/FeedQueryDto.java(1 hunks)src/main/java/konkuk/thip/feed/application/service/FeedShowAllOfUserService.java(1 hunks)src/main/java/konkuk/thip/feed/application/service/FeedShowSingleService.java(1 hunks)
⏰ 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 (21)
src/main/java/konkuk/thip/feed/application/port/out/dto/FeedQueryDto.java (2)
24-26: isWriter 필드 추가 방향 적합요구사항(조회 응답 전반에 isWriter 추가)에 정확히 부합합니다. 기존 boolean 계열(isPublic 등)과의 네이밍/타입/위치 일관성도 좋습니다.
24-26: 검증 결과 — FeedQueryDto 호출부 점검 완료(빌더 사용만 존재)간단히 요약하면: 직접 new FeedQueryDto(...) 호출과 JPQL/QueryDSL의 select new 프로젝션은 발견되지 않았고, FeedQueryDto를 사용하는 곳은 빌더 호출 한 곳뿐입니다.
- 확인된 위치
- src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java (약 269–286행)
- FeedQueryDto.builder() 호출, .isWriter(isWriter), .isPriorityFeed(isPriorityFeed) 사용 후 .build() 호출 확인
권고: 변경 후 로컬 또는 CI에서 한 번 컴파일하여 빌더/생성자 시그니처 불일치로 인한 컴파일 에러가 없는지 최종 확인해주세요.
src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowMineResponse.java (2)
20-22: 내 피드 응답에 isWriter 추가 OK내 피드의 성격상 isWriter는 항상 true가 되어야 하므로 프런트 요구와도 부합합니다.
20-22: 검증 완료 — isPublic / isWriter 순서 문제 없음 (매퍼가 이름 기준으로 매핑함)결론: 직접적인 위치 기반 생성(new FeedShowMineResponse.FeedDto(...)) 호출은 발견되지 않았고, 매핑 파이프라인이 이름 기반으로 올바르게 매핑하므로 현재 코드상 문제 없습니다.
확인한 위치:
- src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowMineResponse.java — FeedDto에 boolean isPublic, boolean isWriter 정의
- src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java — toDto(...)에서 FeedQueryDto.builder().isPublic(...).isWriter(isWriter).build()
- src/main/java/konkuk/thip/feed/application/port/out/dto/FeedQueryDto.java — record에 isPublic / isWriter 포함
- src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java — toFeedShowMineDto(FeedQueryDto dto) (postDate는 expression으로 매핑, 나머지는 이름 기준 매핑 → isPublic/isWriter 정상 전달)
권고: 즉시 수정 불필요. (원하면 가독성·안전성 향상을 위해 정적 팩토리나 빌더 도입 고려 가능)
src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowAllResponse.java (2)
26-28: 피드 목록 응답에 isWriter 추가 OKisSaved/isLiked와 함께 isWriter를 인접 배치해 응답 구조가 직관적입니다. 프런트에서의 사용성도 좋아 보입니다.
26-28: FeedDto 생성자 인자 순서 변경 영향 검증 — 확인 필요요약: FeedShowAllResponse.FeedDto의 boolean 순서는 (isSaved, isLiked, isWriter)로 되어 있고, 코드 내에 직접 new FeedShowAllResponse.FeedDto(...) 호출은 없습니다. 대신 FeedQueryMapper.toFeedShowAllResponse(...)가 매핑을 담당하며, isSaved/isLiked는 expression으로 매핑되고 isWriter는 FeedQueryDto의 프로퍼티로 매핑됩니다. 생성된 MapStruct 코드(또는 빌드 테스트)로 최종적으로 매핑 인자 순서/이름 매칭이 올바른지 검증해 주세요.
검토 위치:
- src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowAllResponse.java — FeedDto 정의 (boolean 순서)
- src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java — toFeedShowAllResponse(FeedQueryDto, Set savedFeedIds, Set likedFeedIds) 및
- src/main/java/konkuk/thip/feed/application/port/out/dto/FeedQueryDto.java — dto에 isWriter 프로퍼티 존재
- src/main/java/konkuk/thip/feed/application/service/BasicFeedShowAllService.java, FollowingPriorityFeedShowAllService.java — 매퍼 호출 지점 (.map(dto -> feedQueryMapper.toFeedShowAllResponse(...)))
권장 조치:
- 생성된 MapStruct 소스(target/generated-sources/annotations 또는 빌드 로그)에서 toFeedShowAllResponse 구현이 기대대로 isSaved/isLiked/isWriter를 올바르게 전달하는지 확인해 주세요(또는 프로젝트 빌드/테스트 실행).
- 확실히 하려면 toFeedShowAllResponse에 명시적 매핑(@mapping(target="isWriter", source="dto.isWriter"))을 추가하는 것을 고려하세요.
src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowByUserResponse.java (1)
31-33: 특정 유저 피드 응답에 isWriter 추가 OK조회 주체(userId)와 피드 작성자 동일 여부를 명확히 전달할 수 있어 요구사항에 부합합니다.
src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentForSinglePostResponse.java (2)
22-24: 루트 댓글 DTO에 isWriter 추가 OK삭제 여부(isDeleted)와 함께 작성자 일치 여부를 내려주도록 확장된 점 적절합니다. 응답 구조 변화가 명확합니다.
36-38: 대댓글 ReplyDto(isWriter) 추가 — 매핑 호출부 점검 완료 (수정 불필요)검증 결과: ReplyDto는 MapStruct 매퍼(CommentQueryMapper.toReply)에 의해 생성되며, isWriter 값은 CommentQueryRepositoryImpl의 Q 프로젝션에서 제공됩니다. 명시적 new ReplyDto 호출은 없습니다.
점검한 위치:
- src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentForSinglePostResponse.java — ReplyDto / RootCommentDto 정의
- src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java — toReply / toRoot 매핑
- src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java — QCommentQueryDto 프로젝션(마지막 인자: commentCreator.userId.eq(userId))
- src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java — repository 호출부
- src/main/java/konkuk/thip/comment/application/service/CommentShowAllService.java — 매퍼 호출부
간단 주의: Boolean → boolean 언박싱 관련 NPE 가능성은 있지만, 현재 프로젝션이 boolean 값을 제공하므로 실무상 문제 없음. 빌드 시 MapStruct가 생성한 코드 컴파일 확인 권장.
src/main/java/konkuk/thip/comment/application/port/out/dto/CommentQueryDto.java (1)
43-47: Querydsl 프로젝션 시그니처 변경 전파 확인 — 완료루트 생성자에 추가된 isWriter 인자는 레포지토리의 모든 Querydsl 프로젝션 호출부에 반영되어 있습니다.
- src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java
- 최상위 댓글 프로젝션: new QCommentQueryDto(...) — 호출부 범위: 34–46 (마지막 인자 commentCreator.userId.eq(userId) 전달 → isWriter)
- 자손 댓글 프로젝션: new QCommentQueryDto(...) — 호출부 범위: 78–92, 141–155 (각각 13개 인자, 마지막 인자 commentCreator.userId.eq(userId) 포함)
결론: 모든 QCommentQueryDto 호출부가 새 시그니처를 따르고 있어 추가 수정 불필요합니다.
src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowSingleResponse.java (1)
19-20: 단일 피드 응답에 isWriter 추가 — LGTMFE 요구사항에 맞춘 boolean 필드 추가가 일관되며, 매퍼/서비스 변경과도 정합적입니다.
src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java (2)
86-89: 확인 완료 — 호출부와 매퍼 시그니처가 일치합니다 (LGTM)rg 결과 서비스 호출부에서 isWriter를 포함해 6개 인자를 전달하고 있어 매핑이 정상입니다.
확인된 위치:
- src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java — lines 86–88: 매핑 및 메서드 시그니처(toFeedShowSingleResponse(..., boolean isWriter))
- src/main/java/konkuk/thip/feed/application/service/FeedShowSingleService.java — line 49: return feedQueryMapper.toFeedShowSingleResponse(feed, feedCreator, book, isSaved, isLiked,isWriter);
86-89: 확인 필요 — Feed 목록 응답의 isWriter 자동 매핑 여부검사 결과: FeedShowAllResponse.FeedDto, FeedShowByUserResponse.FeedDto, FeedShowMineResponse.FeedDto에 boolean isWriter 필드가 존재합니다. 다만 생성된 MapStruct 구현(FeedQueryMapperImpl.java)을 리포지토리에서 찾지 못해 by-name 자동 매핑이 실제로 적용되는지는 확정할 수 없습니다.
- 확인된 파일:
- src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowAllResponse.java (FeedDto에 isWriter 포함)
- src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowByUserResponse.java (FeedDto에 isWriter 포함)
- src/main/java/konkuk/thip/feed/adapter/in/web/response/FeedShowMineResponse.java (FeedDto에 isWriter 포함)
- 검토가 필요한 매퍼 메서드:
- toFeedShowAllResponse(...)
- toFeedShowByUserResponse(...)
- toFeedShowMineDto(...)
권고: 생성된 구현(FeedQueryMapperImpl.java)을 확인하거나, 다중 소스 파라미터가 있는 메서드의 암묵 매핑이 깨질 위험을 없애기 위해 명시 매핑을 추가하세요. 예:
@mapping(target = "isWriter", source = "dto.isWriter") // 'dto'를 실제 파라미터명으로 교체src/main/java/konkuk/thip/feed/application/service/FeedShowSingleService.java (1)
49-49: 검증 완료 — Mapper 시그니처 일치선언부와 호출부가 동일한 시그니처를 사용하고 있어 추가 수정 불필요합니다.
- src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:88 — 선언부: FeedShowSingleResponse toFeedShowSingleResponse(Feed feed, User feedCreator, Book book, boolean isSaved, boolean isLiked, boolean isWriter);
- src/main/java/konkuk/thip/feed/application/service/FeedShowSingleService.java:49 — 호출부: return feedQueryMapper.toFeedShowSingleResponse(feed, feedCreator, book, isSaved, isLiked,isWriter);
src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java (1)
92-97: 확인 완료 — 포트/어댑터 시그니처 일치rg 검사 결과, FeedQueryPort와 어댑터/호출부의 시그니처가 일치하며(어댑터가 Cursor를 LocalDateTime+size로 변환해 레포지토리를 호출하는 구조) 문제 없음이 확인되었습니다.
- src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java
- findSpecificUserFeedsByCreatedAt(Long userId, Long feedOwnerId, Cursor cursor)
- src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java
- public CursorBasedList findSpecificUserFeedsByCreatedAt(Long userId, Long feedOwnerId, Cursor cursor)
- src/main/java/konkuk/thip/feed/application/service/FeedShowAllOfUserService.java
- 호출부: feedQueryPort.findSpecificUserFeedsByCreatedAt(userId, feedOwnerId, nextCursor)
- src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepository.java
- 레포지토리 메서드: findSpecificUserFeedsByCreatedAt(Long, Long, LocalDateTime, int) — 어댑터에서 Cursor → lastCreatedAt, size로 매핑
따라서 원래 코멘트대로 승인합니다.
src/main/java/konkuk/thip/comment/application/service/CommentShowAllService.java (3)
32-32: 루트 댓글 조회 시 userId 전달 변경 LGTM
findLatestRootCommentsWithDeleted(..., query.userId(), ...)전달 변경은 isWriter 계산을 위한 문맥 제공 측면에서 타당합니다.
40-41: 자식 댓글 일괄 조회에 userId 전달 변경 LGTM
findAllActiveChildCommentsOldestFirst(rootCommentIds, query.userId())수정이 일관되게 적용되었습니다.
32-41: userId가 null인 비로그인 요청 처리 확인 요청조회자가 비로그인일 수 있다면(
query.userId()가 null), 레포지토리/쿼리단에서= null비교나 NPE가 발생하지 않도록 방어 로직이 필요합니다. QueryDSL을 사용한다면eq(userId)에 null이 들어가도 안전하게 무시되도록 조건식을 분기하거나Objects.equals기반으로 서버 단에서 계산하는 방식을 고려해 주세요.src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java (2)
55-84: limit(size + 1) 사용 시 상위 레이어에서 페이지 슬라이싱 확인 필요내부 ID 조회는
limit(size + 1)을 사용하지만, 본 메서드는 그대로toList()로 모두 DTO 변환해 반환합니다. 상위 계층(어댑터/서비스)에서 size 초과분을 제거하고hasNext계산을 하고 있다면 OK입니다. 아니라면 여기서 슬라이싱이 필요합니다.예시(옵션):
- return ordered.stream() - .map(e -> toDto(e, priorityMap.get(e.getPostId()), userId)) - .toList(); + return ordered.stream() + .limit(size) // size + 1 로 조회한 경우 상위 hasNext 계산을 위해 한 개 초과분은 제거 + .map(e -> toDto(e, priorityMap.get(e.getPostId()), userId)) + .toList();동일 패턴의 다른 경로들(
findLatestFeedsByCreatedAt,findMyFeedsByCreatedAt)도 점검 부탁드립니다.Also applies to: 86-106, 180-197
262-286: toDto에서 isWriter 계산 로직 적절
e.getUserJpaEntity().getUserId().equals(userId)는 요청자 null 시에도 안전하게 false를 반환합니다. isWriter 채움 로직은 요구사항과 부합합니다.src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java (1)
25-35: userId null 입력에 대한 정책 명확화 필요여기서
userId가 null로 전달될 수 있는지(비로그인 사용자 조회 지원 여부)를 결정해 주세요. 현재 Repository 구현은eq(userId)를 직접 사용 중이라 null이면 런타임 오류 가능성이 있습니다. 아래 Repository 제안처럼 null 시Expressions.FALSE로 대체하여 항상 isWriter=false가 되도록 처리하는 것이 안전합니다.
| 0, | ||
| false, | ||
| true, // true | ||
| false, |
There was a problem hiding this comment.
굳굳
굳이 Boolean 으로 감싸서 null 값을 넣지 않고 이렇게 쓰레기값을 줘도 어차피 FE에서는 isDelete 값이 true이면 다른 값들 전부 무시하니 괜찮을 것 같습니다!
| int likeCount, | ||
| int commentCount, | ||
| boolean isPublic, | ||
| boolean isWriter, |
There was a problem hiding this comment.
저도 같은 생각입니다! 현재 creatorId는 반환되고 있으니 해당 id와 현재 로그인된 유저의 id를 비교해서 isWriter를 구성해주는게 좋을 것 같아욥
buzz0331
left a comment
There was a problem hiding this comment.
수정 감사합니다! 성준님 말대로 서비스 레벨에서 isWriter를 파싱하는 방식을 고려해보면 좋을 것 같습니다~
| int likeCount, | ||
| int commentCount, | ||
| boolean isPublic, | ||
| boolean isWriter, |
There was a problem hiding this comment.
저도 같은 생각입니다! 현재 creatorId는 반환되고 있으니 해당 id와 현재 로그인된 유저의 id를 비교해서 isWriter를 구성해주는게 좋을 것 같아욥
| @Named("isWriter") | ||
| default boolean isWriter(Long creatorId, @Context Long userId) { | ||
| return creatorId != null && creatorId.equals(userId); | ||
| } |
There was a problem hiding this comment.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
src/main/java/konkuk/thip/comment/application/service/CommentShowAllService.java (1)
56-64: 긴급: childrenMap 접근 NPE 및 루트 댓글 누락 위험 — 수정 필요childrenMap.get(rootCommentId).stream()에서 get(...)가 null일 경우 NPE가 발생할 수 있으며, childrenMap.keySet()만으로는 자식이 없는 루트 댓글이 map에 키로 존재하지 않을 때 루트 댓글 ID가 누락되어 조회 누락이 발생할 수 있습니다.
- 대상 파일
- src/main/java/konkuk/thip/comment/application/service/CommentShowAllService.java (parseAllCommentIds, 약 라인 56–64)
즉시 적용 가능한 최소 수정(널-안전):
- private Set<Long> parseAllCommentIds(Map<Long, List<CommentQueryDto>> childrenMap) { - Set<Long> allCommentIds = new HashSet<>(childrenMap.keySet()); // 루트 댓글들 - for (Long rootCommentId : childrenMap.keySet()) { - childrenMap.get(rootCommentId).stream() - .map(CommentQueryDto::commentId) - .forEach(allCommentIds::add); - } - return allCommentIds; - } + private Set<Long> parseAllCommentIds(Map<Long, List<CommentQueryDto>> childrenMap) { + Set<Long> allCommentIds = new HashSet<>(childrenMap.keySet()); // 루트 댓글들 + for (Long rootCommentId : childrenMap.keySet()) { + childrenMap.getOrDefault(rootCommentId, Collections.emptyList()).stream() + .map(CommentQueryDto::commentId) + .forEach(allCommentIds::add); + } + return allCommentIds; + }권장 리팩터(루트 댓글 누락 방지 — 호출부 변경 필요):
// 제안: 시그니처 변경 private Set<Long> parseAllCommentIds(Map<Long, List<CommentQueryDto>> childrenMap, Set<Long> rootCommentIds) { Set<Long> allCommentIds = new HashSet<>(rootCommentIds); // 루트 댓글 전부 포함 보장 for (Long rootCommentId : rootCommentIds) { childrenMap.getOrDefault(rootCommentId, Collections.emptyList()).stream() .map(CommentQueryDto::commentId) .forEach(allCommentIds::add); } return allCommentIds; } // 호출부 예시 변경 Set<Long> rootCommentIds = rootsInOrder.stream() .map(CommentQueryDto::commentId) .collect(Collectors.toUnmodifiableSet()); Map<Long, List<CommentQueryDto>> childrenMap = commentQueryPort.findAllActiveChildCommentsOldestFirst(rootCommentIds); Set<Long> allCommentIds = parseAllCommentIds(childrenMap, rootCommentIds);참고: Collections.emptyList() 사용 시 java.util.Collections import 필요. 위 즉시 수정은 NPE를 방지하지만, 루트 댓글 전부 포함 보장을 위해 권장 리팩터를 적용해 주세요.
🧹 Nitpick comments (2)
src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java (1)
64-67: isWriter 내부 비교를 Objects.equals로 단순화 제안가독성을 위해
Objects.equals(creatorId, userId)사용을 권장합니다. 또한 동일한 isWriter 로직이 feed/댓글 매퍼에 중복될 가능성이 높으므로, 공용 유틸(예:WriterMappingUtils)로 분리하여@Named메서드를 재사용하면 유지보수성이 좋아집니다.적용 예시(diff):
@Named("isWriter") default boolean isWriter(Long creatorId, @Context Long userId) { - return creatorId != null && creatorId.equals(userId); + return java.util.Objects.equals(creatorId, userId); }src/main/java/konkuk/thip/feed/application/service/FeedShowAllOfUserService.java (1)
52-52: 사소한 스타일: 쉼표 앞 불필요한 공백 제거 권장메서드 인자 사이에 쉼표 앞 공백이 들어갔습니다. 코드 컨벤션 일관성을 위해 공백 제거를 제안합니다.
적용 diff:
- CursorBasedList<FeedQueryDto> result = feedQueryPort.findSpecificUserFeedsByCreatedAt(feedOwnerId ,nextCursor); + CursorBasedList<FeedQueryDto> result = feedQueryPort.findSpecificUserFeedsByCreatedAt(feedOwnerId, nextCursor);
📜 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 settings in your CodeRabbit configuration.
📒 Files selected for processing (11)
src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java(1 hunks)src/main/java/konkuk/thip/comment/application/service/CommentShowAllService.java(2 hunks)src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepository.java(1 hunks)src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java(4 hunks)src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java(1 hunks)src/main/java/konkuk/thip/feed/application/service/BasicFeedShowAllService.java(1 hunks)src/main/java/konkuk/thip/feed/application/service/FeedShowAllOfUserService.java(3 hunks)src/main/java/konkuk/thip/feed/application/service/FeedShowSingleService.java(1 hunks)src/main/java/konkuk/thip/feed/application/service/FollowingPriorityFeedShowAllService.java(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/main/java/konkuk/thip/feed/application/port/out/FeedQueryPort.java
🚧 Files skipped from review as they are similar to previous changes (3)
- src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java
- src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedQueryPersistenceAdapter.java
- src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepository.java
⏰ 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 (15)
src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java (2)
27-28: isWriter 매핑 추가 LGTMMapStruct
@Named활용으로creatorId대비 현재 사용자userId를 비교해isWriter를 산출하는 접근이 명확하고, null-safe 비교로 방어 코드도 적절합니다.Also applies to: 36-37
42-48: @context userId 전파와 중첩 매핑 체인 구성 적절
mapReplies→toReply,toRootCommentResponseWithChildren→toRoot로@Context Long userId를 일관되게 전달해 writer 판정을 보존하는 구조가 깔끔합니다.replyList초기화 후addAll하는 패턴도 안전합니다.Also applies to: 51-54, 59-59
src/main/java/konkuk/thip/comment/application/service/CommentShowAllService.java (1)
47-47: userId 컨텍스트 전파 LGTM매퍼 호출부(
buildRootCommentResponses,toRootCommentResponseWithChildren)까지userId를 넘겨isWriter산출이 가능해졌습니다. 의도와 구현이 일치합니다.Also applies to: 69-71, 78-78
src/main/java/konkuk/thip/feed/application/service/BasicFeedShowAllService.java (1)
56-56: toFeedShowAllResponse에 userId 전달 LGTMwriter 판정을 위한 컨텍스트 전달이 반영되어 일관성이 확보되었습니다.
src/main/java/konkuk/thip/feed/application/service/FollowingPriorityFeedShowAllService.java (1)
55-55: toFeedShowAllResponse에 userId 전달 LGTM팔로잉 우선 조회 경로에서도 동일하게 컨텍스트가 전파되어 일관됩니다.
src/main/java/konkuk/thip/feed/application/service/FeedShowSingleService.java (2)
48-48: 단일 피드 응답 매핑 시 userId 전달 LGTM
isWriter산출을 위한 맥락 전달로, 이전 커밋에서 지적되던 서비스 레벨 비교 로직이 매퍼로 일원화된 점도 긍정적입니다.
37-37: 결론: feedCreator가 null일 가능성 없음 — UserCommandPort.findById는 존재하지 않으면 예외를 던집니다간단히 확인 결과, UserCommandPort.findById(Long)는 Optional이 아닌 User를 반환하도록 정의되어 있고, 구현체에서 없을 경우 예외를 던지도록 구현되어 있습니다. 따라서 NPE 우려는 불필요합니다.
확인한 위치:
- src/main/java/konkuk/thip/feed/application/service/FeedShowSingleService.java — User feedCreator = userCommandPort.findById(feed.getCreatorId());
- src/main/java/konkuk/thip/user/application/port/out/UserCommandPort.java — User findById(Long userId);
- src/main/java/konkuk/thip/user/adapter/out/persistence/UserCommandPersistenceAdapter.java — findById 구현에서 userJpaRepository.findById(...).orElseThrow(...) 사용
조치: 원래 리뷰 코멘트는 부정확하므로 무시해 주세요.
Likely an incorrect or invalid review comment.
src/main/java/konkuk/thip/feed/application/service/FeedShowAllOfUserService.java (2)
39-39: isWriter 컨텍스트 전파 적용 — 좋습니다
toFeedShowMineResponse호출부에userId를 전달하여 isWriter 계산 맥락을 정확히 반영했습니다.
57-64: 비로그인(userId==null) 및 빈 feedIds 처리 가드 추가 권장userId가 null이거나 feedIds가 비어있을 때 포트 호출로 NPE 또는 불필요한 DB 쿼리가 발생할 수 있으므로 가드 추가를 권장합니다.
- 파일: src/main/java/konkuk/thip/feed/application/service/FeedShowAllOfUserService.java (라인 57-64)
적용 제안 diff:
- // 3. 유저가 저장한 피드들, 좋아한 피드들 조회 - Set<Long> savedFeedIdsByUser = feedQueryPort.findSavedFeedIdsByUserIdAndFeedIds(feedIds, userId); - Set<Long> likedFeedIdsByUser = postLikeQueryPort.findPostIdsLikedByUser(feedIds, userId); + // 3. 유저가 저장한 피드들, 좋아한 피드들 조회 (비로그인/빈 결과 가드) + Set<Long> savedFeedIdsByUser = Set.of(); + Set<Long> likedFeedIdsByUser = Set.of(); + if (!feedIds.isEmpty() && userId != null) { + savedFeedIdsByUser = feedQueryPort.findSavedFeedIdsByUserIdAndFeedIds(feedIds, userId); + likedFeedIdsByUser = postLikeQueryPort.findPostIdsLikedByUser(feedIds, userId); + }검증 요청:
- 이 엔드포인트가 비로그인 접근을 허용하는지 확인해 주세요. 허용한다면 위 가드 적용이 필요합니다.
- 허용하지 않는다면 컨트롤러/인터셉터 레벨에서 userId가 항상 비null인지 보장되는지 확인해 주세요.
src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java (6)
34-40: toFeedShowAllResponse에 isWriter 매핑과 @context 전달 — 적절합니다작성자 판별을
@Named("isWriter")로 위임하고@Context Long userId를 통해 컨텍스트를 주입한 접근이 명확하고 테스트 용이합니다.
43-47: 내 피드 DTO 매핑에 isWriter 추가 — 일관성 있게 반영됨
toFeedShowMineDto와 리스트 매핑에@Context userId를 연결해 isWriter가 정확히 채워지도록 했습니다.
54-60: 사용자별 공개 피드 DTO 매핑에 isWriter 추가 — OK저장/좋아요 플래그 계산과 함께 isWriter가 동일 컨텍스트로 계산되도록 한 점 좋습니다.
88-92: 단건 조회 매핑에 isWriter 추가 — OK
feedCreator.id를 소스로 사용하여 컨텍스트 사용자와 일치 여부를 판단하는 로직이 명확합니다.
120-123: isWriter 매핑 함수의 null-세이프 처리 확인 — LGTM
creatorId != null && creatorId.equals(userId)로 null 안전하게 비교되어, 익명 사용자 시 false가 반환됩니다.
34-40: 확인 완료 — 모든 호출부가 @context Long userId 시그니처로 업데이트되어 있습니다아래 호출부들이 모두 userId 인자를 전달하고 있어 변경이 반영된 것으로 확인됩니다:
- src/main/java/konkuk/thip/feed/application/service/FollowingPriorityFeedShowAllService.java:55
- .map(dto -> feedQueryMapper.toFeedShowAllResponse(dto, savedFeedIdsByUser, likedFeedIdsByUser, userId))
- src/main/java/konkuk/thip/feed/application/service/BasicFeedShowAllService.java:56
- .map(dto -> feedQueryMapper.toFeedShowAllResponse(dto, savedFeedIdsByUser, likedFeedIdsByUser, userId))
- src/main/java/konkuk/thip/feed/application/service/FeedShowAllOfUserService.java:39
- feedQueryMapper.toFeedShowMineResponse(result.contents(), userId)
- src/main/java/konkuk/thip/feed/application/service/FeedShowAllOfUserService.java:63
- .map(dto -> feedQueryMapper.toFeedShowByUserResponse(dto, savedFeedIdsByUser, likedFeedIdsByUser, userId))
- src/main/java/konkuk/thip/feed/application/service/FeedShowSingleService.java:48
- return feedQueryMapper.toFeedShowSingleResponse(feed, feedCreator, book, isSaved, isLiked, userId);
#️⃣ 연관된 이슈
📝 작업 내용
📸 스크린샷
💬 리뷰 요구사항
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit