Conversation
|
Caution Review failedThe pull request is closed. """ Walkthrough이번 변경에서는 기록장(Record) 및 투표(Vote) 통합 조회 API를 새롭게 도입하고, 관련 도메인/응답/쿼리/테스트 코드 일체를 구현했습니다. 정렬, 필터링, 페이지네이션, DTO 구조, 예외 코드, 유틸리티, JPA 커스텀 쿼리 등 기록장 조회와 관련된 백엔드 전반이 추가·수정되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant RecordQueryController
participant RecordSearchService
participant RecordQueryPort
participant UserQueryPort
participant PostLikeQueryPort
participant CommentQueryPort
participant VoteQueryPort
participant VoteCommandPort
participant DateUtil
Client->>RecordQueryController: GET /rooms/{roomId}/posts?type=...&sort=...&...
RecordQueryController->>RecordSearchService: search(RecordSearchQuery)
RecordSearchService->>RecordQueryPort: findRecordsByRoom(...)
RecordQueryPort-->>RecordSearchService: RecordSearchResult(records, votes)
loop for each record/vote
RecordSearchService->>UserQueryPort: getUser(...)
RecordSearchService->>PostLikeQueryPort: countByPostId(...)
RecordSearchService->>PostLikeQueryPort: existsByPostIdAndUserId(...)
RecordSearchService->>CommentQueryPort: countByPostId(...)
RecordSearchService->>VoteCommandPort: findVoteItemsByVoteId(...) (for votes)
RecordSearchService->>VoteQueryPort: isUserVoted(...) (for votes)
RecordSearchService->>DateUtil: formatLastActivityTime(...)
end
RecordSearchService-->>RecordQueryController: RecordSearchResponse
RecordQueryController-->>Client: BaseResponse<RecordSearchResponse>
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (13)
src/main/java/konkuk/thip/common/util/DateUtil.java (1)
12-19: 시간 포맷팅 로직 개선 제안현재 구현은 기본적인 요구사항을 만족하지만 몇 가지 개선점이 있습니다:
LocalDateTime.now()직접 사용으로 인한 테스트 어려움- 타임존 처리 부재
- 매우 큰 시간 차이에 대한 처리 부재
개선된 구현:
+ private static final String JUST_NOW = "방금 전"; + private static final String MINUTES_AGO = "분 전"; + private static final String HOURS_AGO = "시간 전"; + private static final String DAYS_AGO = "일 전"; + - public String formatLastActivityTime(LocalDateTime createdAt) { - long minutes = Duration.between(createdAt, LocalDateTime.now()).toMinutes(); - if (minutes < 1) return "방금 전"; - if (minutes < 60) return minutes + "분 전"; + public String formatLastActivityTime(LocalDateTime createdAt) { + return formatLastActivityTime(createdAt, LocalDateTime.now()); + } + + public String formatLastActivityTime(LocalDateTime createdAt, LocalDateTime now) { + long minutes = Duration.between(createdAt, now).toMinutes(); + if (minutes < 1) return JUST_NOW; + if (minutes < 60) return minutes + MINUTES_AGO; long hours = minutes / 60; - if (hours < 24) return hours + "시간 전"; - return (hours / 24) + "일 전"; + if (hours < 24) return hours + HOURS_AGO; + long days = hours / 24; + if (days > 365) return days / 365 + "년 전"; + return days + DAYS_AGO; }src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchUseCase.java (1)
1-1: 패키지 위치에 대한 검토 필요UseCase 인터페이스가
port/in/dto패키지에 위치하는 것이 적절한지 검토가 필요합니다. UseCase는 일반적으로 DTO가 아니므로port/in또는 별도의usecase패키지에 위치하는 것이 더 명확할 수 있습니다.src/main/java/konkuk/thip/vote/domain/VoteItem.java (1)
28-30: 백분율 계산 메서드 구현 검토메서드 로직은 올바르게 구현되었으나 몇 가지 개선점을 고려해보세요:
- 부동소수점 정확도:
Math.round((this.count * 100.0) / totalCount)로 계산할 때 부동소수점 연산의 정확도 문제가 발생할 수 있습니다.- 반환 타입 고려: 백분율을 정수로 반환하는 것이 적절한지 검토해보세요. 소수점 이하 정보가 필요한 경우
double을 고려할 수 있습니다.더 정확한 계산을 위한 개선 방안:
public int calculatePercentage(int totalCount) { - return totalCount == 0 ? 0 : (int) Math.round((this.count * 100.0) / totalCount); + if (totalCount == 0) return 0; + return (int) Math.round((double) this.count * 100 / totalCount); }src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchQuery.java (1)
6-13: 페이지네이션 관련 필드들의 역할 명확화 필요
pageStart,pageEnd,pageNum필드가 모두 존재하는데, 이들의 역할과 관계가 명확하지 않습니다. 코드를 보면 서로 다른 페이지네이션 방식을 지원하는 것으로 보이지만, 필드명만으로는 어떤 상황에서 어떤 필드를 사용해야 하는지 파악하기 어렵습니다.JavaDoc이나 주석을 통해 각 필드의 사용 목적을 명확히 해주세요.
src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchResult.java (1)
14-22: 중복되는 객체 생성 방법 제거 권장
@Builder어노테이션과 정적 팩토리 메서드of()가 동일한 기능을 제공하고 있습니다. 정적 팩토리 메서드는 단순히 빌더를 호출하는 wrapper 역할만 하므로 코드 중복을 야기합니다.다음 중 하나를 선택하여 일관성을 유지하세요:
@Builder어노테이션만 사용- 정적 팩토리 메서드만 사용하고
@Builder제거다음과 같이 정적 팩토리 메서드만 사용하는 것을 권장합니다:
-@Builder public record RecordSearchResult( List<Record> records, List<Vote> votes ) { public static RecordSearchResult of( List<Record> records, List<Vote> votes ) { - return RecordSearchResult.builder() - .records(records) - .votes(votes) - .build(); + return new RecordSearchResult(records, votes); } }src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryPersistenceAdapter.java (1)
24-24: 사용되지 않는 상수 제거 필요
PAGE_SIZE상수가 정의되었으나 실제로는 사용되지 않습니다. 미사용 상수는 코드 혼란을 야기할 수 있으므로 제거하거나 실제로 사용하도록 수정해주세요.src/main/java/konkuk/thip/vote/adapter/out/persistence/UserVoteJpaRepository.java (1)
7-7: 메서드명이 너무 길어 가독성이 떨어집니다
existsByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId메서드명이 Spring Data JPA 규칙을 따르고 있지만 매우 길어서 가독성이 떨어집니다.다음과 같이
@Query어노테이션을 사용하여 더 간결한 메서드명을 사용하는 것을 고려해보세요:+@Query("SELECT COUNT(uv) > 0 FROM UserVoteJpaEntity uv WHERE uv.userJpaEntity.userId = :userId AND uv.voteItemJpaEntity.voteItemId = :voteItemId") +boolean existsByUserIdAndVoteItemId(@Param("userId") Long userId, @Param("voteItemId") Long voteItemId); -boolean existsByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId(Long userId, Long voteItemId);src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java (1)
40-45: 매개변수 명명 개선 필요
filterByType메서드에서QVoteJpaEntity타입의 매개변수가post로 명명되어 있어 혼란을 야기할 수 있습니다.-private BooleanExpression filterByType(String type, QVoteJpaEntity post, Long userId) { +private BooleanExpression filterByType(String type, QVoteJpaEntity vote, Long userId) { if ("mine".equalsIgnoreCase(type)) { - return post.userJpaEntity.userId.eq(userId); + return vote.userJpaEntity.userId.eq(userId); } return null; }src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1)
20-34: 쿼리 구현 로직 승인 및 개선 제안QueryDSL을 사용한 구현이 적절합니다. 다만 몇 가지 개선사항이 있습니다.
return jpaQueryFactory .select(record) .from(record) .leftJoin(record.userJpaEntity, user).fetchJoin() + .leftJoin(record.roomJpaEntity).fetchJoin() .where( record.roomJpaEntity.roomId.eq(roomId), filterByType(type, record, userId), (startEndNull(pageStart, pageEnd) ? record.isOverview.isTrue() : record.page.between(pageStart, pageEnd)) ) + .orderBy(record.createdAt.desc()) .fetch();src/test/java/konkuk/thip/record/adapter/in/web/RecordQueryControllerTest.java (1)
158-179: 테스트 검증 로직 개선 제안하드코딩된 값 대신 상수를 사용하고, 더 구체적인 검증을 추가해야 합니다.
+private static final String EXPECTED_RECORD_TYPE = "RECORD"; +private static final String EXPECTED_VOTE_TYPE = "VOTE"; JsonNode recordNode = jsonNode.path("data").path("recordList").get(0); -assertThat(recordNode.path("type").asText()).isEqualTo("RECORD"); +assertThat(recordNode.path("type").asText()).isEqualTo(EXPECTED_RECORD_TYPE);src/main/java/konkuk/thip/record/adapter/in/web/response/RecordDto.java (1)
26-41: 팩토리 메소드 매개변수 순서 개선 제안매개변수가 많아 실수할 가능성이 있습니다. 빌더 패턴을 더 활용하거나 매개변수 객체를 고려해보세요.
-public static RecordDto of(Record record, String postDate, User user, int likeCount, int commentCount, boolean isLiked, boolean isWriter) { +public static RecordDto of(RecordDtoParams params) { + return RecordDto.builder() + .postDate(params.postDate()) + .page(params.record().getPage()) + .userId(params.record().getCreatorId()) + .nickName(params.user().getNickname()) + .profileImageUrl(params.user().getImageUrl()) + .content(params.record().getContent()) + .likeCount(params.likeCount()) + .commentCount(params.commentCount()) + .isLiked(params.isLiked()) + .isWriter(params.isWriter()) + .recordId(params.record().getId()) + .build(); +} + +public record RecordDtoParams(Record record, String postDate, User user, int likeCount, int commentCount, boolean isLiked, boolean isWriter) {}src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (1)
31-35: 구현체 대신 인터페이스 주입 권장
RecordQueryRepositoryImpl을 직접 주입받는 대신,RecordJpaRepository를 통해 테스트하는 것이 더 적절합니다. 구현체를 직접 테스트하는 것은 구현 세부사항에 대한 의존성을 만들 수 있습니다.src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (1)
97-99: 검증 로직 가독성 개선페이지 파라미터 검증 로직을 더 명확하게 표현할 수 있습니다.
-if(query.pageStart() != null && query.pageEnd() == null || query.pageStart() == null && query.pageEnd() != null) { - throw new InvalidStateException(ErrorCode.API_INVALID_PARAM, new InvalidRequestStateException("pageStart와 pageEnd는 모두 설정되어야 합니다.")); +if((query.pageStart() == null) != (query.pageEnd() == null)) { + throw new InvalidStateException(ErrorCode.API_INVALID_PARAM, "pageStart와 pageEnd는 모두 설정되어야 합니다."); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (35)
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentJpaRepository.java(1 hunks)src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java(1 hunks)src/main/java/konkuk/thip/common/exception/code/ErrorCode.java(2 hunks)src/main/java/konkuk/thip/common/util/DateUtil.java(1 hunks)src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java(1 hunks)src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java(1 hunks)src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java(1 hunks)src/main/java/konkuk/thip/record/adapter/in/web/response/RecordDto.java(1 hunks)src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java(1 hunks)src/main/java/konkuk/thip/record/adapter/in/web/response/VoteDto.java(1 hunks)src/main/java/konkuk/thip/record/adapter/out/persistence/RecordJpaRepository.java(1 hunks)src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepository.java(1 hunks)src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchQuery.java(1 hunks)src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchResult.java(1 hunks)src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchUseCase.java(1 hunks)src/main/java/konkuk/thip/record/application/port/out/RecordQueryPort.java(1 hunks)src/main/java/konkuk/thip/record/application/service/RecordSearchService.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/jpa/UserVoteJpaEntity.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/mapper/UserVoteMapper.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/UserVoteJpaRepository.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java(3 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteJpaRepository.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepository.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java(1 hunks)src/main/java/konkuk/thip/vote/domain/UserVote.java(1 hunks)src/main/java/konkuk/thip/vote/domain/VoteItem.java(1 hunks)src/test/java/konkuk/thip/common/util/TestEntityFactory.java(2 hunks)src/test/java/konkuk/thip/record/adapter/in/web/RecordQueryControllerTest.java(1 hunks)src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java(1 hunks)src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (15)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/port/out/RecordQueryPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchUseCase.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepository.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchQuery.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#36
File: src/main/java/konkuk/thip/user/adapter/out/persistence/UserJpaRepository.java:7-7
Timestamp: 2025-06-29T09:47:31.299Z
Learning: Spring Data JPA에서 findBy{FieldName} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, Optional<Entity> 반환 타입을 사용하는 것이 null 안전성을 위해 권장됩니다.
src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java (1)
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.756Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
🧬 Code Graph Analysis (1)
src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(16-96)
🔇 Additional comments (31)
src/main/java/konkuk/thip/vote/adapter/out/jpa/UserVoteJpaEntity.java (2)
1-1: 도메인 분리 관점에서 적절한 패키지 이동UserVote 관련 클래스들을 user 패키지에서 vote 패키지로 이동한 것은 도메인 경계를 명확히 하는 좋은 변경입니다.
5-5: 임포트 경로 확인 완료
UserVoteJpaEntity를 참조하는UserVoteJpaRepository와UserVoteMapper모두konkuk.thip.vote.adapter.out.jpa.UserVoteJpaEntity를 사용하고 있으며, 이전 패키지 경로를 참조하는 코드는 없습니다.src/main/java/konkuk/thip/vote/domain/UserVote.java (1)
1-1: 도메인 엔티티 패키지 이동 승인UserVote 도메인 엔티티를 user 패키지에서 vote 패키지로 이동한 것은 도메인 경계를 명확히 하는 적절한 변경입니다.
src/main/java/konkuk/thip/vote/adapter/out/mapper/UserVoteMapper.java (2)
1-1: 매퍼 클래스 패키지 이동 승인UserVoteMapper를 vote 패키지로 이동한 것은 다른 UserVote 관련 클래스들과 일관성을 유지하는 좋은 변경입니다.
4-5: 임포트 업데이트 확인패키지 이동에 따른 임포트 경로가 올바르게 업데이트되었습니다.
src/main/java/konkuk/thip/record/application/port/out/RecordQueryPort.java (2)
3-3: 적절한 import 추가RecordSearchResult를 위한 import가 올바르게 추가되었습니다.
7-7: CQRS 컨벤션에 맞는 QueryPort 메서드 추가검색된 학습 내용에 따르면, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가하는 것이 이 프로젝트의 컨벤션입니다. 이 메서드는 레코드와 투표를 조합한 결과를 반환하여 API 응답 데이터를 제공하므로 적절합니다.
src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteJpaRepository.java (1)
6-6: 커스텀 쿼리 기능을 위한 적절한 인터페이스 확장VoteQueryRepository를 추가로 확장하여 커스텀 쿼리 기능을 제공하는 것은 Spring Data JPA의 표준 패턴입니다. 이를 통해 복잡한 투표 조회 로직을 구현할 수 있습니다.
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentJpaRepository.java (1)
7-7: 특정 게시물의 사용자 댓글 수 조회를 위한 메서드 추가Spring Data JPA의 메서드 명명 규칙을 따르는 적절한 메서드입니다. 특정 게시물에 대한 사용자의 댓글 수를 조회하는 기능을 제공합니다.
src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchUseCase.java (1)
5-8: 적절한 Use Case 인터페이스 구조헥사고날 아키텍처의 Use Case 패턴을 따르는 적절한 인터페이스 구조입니다. 단일 메서드로 검색 기능을 정의하고 있으며, 명확한 입력과 출력을 가지고 있습니다.
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java (1)
15-18: 메서드 구현이 올바르게 작성되었습니다.CommandPort 인터페이스의 요구사항에 맞게 JPA 저장소 메서드를 적절히 위임하고 있습니다. 네이밍 컨벤션도 일관성 있게 적용되었습니다.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordJpaRepository.java (1)
6-6: 커스텀 쿼리 인터페이스 확장이 적절하게 구현되었습니다.
RecordQueryRepository인터페이스를 추가로 확장하여 커스텀 쿼리 기능을 제공하는 것은 Spring Data JPA의 표준 패턴을 잘 따르고 있습니다.src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepository.java (1)
9-9: 메서드 시그니처가 명확하고 적절하게 정의되었습니다.
findRecordsByRoom메서드의 파라미터들이 룸별 필터링, 타입별 조회, 페이지네이션, 유저별 조회 기능을 잘 지원하도록 설계되었습니다.src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java (1)
14-16: CQRS 패턴에 맞는 메서드들이 추가되었습니다.
findVoteItemsByVoteId와isUserVoted메서드들이 CommandPort의 역할에 맞게 도메인 엔티티 조회 기능을 제공하고 있습니다. 메서드명도 의도를 명확히 표현하고 있습니다.src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepository.java (1)
7-10: 인터페이스 구조가 깔끔하고 명확합니다메서드 시그니처가 명확하고 파라미터들이 논리적인 순서로 배치되어 있습니다. 커스텀 쿼리 레포지토리 패턴을 올바르게 적용했습니다.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryPersistenceAdapter.java (2)
27-27: 사용되지 않는 pageNum 파라미터 확인 필요
findRecordsByRoom메서드의pageNum파라미터가 메서드 내부에서 사용되지 않습니다. 이 파라미터가 실제로 필요한지 확인하고, 필요하다면 로직에 추가하거나 불필요하다면 제거해주세요.
28-36: 도메인 매핑 로직이 잘 구현되었습니다Record와 Vote를 각각 조회하여 도메인 객체로 매핑하는 로직이 깔끔하게 구현되었습니다. 스트림 API를 적절히 활용하여 가독성도 좋습니다.
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (3)
10-12: 새로운 테스트 엔티티 팩토리 메서드 추가 - 좋습니다!투표와 댓글 기능을 위한 테스트 엔티티 팩토리 메서드가 추가되었습니다. 기존 패턴과 일관성이 유지되고 있어 좋습니다.
79-87: 투표 엔티티 팩토리 메서드 구현 확인투표 엔티티 생성 메서드가 적절하게 구현되었습니다. 기본값들이 합리적으로 설정되어 있습니다.
89-95: 댓글 엔티티 팩토리 메서드 구현 확인댓글 엔티티 생성 메서드가 적절하게 구현되었습니다. PostJpaEntity를 매개변수로 받는 것이 올바른 설계입니다.
src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java (1)
1-12: 표준 JPA 리포지토리 인터페이스 구현 - 좋습니다!PostLikeJpaRepository가 Spring Data JPA 컨벤션을 올바르게 따르고 있습니다. 메서드 명명도 적절하고 필요한 기능을 제공합니다.
src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java (1)
20-34: QueryDSL 구현 로직 확인투표 조회 로직이 적절하게 구현되었습니다. leftJoin fetch를 사용한 최적화도 좋습니다.
src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java (2)
19-21: 사용자별 좋아요 존재 여부 확인 로직 확인사용자별 좋아요 존재 확인 로직이 적절하게 구현되었습니다. 매개변수와 메서드명이 일치합니다.
7-12: CQRS CommandPort 패턴 구현 확인CommandPort 인터페이스를 구현한 어댑터가 올바르게 구현되었습니다. 기존 학습된 컨벤션을 따르고 있습니다.
src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java (2)
18-18: 새로운 UseCase 의존성 주입 확인RecordSearchUseCase 의존성이 적절하게 주입되었습니다.
20-41: 기록장 및 투표 조회 API 엔드포인트 구현 확인REST API 엔드포인트가 적절하게 구현되었습니다. 매개변수 설계와 응답 구조가 합리적입니다.
주요 장점:
- 페이지네이션 매개변수를 optional로 처리
- @userid 어노테이션을 통한 사용자 인증 처리
- Builder 패턴을 사용한 query 객체 생성
- BaseResponse로 일관된 응답 구조 제공
src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java (1)
31-31: 새로운 의존성 추가 승인UserVoteJpaRepository 추가가 적절합니다. 사용자 투표 상태 확인을 위해 필요한 의존성입니다.
src/main/java/konkuk/thip/record/adapter/in/web/response/RecordDto.java (1)
7-24: 레코드 클래스 설계 승인Java record와 @builder 어노테이션의 조합이 적절하며, 인터페이스 구현도 올바릅니다.
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1)
81-84: 새로운 에러 코드 추가 승인VOTE_ITEM_NOT_FOUND 에러 코드가 적절하게 추가되었습니다. 투표는 존재하지만 투표 항목이 없는 상황을 명확히 표현합니다.
src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java (1)
24-41: sealed interface와 Jackson 어노테이션 활용이 훌륭합니다다형성 DTO 처리를 위한 sealed interface 사용과 Jackson 어노테이션 설정이 매우 적절합니다. 타입 안정성과 JSON 직렬화를 동시에 해결한 좋은 설계입니다.
src/main/java/konkuk/thip/record/adapter/in/web/response/VoteDto.java (1)
10-48: DTO 구조가 잘 설계되었습니다VoteDto와 중첩된 VoteItemDto의 구조가 명확하고, 정적 팩토리 메서드를 통한 객체 생성 방식이 깔끔합니다.
src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java
Outdated
Show resolved
Hide resolved
| package konkuk.thip.post.application.port.out; | ||
|
|
||
| public interface PostLikeCommandPort { | ||
| int countByPostIdAndUserId(Long postId); |
There was a problem hiding this comment.
메서드명과 파라미터가 일치하지 않습니다.
countByPostIdAndUserId(Long postId) 메서드명에는 AndUserId가 포함되어 있지만 실제 파라미터에는 userId가 없습니다. 게시물의 전체 좋아요 수를 조회하는 것이라면 countByPostId(Long postId)로 수정해야 합니다.
다음과 같이 수정하는 것을 권장합니다:
- int countByPostIdAndUserId(Long postId);
+ int countByPostId(Long postId);📝 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.
| int countByPostIdAndUserId(Long postId); | |
| int countByPostId(Long postId); |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java
at line 4, the method name countByPostIdAndUserId suggests it requires both
postId and userId parameters, but only postId is present. To fix this, either
add a userId parameter to match the method name or rename the method to
countByPostId if it only counts likes by postId. Adjust the method signature
accordingly to ensure the name and parameters are consistent.
| public int countByPostIdAndUserId(Long postId) { | ||
| return postLikeJpaRepository.countByPostJpaEntity_PostId(postId); | ||
| } |
There was a problem hiding this comment.
메서드 명명 오류 수정 필요
메서드명이 countByPostIdAndUserId인데 매개변수는 postId만 받고 있어 혼란을 야기합니다.
-public int countByPostIdAndUserId(Long postId) {
+public int countByPostId(Long postId) {
return postLikeJpaRepository.countByPostJpaEntity_PostId(postId);
}📝 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.
| public int countByPostIdAndUserId(Long postId) { | |
| return postLikeJpaRepository.countByPostJpaEntity_PostId(postId); | |
| } | |
| public int countByPostId(Long postId) { | |
| return postLikeJpaRepository.countByPostJpaEntity_PostId(postId); | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java
around lines 14 to 16, the method name countByPostIdAndUserId suggests it
requires both postId and userId parameters, but currently it only accepts
postId. Rename the method to accurately reflect that it counts by postId only,
or modify the method to accept userId as a parameter and update the repository
call accordingly to match the method name.
src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java
Show resolved
Hide resolved
| @Override | ||
| public boolean isUserVoted(Long userId, Long voteId) { | ||
| return userVoteJpaRepository.existsByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId(userId, voteId); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
사용자 투표 상태 확인 로직 개선 제안
현재 메소드명이 정확한 동작을 반영하지 않습니다. voteId가 아닌 voteItemId를 사용하고 있습니다.
@Override
public boolean isUserVoted(Long userId, Long voteId) {
- return userVoteJpaRepository.existsByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId(userId, voteId);
+ return userVoteJpaRepository.existsByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteJpaEntity_PostId(userId, voteId);
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java
around lines 78 to 81, the method isUserVoted uses voteId as a parameter but
actually checks existence by voteItemId. Rename the parameter from voteId to
voteItemId to accurately reflect its purpose and update all references
accordingly to improve code clarity and correctness.
| RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, 130000, "존재하지 않는 RECORD 입니다."), | ||
| RECORD_CANNOT_BE_OVERVIEW(HttpStatus.BAD_REQUEST, 130001, "총평이 될 수 없는 RECORD 입니다."), | ||
| INVALID_RECORD_PAGE_RANGE(HttpStatus.BAD_REQUEST, 130002, "RECORD의 page 값이 유효하지 않습니다."), | ||
| RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 120003, "만료된 방에는 기록을 남길 수 없습니다."), |
There was a problem hiding this comment.
에러 코드 번호 불일치 수정 필요
에러 코드가 120003으로 되어 있으나, 130000 범위에 속해야 합니다.
-RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 120003, "만료된 방에는 기록을 남길 수 없습니다."),
+RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 130003, "만료된 방에는 기록을 남길 수 없습니다."),📝 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.
| RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 120003, "만료된 방에는 기록을 남길 수 없습니다."), | |
| RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 130003, "만료된 방에는 기록을 남길 수 없습니다."), |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/common/exception/code/ErrorCode.java at line 91,
the error code number for RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM is incorrectly set
to 120003 but should be within the 130000 range. Update the error code number to
a valid value in the 130000 range to maintain consistency with the error code
classification.
| * 130000 : record error | ||
| */ | ||
| RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, 120000, "존재하지 않는 RECORD 입니다."), | ||
| RECORD_CANNOT_BE_OVERVIEW(HttpStatus.BAD_REQUEST, 120001, "총평이 될 수 없는 RECORD 입니다."), | ||
| INVALID_RECORD_PAGE_RANGE(HttpStatus.BAD_REQUEST, 120002, "RECORD의 page 값이 유효하지 않습니다."), | ||
| RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, 130000, "존재하지 않는 RECORD 입니다."), | ||
| RECORD_CANNOT_BE_OVERVIEW(HttpStatus.BAD_REQUEST, 130001, "총평이 될 수 없는 RECORD 입니다."), | ||
| INVALID_RECORD_PAGE_RANGE(HttpStatus.BAD_REQUEST, 130002, "RECORD의 page 값이 유효하지 않습니다."), | ||
| RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 120003, "만료된 방에는 기록을 남길 수 없습니다."), | ||
|
|
||
| /** | ||
| * 130000 : userRoom error | ||
| * 140000 : userRoom error | ||
| */ | ||
| USER_ROOM_NOT_FOUND(HttpStatus.NOT_FOUND, 130000, "존재하지 않는 USER_ROOM (방과 사용자 관계) 입니다."), | ||
| USER_ROOM_NOT_FOUND(HttpStatus.NOT_FOUND, 140000, "존재하지 않는 USER_ROOM (방과 사용자 관계) 입니다."), | ||
|
|
||
| /** | ||
| * 140000 : Category error | ||
| * 150000 : Category error | ||
| */ | ||
| CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, 140000, "존재하지 않는 CATEGORY 입니다.") | ||
| CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, 150000, "존재하지 않는 CATEGORY 입니다.") |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
에러 코드 번호 재구성 확인 필요
에러 코드 번호가 변경되었습니다. 기존 클라이언트와의 호환성 문제가 없는지 확인해야 합니다.
🏁 Script executed:
#!/bin/bash
# 에러 코드 사용 위치 확인
rg "120000|120001|120002|130000|140000" --type javaLength of output: 772
🏁 Script executed:
#!/bin/bash
# RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM 에러 코드 사용 위치 확인
rg "120003" --type javaLength of output: 185
RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM 에러 코드 그룹 확인 및 수정 필요
현재 RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM는 잘못된 그룹(120003, voteItem error)에 속해 있어, record error 그룹(130xxx)과 일관성이 맞지 않습니다. 아래를 확인·수정해 주세요.
• 위치
- src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (
RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM)
• 현재 값 - 120003 (voteItem error 그룹)
• 권장 수정 - 130003 (record error 그룹)
또한, 이 변경이 기존 클라이언트와의 에러 코드 호환성에 미치는 영향을 검토해 주세요.
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/common/exception/code/ErrorCode.java around lines
86 to 101, the error code for RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM is incorrectly
set to 120003, which belongs to the voteItem error group. Change this error code
to 130003 to align it with the record error group. After updating the code,
review any client dependencies to ensure this change does not break error code
compatibility.
| @@ -0,0 +1,153 @@ | |||
| package konkuk.thip.record.application.service; | |||
|
|
|||
| import com.sun.jdi.request.InvalidRequestStateException; | |||
There was a problem hiding this comment.
잘못된 import 제거 필요
com.sun.jdi.request.InvalidRequestStateException은 디버거 API의 예외 클래스입니다. 일반적인 비즈니스 로직에서 사용하면 안 됩니다.
-import com.sun.jdi.request.InvalidRequestStateException;검증 오류 메시지를 위해서는 단순히 문자열을 사용하거나, 커스텀 예외 클래스를 만들어 사용하세요.
📝 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.
| import com.sun.jdi.request.InvalidRequestStateException; |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/record/application/service/RecordSearchService.java
at line 3, remove the import of com.sun.jdi.request.InvalidRequestStateException
because it is a debugger API exception and should not be used in business logic.
Replace its usage with either a simple string message for validation errors or
create and use a custom exception class appropriate for your application.
src/main/java/konkuk/thip/record/application/service/RecordSearchService.java
Outdated
Show resolved
Hide resolved
| assertThat(response.recordList()).hasSize(2); | ||
| assertThat(response.recordList().get(0).type()).isEqualTo("RECORD"); // 최신순이므로 Record가 먼저 |
There was a problem hiding this comment.
테스트 주석과 실제 동작 불일치
주석에는 "최신순이므로 Record가 먼저"라고 되어 있지만, 실제로는 Vote가 더 최근에 생성되었습니다 (Record: 10분 전, Vote: 현재 시간). 최신순 정렬 시 Vote가 먼저 와야 합니다.
-assertThat(response.recordList().get(0).type()).isEqualTo("RECORD"); // 최신순이므로 Record가 먼저
+assertThat(response.recordList().get(0).type()).isEqualTo("VOTE"); // 최신순이므로 Vote가 먼저📝 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.
| assertThat(response.recordList()).hasSize(2); | |
| assertThat(response.recordList().get(0).type()).isEqualTo("RECORD"); // 최신순이므로 Record가 먼저 | |
| assertThat(response.recordList()).hasSize(2); | |
| assertThat(response.recordList().get(0).type()).isEqualTo("VOTE"); // 최신순이므로 Vote가 먼저 |
🤖 Prompt for AI Agents
In
src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java
around lines 95 to 96, the test comment incorrectly states that "Record" should
come first due to sorting by latest, but the actual data has "Vote" as the most
recent. Update the assertion to expect "Vote" as the first element in the list
to match the latest-first sorting order, and revise the comment accordingly to
reflect this correct behavior.
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (12)
src/main/java/konkuk/thip/record/application/port/out/RecordQueryPort.java (1)
7-7: 메서드 파라미터 수를 줄이는 것을 고려해보세요.6개의 파라미터는 다소 많습니다. 검색 조건들을 하나의 파라미터 객체로 묶는 것을 고려해보세요. 하지만 현재 구현도 검색 기능의 복잡성을 고려하면 허용 가능한 수준입니다.
다음과 같은 방식으로 리팩토링할 수 있습니다:
-RecordSearchResult findRecordsByRoom(Long roomId, String type, Integer pageStart, Integer pageEnd, Long userId, Integer pageNum); +RecordSearchResult findRecordsByRoom(RecordSearchCriteria criteria);src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepository.java (1)
9-9: 메서드 파라미터 수 개선 및 문서화 필요메서드에 5개의 파라미터가 있어 가독성과 유지보수성이 저하됩니다. 파라미터 객체를 사용하여 개선하는 것을 고려해보세요.
+/** + * 방별 기록 조회 리포지토리 + */ public interface RecordQueryRepository { + /** + * 방의 기록들을 조회합니다. + * + * @param query 검색 조건 + * @return 조회된 기록 리스트 + */ - List<RecordJpaEntity> findRecordsByRoom(Long roomId, String type, Integer pageStart, Integer pageEnd, Long userId); + List<RecordJpaEntity> findRecordsByRoom(RecordSearchQuery query);또는 현재 구조를 유지하면서 문서화만 추가:
+ /** + * 방의 기록들을 조회합니다. + * + * @param roomId 방 ID + * @param type 검색 타입 + * @param pageStart 페이지 시작 번호 + * @param pageEnd 페이지 끝 번호 + * @param userId 사용자 ID + * @return 조회된 기록 리스트 + */ List<RecordJpaEntity> findRecordsByRoom(Long roomId, String type, Integer pageStart, Integer pageEnd, Long userId);src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchResult.java (1)
14-22: 불필요한 정적 팩토리 메서드 제거
of정적 팩토리 메서드가 단순히 빌더를 호출하는 것 외에 추가 로직이 없어 중복됩니다. 빌더 패턴을 직접 사용하는 것이 더 명확합니다.- public static RecordSearchResult of( - List<Record> records, - List<Vote> votes - ) { - return RecordSearchResult.builder() - .records(records) - .votes(votes) - .build(); - }또는 정적 팩토리 메서드에 추가 로직(null 체크, 불변 리스트 변환 등)을 추가하여 의미를 부여:
public static RecordSearchResult of( List<Record> records, List<Vote> votes ) { + return RecordSearchResult.builder() + .records(records != null ? List.copyOf(records) : List.of()) + .votes(votes != null ? List.copyOf(votes) : List.of()) + .build(); - return RecordSearchResult.builder() - .records(records) - .votes(votes) - .build(); }src/main/java/konkuk/thip/common/util/DateUtil.java (1)
12-19: 정적 메서드 사용을 고려해보세요.이 유틸리티 메서드는 인스턴스 상태를 사용하지 않으므로 static 메서드로 만드는 것을 고려할 수 있습니다. 이렇게 하면 메모리 효율성과 성능 면에서 이점이 있습니다.
- public String formatLastActivityTime(LocalDateTime createdAt) { + public static String formatLastActivityTime(LocalDateTime createdAt) {그리고 클래스에서 @component 애노테이션을 제거할 수 있습니다.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (2)
36-38: 헬퍼 메서드 이름을 더 명확하게 개선할 수 있습니다.
startEndNull메서드 이름이 기능을 명확하게 표현하지 못합니다.- private boolean startEndNull(Integer start, Integer end) { + private boolean isPageRangeNotSpecified(Integer pageStart, Integer pageEnd) {또는
- private boolean startEndNull(Integer start, Integer end) { + private boolean shouldFilterByOverview(Integer pageStart, Integer pageEnd) {
40-45: 타입 필터링 확장성을 고려해주세요.현재 "mine" 타입만 지원하고 있습니다. 향후 다른 타입들이 추가될 예정이라면 enum이나 strategy 패턴 사용을 고려해보세요.
- private BooleanExpression filterByType(String type, QRecordJpaEntity post, Long userId) { - if ("mine".equalsIgnoreCase(type)) { - return post.userJpaEntity.userId.eq(userId); - } - return null; - } + private BooleanExpression filterByType(String type, QRecordJpaEntity post, Long userId) { + return switch (type != null ? type.toLowerCase() : "") { + case "mine" -> post.userJpaEntity.userId.eq(userId); + // case "popular" -> // 인기 게시물 필터링 로직 + // case "recent" -> // 최근 게시물 필터링 로직 + default -> null; + }; + }src/test/java/konkuk/thip/record/adapter/in/web/RecordQueryControllerTest.java (1)
75-142: 테스트 데이터 생성을 TestEntityFactory를 활용하여 개선하세요.현재 테스트 데이터를 직접 생성하고 있지만, 이미 존재하는
TestEntityFactory를 활용하면 코드 중복을 줄이고 유지보수성을 높일 수 있습니다.- AliasJpaEntity alias = aliasJpaRepository.save(AliasJpaEntity.builder() - .value("별명") - .color("blue") - .imageUrl("http://alias.img") - .build()); + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createAlias()); - UserJpaEntity user = userJpaRepository.save(UserJpaEntity.builder() - .oauth2Id("kakao_123") - .nickname("사용자") - .imageUrl("http://user.img") - .role(UserRole.USER) - .aliasForUserJpaEntity(alias) - .build()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(alias));src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java (2)
8-14: 필드명을 더 명확하게 수정하는 것을 고려하세요.
recordList필드명이 실제로는 record와 vote를 모두 포함하므로 혼란을 줄 수 있습니다. 더 명확한 이름을 사용하는 것이 좋습니다.public record RecordSearchResponse( - List<PostDto> recordList, + List<PostDto> posts, Integer page, Integer size, Boolean first, Boolean last ){
16-22: 팩토리 메서드의 필요성을 검토하세요.현재 팩토리 메서드가 단순히 생성자를 호출하는 것 외에 추가적인 로직이 없습니다. record의 경우 생성자가 이미 public이므로 팩토리 메서드가 불필요할 수 있습니다.
팩토리 메서드를 제거하고 직접 생성자를 사용하거나, 향후 확장을 위해 유지할지 결정하세요.
src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (1)
58-72: 테스트용 RecordJpaEntity 생성 코드를 팩토리로 추출하는 것을 고려하세요.Record 엔티티 생성 코드가 두 테스트에서 유사하게 반복되고 있습니다.
TestEntityFactory에 다양한 설정을 지원하는 메서드를 추가하는 것을 고려해보세요.
TestEntityFactory에 다음과 같은 메서드를 추가할 수 있습니다:public static RecordJpaEntity createRecordWithUser(UserJpaEntity user, RoomJpaEntity room, String content, boolean isOverview) { return RecordJpaEntity.builder() .userJpaEntity(user) .roomJpaEntity(room) .content(content) .page(1) .isOverview(isOverview) .build(); }src/main/java/konkuk/thip/record/adapter/in/web/response/VoteDto.java (2)
10-24: @builder 어노테이션과 record 조합을 검토하세요.record 타입에
@Builder어노테이션을 사용하는 것이 일반적이지 않습니다. record는 이미 간결한 생성자를 제공하므로 Builder 패턴이 불필요할 수 있습니다.- @Builder public record VoteDto( // fields... ) implements RecordSearchResponse.PostDto {또는 Builder가 필요하다면 클래스로 변경하는 것을 고려하세요.
30-48: 팩토리 메서드의 파라미터 수를 줄이는 것을 고려하세요.현재 팩토리 메서드가 8개의 파라미터를 받고 있어 복잡합니다. 관련 데이터를 그룹화하거나 별도의 DTO로 묶는 것을 고려해보세요.
public record VoteMetadata( int likeCount, int commentCount, boolean isLiked, boolean isWriter ) {} public static VoteDto of(Vote vote, String postDate, User user, VoteMetadata metadata, List<VoteItemDto> voteItems) { // implementation }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (35)
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentJpaRepository.java(1 hunks)src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java(1 hunks)src/main/java/konkuk/thip/common/exception/code/ErrorCode.java(2 hunks)src/main/java/konkuk/thip/common/util/DateUtil.java(1 hunks)src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java(1 hunks)src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java(1 hunks)src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java(1 hunks)src/main/java/konkuk/thip/record/adapter/in/web/response/RecordDto.java(1 hunks)src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java(1 hunks)src/main/java/konkuk/thip/record/adapter/in/web/response/VoteDto.java(1 hunks)src/main/java/konkuk/thip/record/adapter/out/persistence/RecordJpaRepository.java(1 hunks)src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepository.java(1 hunks)src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchQuery.java(1 hunks)src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchResult.java(1 hunks)src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchUseCase.java(1 hunks)src/main/java/konkuk/thip/record/application/port/out/RecordQueryPort.java(1 hunks)src/main/java/konkuk/thip/record/application/service/RecordSearchService.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/jpa/UserVoteJpaEntity.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/mapper/UserVoteMapper.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/UserVoteJpaRepository.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java(3 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteJpaRepository.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepository.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java(1 hunks)src/main/java/konkuk/thip/vote/domain/UserVote.java(1 hunks)src/main/java/konkuk/thip/vote/domain/VoteItem.java(1 hunks)src/test/java/konkuk/thip/common/util/TestEntityFactory.java(2 hunks)src/test/java/konkuk/thip/record/adapter/in/web/RecordQueryControllerTest.java(1 hunks)src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java(1 hunks)src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/port/out/RecordQueryPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchUseCase.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepository.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java (1)
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.756Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchQuery.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#36
File: src/main/java/konkuk/thip/user/adapter/out/persistence/UserJpaRepository.java:7-7
Timestamp: 2025-06-29T09:47:31.299Z
Learning: Spring Data JPA에서 findBy{FieldName} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, Optional<Entity> 반환 타입을 사용하는 것이 null 안전성을 위해 권장됩니다.
src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/record/application/service/RecordSearchService.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
🧬 Code Graph Analysis (1)
src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(16-96)
🔇 Additional comments (40)
src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java (1)
1-12: 새로운 PostLikeJpaRepository 구현이 적절합니다.Spring Data JPA 컨벤션을 잘 따르고 있으며, 메서드 네이밍과 반환 타입이 적절합니다. 게시글 좋아요 개수 조회와 사용자별 좋아요 여부 확인 기능을 제공하는 표준적인 JPA 리포지토리 구현입니다.
src/main/java/konkuk/thip/vote/domain/UserVote.java (1)
1-1: 도메인 경계를 명확히 하는 적절한 패키지 이동입니다.UserVote 엔티티를 user 패키지에서 vote 패키지로 이동시킨 것은 PR 목표에서 언급한 대로 도메인 경계를 반영하고 vote aggregate를 통한 접근을 보장하는 좋은 리팩토링입니다.
src/main/java/konkuk/thip/vote/adapter/out/jpa/UserVoteJpaEntity.java (2)
1-1: 패키지 이동이 도메인 리팩토링과 일치합니다.UserVoteJpaEntity의 패키지 이동이 도메인 엔티티 이동과 일관성을 유지하고 있으며, 필요한 import 구문도 올바르게 업데이트되었습니다.
5-5: import 구문이 올바르게 업데이트되었습니다.패키지 이동에 따라 UserJpaEntity import가 적절하게 수정되었습니다.
src/main/java/konkuk/thip/vote/adapter/out/mapper/UserVoteMapper.java (2)
1-1: 매퍼 클래스의 패키지 이동이 적절합니다.UserVoteMapper의 패키지 이동이 전체 vote 도메인 리팩토링과 일관성을 유지하고 있습니다.
4-5: import 구문이 새로운 패키지 구조를 정확히 반영합니다.UserVoteJpaEntity와 UserVote의 import가 새로운 패키지 위치에 맞게 올바르게 업데이트되었습니다.
src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java (1)
6-7: count 메서드 위치 일관성 확인PostLikeCommandPort에도 이미
countByPostIdAndUserId메서드가 CommandPort에 위치해 있어, CommentCommandPort 역시 같은 위치에 두는 것이 기존 구현과 일관됩니다. CQRS 컨벤션상 별도 QueryPort로 이동할 필요는 없습니다.
- src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java:
int countByPostIdAndUserId(Long postId);Likely an incorrect or invalid review comment.
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentJpaRepository.java (1)
7-7: 적절한 JPA 파생 쿼리 메서드 구현입니다.Spring Data JPA의 네이밍 컨벤션을 정확히 따르고 있으며, 메서드명이 길지만 기능을 명확히 표현하고 있습니다.
src/main/java/konkuk/thip/vote/domain/VoteItem.java (1)
28-30: 올바른 퍼센트 계산 로직입니다.division by zero 예외 처리가 적절하고, Math.round()를 사용한 반올림 처리도 정확합니다. 100.0을 곱하여 부동소수점 연산을 보장한 점도 좋습니다.
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java (1)
15-18: 적절한 어댑터 패턴 구현입니다.CommentCommandPort 인터페이스를 올바르게 구현하고 있으며, JPA 리포지토리로의 위임도 정확합니다.
src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchUseCase.java (1)
1-8: 깔끔한 유스케이스 인터페이스 정의입니다.헥사고날 아키텍처를 잘 따르고 있으며, 단일 책임 원칙에 부합합니다. 메서드 시그니처도 명확하고 적절합니다.
src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java (1)
5-5: 메서드 시그니처가 올바릅니다.
existsByPostIdAndUserId메서드는 두 파라미터를 모두 받아 명명과 일치합니다.src/main/java/konkuk/thip/record/adapter/out/persistence/RecordJpaRepository.java (1)
6-6: Repository 확장이 올바르게 구현되었습니다.
RecordQueryRepository를 확장하여 커스텀 쿼리 기능을 추가한 것이 적절합니다. Spring Data JPA의 믹스인 패턴을 올바르게 따르고 있습니다.src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteJpaRepository.java (1)
6-6: Repository 확장이 일관된 패턴을 따릅니다.
VoteQueryRepository를 확장하여RecordJpaRepository와 동일한 패턴을 따르고 있어 일관성이 유지됩니다.src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepository.java (1)
7-10: 쿼리 메서드 시그니처가 적절하게 설계되었습니다.
findVotesByRoom메서드는 방별 투표 조회에 필요한 모든 파라미터(roomId, type, pageStart, pageEnd, userId)를 포함하고 있으며, 페이징과 필터링을 지원하는 구조로 잘 설계되었습니다.src/main/java/konkuk/thip/vote/adapter/out/persistence/UserVoteJpaRepository.java (1)
7-7: JPA 메서드 명명 규칙을 올바르게 따릅니다.
existsByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId메서드는 Spring Data JPA의 관계 탐색 명명 규칙을 정확히 따르고 있으며, 사용자의 특정 투표 항목에 대한 투표 여부를 확인하는 목적에 적합합니다.src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java (1)
14-16: CQRS 컨벤션에 맞는 구현프로젝트의 CQRS 포트 분리 컨벤션에 맞게 CommandPort에 도메인 엔티티 조회 메서드들이 적절히 추가되었습니다. 메서드명과 반환 타입이 명확하고 일관성 있습니다.
src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchResult.java (1)
10-12: 레코드 구조 승인records와 votes를 분리하여 담는 구조가 명확하고 타입 안전성을 제공합니다.
src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java (1)
30-40: 빌더 패턴 사용 승인RecordSearchQuery 빌더 패턴을 사용하여 파라미터를 명확하게 전달하고 있습니다. 코드 가독성이 좋습니다.
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (3)
10-12: 새로운 엔티티 팩토리 메서드를 위한 임포트 추가가 적절합니다.VoteJpaEntity, CommentJpaEntity, PostJpaEntity에 대한 임포트가 새로 추가된 팩토리 메서드들과 일치합니다.
79-87: 테스트 투표 엔티티 생성 메서드가 올바르게 구현되었습니다.기존 팩토리 메서드들과 동일한 패턴을 따르고 있으며, 기본값들도 적절히 설정되어 있습니다.
89-95: 테스트 댓글 엔티티 생성 메서드가 올바르게 구현되었습니다.PostJpaEntity와 UserJpaEntity를 매개변수로 받아 연관관계를 올바르게 설정하고 있습니다.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryPersistenceAdapter.java (3)
4-9: 새로운 기능을 위한 임포트들이 적절히 추가되었습니다.RecordSearchResult, Vote, VoteMapper, VoteJpaRepository 임포트가 새로 구현된 기능과 일치합니다.
20-22: 의존성 주입이 올바르게 설정되었습니다.VoteJpaRepository와 VoteMapper가 적절히 주입되도록 구성되어 있습니다.
26-37: 레코드와 투표 데이터를 통합 조회하는 메서드가 올바르게 구현되었습니다.두 개의 별도 쿼리를 실행하여 결과를 결합하는 방식이 적절하며, 도메인 객체로의 매핑도 정확합니다.
src/main/java/konkuk/thip/common/util/DateUtil.java (2)
8-9: 유틸리티 클래스의 Spring 컴포넌트 등록이 적절합니다.@component 애노테이션을 통해 의존성 주입이 가능하도록 구성되어 있습니다.
12-19: 시간 포맷팅 로직이 올바르게 구현되었습니다.Duration.between을 사용한 시간 계산과 한국어 포맷팅이 적절하며, 시간 임계값들도 합리적입니다.
src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java (1)
7-11: 퍼시스턴스 어댑터 구조가 올바르게 구성되었습니다.@repository 애노테이션과 @requiredargsconstructor를 사용한 의존성 주입이 적절합니다.
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImpl.java (2)
13-17: QueryDSL 기반 리포지토리 구조가 올바르게 설정되었습니다.@repository 애노테이션과 JPAQueryFactory 의존성 주입이 적절합니다.
19-34: 복잡한 필터링 로직이 포함된 쿼리가 올바르게 구현되었습니다.leftJoin과 fetchJoin을 사용한 연관 엔티티 조회, 다중 조건 필터링, 페이지 범위와 오버뷰 모드 분기 처리가 적절합니다.
src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java (1)
1-249: 테스트 코드가 잘 작성되었습니다!서비스의 주요 기능인 정렬, 페이징, 파라미터 검증에 대한 포괄적인 테스트 케이스가 포함되어 있습니다. 엣지 케이스 처리도 적절합니다.
src/main/java/konkuk/thip/record/adapter/in/web/response/RecordDto.java (1)
1-42: 깔끔한 DTO 구현입니다!Java record와 sealed interface를 활용한 타입 안전한 구현이 잘 되어 있습니다.
src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java (1)
1-47: QueryDSL을 활용한 효율적인 구현입니다!Fetch join을 사용하여 N+1 문제를 방지하고, 필터링 로직이 명확하게 구현되어 있습니다.
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1)
81-84: 새로운 에러 코드 추가를 승인합니다.voteItem 도메인을 위한 새로운 에러 코드가 적절하게 추가되었습니다. 투표는 존재하지만 투표 항목이 없는 경우를 명확히 구분할 수 있어 좋습니다.
src/main/java/konkuk/thip/record/adapter/in/web/response/RecordSearchResponse.java (1)
24-41: sealed interface와 Jackson 어노테이션을 활용한 다형성 설계가 우수합니다.
PostDto를 sealed interface로 설계하고 Jackson의@JsonTypeInfo와@JsonSubTypes를 사용하여 다형성 직렬화를 지원하는 것이 매우 좋습니다. 타입 안전성과 JSON 직렬화 모두를 효과적으로 지원합니다.src/test/java/konkuk/thip/record/adapter/out/persistence/RecordQueryRepositoryImplTest.java (3)
41-77: TestEntityFactory 활용이 우수합니다.
TestEntityFactory를 적절히 활용하여 테스트 데이터를 생성하고 있어 코드 중복을 줄이고 유지보수성을 높였습니다. 특히 기본 엔티티들의 생성에서 factory를 사용한 것이 좋습니다.
80-92: 테스트 시나리오가 명확하고 적절합니다."mine" 타입에 대한 테스트가 사용자별 필터링을 정확히 검증하고 있습니다. 두 명의 사용자를 생성하고 특정 사용자의 레코드만 조회되는지 확인하는 로직이 정확합니다.
133-144: overview 모드 테스트 시나리오가 정확합니다.
pageStart와pageEnd가 null일 때isOverview가 true인 레코드만 조회되는지 확인하는 테스트가 적절합니다. 일반 레코드와 요약 레코드를 구분하여 검증하는 로직이 정확합니다.src/main/java/konkuk/thip/record/adapter/in/web/response/VoteDto.java (2)
50-64: VoteItemDto 중첩 클래스 설계가 적절합니다.
VoteItemDto를VoteDto내부에 중첩 클래스로 정의한 것이 적절합니다. 투표 항목은 투표와 밀접한 관련이 있으므로 응집도를 높이는 좋은 설계입니다.
25-28: type() 메서드 구현이 올바릅니다.sealed interface인
PostDto의type()메서드를 올바르게 구현하여 "VOTE" 타입을 반환하고 있습니다. 이는 JSON 직렬화 시 다형성 처리에 필수적입니다.
| package konkuk.thip.post.application.port.out; | ||
|
|
||
| public interface PostLikeCommandPort { | ||
| int countByPostIdAndUserId(Long postId); |
There was a problem hiding this comment.
메서드 명과 파라미터가 일치하지 않습니다.
countByPostIdAndUserId 메서드명은 postId와 userId 두 파라미터를 받는 것을 암시하지만, 실제로는 postId만 받고 있습니다. 메서드명을 countByPostId로 변경하거나, userId 파라미터를 추가해야 합니다.
- int countByPostIdAndUserId(Long postId);
+ int countByPostId(Long postId);또는 userId 파라미터가 필요하다면:
- int countByPostIdAndUserId(Long postId);
+ int countByPostIdAndUserId(Long postId, Long userId);🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java
at line 4, the method countByPostIdAndUserId is declared with only one parameter
postId, but the name implies it should also take a userId parameter. To fix
this, either rename the method to countByPostId if only postId is needed, or add
a Long userId parameter to the method signature to match the method name.
| public record RecordSearchQuery( | ||
| Long roomId, | ||
| String type, | ||
| String sort, | ||
| Integer pageStart, | ||
| Integer pageEnd, | ||
| Integer pageNum, | ||
| Long userId) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
입력 검증 및 문서화 추가 필요
검색 파라미터에 대한 입력 검증과 문서화가 필요합니다:
roomId,userId에 대한@NotNull검증type,sort에 대한@NotBlank및 허용 값 검증- 페이지 관련 파라미터에 대한 범위 검증
- 각 필드에 대한 JavaDoc 추가
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+
+/**
+ * 기록 검색 쿼리 DTO
+ *
+ * @param roomId 방 ID
+ * @param type 검색 타입 (all, record, vote, mine)
+ * @param sort 정렬 기준 (latest, oldest, popular)
+ * @param pageStart 페이지 시작 번호
+ * @param pageEnd 페이지 끝 번호
+ * @param pageNum 페이지 번호
+ * @param userId 사용자 ID
+ */
@Builder
public record RecordSearchQuery(
+ @NotNull
Long roomId,
+ @NotBlank
String type,
+ @NotBlank
String sort,
+ @Positive
Integer pageStart,
+ @Positive
Integer pageEnd,
+ @Positive
Integer pageNum,
+ @NotNull
Long userId) {
}📝 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.
| public record RecordSearchQuery( | |
| Long roomId, | |
| String type, | |
| String sort, | |
| Integer pageStart, | |
| Integer pageEnd, | |
| Integer pageNum, | |
| Long userId) { | |
| import jakarta.validation.constraints.NotBlank; | |
| import jakarta.validation.constraints.NotNull; | |
| import jakarta.validation.constraints.Positive; | |
| /** | |
| * 기록 검색 쿼리 DTO | |
| * | |
| * @param roomId 방 ID | |
| * @param type 검색 타입 (all, record, vote, mine) | |
| * @param sort 정렬 기준 (latest, oldest, popular) | |
| * @param pageStart 페이지 시작 번호 | |
| * @param pageEnd 페이지 끝 번호 | |
| * @param pageNum 페이지 번호 | |
| * @param userId 사용자 ID | |
| */ | |
| @Builder | |
| public record RecordSearchQuery( | |
| @NotNull | |
| Long roomId, | |
| @NotBlank | |
| String type, | |
| @NotBlank | |
| String sort, | |
| @Positive | |
| Integer pageStart, | |
| @Positive | |
| Integer pageEnd, | |
| @Positive | |
| Integer pageNum, | |
| @NotNull | |
| Long userId) { | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/record/application/port/in/dto/RecordSearchQuery.java
around lines 6 to 13, add input validation annotations such as @NotNull for
roomId and userId, @NotBlank for type and sort, and implement allowed value
checks for type and sort fields. Also, add range validations for pageStart,
pageEnd, and pageNum to ensure they are within acceptable limits. Finally, add
JavaDoc comments for each field explaining their purpose and constraints.
| @GetMapping("/rooms/{roomId}/posts") | ||
| public BaseResponse<RecordSearchResponse> viewRecordList( | ||
| @PathVariable final Long roomId, | ||
| @RequestParam final String type, | ||
| @RequestParam final String sort, | ||
| @RequestParam(required = false) final Integer pageStart, | ||
| @RequestParam(required = false) final Integer pageEnd, | ||
| @RequestParam final Integer pageNum, | ||
| @UserId final Long userId | ||
| ) { | ||
| return BaseResponse.ok(recordSearchUseCase.search( | ||
| RecordSearchQuery.builder() | ||
| .roomId(roomId) | ||
| .type(type) | ||
| .sort(sort) | ||
| .pageStart(pageStart) | ||
| .pageEnd(pageEnd) | ||
| .pageNum(pageNum) | ||
| .userId(userId) | ||
| .build() | ||
| )); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
입력 검증 및 문서화 추가 필요
컨트롤러 메서드에 입력 검증과 문서화가 필요합니다:
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+
+ /**
+ * 방의 기록 및 투표 목록을 조회합니다.
+ *
+ * @param roomId 방 ID
+ * @param type 검색 타입 (all, record, vote, mine)
+ * @param sort 정렬 기준 (latest, oldest, popular)
+ * @param pageStart 페이지 시작 번호 (선택사항)
+ * @param pageEnd 페이지 끝 번호 (선택사항)
+ * @param pageNum 페이지 번호
+ * @param userId 현재 사용자 ID
+ * @return 검색 결과
+ */
+ @Operation(summary = "방의 기록 및 투표 목록 조회")
@GetMapping("/rooms/{roomId}/posts")
public BaseResponse<RecordSearchResponse> viewRecordList(
+ @Parameter(description = "방 ID", required = true)
+ @NotNull
@PathVariable final Long roomId,
+ @Parameter(description = "검색 타입", required = true)
+ @NotBlank
@RequestParam final String type,
+ @Parameter(description = "정렬 기준", required = true)
+ @NotBlank
@RequestParam final String sort,
+ @Parameter(description = "페이지 시작 번호")
+ @Positive
@RequestParam(required = false) final Integer pageStart,
+ @Parameter(description = "페이지 끝 번호")
+ @Positive
@RequestParam(required = false) final Integer pageEnd,
+ @Parameter(description = "페이지 번호", required = true)
+ @Positive
@RequestParam final Integer pageNum,
+ @Parameter(description = "사용자 ID", hidden = true)
@UserId final Long userId
) {📝 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.
| @GetMapping("/rooms/{roomId}/posts") | |
| public BaseResponse<RecordSearchResponse> viewRecordList( | |
| @PathVariable final Long roomId, | |
| @RequestParam final String type, | |
| @RequestParam final String sort, | |
| @RequestParam(required = false) final Integer pageStart, | |
| @RequestParam(required = false) final Integer pageEnd, | |
| @RequestParam final Integer pageNum, | |
| @UserId final Long userId | |
| ) { | |
| return BaseResponse.ok(recordSearchUseCase.search( | |
| RecordSearchQuery.builder() | |
| .roomId(roomId) | |
| .type(type) | |
| .sort(sort) | |
| .pageStart(pageStart) | |
| .pageEnd(pageEnd) | |
| .pageNum(pageNum) | |
| .userId(userId) | |
| .build() | |
| )); | |
| } | |
| import jakarta.validation.constraints.NotBlank; | |
| import jakarta.validation.constraints.NotNull; | |
| import jakarta.validation.constraints.Positive; | |
| import io.swagger.v3.oas.annotations.Operation; | |
| import io.swagger.v3.oas.annotations.Parameter; | |
| /** | |
| * 방의 기록 및 투표 목록을 조회합니다. | |
| * | |
| * @param roomId 방 ID | |
| * @param type 검색 타입 (all, record, vote, mine) | |
| * @param sort 정렬 기준 (latest, oldest, popular) | |
| * @param pageStart 페이지 시작 번호 (선택사항) | |
| * @param pageEnd 페이지 끝 번호 (선택사항) | |
| * @param pageNum 페이지 번호 | |
| * @param userId 현재 사용자 ID | |
| * @return 검색 결과 | |
| */ | |
| @Operation(summary = "방의 기록 및 투표 목록 조회") | |
| @GetMapping("/rooms/{roomId}/posts") | |
| public BaseResponse<RecordSearchResponse> viewRecordList( | |
| @Parameter(description = "방 ID", required = true) | |
| @NotNull | |
| @PathVariable final Long roomId, | |
| @Parameter(description = "검색 타입", required = true) | |
| @NotBlank | |
| @RequestParam final String type, | |
| @Parameter(description = "정렬 기준", required = true) | |
| @NotBlank | |
| @RequestParam final String sort, | |
| @Parameter(description = "페이지 시작 번호") | |
| @Positive | |
| @RequestParam(required = false) final Integer pageStart, | |
| @Parameter(description = "페이지 끝 번호") | |
| @Positive | |
| @RequestParam(required = false) final Integer pageEnd, | |
| @Parameter(description = "페이지 번호", required = true) | |
| @Positive | |
| @RequestParam final Integer pageNum, | |
| @Parameter(description = "사용자 ID", hidden = true) | |
| @UserId final Long userId | |
| ) { | |
| return BaseResponse.ok(recordSearchUseCase.search( | |
| RecordSearchQuery.builder() | |
| .roomId(roomId) | |
| .type(type) | |
| .sort(sort) | |
| .pageStart(pageStart) | |
| .pageEnd(pageEnd) | |
| .pageNum(pageNum) | |
| .userId(userId) | |
| .build() | |
| )); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/record/adapter/in/web/RecordQueryController.java
around lines 20 to 41, the controller method lacks input validation and API
documentation. Add validation annotations like @NotNull, @Min, or @Max to the
method parameters to enforce input constraints. Also, include Swagger or similar
API documentation annotations to describe the endpoint, parameters, and expected
responses for better clarity and maintainability.
| private final RecordMapper recordMapper; | ||
| private final VoteMapper voteMapper; | ||
|
|
||
| private static final Integer PAGE_SIZE = 10; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
사용되지 않는 PAGE_SIZE 상수를 확인해주세요.
PAGE_SIZE 상수가 정의되어 있지만 현재 코드에서 사용되지 않고 있습니다. 필요한 경우 사용하거나 불필요하다면 제거를 고려해주세요.
🏁 Script executed:
#!/bin/bash
# PAGE_SIZE 상수가 다른 곳에서 사용되는지 확인
rg -A 3 -B 3 "PAGE_SIZE" --type javaLength of output: 8367
미사용 상수 PAGE_SIZE 제거를 권고합니다
RecordQueryPersistenceAdapter 클래스에 선언된 PAGE_SIZE 상수가 사용되지 않고 있어 불필요합니다. 제거를 검토해주세요.
• 파일: src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryPersistenceAdapter.java
• 제거할 코드:
- private static final Integer PAGE_SIZE = 10;📝 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.
| private static final Integer PAGE_SIZE = 10; |
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/record/adapter/out/persistence/RecordQueryPersistenceAdapter.java
at line 24, the constant PAGE_SIZE is declared but never used in the class.
Remove the declaration of the PAGE_SIZE constant to clean up unused code and
improve maintainability.
| @Override | ||
| public int countByPostIdAndUserId(Long postId) { | ||
| return postLikeJpaRepository.countByPostJpaEntity_PostId(postId); | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
메서드 이름과 구현 사이의 불일치를 확인해주세요.
countByPostIdAndUserId 메서드 이름은 postId와 userId 모두를 사용하는 것처럼 보이지만, 실제 구현에서는 postId만 사용하고 있습니다.
🏁 Script executed:
#!/bin/bash
# PostLikeCommandPort 인터페이스의 메서드 시그니처 확인
ast-grep --pattern 'interface PostLikeCommandPort {
$$$
countByPostIdAndUserId($$$) {
$$$
}
$$$
}'Length of output: 112
여전히 PostLikeCommandPort 인터페이스 및 관련 메서드 정의를 찾지 못했습니다. 추가 확인을 위해 아래 스크립트를 실행해 주세요.
🏁 Script executed:
#!/bin/bash
set -e
echo "1) PostLikeCommandPort 인터페이스 검색:"
rg "interface PostLikeCommandPort" -n
echo -e "\n2) countByPostIdAndUserId 호출 위치 검색:"
rg "countByPostIdAndUserId" -n
echo -e "\n3) PostLikeJpaRepository 정의 검색:"
rg "interface PostLikeJpaRepository" -n
echo -e "\n4) countByPostJpaEntity 기반 메서드 검색 (PostId, UserId 포함 여부 확인):"
rg "countByPostJpaEntity" -nLength of output: 3997
메서드 이름과 시그니처 불일치 수정 필요
현재 countByPostIdAndUserId는 이름에 UserId가 포함되어 있지만, 실제로는 Long postId만 파라미터로 받고 JPA 호출도 postId만 사용하고 있습니다. 아래 중 하나로 일관성을 맞춰주세요.
• 수정 대상
- src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java:4
- src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java:14–16
• 선택지
- 메서드 이름을
countByPostId로 변경 - 메서드 시그니처에
Long userId를 추가하고,
JPA 리포지토리 호출을
countByPostJpaEntity_PostIdAndUserJpaEntity_UserId(postId, userId)로 변경
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java
lines 13 to 16, the method name countByPostIdAndUserId does not match its
signature or implementation as it only takes postId and calls the repository
with postId alone. To fix this, either rename the method to countByPostId to
reflect the single parameter or update the method signature to include Long
userId and modify the repository call to
countByPostJpaEntity_PostIdAndUserJpaEntity_UserId(postId, userId) for
consistency with the method name.
| @@ -0,0 +1,153 @@ | |||
| package konkuk.thip.record.application.service; | |||
|
|
|||
| import com.sun.jdi.request.InvalidRequestStateException; | |||
There was a problem hiding this comment.
잘못된 import를 사용하고 있습니다.
com.sun.jdi.request.InvalidRequestStateException은 Java Debug Interface의 클래스입니다. 적절한 예외 타입을 사용하세요.
-import com.sun.jdi.request.InvalidRequestStateException;그리고 라인 98, 102, 106, 110에서 사용하는 곳도 수정하세요:
-throw new InvalidStateException(ErrorCode.API_INVALID_PARAM, new InvalidRequestStateException("..."));
+throw new InvalidStateException(ErrorCode.API_INVALID_PARAM);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/record/application/service/RecordSearchService.java
at line 3, replace the incorrect import of
com.sun.jdi.request.InvalidRequestStateException with a more appropriate
exception class such as java.lang.IllegalStateException or a custom exception
relevant to your application. Then, update the usage of this exception at lines
98, 102, 106, and 110 to throw the newly imported or defined exception type
instead.
| @AfterEach | ||
| void tearDown() { | ||
| voteItemJpaRepository.deleteAll(); | ||
| voteJpaRepository.deleteAll(); | ||
| recordJpaRepository.deleteAll(); | ||
| roomJpaRepository.deleteAll(); | ||
| bookJpaRepository.deleteAll(); | ||
| categoryJpaRepository.deleteAll(); | ||
| userJpaRepository.deleteAll(); | ||
| aliasJpaRepository.deleteAll(); | ||
| } |
There was a problem hiding this comment.
tearDown 메서드의 데이터 정리 순서를 외래 키 제약 조건에 맞게 수정하세요.
현재 tearDown 메서드에서 repository 정리 순서가 외래 키 제약 조건을 고려하지 않아 제약 조건 위반 오류가 발생할 수 있습니다. 의존 관계에 따라 자식 엔티티부터 삭제해야 합니다.
다음과 같이 수정하세요:
@AfterEach
void tearDown() {
voteItemJpaRepository.deleteAll();
voteJpaRepository.deleteAll();
recordJpaRepository.deleteAll();
roomJpaRepository.deleteAll();
bookJpaRepository.deleteAll();
categoryJpaRepository.deleteAll();
- userJpaRepository.deleteAll();
- aliasJpaRepository.deleteAll();
+ aliasJpaRepository.deleteAll();
+ userJpaRepository.deleteAll();
}📝 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.
| @AfterEach | |
| void tearDown() { | |
| voteItemJpaRepository.deleteAll(); | |
| voteJpaRepository.deleteAll(); | |
| recordJpaRepository.deleteAll(); | |
| roomJpaRepository.deleteAll(); | |
| bookJpaRepository.deleteAll(); | |
| categoryJpaRepository.deleteAll(); | |
| userJpaRepository.deleteAll(); | |
| aliasJpaRepository.deleteAll(); | |
| } | |
| @AfterEach | |
| void tearDown() { | |
| voteItemJpaRepository.deleteAll(); | |
| voteJpaRepository.deleteAll(); | |
| recordJpaRepository.deleteAll(); | |
| roomJpaRepository.deleteAll(); | |
| bookJpaRepository.deleteAll(); | |
| categoryJpaRepository.deleteAll(); | |
| aliasJpaRepository.deleteAll(); | |
| userJpaRepository.deleteAll(); | |
| } |
🤖 Prompt for AI Agents
In
src/test/java/konkuk/thip/record/adapter/in/web/RecordQueryControllerTest.java
around lines 59 to 69, the tearDown method deletes data in an order that may
violate foreign key constraints. To fix this, reorder the deletions to start
from child entities to parent entities, ensuring that dependent records are
deleted before their parents to avoid constraint violations.
| assertThat(recordNode.path("page").asInt()).isEqualTo(1); | ||
| assertThat(recordNode.path("content").asText()).isEqualTo("레코드 내용"); | ||
| assertThat(recordNode.path("nickName").asText()).isEqualTo("사용자"); | ||
| assertThat(recordNode.path("postDate").asText()).isEqualTo(dateUtil.formatLastActivityTime(LocalDateTime.now())); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
날짜 검증 로직을 개선하여 타이밍 이슈를 방지하세요.
LocalDateTime.now()를 사용한 날짜 검증은 테스트 실행 시점에 따라 실패할 수 있습니다. 더 안정적인 검증 방법을 사용하는 것이 좋습니다.
- assertThat(recordNode.path("postDate").asText()).isEqualTo(dateUtil.formatLastActivityTime(LocalDateTime.now()));
+ // 날짜 형식이 올바른지만 확인하거나, 특정 시간 범위 내에 있는지 확인
+ assertThat(recordNode.path("postDate").asText()).isNotEmpty();
+ // 또는 저장된 entity의 생성 시간을 사용
+ assertThat(recordNode.path("postDate").asText()).isEqualTo(dateUtil.formatLastActivityTime(record.getCreatedAt()));📝 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.
| assertThat(recordNode.path("postDate").asText()).isEqualTo(dateUtil.formatLastActivityTime(LocalDateTime.now())); | |
| // 날짜 형식이 올바른지만 확인하거나, 특정 시간 범위 내에 있는지 확인 | |
| assertThat(recordNode.path("postDate").asText()).isNotEmpty(); | |
| // 또는 저장된 entity의 생성 시간을 사용 | |
| assertThat(recordNode.path("postDate").asText()).isEqualTo(dateUtil.formatLastActivityTime(record.getCreatedAt())); |
🤖 Prompt for AI Agents
In
src/test/java/konkuk/thip/record/adapter/in/web/RecordQueryControllerTest.java
at line 163, the test uses LocalDateTime.now() directly for date validation,
which can cause timing issues and flaky tests. To fix this, capture the current
time in a variable before the operation under test, then use that variable for
both the operation and the assertion to ensure consistent and stable date
comparison.
| RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, 130000, "존재하지 않는 RECORD 입니다."), | ||
| RECORD_CANNOT_BE_OVERVIEW(HttpStatus.BAD_REQUEST, 130001, "총평이 될 수 없는 RECORD 입니다."), | ||
| INVALID_RECORD_PAGE_RANGE(HttpStatus.BAD_REQUEST, 130002, "RECORD의 page 값이 유효하지 않습니다."), | ||
| RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 120003, "만료된 방에는 기록을 남길 수 없습니다."), |
There was a problem hiding this comment.
에러 코드 번호 불일치를 수정하세요.
RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM의 에러 코드가 120003으로 되어 있지만, record 도메인은 130000 범위를 사용해야 합니다.
- RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 120003, "만료된 방에는 기록을 남길 수 없습니다."),
+ RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 130003, "만료된 방에는 기록을 남길 수 없습니다."),📝 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.
| RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 120003, "만료된 방에는 기록을 남길 수 없습니다."), | |
| RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 130003, "만료된 방에는 기록을 남길 수 없습니다."), |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/common/exception/code/ErrorCode.java at line 91,
the error code for RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM is incorrectly set to
120003, which does not match the record domain's 130000 range. Change the error
code number from 120003 to a valid 130000-range code to align with the record
domain's error code conventions.
| RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, 130000, "존재하지 않는 RECORD 입니다."), | ||
| RECORD_CANNOT_BE_OVERVIEW(HttpStatus.BAD_REQUEST, 130001, "총평이 될 수 없는 RECORD 입니다."), | ||
| INVALID_RECORD_PAGE_RANGE(HttpStatus.BAD_REQUEST, 130002, "RECORD의 page 값이 유효하지 않습니다."), | ||
| RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 120003, "만료된 방에는 기록을 남길 수 없습니다."), | ||
|
|
||
| /** | ||
| * 130000 : userRoom error | ||
| * 140000 : userRoom error | ||
| */ | ||
| USER_ROOM_NOT_FOUND(HttpStatus.NOT_FOUND, 130000, "존재하지 않는 USER_ROOM (방과 사용자 관계) 입니다."), | ||
| USER_ROOM_NOT_FOUND(HttpStatus.NOT_FOUND, 140000, "존재하지 않는 USER_ROOM (방과 사용자 관계) 입니다."), | ||
|
|
||
| /** | ||
| * 140000 : Category error | ||
| * 150000 : Category error | ||
| */ | ||
| CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, 140000, "존재하지 않는 CATEGORY 입니다.") | ||
| CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, 150000, "존재하지 않는 CATEGORY 입니다.") |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
기존 에러 코드 변경의 호환성을 확인하세요.
RECORD, USER_ROOM, CATEGORY 관련 에러 코드들이 다른 범위로 이동되었습니다. 이미 사용 중인 클라이언트가 있다면 호환성 문제가 발생할 수 있습니다.
다음 스크립트를 실행하여 기존 에러 코드 사용 현황을 확인하세요:
🏁 Script executed:
#!/bin/bash
# 기존 에러 코드 사용 현황 검색
echo "=== RECORD 관련 에러 코드 사용 현황 ==="
rg -n "120000|120001|120002" --type java
echo "=== USER_ROOM 관련 에러 코드 사용 현황 ==="
rg -n "130000" --type java
echo "=== CATEGORY 관련 에러 코드 사용 현황 ==="
rg -n "140000" --type javaLength of output: 1050
RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM 에러 코드 범위 수정 필요
RECORD 관련 에러 코드들은 모두 130xxx 범위를 사용하고 있으나, 해당 항목만 잘못된 120003으로 지정되어 있습니다. 아래와 같이 변경해주세요:
- 파일: src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
- 위치: RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM 선언부
- RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 120003, "만료된 방에는 기록을 남길 수 없습니다."),
+ RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 130003, "만료된 방에는 기록을 남길 수 없습니다."),수정 후 다음 사항을 점검하세요:
- 에러 코드 고유성 및 연속성
- API 문서나 클라이언트 로직에서 사용하는 코드 반영 여부
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/common/exception/code/ErrorCode.java at line 91,
the error code for RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM is incorrectly set to
120003, which is outside the 130xxx range used for RECORD errors. Change this
error code to a unique and continuous number within the 130xxx range, such as
130003. After updating, verify that the new code is unique and consistent with
other RECORD error codes and update any API documentation or client logic that
references this error code accordingly.
…into feat/#52-get-record-vote
|
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java (1)
99-100: 테스트 주석과 실제 동작 불일치주석에는 "최신순이므로 Record가 먼저"라고 되어 있지만, 실제로는 Vote가 더 최근에 생성되었습니다 (Record: 10분 전, Vote: 현재 시간). 최신순 정렬 시 Vote가 먼저 와야 합니다.
-assertThat(response.recordList().get(0).type()).isEqualTo("RECORD"); // 최신순이므로 Record가 먼저 +assertThat(response.recordList().get(0).type()).isEqualTo("VOTE"); // 최신순이므로 Vote가 먼저
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java(0 hunks)src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentJpaRepository.java(1 hunks)src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java(1 hunks)src/main/java/konkuk/thip/common/exception/code/ErrorCode.java(1 hunks)src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java(1 hunks)src/main/java/konkuk/thip/post/application/port/out/PostLikeQueryPort.java(1 hunks)src/main/java/konkuk/thip/record/application/service/RecordSearchService.java(1 hunks)src/main/java/konkuk/thip/room/adapter/in/web/request/RoomVerifyPasswordRequest.java(0 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java(2 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java(1 hunks)src/main/java/konkuk/thip/vote/application/port/out/VoteQueryPort.java(1 hunks)src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java(1 hunks)src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordControllerTest.java(1 hunks)
💤 Files with no reviewable changes (2)
- src/main/java/konkuk/thip/room/adapter/in/web/request/RoomVerifyPasswordRequest.java
- src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java
🚧 Files skipped from review as they are similar to previous changes (7)
- src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java
- src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java
- src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentJpaRepository.java
- src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java
- src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java
- src/main/java/konkuk/thip/record/application/service/RecordSearchService.java
- src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
🧰 Additional context used
🧠 Learnings (4)
src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/post/application/port/out/PostLikeQueryPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java (1)
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.756Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeQueryPersistenceAdapter.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
🔇 Additional comments (13)
src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java (1)
5-5: 인터페이스 메서드 추가가 적절합니다.CQRS 패턴에 따라 QueryPort에 조회 관련 메서드를 추가한 것이 올바른 구조입니다. 메서드명과 시그니처가 명확하고 일관성 있습니다.
src/main/java/konkuk/thip/vote/application/port/out/VoteQueryPort.java (1)
5-5: 사용자 투표 여부 확인 메서드가 적절합니다.메서드명이 의도를 명확하게 표현하고 있으며, boolean 반환 타입이 존재 여부 확인에 적합합니다.
src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java (1)
15-18: QueryPort 구현이 적절합니다.JPA Repository로의 위임이 올바르게 구현되었으며, 메서드명이 JPA 컨벤션을 잘 따르고 있습니다.
src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryPersistenceAdapter.java (1)
14-14: 의존성 추가가 적절합니다.UserVoteJpaRepository 의존성이 투표 상태 확인을 위해 올바르게 추가되었습니다.
src/main/java/konkuk/thip/post/application/port/out/PostLikeQueryPort.java (1)
1-8: PostLikeQueryPort 인터페이스 설계가 우수합니다.두 메서드 모두 명확한 목적을 가지고 있으며:
countByPostId: 게시글별 좋아요 수 조회existsByPostIdAndUserId: 특정 사용자의 좋아요 여부 확인메서드명이 직관적이고 반환 타입이 용도에 적합합니다.
src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeQueryPersistenceAdapter.java (1)
1-22: 구현이 올바르고 CQRS 패턴을 잘 따르고 있습니다.PostLikeQueryPort 인터페이스의 구현이 올바르며, 학습된 컨벤션에 따라 QueryPort는 조회 API 응답 데이터 조회용으로 적절히 사용되고 있습니다. JPA 리포지토리로의 단순 위임 구조도 깔끔합니다.
src/test/java/konkuk/thip/record/application/service/RecordSearchServiceTest.java (4)
44-63: 테스트 설정이 올바르고 의존성 모킹이 적절합니다.모든 필요한 의존성이 적절히 모킹되었고, 생성자 주입 방식의 서비스 초기화도 올바릅니다.
107-142: 좋아요 순 정렬 테스트가 올바르게 구현되었습니다.좋아요 수에 따른 정렬 로직이 정확히 테스트되고 있으며, Vote(10개)가 Record(5개)보다 먼저 오는 것이 올바릅니다.
144-184: 페이징 로직 테스트가 철저하게 구현되었습니다.11개의 레코드로 페이징 처리를 테스트하여 페이지 사이즈(10개), first/last 플래그 설정이 올바른지 확인하는 것이 적절합니다.
197-252: 검증 로직 테스트가 포괄적으로 구현되었습니다.다양한 유효하지 않은 파라미터 케이스들에 대한 예외 처리 테스트가 잘 구현되어 있습니다. pageStart/pageEnd 조합, pageNum 범위, sort/type 값 검증 등이 모두 포함되어 있습니다.
src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordControllerTest.java (3)
5-6: 임포트 재정렬 확인됨.임포트 순서 변경이 기능에 미치는 영향은 없습니다.
20-20: 임포트 재정렬 확인됨.임포트 순서 변경이 기능에 미치는 영향은 없습니다.
53-73: 빈 비밀번호 검증 의도 확인 필요
PasswordValidation네스트 클래스에서 빈 비밀번호를 검증하던blank_*테스트가 제거되었습니다. 이는RoomVerifyPasswordRequest의password필드에 있던@NotBlank(또는 유사 검증 어노테이션)가 삭제된 것으로 보이는데, 빈 문자열 허용이 비즈니스 요구사항에 부합하는지 확인 부탁드립니다.검토가 필요한 지점:
RoomVerifyPasswordRequest클래스 내password필드 검증 어노테이션RoomVerifyPasswordController또는 전역 예외 핸들러에서 입력 유효성 검사 설정
| @Override | ||
| public boolean isUserVoted(Long userId, Long voteItemId) { | ||
| return userVoteJpaRepository.existsByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId(userId, voteItemId); | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
메서드 구현의 매개변수 불일치를 확인해주세요.
인터페이스에서는 Long voteId를 받지만, 구현에서는 voteItemId로 사용하고 있습니다. 실제로는 voteItemId가 필요한 것으로 보이는데, 인터페이스 시그니처와 구현이 일치하는지 확인이 필요합니다.
다음 스크립트로 관련 코드를 확인해보겠습니다:
🏁 Script executed:
#!/bin/bash
# Vote 관련 엔티티 구조와 메서드 사용을 확인
echo "=== Vote 관련 엔티티 구조 확인 ==="
fd -e java -x grep -l "VoteItem" {} \;
echo "=== VoteQueryPort 인터페이스 확인 ==="
rg -A 5 -B 5 "isUserVoted"
echo "=== UserVoteJpaRepository 메서드 확인 ==="
rg -A 3 -B 3 "existsByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId"Length of output: 11151
메서드 매개변수명 일관성 수정 제안
인터페이스 VoteQueryPort.isUserVoted(Long userId, Long voteId)의 두 번째 파라미터명이 구현체에서는 voteItemId로 사용되고 있어 혼동을 유발할 수 있습니다. 자바 오버라이드는 파라미터명 불일치에도 동작하지만, 의미를 분명히 하기 위해 인터페이스 시그니처를 다음과 같이 변경하는 것을 권장드립니다.
• 수정 대상
- src/main/java/konkuk/thip/vote/application/port/out/VoteQueryPort.java
- (기존) boolean isUserVoted(Long userId, Long voteId)
- (변경) boolean isUserVoted(Long userId, Long voteItemId)
• 제안하는 diff
--- a/src/main/java/konkuk/thip/vote/application/port/out/VoteQueryPort.java
+++ b/src/main/java/konkuk/thip/vote/application/port/out/VoteQueryPort.java
- boolean isUserVoted(Long userId, Long voteId);
+ boolean isUserVoted(Long userId, Long voteItemId);필요에 따라 메서드 Javadoc에도 파라미터 의미를 명확히 기재해주세요.
📝 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.
| @Override | |
| public boolean isUserVoted(Long userId, Long voteItemId) { | |
| return userVoteJpaRepository.existsByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId(userId, voteItemId); | |
| } | |
| boolean isUserVoted(Long userId, Long voteItemId); |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/vote/application/port/out/VoteQueryPort.java around
the isUserVoted method, change the second parameter name from voteId to
voteItemId to match the implementation in VoteQueryPersistenceAdapter.java. Also
update the method Javadoc to clearly describe the parameters, ensuring
consistency and clarity between the interface and its implementation.



#️⃣ 연관된 이슈
📝 작업 내용
service
repository
adapter
테스트 코드는 총 3 분류로 진행했습니다.
📸 스크린샷
💬 리뷰 요구사항
카톡에서도 말씀드렸던 것처럼 기록장 조회 api를 구현하는 방법을 총 3가지를 고려했습니다.
1번의 경우에는 구현까지 완료하고 테스트해보니 저희의 PostJpaEntity <- RecordJpaEntity, VoteJpaEntity로 상속 구조이기 때문에 불필요한 left join이 나가는 것을 확인했습니다. 그리고 querydsl에서만 모든 것을 담당하기 때문에 코드가 너무 보기 어려워서 유지보수성이 매우 떨어질 것 같다고 판단했습니다.
2번의 경우, querydsl의 결과를 담기 위해 likeCount와 commentCount, Post를 포함하는 클래스 (예를 들어, PostWithCountInfo)를 리스트로 갖고 있는 (예를 들어, List. 이 클래스는 페이지네이션 변수도 갖고 있어야 함)를 정의해야 했는데, 저희의 도메인 상에서는 Post <- Record, Vote 구조가 아니기 때문에 querydsl -> adapter, adapter -> service로 가는 dto를 모두 만들어야 하고 또 한번의 상속 구조를 갖는 dto를 만드는 것은 그만큼 유지보수 비용이 막대하게 증가하는 것이라고 판단해서 배제하였습니다..
따라서, 3번을 채택했는데, 위에서 말씀 드린 것처럼 querydsl에서 record와 vote를 각각 조회하는데 이때, isOverview와 그룹기록/내 기록에 대한 필터링만 이루어집니다.
서비스 로직에서는 필터링된 모든 record, vote 리스트를 받아서 정렬 기준에 맞게 정렬 후, 알맞은 페이지로 리스트를 잘라서 반환합니다.
<- 이때 당연히 PostLike와 Comment, VoteItem, UserVote 등의 repository에 접근하여 사용자 좋아요 여부, 투표 여부, 댓글 갯수 등을 조회하여 dto에 파싱해줍니다.
또, UserVote의 경우 사용자가 저희 요구사항에 '사용자가 자신이 투표한 항목을 조회하는 기능'이 존재하지 않고, 모든 UserVote 접근은 투표 애그리거트에서 이루어지기 때문에 Vote 도메인에 들어가는 것이 맞다고 생각하여 vote 패키지 아래로 파일 위치를 이동했습니다!
추가적으로, 투표 퍼센트 구하는 로직은 좀더 고민해봐야 될 것 같습니다 🤔
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit
신규 기능
버그 수정
테스트
기타