[FEAT] 사용자 프로필 조회 및 관리 API 구현#47
Conversation
|
Warning Review limit reached
Next review available in: 25 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthrough사용자 프로필 조회·등록·수정 API와 요청·응답 DTO가 추가되었습니다. 사용자·학생·악기·구독·통계 저장소와 오류 상태가 확장되었으며, 애플리케이션 시작 시 피아노 시딩과 컨트롤러 테스트가 추가되었습니다. Changes사용자 프로필 관리
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant UserProfileController
participant UserProfileService
participant Repositories
Client->>UserProfileController: 프로필 API 요청
UserProfileController->>UserProfileService: 요청 DTO 전달
UserProfileService->>Repositories: 사용자·학생·악기·구독·통계 조회/저장
Repositories-->>UserProfileService: 도메인 데이터 반환
UserProfileService-->>UserProfileController: 응답 DTO 반환
UserProfileController-->>Client: ApiResponse 반환
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Actionable comments posted: 5
🧹 Nitpick comments (1)
src/main/java/com/mr/domain/user/service/UserProfileService.java (1)
46-49: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win미결정 예외는
GeneralException+BaseCode패턴으로 전환해 주세요.
getUser,getStudent,ensureNicknameNotTaken는 잘못된 요청 상태를ERROR_XXX코드와 HTTP 상태 기반으로 처리하므로, 대표 악기/구독 정보/PIANO 시드 미존재 역시 동일하게 처리해 주세요.IllegalStateException은 전역Exception예외 핸들러로 내려가 일관되지 않은 500 응답이 됩니다.UserErrorStatus또는StudentErrorStatus에 각 사례에 맞는 신규 코드를 추가하고getMyProfile()/onboarding 경로의orElseThrow에서new GeneralException(...)로 연결해 주세요. (예: 대표 악기 미존재 →STUDENT_NOT_FOUND를 재사용하거나 별도 코드를 추가하는 것이 적절합니다.)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/mr/domain/user/service/UserProfileService.java` around lines 46 - 49, Replace the IllegalStateException fallbacks in UserProfileService.getMyProfile() and the onboarding path at UserProfileService lines 46-49, 51-53, and 79-80 with GeneralException using the appropriate UserErrorStatus or StudentErrorStatus BaseCode. Add dedicated error codes where needed, or reuse STUDENT_NOT_FOUND for missing representative instrument, subscription, and PIANO seed cases, while preserving each existing orElseThrow condition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/mr/domain/subscriptions/repository/SubscriptionRepository.java`:
- Line 10: Update SubscriptionRepository.findFirstByUserOrderByStartDateDesc to
filter for currently active subscriptions according to Subscription.isActive(),
rather than returning expired history; then update
UserProfileService.getMyProfile() to call the new active-subscription query
while preserving the existing latest-start-date ordering.
In `@src/main/java/com/mr/domain/user/config/InstrumentSeeder.java`:
- Around line 23-24: Update InstrumentSeeder’s PIANO seeding flow to be safe
across concurrent application instances: enforce a database-level unique
constraint on Instrument.code and use an idempotent duplicate-key-ignore or
database-native upsert approach instead of the current findByCode/orElseGet
check-then-act pattern. Preserve successful retrieval or creation of the single
PIANO instrument.
In `@src/main/java/com/mr/domain/user/repository/UserRepository.java`:
- Around line 6-10: Merge the duplicate UserRepository definitions before
integration by removing the empty counterpart and retaining a single repository
interface with the existsByNicknameAndUserIdNot method. Ensure the resulting
UserRepository keeps the JpaRepository<User, Long> contract and avoids duplicate
FQCN definitions.
In `@src/main/java/com/mr/domain/user/service/UserProfileService.java`:
- Around line 74-75: In UserProfileService, update both registerProfile (lines
74-75) and updateProfile (lines 104-106) to validate nickname availability with
ensureNicknameNotTaken before calling user.updateNickname(request.nickname()).
Preserve the existing NICKNAME_DUPLICATED business exception behavior and apply
the same ordering at both sites.
- Around line 65-96: Update registerProfile to finalize onboarding only after
Student, primary StudentInstrument, and Subscription creation succeeds: if
onboarding state is stored as a User flag, call user.completeOnboarding() before
the transaction completes; otherwise check for an existing Student by user and
throw ONBOARDING_ALREADY_COMPLETED before creating duplicates. Preserve the
existing initial onboarding guard and ensure the successful path synchronizes
the User onboarding state with the Student relationship.
---
Nitpick comments:
In `@src/main/java/com/mr/domain/user/service/UserProfileService.java`:
- Around line 46-49: Replace the IllegalStateException fallbacks in
UserProfileService.getMyProfile() and the onboarding path at UserProfileService
lines 46-49, 51-53, and 79-80 with GeneralException using the appropriate
UserErrorStatus or StudentErrorStatus BaseCode. Add dedicated error codes where
needed, or reuse STUDENT_NOT_FOUND for missing representative instrument,
subscription, and PIANO seed cases, while preserving each existing orElseThrow
condition.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 05a8619f-ae4d-440c-ae53-a2425110257c
📒 Files selected for processing (14)
src/main/java/com/mr/domain/statistics/repository/UserStatisticsRepository.javasrc/main/java/com/mr/domain/subscriptions/entity/enums/SubscriptionTier.javasrc/main/java/com/mr/domain/subscriptions/repository/SubscriptionRepository.javasrc/main/java/com/mr/domain/user/config/InstrumentSeeder.javasrc/main/java/com/mr/domain/user/controller/UserProfileController.javasrc/main/java/com/mr/domain/user/dto/UserProfileRequestDTO.javasrc/main/java/com/mr/domain/user/dto/UserProfileResponseDTO.javasrc/main/java/com/mr/domain/user/exception/StudentErrorStatus.javasrc/main/java/com/mr/domain/user/exception/UserErrorStatus.javasrc/main/java/com/mr/domain/user/repository/InstrumentRepository.javasrc/main/java/com/mr/domain/user/repository/StudentInstrumentRepository.javasrc/main/java/com/mr/domain/user/repository/StudentRepository.javasrc/main/java/com/mr/domain/user/repository/UserRepository.javasrc/main/java/com/mr/domain/user/service/UserProfileService.java
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/java/com/mr/domain/user/config/InstrumentSeeder.java`:
- Around line 26-36: InstrumentSeeder의 중복 시드 처리에서
DataIntegrityViolationException을 save 호출 내부에만 의존하지 말고, 실제 DB flush/commit 경계에서
예외가 발생하도록 트랜잭션 경계를 분리하세요. 시드 저장 작업을 별도 트랜잭션 메서드로 이동하거나 호출해 saveAndFlush()로 즉시
반영하고, 바깥 흐름에서 해당 예외를 잡아 기존처럼 경쟁 인스턴스의 중복 삽입을 무시하도록 수정하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 57c7a686-182b-4504-9482-aa822af6daf2
📒 Files selected for processing (4)
src/main/java/com/mr/domain/subscriptions/repository/SubscriptionRepository.javasrc/main/java/com/mr/domain/user/config/InstrumentSeeder.javasrc/main/java/com/mr/domain/user/service/UserProfileService.javasrc/test/java/com/mr/domain/user/controller/UserProfileControllerTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/com/mr/domain/user/service/UserProfileService.java
|
|
||
| import com.mr.domain.user.entity.enums.TheoryLevel; | ||
|
|
||
| public class UserProfileRequestDTO { |
There was a problem hiding this comment.
의도적으로 DTO에 검증 어노테이션을 넣지 않았습니다.
닉네임/숙련도 형식 오류는 명세서 기준으로 USER_400_01/USER_400_02/STUDENT_400_02처럼 도메인별 코드로 응답해야 하는데, @Valid를 걸면 MethodArgumentNotValidException이 먼저 잡혀서 일괄 COMMON_400_01로만 응답이 나가 명세와 어긋나게 됩니다. 이미 User.updateNickname()/Student.create() 엔티티 자체에서 같은 검증(형식/필수값)을 하고 있어서, DTO에 중복으로 검증을 걸면 오히려 같은 종류의 오류가 케이스에 따라 COMMON_400_01과 도메인 코드로 서로 다르게 나가는 일관성 문제가 생길 수 있어 이번엔 반영하지 않았습니다...!
| @PostMapping | ||
| @ResponseStatus(HttpStatus.CREATED) | ||
| public ApiResponse<UserProfileResponseDTO.OnboardingResponse> registerProfile( | ||
| @RequestBody UserProfileRequestDTO.OnboardingRequest request |
There was a problem hiding this comment.
P1: DTO에 검증 로직을 추가할 경우 @RequestBody 뒤에 @Valid 어노테이션도 추가해야 합니다~
@Valid 어노테이션이 없을 경우에는 DTO에 추가한 검증 로직이 동작하지 않을 수 있어요! 서비스 로직 진입 전에 400 에러로 걸러냅시다
There was a problem hiding this comment.
의도적으로 DTO에 검증 어노테이션을 넣지 않았습니다.
닉네임/숙련도 형식 오류는 명세서 기준으로 USER_400_01/USER_400_02/STUDENT_400_02처럼 도메인별 코드로 응답해야 하는데, @Valid를 걸면 MethodArgumentNotValidException이 먼저 잡혀서 일괄 COMMON_400_01로만 응답이 나가 명세와 어긋나게 됩니다. 이미 User.updateNickname()/Student.create() 엔티티 자체에서 같은 검증(형식/필수값)을 하고 있어서, DTO에 중복으로 검증을 걸면 오히려 같은 종류의 오류가 케이스에 따라 COMMON_400_01과 도메인 코드로 서로 다르게 나가는 일관성 문제가 생길 수 있어 이번엔 반영하지 않았습니다...!
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/main/java/com/mr/domain/user/config/InstrumentSeeder.java (1)
32-38: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win중복 키 외의 무결성 오류는 그대로 전파해 주세요.
DataIntegrityViolationException은 unique 충돌뿐 아니라 Hibernate에서 감싸는 무결성 오류 일반을 포함할 수 있습니다. 현재는Instrument의codeunique 제약 전용 예외까지 “이미 생성됨”으로 삼키기 때문에, 다른 저장 소스 무결성 사고가 발생하면 애플리케이션은 정상 기동되지만 온보딩에서INSTRUMENT_NOT_SEEDED가 발생합니다. 중복(unique) 제약 원인일 때만 무시하고, 그 외 무결성 오류는 다시 던지도록 수비적인isDuplicateKeyViolation(e)판별 패턴을 함께 사용하세요. PostgreSQL의 unique violation은SQLState == "23505"기준으로 필터링할 수 있고, Spring Framework의 exception translation 참고도 도움이 됩니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/mr/domain/user/config/InstrumentSeeder.java` around lines 32 - 38, Update the exception handling around InstrumentSeeder’s instrumentRepository.saveAndFlush call to ignore only duplicate-key violations identified by an isDuplicateKeyViolation(e) predicate, including PostgreSQL SQLState "23505". Re-throw DataIntegrityViolationException for all other integrity failures, while preserving the existing informational log for genuine duplicate seed attempts.src/main/java/com/mr/domain/user/service/UserProfileService.java (1)
86-92: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win온보딩 중복 제약은 flush 시점에 감지해야 합니다.
StudentRepository가JpaRepository를 구현하고,Student.user_id는@JoinColumn(..., unique = true)로 지정되어 있습니다.save()는 persistence context에 저장 상태를 등록하지만 INSERT 실행은 대부분 commit 직전 flush 시점에 수행되므로, 동시 요청으로 인한DataIntegrityViolationException이try외부에서 발생할 수 있습니다. 이때 409 변환이 아니라 일반 서버 오류로 처리됩니다.
save()대신saveAndFlush()를 사용해 주석을 유지하면서 constraint violation을 해당 블록 내부로 당겨오세요. 추가로 동시 온보딩 요청에 대한 테스트도 함께 추가하면 안전합니다. 관련 개념: Spring Data JPA flush/commit, Hibernate write-behind flushing.수정 예시
- student = studentRepository.save(Student.create(user, request.skillLevel())); + student = studentRepository.saveAndFlush( + Student.create(user, request.skillLevel()));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/mr/domain/user/service/UserProfileService.java` around lines 86 - 92, Update the onboarding persistence in UserProfileService to use StudentRepository.saveAndFlush() instead of save() within the existing try/catch, ensuring the unique-constraint DataIntegrityViolationException is raised and translated to ONBOARDING_ALREADY_COMPLETED inside that block. Add coverage for concurrent onboarding requests if the existing test structure supports it.
🧹 Nitpick comments (1)
src/main/java/com/mr/domain/user/config/InstrumentSeeder.java (1)
22-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win주석을 실제 호출인
saveAndFlush()에 맞춰 주세요.현재 구현은
saveAndFlush()를 사용하지만 주석은save()의 독립 트랜잭션을 설명하고 있어, 이후 트랜잭션 동작을 잘못 이해하게 만들 수 있습니다. Spring Data JPA의 실제 호출 경계에 맞게 주석을 갱신해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/mr/domain/user/config/InstrumentSeeder.java` around lines 22 - 25, Update the transaction-behavior comment in InstrumentSeeder.run() to refer to the actual saveAndFlush() calls rather than save(), while preserving its explanation that run() is not transactional and each persistence call handles duplicate failures independently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/main/java/com/mr/domain/user/config/InstrumentSeeder.java`:
- Around line 32-38: Update the exception handling around InstrumentSeeder’s
instrumentRepository.saveAndFlush call to ignore only duplicate-key violations
identified by an isDuplicateKeyViolation(e) predicate, including PostgreSQL
SQLState "23505". Re-throw DataIntegrityViolationException for all other
integrity failures, while preserving the existing informational log for genuine
duplicate seed attempts.
In `@src/main/java/com/mr/domain/user/service/UserProfileService.java`:
- Around line 86-92: Update the onboarding persistence in UserProfileService to
use StudentRepository.saveAndFlush() instead of save() within the existing
try/catch, ensuring the unique-constraint DataIntegrityViolationException is
raised and translated to ONBOARDING_ALREADY_COMPLETED inside that block. Add
coverage for concurrent onboarding requests if the existing test structure
supports it.
---
Nitpick comments:
In `@src/main/java/com/mr/domain/user/config/InstrumentSeeder.java`:
- Around line 22-25: Update the transaction-behavior comment in
InstrumentSeeder.run() to refer to the actual saveAndFlush() calls rather than
save(), while preserving its explanation that run() is not transactional and
each persistence call handles duplicate failures independently.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 2fa2941a-9a7e-40eb-89fc-c979824693e0
📒 Files selected for processing (7)
src/main/java/com/mr/domain/subscriptions/exception/SubscriptionErrorStatus.javasrc/main/java/com/mr/domain/user/config/InstrumentSeeder.javasrc/main/java/com/mr/domain/user/dto/UserProfileRequestDTO.javasrc/main/java/com/mr/domain/user/exception/InstrumentErrorStatus.javasrc/main/java/com/mr/domain/user/exception/StudentInstrumentErrorStatus.javasrc/main/java/com/mr/domain/user/service/UserProfileService.javasrc/test/java/com/mr/domain/user/controller/UserProfileControllerTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/com/mr/domain/user/dto/UserProfileRequestDTO.java
- src/test/java/com/mr/domain/user/controller/UserProfileControllerTest.java
|
|
||
| Student student; | ||
| try { | ||
| student = studentRepository.save(Student.create(user, request.skillLevel())); |
There was a problem hiding this comment.
P2: 동시 온보딩 요청에 대한 DataIntegrityViolationException을 studentRepository.save() 주변에서 처리하고 있는데, JPA에서는 실제 INSERT나 UNIQUE 제약 검증이 flush 또는 트랜잭션 커밋 시점에 발생할 수 있어 현재 catch 문에서 잡히지 않을 가능성이 있어요!
이럴 경우에 중복 요청이 의도한 ONBOARDING_ALREADY_COMPLETED 409가 아니라 500으로 응답될 수 있습니당.
→ saveAndFlush()를 사용해 이 위치에서 예외를 발생시키거나, DB 무결성 예외를 전역에서 도메인 예외로 변환하는 방안도 한 번 고려해 주세요!
There was a problem hiding this comment.
Student도 Instrument처럼 GenerationType.IDENTITY라 save() 시점에 이미 즉시 INSERT가 나가긴 합니다!
명시적으로 saveAndFlush()로 바꿔서 의도를 분명히 했습니다!
| .orElseThrow(() -> new GeneralException(InstrumentErrorStatus.INSTRUMENT_NOT_SEEDED)); | ||
| studentInstrumentRepository.save(StudentInstrument.createPrimary(student, piano)); | ||
|
|
||
| String tier = validateSubscriptionTier(request.subscriptionTier()); |
There was a problem hiding this comment.
P2: subscriptionTier 검증이 Student와 StudentInstrument 생성 이후에 수행되고 있어, 잘못된 등급이 전달될 경우에도 여러 저장 로직을 거친 뒤 예외가 발생하는 것 같습니다.
트랜잭션 롤백으로 데이터는 남지 않겠지만, fail-fast 관점에서 구독 등급 검증을 메서드 초반으로 이동하면 좋을 것 같아요~
There was a problem hiding this comment.
피드백 감사합니다!
fail-fast 관점에서 subscriptionTier 검증을 온보딩 완료 체크 바로 다음, 닉네임/Student 생성보다 앞으로 옮겼습니다.
📍 개요
⛓️💥 관련 이슈
🛠️ 작업 내용
GET /api/users/me/profile프로필 조회 API 구현 (닉네임/프로필이미지/악기/숙련도/구독등급/누적 통계)POST /api/users/me/profile프로필 최초 등록(온보딩) API 구현— User/Student/StudentInstrument/Subscription 생성을 하나의 트랜잭션으로 처리
PATCH /api/users/me/profile프로필 수정 API 구현 (닉네임/화성학 숙련도)UserErrorStatus/StudentErrorStatus에 신규 에러 코드 4개 추가(
USER_404_01,USER_409_01,USER_409_02,STUDENT_404_01)InstrumentSeeder추가🔥 리뷰 요청 사항
completedLearningCount는 동균 강님의feat/#32-learning-status-api병합 전까지0으로 고정해뒀습니다(TODO 주석 남김). 병합 후 실제 집계 쿼리로 교체 예정인데, 이 방식으로 우선 진행해도 괜찮을지 확인 부탁드려요.SecurityUtil.getCurrentUserId()를 그대로 재사용했습니다.✅ 체크리스트
📎 참고 사항
소셜 로그인/회원가입 시 등록되는 값이기에 아직 미구현으로
유저에게 기본 이미지가 실제로 적용되는지는 이번 PR 범위에서 검증하지 못했습니다.
해당 필터 아직 구현 전이라 지금은 별도 조치가 없습니다. 필터 구현 시 조율 필요합니다.
UserRepository/UserErrorStatus의USER_NOT_FOUND는동균님의 미병합 브랜치(
feat/#32-learning-status-api, PR [FEAT] 학습 도메인 api 기본 구현 #46)에도 동일하게 추가되어 있어서 그쪽이 먼저 머지되면 병합 시 사소한 정리가 필요할 수 있습니다.테스트는 일단 PR 올리고 추후 진행할 예정입니다> 했어용Summary by CodeRabbit
Summary by CodeRabbit
/api/users/me/profile에서 내 프로필 조회/등록/수정을 추가했습니다.