-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] 내 인증현황 조회 API 구현 #138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthrough인증 이력 조회 API가 추가되었습니다. UserController에 새 엔드포인트와 VerificationService 의존성이 추가되고, VerificationRepository/Service/Converter/DTO에 히스토리 전용 쿼리·변환·페이징 로직(Slice 기반, COMPLETED 필터)이 도입되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
%%{init: {"themeVariables":{"actorBackground":"#F7FBFF","actorBorder":"#CFE8FF","noteBackground":"#FFF8E6"}}}%%
participant Client as 클라이언트
participant Controller as UserController
participant Auth as AuthenticationPrincipal
participant Service as VerificationService
participant Repo as VerificationRepository
participant Conv as VerificationConverter
participant DB as Database
Client->>Controller: GET /api/v1/user/me/verifications/history?page=1&size=10
Controller->>Auth: 인증 정보(`@AuthenticationPrincipal`) 요청
Auth-->>Controller: CustomUserDetails (userId)
Controller->>Service: getVerificationHistory(userId, page-1, size)
Service->>Repo: findVerificationHistoryByUser(User, COMPLETED, pageable)
Repo->>DB: SQL (JOIN fetch, WHERE status=COMPLETED, ORDER BY created_at DESC)
DB-->>Repo: Slice<Verification>
Service->>Conv: toHistoryDto(...) 반복 호출
Conv-->>Service: List<HistoryDto>
Service-->>Controller: SliceResponseDto<HistoryDto>
Controller-->>Client: ApiResponse(200, body: SliceResponseDto<HistoryDto>)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/main/java/com/hrr/backend/domain/user/dto/UserVerificationResponseDto.java (1)
42-44:@JsonFormat의 'Z' 리터럴 사용 관련 참고사항
LocalDateTime은 타임존 정보가 없지만, 패턴에서'Z'는 리터럴 문자로 출력됩니다. 클라이언트가 이를 UTC로 해석할 수 있으므로, 실제로 UTC 기준 시간을 저장/반환하는지 확인이 필요합니다.기존 코드베이스 패턴을 따르고 있으므로 현재 구현은 유지하되, 향후 타임존 명확화가 필요하다면
ZonedDateTime또는OffsetDateTime사용을 고려해보세요.src/main/java/com/hrr/backend/domain/user/controller/UserController.java (1)
155-159: 검색 API 페이징 size 상한을 다른 엔드포인트와 통일하면 좋겠습니다
- 이번에
@Min(1)과 기본값을 추가해 주신 덕분에 0 이하 페이지/사이즈 방어가 잘 되어서 👍 입니다.- 다만 다른 엔드포인트들(
/me/challenge/ongoing,/challenges/history)은size파라미터에@Max(100)까지 걸려 있는데, 여기/search만 상한이 없어 대용량 요청 시 성능 이슈가 날 수 있습니다.개선 제안:
- 아래처럼
size에도@Max(100)을 추가해서 검증 규칙을 통일하는 쪽을 추천드립니다.- @Min(1) - @RequestParam(name = "page", defaultValue = "1") int page, // 페이지 번호 (1부터 시작) - @Min(1) - @RequestParam(name = "size", defaultValue = "10") int size, // 페이지 크기 + @Min(1) + @RequestParam(name = "page", defaultValue = "1") int page, // 페이지 번호 (1부터 시작) + @Min(1) @Max(100) + @RequestParam(name = "size", defaultValue = "10") int size, // 페이지 크기참고로 자카르타 Bean Validation(
jakarta.validation.constraints.Min/Max) 스펙을 보면, 이런 단순 범위 체크는 애노테이션만으로도 충분히 표현 가능하니 지금 방향이 맞습니다.src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepository.java (1)
1-57: JPQL 쿼리 표현식과 인덱싱 관점에서 소소한 개선 포인트 제안전체적으로 메서드 네이밍과 JPQL이 역할별로 잘 나뉘어 있어서 읽기 좋습니다. 살짝만 다듬으면 더 안전하고 성능 친화적으로 가져갈 수 있을 것 같습니다.
existsTodayVerification의SELECT COUNT(v) > 0표현식
- Hibernate 기준으로는
SELECT COUNT(v) > 0이 잘 동작하지만, 순수 JPA 스펙 관점에서는 약간 그레이존에 가까운 표현이라 벤더 의존성이 생길 수 있습니다.- 보다 포터블하게 가져가려면 아래와 같이
CASE WHEN으로 boolean을 만들어 주는 패턴을 고려할 수 있습니다.@Query(""" SELECT CASE WHEN COUNT(v) > 0 THEN true ELSE false END FROM Verification v WHERE v.roundRecord.userChallenge.id = :userChallengeId AND v.status = :status AND v.createdAt >= :startOfDay AND v.createdAt <= :endOfDay """)JPA 쿼리 문법을 엄격하게 따르는 편이, 향후 Hibernate 버전 업이나 다른 JPA 구현체로의 전환 시 리스크를 줄여 줍니다.
날짜 범위(
BETWEEN/>=&<=) 경계 조건 재확인
findWeeklyVerifications의v.createdAt BETWEEN :start AND :end와existsTodayVerification의>= :startOfDay AND <= :endOfDay모두 양 끝단이 포함(inclusive) 입니다.- 주간/일간 통계를 만들 때, 호출부에서
end를 “마지막 날의 23:59:59.999…” 식으로 맞추는지, 혹은 [start, end) 형태로 쓰고 싶은지에 따라 off-by-one/밀리초 단위 누락이 발생할 수 있어, 서비스 레이어에서 파라미터 생성 규칙을 한 번 정리해 두는 걸 권장드립니다.쿼리 대상 컬럼에 대한 인덱싱 고려
- 세 쿼리 모두 공통적으로 다음 컬럼들을 where/order by에서 많이 사용합니다.
roundRecord.userChallenge.idstatuscreatedAt- (weekly 쿼리)
uc.user.id,uc.challenge.id- 인증 데이터가 쌓이면 해당 조건 조합으로 자주 조회될 가능성이 높아서, 실제 DB 스키마에서 위 컬럼들에 대해 적절한 인덱스(특히
userId + challengeId + createdAt조합 등)가 잡혀 있는지 한 번 확인해 보시면 좋겠습니다.이 부분들은 모두 기능 버그라기보다는 JPA/Hibernate 베스트 프랙티스와 성능 튜닝에 가까운 내용이라, 여유 있을 때 반영해도 충분할 것 같습니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/main/java/com/hrr/backend/domain/user/controller/UserController.java(4 hunks)src/main/java/com/hrr/backend/domain/user/dto/UserVerificationResponseDto.java(1 hunks)src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepository.java(1 hunks)src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepositoryCustom.java(1 hunks)src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepositoryCustomImpl.java(1 hunks)src/main/java/com/hrr/backend/domain/user/service/UserVerificationService.java(1 hunks)src/main/java/com/hrr/backend/domain/user/service/UserVerificationServiceImpl.java(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-06T08:06:38.145Z
Learnt from: yerinchun
Repo: Hrr-HabbitRoutineResult/hrr-server PR: 26
File: src/main/java/com/hrr/backend/domain/user/entity/User.java:42-44
Timestamp: 2025-11-06T08:06:38.145Z
Learning: In the hrr-server project User entity (src/main/java/com/hrr/backend/domain/user/entity/User.java), the password field is nullable because the application uses social login (OAuth), and social login users do not have passwords stored in the database.
Applied to files:
src/main/java/com/hrr/backend/domain/user/controller/UserController.java
🧬 Code graph analysis (6)
src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepositoryCustomImpl.java (1)
src/main/java/com/hrr/backend/domain/user/dto/UserVerificationResponseDto.java (1)
UserVerificationResponseDto(12-46)
src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepositoryCustom.java (4)
src/main/java/com/hrr/backend/domain/user/dto/UserVerificationResponseDto.java (1)
UserVerificationResponseDto(12-46)src/main/java/com/hrr/backend/domain/user/repository/UserChallengeRepositoryCustom.java (1)
UserChallengeRepositoryCustom(8-17)src/main/java/com/hrr/backend/domain/user/service/UserServiceImpl.java (1)
Override(70-126)src/main/java/com/hrr/backend/domain/user/repository/UserChallengeRepositoryCustomImpl.java (1)
Override(26-66)
src/main/java/com/hrr/backend/domain/user/service/UserVerificationServiceImpl.java (1)
src/main/java/com/hrr/backend/domain/user/dto/UserVerificationResponseDto.java (1)
UserVerificationResponseDto(12-46)
src/main/java/com/hrr/backend/domain/user/dto/UserVerificationResponseDto.java (4)
src/main/java/com/hrr/backend/domain/verification/dto/VerificationDetailResponseDto.java (3)
Data(55-64)Data(14-76)Data(66-75)src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java (4)
Getter(102-139)Getter(80-100)VerificationResponseDto(16-140)Getter(18-61)src/main/java/com/hrr/backend/domain/user/dto/UserMissionResponseDto.java (1)
UserMissionResponseDto(10-33)src/main/java/com/hrr/backend/domain/user/dto/UserResponseDto.java (1)
Getter(58-88)
src/main/java/com/hrr/backend/domain/user/controller/UserController.java (1)
src/main/java/com/hrr/backend/domain/user/dto/UserVerificationResponseDto.java (1)
UserVerificationResponseDto(12-46)
src/main/java/com/hrr/backend/domain/user/service/UserVerificationService.java (1)
src/main/java/com/hrr/backend/domain/user/dto/UserVerificationResponseDto.java (1)
UserVerificationResponseDto(12-46)
🔇 Additional comments (5)
src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepositoryCustom.java (1)
8-17: 깔끔한 인터페이스 정의입니다! 👍기존
UserChallengeRepositoryCustom패턴을 잘 따르고 있으며,Slice를 사용한 페이지네이션 방식도 적절합니다. Javadoc 주석까지 잘 작성되어 있네요.src/main/java/com/hrr/backend/domain/user/service/UserVerificationService.java (1)
6-14: 간결하고 명확한 서비스 인터페이스입니다!메서드 시그니처가 명확하고, 단일 책임 원칙을 잘 따르고 있습니다.
SliceResponseDto를 반환 타입으로 사용하여 페이지네이션 메타데이터까지 포함하는 것도 좋습니다.src/main/java/com/hrr/backend/domain/user/dto/UserVerificationResponseDto.java (1)
14-19: S3 URL 변환 시@Setter필요 여부 확인서비스 레이어에서
imageUrl에 대한 S3 URL 변환이 필요하다면,OngoingChallengeDto처럼 해당 필드에@Setter를 추가해야 합니다.@Getter @Builder @NoArgsConstructor @AllArgsConstructor @Schema(description = "개별 인증 기록 DTO") public static class VerificationItemDto { + @Setter + private String imageUrl; // ... 기존 필드들또는 클래스 레벨에
@Setter를 추가하거나, 빌더 패턴으로 새 객체를 생성하는 방법도 있습니다.src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepositoryCustomImpl.java (2)
58-64: Slice 생성 로직 LGTM! 🎯
hasNext판단과 리스트 조정 로직이 정확합니다. 기존 패턴과 동일하게 구현되어 있어 유지보수성도 좋습니다.
33-56: QueryDSL 쿼리 구현이 잘 되어 있습니다!기존
UserChallengeRepositoryCustomImpl패턴을 일관성 있게 따르고 있으며,hasNext판단을 위한+1패턴도 적절합니다. Verification 엔티티는createdAt필드만 가지고 있으므로, 이를 DTO에서verifiedAt으로 매핑하는 것이 올바른 구현입니다.Likely an incorrect or invalid review comment.
| private final UserVerificationService userVerificationService; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "UserController.java" -type fRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 152
🏁 Script executed:
cat -n ./src/main/java/com/hrr/backend/domain/user/controller/UserController.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 11900
🏁 Script executed:
find . -name "UserVerificationService.java" -type fRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 158
🏁 Script executed:
cat -n ./src/main/java/com/hrr/backend/domain/user/service/UserVerificationService.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 583
🏁 Script executed:
find . -name "*UserVerificationService*Impl*.java" -type fRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 162
🏁 Script executed:
cat -n ./src/main/java/com/hrr/backend/domain/user/service/UserVerificationServiceImpl.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 2135
🏁 Script executed:
find . -name "UserVerificationRepository.java" -type fRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 164
🏁 Script executed:
cat -n ./src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepository.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 2708
🏁 Script executed:
find . -name "UserVerificationRepositoryCustom*" -type fRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 267
🏁 Script executed:
cat -n ./src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepositoryCustom.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 764
🏁 Script executed:
cat -n ./src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepositoryCustomImpl.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 3524
🏁 Script executed:
find . -name "SliceResponseDto.java" -type fRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 147
🏁 Script executed:
cat -n ./src/main/java/com/hrr/backend/global/response/SliceResponseDto.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 1184
인증 기록 조회 API의 페이지네이션 설계와 응답 필드 일관성을 정리하면 좋겠습니다
구현 자체는 전반적으로 잘 나왔고, 경로(/api/v1/user/challenges/history)와 Swagger 예제도 읽기 편합니다. 다만 페이지네이션 스펙과 문서의 일관성에서 두 가지 정도 정리가 필요합니다.
-
Slice vs Page 선택 재확인
- 현재 구현은
SliceResponseDto와Slice<T>를 사용하고 있어, 응답에totalCount/totalPages가 없습니다. - 이는 count 쿼리를 절약하는 성능 이점이 있지만, 프론트에서 "전체 n건, m/n 페이지" 같은 UI를 구현할 수 없습니다.
현재 상태를 PM/프론트와 재확인해서:
- A안: Slice 유지 + Swagger 문서에 "totalCount/totalPages는 제공하지 않음" 명시
- B안:
Page<T>로 변경해서totalCount/totalPages제공
- 현재 구현은
-
응답
currentPage필드의 기준 통일SliceResponseDto코드(line 21)에는slice.getNumber() + 1로 1-기반 변환이 구현되어 있고, 필드 주석도 "1부터 시작"이라고 명시되어 있습니다.- 그런데 Swagger 예제에는
currentPage: 0으로 되어 있어, 클라이언트 개발자가 혼동할 수 있습니다.
개선 방향:
- Swagger 예제를
currentPage: 1로 수정 (DTO의 변환 로직과 일치) - 또는 DTO 변환 로직을 제거하고 문서/주석에 "0-기반"으로 명시
-
비즈니스 규칙 확인 완료
VerificationStatus.COMPLETED필터 ✓verifiedAtDESC 정렬 ✓- 두 규칙 모두 리포지토리에서 올바르게 구현되어 있습니다.
🤖 Prompt for AI Agents
In src/main/java/com/hrr/backend/domain/user/controller/UserController.java
around lines 41-42, the pagination/design consistency needs clarifying: confirm
with PM/frontend whether to keep Slice (no totalCount/totalPages) or switch to
Page (provide totals); if you choose Slice, update Swagger and API docs to
explicitly state totalCount/totalPages are not provided and keep current DTO
conversion; if you choose Page, change service/repo/controller and response DTOs
to use Page<T> and include totalCount/totalPages in the response; also fix the
currentPage convention mismatch by either adjusting the SliceResponseDto to
return 0-based paging to match the Swagger example or update the Swagger example
to currentPage: 1 so it matches the DTO (and ensure comments and examples are
consistent).
| // Repository에서 인증 기록 조회 | ||
| Slice<UserVerificationResponseDto.VerificationItemDto> slice = | ||
| userVerificationRepository.findVerificationHistoryByUser(user, pageable); | ||
|
|
||
| // SliceResponseDto로 변환하여 반환 | ||
| return new SliceResponseDto<>(slice); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and examine the UserVerificationResponseDto structure
find . -type f -name "*UserVerificationResponseDto*" | head -10Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 158
🏁 Script executed:
# Search for photoUrl and imageUrl usage across the codebase
rg -n "photoUrl|imageUrl" --type java -B 2 -A 2 | head -100Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 10212
🏁 Script executed:
# Examine UserServiceImpl to see the pattern referenced in the comment
rg -n "getOngoingChallenges" --type java -A 50 | head -80Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 8365
🏁 Script executed:
# Check s3UrlUtil usage patterns
rg -n "s3UrlUtil\.toFullUrl" --type java -B 2 -A 2Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 5543
🏁 Script executed:
# Look for the actual UserVerificationServiceImpl implementation
find . -type f -name "UserVerificationServiceImpl.java" -exec cat -n {} \;Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 2135
🏁 Script executed:
# Examine UserVerificationResponseDto structure
cat -n ./src/main/java/com/hrr/backend/domain/user/dto/UserVerificationResponseDto.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 1823
🏁 Script executed:
# Also search for how VerificationItemDto is used elsewhere
rg -n "VerificationItemDto" --type java -B 2 -A 5Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 8613
🏁 Script executed:
# Examine the full projection mapping in UserVerificationRepositoryCustomImpl
sed -n '25,60p' ./src/main/java/com/hrr/backend/domain/user/repository/UserVerificationRepositoryCustomImpl.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 1917
🏁 Script executed:
# Check if photoUrl is indeed stored as S3 key in Verification entity
rg -n "photoUrl" ./src/main/java/com/hrr/backend/domain/verification/entity/Verification.java -B 2 -A 2Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 931
S3 이미지 URL 변환 누락 필요
Verification.photoUrl은 S3 키로 저장되며, 다른 Service 및 Converter에서 s3UrlUtil.toFullUrl()로 변환하는 패턴이 일관되게 적용되어 있습니다 (VerificationConverter 참고). 현재 UserVerificationServiceImpl.getVerificationHistory()에서는 이 변환이 누락되어 있습니다.
두 가지 해결 방안이 있습니다:
방안 1: Repository 레이어에서 변환 (권장)
UserVerificationRepositoryCustomImpl 라인 41의 projection에서 직접 변환:
- qVerification.photoUrl.as("imageUrl"),
+ s3UrlUtil.toFullUrl(qVerification.photoUrl).as("imageUrl"),방안 2: Service 레이어에서 변환
VerificationItemDto에 @Setter 추가 후 UserVerificationServiceImpl에서 URL 변환 (단, 현재 @Getter만 있어 setter가 없음):
+ private final S3UrlUtil s3UrlUtil;
+
// Repository에서 인증 기록 조회
Slice<UserVerificationResponseDto.VerificationItemDto> slice =
userVerificationRepository.findVerificationHistoryByUser(user, pageable);
+ slice.getContent().forEach(dto ->
+ dto.setImageUrl(s3UrlUtil.toFullUrl(dto.getImageUrl()))
+ );Committable suggestion skipped: line range outside the PR's diff.
yc3697
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Verification이라는 테이블에 관한 DTO, Repository, Service이기 때문에 불필요한 prefix인 User는 제거하는 것이 좋아보입니다. 지금은 없지만 혹시 나중에 UserVerification이라는 테이블이 생기면 혼동이 생기니깐요.
- 확인해보니 VerificationRepository에 구현된 것들이 있어서 중복된 건 삭제하고 새로 구현된 것들만 해당 클래스에 추가해주세요.
- 반환 결과에 '링크 포함 여부'도 필요합니다.
| userVerificationRepository.findVerificationHistoryByUser(user, pageable); | ||
|
|
||
| // SliceResponseDto로 변환하여 반환 | ||
| return new SliceResponseDto<>(slice); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
url의 변환 작업이 필요하기 때문에 객체 생성 방식이 아닌 빌더 방식으로 반환 데이터를 생성해주세요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/main/java/com/hrr/backend/domain/user/controller/UserController.java (1)
173-220: Swagger 예제의 currentPage 값을 수정하세요Line 209에서 Swagger 예제의
currentPage: 0으로 표시되어 있지만, 실제SliceResponseDto는slice.getNumber() + 1을 사용해 1-based 페이지 번호를 반환합니다(src/main/java/com/hrr/backend/global/response/SliceResponseDto.java line 21 참조).이는 과거 리뷰에서도 지적된 사항으로, API 문서와 실제 응답이 불일치하여 클라이언트 개발자를 혼란스럽게 만듭니다.
수정 방법:
], - "currentPage": 0, + "currentPage": 1, "size": 10,
🧹 Nitpick comments (1)
src/main/java/com/hrr/backend/domain/verification/repository/VerificationRepository.java (1)
162-175: 쿼리의 상태 값을 파라미터화하여 일관성을 개선하세요Line 170에서
'COMPLETED'를 문자열로 하드코딩했는데, 같은 파일 내 다른 메서드들(lines 24, 44, 59, 77, 90, 138, 152, 158)은 모두@Param("status") VerificationStatus status파라미터를 사용합니다.현재 구현도 동작하지만, 파라미터화하면:
- 필터링 조건 변경 시 유연성 확보
- 코드 스타일 일관성 유지
- 향후 다른 상태 조회 요구사항 대응 용이
🔎 제안하는 리팩토링
/** * 사용자의 전체 챌린지 인증 기록 조회 (페이징) */ @Query("SELECT v FROM Verification v " + "JOIN FETCH v.roundRecord rr " + "JOIN FETCH rr.userChallenge uc " + "JOIN FETCH uc.challenge c " + "WHERE uc.user = :user " + - "AND v.status = 'COMPLETED' " + + "AND v.status = :status " + "ORDER BY v.createdAt DESC") Slice<Verification> findVerificationHistoryByUser( @Param("user") User user, + @Param("status") VerificationStatus status, Pageable pageable );그리고 호출부(VerificationServiceImpl line 288)도 수정:
Slice<Verification> verificationSlice = - verificationRepository.findVerificationHistoryByUser(user, pageable); + verificationRepository.findVerificationHistoryByUser(user, VerificationStatus.COMPLETED, pageable);
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/main/java/com/hrr/backend/domain/user/controller/UserController.javasrc/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.javasrc/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.javasrc/main/java/com/hrr/backend/domain/verification/repository/VerificationRepository.javasrc/main/java/com/hrr/backend/domain/verification/service/VerificationService.javasrc/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java
🧰 Additional context used
🧬 Code graph analysis (3)
src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java (1)
src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java (1)
VerificationResponseDto(16-176)
src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java (4)
src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestDto.java (1)
Getter(9-26)src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestPhotoDto.java (1)
Getter(8-24)src/main/java/com/hrr/backend/domain/verification/dto/VerificationListResponseDto.java (3)
Getter(12-51)Getter(15-40)Getter(43-50)src/main/java/com/hrr/backend/domain/verification/dto/VerificationMyResponseDto.java (2)
Getter(16-38)Getter(43-49)
src/main/java/com/hrr/backend/domain/user/controller/UserController.java (1)
src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java (1)
VerificationResponseDto(16-176)
🔇 Additional comments (4)
src/main/java/com/hrr/backend/domain/verification/service/VerificationService.java (1)
58-65: LGTM!인터페이스 정의가 명확하고 Javadoc도 제공되어 있습니다. 반환 타입도 적절하게 선택되었습니다.
src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java (1)
270-296: LGTM!구현이 명확하고 에러 처리도 적절합니다. Repository → Converter → SliceResponseDto 흐름이 깔끔하게 구성되어 있습니다.
src/main/java/com/hrr/backend/domain/user/controller/UserController.java (2)
168-172: 엔드포인트 경로가 이슈 요구사항과 다릅니다이슈 #128에서는 엔드포인트를
GET /api/v1/user/challenges/history로 명시했지만, 구현된 경로는/api/v1/user/me/verifications/history입니다.두 경로 모두 의미적으로는 타당하지만, PM/프론트엔드와 합의된 경로를 사용해야 클라이언트 구현이 혼란스럽지 않습니다. 의도적으로 변경한 것이라면 이슈를 업데이트하시고, 그렇지 않다면 이슈 명세에 맞춰 수정하세요.
221-238: 엔드포인트 구조가 잘 설계되었습니다JWT 인증 기반으로 사용자를 식별하고, 페이지네이션 파라미터 검증(
@Min,@Max)도 적절하게 적용되어 있습니다. 서비스 레이어로의 위임도 깔끔합니다.
src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java
Show resolved
Hide resolved
| @Getter | ||
| @Builder | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| @Schema(description = "사용자 전체 인증 기록 DTO") | ||
| public static class HistoryDto { | ||
|
|
||
| @Schema(description = "인증 ID", example = "1") | ||
| private Long verificationId; | ||
|
|
||
| @Schema(description = "챌린지 ID", example = "101") | ||
| private Long challengeId; | ||
|
|
||
| @Schema(description = "챌린지 제목", example = "미라클 모닝") | ||
| private String challengeTitle; | ||
|
|
||
| @Schema(description = "인증 타입", example = "TEXT", allowableValues = {"TEXT", "CAMERA"}) | ||
| private String type; | ||
|
|
||
| @Schema(description = "인증 제목", example = "해피뉴이어! 올해 마지막 인증 올립니다") | ||
| private String title; | ||
|
|
||
| @Schema(description = "인증 내용", example = "여기엔 상세내용이 들어가유~") | ||
| private String content; | ||
|
|
||
| @Schema(description = "사진 URL (사진 인증)", example = "https://example.com/verification_image_1.jpg") | ||
| private String photoUrl; | ||
|
|
||
| @Schema(description = "글 URL (글 인증)", example = "https://blog.example.com/post/123") | ||
| private String textUrl; | ||
|
|
||
| @Schema(description = "인증 일시", example = "2025-09-18T08:00:00Z") | ||
| @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") | ||
| private LocalDateTime verifiedAt; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and check the VerificationResponseDto.java file
find . -name "VerificationResponseDto.java" -type fRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 162
🏁 Script executed:
# Check all @JsonFormat patterns in the project
rg -n '@JsonFormat.*pattern' --type java -A1 -B1Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 2109
🏁 Script executed:
# Let's check the exact content around line 173 and also look at the CreateResponseDto section
sed -n '140,175p' ./src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 1294
🏁 Script executed:
# Also check the CreateResponseDto section (around line 125-129)
sed -n '110,135p' ./src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.javaRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 1021
🏁 Script executed:
# Let's also check if there are any ZonedDateTime or Instant fields to understand the full picture
rg 'ZonedDateTime|Instant' --type java ./src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java -nRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 60
🏁 Script executed:
# Check for any timezone-related configuration in the project
rg 'TimeZone|ZoneId|spring.jackson' --type java --type properties -n | head -20Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 112
🏁 Script executed:
# Check for any application.properties or application.yml files with Jackson configuration
find . -name "application*.properties" -o -name "application*.yml" -o -name "application*.yaml" | head -5Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 242
🏁 Script executed:
# Let's also see the entire HistoryDto class to understand the context better
sed -n '141,175p' ./src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java | cat -nRepository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 1538
🏁 Script executed:
# Check the Jackson configuration in application.yml
cat ./src/main/resources/application.yml | grep -A 20 "jackson:" || echo "No jackson config found"Repository: Hrr-HabbitRoutineResult/hrr-server
Length of output: 428
verifiedAt의 JsonFormat 패턴에서 타임존 표현을 수정하세요
Line 173의 @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") 패턴이 문제입니다. verifiedAt은 LocalDateTime 타입(타임존 정보 없음)인데, 'Z' 리터럴을 추가하면 UTC를 의미하는 신호를 보냅니다.
실제 상황:
- 프로젝트 전역 Jackson 설정은
time-zone: Asia/Seoul(KST) - LocalDateTime 값이 "2025-09-18T08:00:00Z" 형태로 직렬화되지만, 실제 타임존은 KST
- API 소비자는 'Z'를 보고 UTC로 잘못 해석할 가능성이 높음
같은 DTO 파일의 CreateResponseDto (line 126-129)는 "yyyy-MM-dd HH:mm:ss" 패턴을 사용하여 일관성도 떨어집니다.
개선 방향:
- 권장: 패턴에서 'Z' 제거 →
"yyyy-MM-dd'T'HH:mm:ss"(다른 timestamp 필드와도 통일) - 또는: 타임존 정보가 필요하면 필드를
Instant또는ZonedDateTime으로 변경하고 명확한 타임존 설정
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java (1)
279-315: 메서드 시그니처가 누락된 고아 코드 블록이 파이프라인 실패 원인입니다 🚨Line 278에서
determineTargetDateTime메서드가 종료된 후, line 279부터 메서드 선언 없이 메서드 본문 코드가 바로 시작됩니다. 이는getVerificationDetail메서드를 제거하는 과정에서 메서드 시그니처만 삭제하고 본문은 남겨둔 것으로 보입니다.이로 인해 Java 컴파일러가 "illegal start of type" 오류를 발생시키며, 이것이 파이프라인 실패의 근본 원인입니다.
해결 방법:
- Line 59의 인터페이스에서
getVerificationDetail메서드 선언을 완전히 제거하거나- Line 279-315의 고아 코드 블록을 완전히 삭제해야 합니다
🔧 제안하는 수정안 (코드 블록 제거)
private LocalDateTime determineTargetDateTime(Challenge challenge, Long roundId) { LocalTime now = LocalTime.now(); LocalTime start = challenge.getVerifyStartTime(); LocalTime end = challenge.getVerifyEndTime(); boolean isVerificationTime; if (start.isBefore(end)) { isVerificationTime = !now.isBefore(start) && !now.isAfter(end); } else { isVerificationTime = !now.isBefore(start) || !now.isAfter(end); } if (isVerificationTime) { return LocalDateTime.now(); } else { return verificationRepository.findLatestVerificationTime( roundId, VerificationStatus.COMPLETED ); } } - // 차단된 게시글 접근 시 예외 발생 - if (verification.getStatus() == VerificationStatus.BLOCKED) { - throw new GlobalException(ErrorCode.ACCESS_DENIED_REPORTED_POST); - } - - RoundRecord roundRecord = verification.getRoundRecord(); - UserChallenge userChallenge = roundRecord.getUserChallenge(); - User author = userChallenge.getUser(); - - boolean isMine = currentUserId != null && author.getId().equals(currentUserId); - boolean isResolved = Boolean.TRUE.equals(verification.getIsResolved()); - boolean canEdit = isMine && !isResolved; - boolean canDelete = isMine && !isResolved; - - boolean canSelectComment = - isMine - && Boolean.TRUE.equals(verification.getIsQuestion()) - && !isResolved; - Pageable pageable = PageRequest.of(page, size); - CommentListResponseDto comments = commentService.getComments(verificationId, pageable); - - Long adoptedCommentId = comments.getComments().stream() - .filter(CommentResponseDto::isAdopted) - .map(CommentResponseDto::getCommentId) - .findFirst() - .orElse(null); - - return verificationConverter.toDetailDto( - verification, - comments, - isMine, - canEdit, - canDelete, - canSelectComment, - adoptedCommentId - ); - } @Transactional @Override
♻️ Duplicate comments (1)
src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java (1)
172-187: photoUrl과 textUrl의 S3 URL 변환이 누락되었습니다이전 리뷰에서 이미 지적된 사항입니다. 같은 클래스 내 다른 변환 메서드들(
toFeedDtoline 35-36,toResponseDtoline 79,toDetailDtoline 120)은 모두s3UrlUtil.toFullUrl()을 사용하여 S3 키를 전체 URL로 변환하는데,toHistoryDto만 line 183, 184에서 직접 getter를 호출하고 있습니다.프론트엔드가 S3 키가 아닌 전체 URL을 기대한다면 일관성을 맞춰야 합니다.
🔧 제안하는 수정안
public VerificationResponseDto.HistoryDto toHistoryDto(Verification verification) { + String photoUrl = verification.getPhotoUrl() != null + ? s3UrlUtil.toFullUrl(verification.getPhotoUrl()) + : null; + String textUrl = verification.getTextUrl() != null + ? s3UrlUtil.toFullUrl(verification.getTextUrl()) + : null; + return VerificationResponseDto.HistoryDto.builder() .verificationId(verification.getId()) .challengeId(verification.getRoundRecord().getUserChallenge().getChallenge().getId()) .challengeTitle(verification.getRoundRecord().getUserChallenge().getChallenge().getTitle()) .type(verification.getType().name()) .title(verification.getTitle()) .content(verification.getContent()) - .photoUrl(verification.getPhotoUrl()) - .textUrl(verification.getTextUrl()) + .photoUrl(photoUrl) + .textUrl(textUrl) .verifiedAt(verification.getCreatedAt()) .build(); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.javasrc/main/java/com/hrr/backend/domain/verification/repository/VerificationRepository.javasrc/main/java/com/hrr/backend/domain/verification/service/VerificationService.javasrc/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java (1)
src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java (1)
VerificationResponseDto(16-176)
🪛 GitHub Actions: CI
src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java
[error] 280-465: gradlew clean build -x test failed: Java compilation errors in VerificationServiceImpl.java (illegal start of type and cascading errors likely due to mismatched braces or misplaced code). See compiler output for details.
🔇 Additional comments (2)
src/main/java/com/hrr/backend/domain/verification/service/VerificationService.java (1)
67-74: 인증 이력 조회 메서드 시그니처가 명확합니다 ✅메서드 시그니처가 깔끔하고, 파라미터 구성도 적절합니다.
SliceResponseDto사용으로 커서 기반 페이징을 지원하는 것도 좋은 선택입니다.src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java (1)
439-465: 인증 이력 조회 로직이 잘 구현되었습니다 ✨사용자 검증, 페이징 처리, 엔티티-DTO 변환, 그리고
SliceResponseDto래핑까지 모든 단계가 명확하게 구현되어 있습니다. 코드 가독성도 좋고 주석도 적절합니다.단, line 279-315의 고아 코드 블록을 제거하면 정상적으로 컴파일될 것입니다.
src/main/java/com/hrr/backend/domain/verification/repository/VerificationRepository.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java (1)
279-315: 🚨 [빌드 실패] 메서드 시그니처 없이 본문만 남아있음Pipeline 로그에서 다수의 컴파일 에러가 발생하고 있습니다. Line 279-315 코드가 메서드 선언 없이 클래스 본문에 직접 작성되어 있습니다.
AI 요약에 따르면
getVerificationDetail메서드가 삭제되었는데, 메서드 시그니처만 삭제되고 본문은 그대로 남아있는 상태입니다.해결 방안:
- 해당 메서드가 필요한 경우: 메서드 시그니처를 복원
- 해당 메서드가 불필요한 경우: lines 279-436의 orphaned 코드 전체 삭제
#!/bin/bash # 확인: getVerificationDetail 메서드가 다른 곳에서 사용되는지 검색 rg -n "getVerificationDetail" --type java
🧹 Nitpick comments (1)
src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java (1)
157-158:type필드를VerificationPostTypeenum으로 변경 권장같은 파일의
FeedDto(line 29)와relevant_code_snippets의ListItem은VerificationPostTypeenum을 사용합니다. 일관성과 타입 안전성을 위해 enum 사용을 권장합니다.🔧 제안하는 수정안
- @Schema(description = "인증 타입", example = "TEXT", allowableValues = {"TEXT", "CAMERA"}) - private String type; + @Schema(description = "인증 타입", example = "TEXT") + private VerificationPostType type;
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.javasrc/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.javasrc/main/java/com/hrr/backend/domain/verification/repository/VerificationRepository.javasrc/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/com/hrr/backend/domain/verification/converter/VerificationConverter.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/hrr/backend/domain/verification/dto/VerificationResponseDto.java (4)
src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestDto.java (1)
Getter(9-26)src/main/java/com/hrr/backend/domain/verification/dto/VerificationRequestPhotoDto.java (1)
Getter(8-24)src/main/java/com/hrr/backend/domain/verification/dto/VerificationListResponseDto.java (3)
Getter(12-51)Getter(15-40)Getter(43-50)src/main/java/com/hrr/backend/domain/verification/dto/VerificationMyResponseDto.java (2)
Getter(16-38)Getter(43-49)
🪛 GitHub Actions: CI
src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java
[error] 280-280: illegal start of type
[error] 306-313: expected / syntax error likely due to previous brace/missing context in return verificationConverter.toDetailDto( ... ) block
[error] 319-319: class, interface, enum, or record expected
[error] 326-331: class, interface, enum, or record expected
[error] 338-343: class, interface, enum, or record expected
[error] 350-356: class, interface, enum, or record expected
[error] 361-368: class, interface, enum, or record expected
[error] 389-399: class, interface, enum, or record expected
[error] 405-468: class, interface, enum, or record expected
🔇 Additional comments (2)
src/main/java/com/hrr/backend/domain/verification/service/VerificationServiceImpl.java (1)
439-468: LGTM! 인증 기록 조회 로직이 잘 구현되었습니다 👍사용자 조회 → 페이지네이션 → Repository 조회 → DTO 변환 → 응답 반환 흐름이 명확하고, 기존 패턴(
getMyVerificationProfile등)과 일관성 있게 구현되었습니다.src/main/java/com/hrr/backend/domain/verification/repository/VerificationRepository.java (1)
174-188: 쿼리 구현이 적절합니다. BLOCKED 필터는 호출처에서 상태를 제어하는 방식으로 처리됩니다.FETCH JOIN과 파라미터화된 status 조건이 잘 구현되었습니다. 같은 파일의 일부 쿼리(
findByRoundIdAndUserChallengeId,findByRoundId,findByUserIdAndChallengeId)는v.status != 'BLOCKED'를 하드코딩하지만, 이 쿼리는 의도적으로 다른 패턴을 따릅니다.현재 구현은 파라미터화된 상태 필터를 사용하여 호출처(
VerificationServiceImpl)가 명시적으로COMPLETED상태를 전달함으로써 BLOCKED 레코드를 제외합니다. 이는 하드코딩된 필터보다 유연한 설계이며, 향후 다른 상태로 필터링할 필요가 있을 때 더 쉽게 확장할 수 있습니다.
#️⃣ 연관된 이슈
✨ 작업 내용 (Summary)
✅ 변경 사항 체크리스트
🧪 테스트 결과
📸 스크린샷
💬 리뷰 요구사항
📎 참고 자료
Summary by CodeRabbit
새로운 기능
개선
✏️ Tip: You can customize this high-level summary in your review settings.