Skip to content

[FEAT] 사용자 프로필 조회 및 관리 API 구현 - #47

Merged
ownue merged 27 commits into
developfrom
feat/#39-user-profile-api
Jul 27, 2026
Merged

[FEAT] 사용자 프로필 조회 및 관리 API 구현#47
ownue merged 27 commits into
developfrom
feat/#39-user-profile-api

Conversation

@p1001q

@p1001q p1001q commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

📍 개요

사용자 프로필 조회/최초 등록(온보딩)/수정 API 3개를 구현합니다.

⛓️‍💥 관련 이슈


🛠️ 작업 내용

  • GET /api/users/me/profile 프로필 조회 API 구현 (닉네임/프로필이미지/악기/숙련도/구독등급/누적 통계)
  • POST /api/users/me/profile 프로필 최초 등록(온보딩) API 구현
    — User/Student/StudentInstrument/Subscription 생성을 하나의 트랜잭션으로 처리
  • PATCH /api/users/me/profile 프로필 수정 API 구현 (닉네임/화성학 숙련도)
  • User/Student/StudentInstrument/Instrument/Subscription/UserStatistics Repository 6개 신규 구현
  • UserErrorStatus/StudentErrorStatus에 신규 에러 코드 4개 추가
    (USER_404_01, USER_409_01, USER_409_02, STUDENT_404_01)
  • 서버 기동 시 PIANO 악기 시드 데이터를 자동 등록하는 InstrumentSeeder 추가

🔥 리뷰 요청 사항

리뷰어가 중점적으로 확인해주었으면 하는 내용을 작성해주세요.

  • 프로필 조회 응답의 completedLearningCount는 동균 강님의 feat/#32-learning-status-api 병합 전까지 0으로 고정해뒀습니다(TODO 주석 남김). 병합 후 실제 집계 쿼리로 교체 예정인데, 이 방식으로 우선 진행해도 괜찮을지 확인 부탁드려요.
  • 인증된 사용자 식별은 새로 구현한 게 아니라 기존 SecurityUtil.getCurrentUserId()를 그대로 재사용했습니다.

✅ 체크리스트

  • 코드 컨벤션을 준수했습니다.
  • 불필요한 코드 및 import를 제거했습니다.
  • 예외 처리를 적용했습니다.
  • 테스트를 완료했습니다.
  • 관련 Issue를 연결했습니다.

📎 참고 사항

  • S3 기본 프로필 이미지(버킷/설정값)는 준비돼 있으나 (aws 버킷 파서 넣어놨어요)
    소셜 로그인/회원가입 시 등록되는 값이기에 아직 미구현으로
    유저에게 기본 이미지가 실제로 적용되는지는 이번 PR 범위에서 검증하지 못했습니다.
  • 프로필 최초 등록 API는 온보딩 전역 차단 필터의 화이트리스트에 포함되어야 합니다.
    해당 필터 아직 구현 전이라 지금은 별도 조치가 없습니다. 필터 구현 시 조율 필요합니다.
  • UserRepository/UserErrorStatusUSER_NOT_FOUND
    동균님의 미병합 브랜치(feat/#32-learning-status-api, PR [FEAT] 학습 도메인 api 기본 구현 #46)에도 동일하게 추가되어 있어서 그쪽이 먼저 머지되면 병합 시 사소한 정리가 필요할 수 있습니다.
    > 정리 했어용
  • 테스트는 일단 PR 올리고 추후 진행할 예정입니다 > 했어용

Summary by CodeRabbit

  • 새 기능
    • 내 프로필 조회, 최초 온보딩 등록, 프로필 수정 기능을 제공합니다.
    • 닉네임, 이론 레벨, 대표 악기, 구독 등급과 학습 통계를 프로필에서 확인할 수 있습니다.
    • 구독 등급으로 FREE, STANDARD, PRO를 지원합니다.
    • 온보딩 시 닉네임 중복 및 완료 여부를 검증합니다.
  • 오류 처리
    • 프로필·온보딩·구독·대표 악기 정보 누락 및 중복 닉네임에 대한 안내 오류를 추가했습니다.
  • 테스트
    • 프로필 API의 성공 및 주요 오류 응답을 검증했습니다.

@p1001q p1001q self-assigned this Jul 24, 2026
@p1001q p1001q added the ✨feat label Jul 24, 2026
@p1001q
p1001q marked this pull request as draft July 24, 2026 13:11
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

인증 사용자의 프로필 조회·온보딩 등록·수정 API가 추가되었습니다. 관련 DTO, 저장소, 오류 상태, 사용자 통계 조합, PIANO 악기 시딩 및 컨트롤러 테스트가 함께 구현되었습니다.

Changes

사용자 프로필 관리

Layer / File(s) Summary
프로필 계약과 저장소 기반
src/main/java/com/mr/domain/{statistics,subscriptions,user}/...
프로필 요청·응답 레코드, 구독 티어, 사용자·학생·악기·구독·통계 저장소와 관련 오류 상태를 정의합니다.
기본 악기 초기화
src/main/java/com/mr/domain/user/config/InstrumentSeeder.java
애플리케이션 시작 시 PIANO 악기를 확인하고 없으면 저장하며, PostgreSQL 중복 키 충돌만 무시합니다.
프로필 서비스 흐름
src/main/java/com/mr/domain/user/service/UserProfileService.java
현재 사용자 기준으로 프로필을 조회·등록·수정하고, 활성 구독·대표 악기·통계를 응답에 조합합니다. 닉네임 중복, 온보딩 중복, 구독 티어를 검증합니다.
프로필 HTTP 엔드포인트와 검증
src/main/java/com/mr/domain/user/controller/UserProfileController.java, src/test/java/com/mr/domain/user/controller/UserProfileControllerTest.java
GET, POST, PATCH 프로필 엔드포인트와 성공 응답 및 주요 404·409 오류 응답 테스트를 추가합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant UserProfileController
  participant UserProfileService
  participant UserRepository
  participant StudentRepository
  participant SubscriptionRepository
  Client->>UserProfileController: GET/POST/PATCH /api/users/me/profile
  UserProfileController->>UserProfileService: 프로필 요청 DTO 전달
  UserProfileService->>UserRepository: 사용자 조회·닉네임 중복 확인
  UserProfileService->>StudentRepository: 학생 조회 또는 생성
  UserProfileService->>SubscriptionRepository: 활성 구독 조회 또는 생성
  UserProfileService-->>UserProfileController: 프로필 응답 DTO 반환
  UserProfileController-->>Client: ApiResponse 반환
Loading

Possibly related PRs

  • Musereview/BE#7: UserStatisticsRepository가 사용하는 통계 엔티티와 직접 연결됩니다.
  • Musereview/BE#18: 이번 변경에서 확장한 사용자·학생·악기 오류 enum과 연결됩니다.
  • Musereview/BE#45: UserErrorStatus enum을 함께 확장합니다.

Poem

피아노 씨앗 톡, 시작을 알리고
프로필 길은 DTO로 열리고
닉네임은 중복을 피해 춤추며
통계는 조용히 숫자를 세네
GET, POST, PATCH—온보딩 완성! 🎹

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning 조회·등록·수정 API와 DTO/리포지토리는 맞지만, 온보딩 차단 필터 화이트리스트 반영은 보이지 않아 #39의 필수 조건이 빠졌습니다. 프로필 최초 등록 엔드포인트를 온보딩 차단 필터 화이트리스트에 추가하고, User.updateNickname() 재사용 여부를 코드로 확인해 보완하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed 프로필 API, 관련 저장소/DTO/예외, PIANO 시드, 테스트로만 구성되어 있어 눈에 띄는 범위 이탈은 없습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 사용자 프로필 조회·등록·수정 API 구현이라는 핵심 변경을 정확히 요약합니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#39-user-profile-api

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@p1001q
p1001q marked this pull request as ready for review July 24, 2026 13:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 274468e and 5d3a2c1.

📒 Files selected for processing (14)
  • src/main/java/com/mr/domain/statistics/repository/UserStatisticsRepository.java
  • src/main/java/com/mr/domain/subscriptions/entity/enums/SubscriptionTier.java
  • src/main/java/com/mr/domain/subscriptions/repository/SubscriptionRepository.java
  • src/main/java/com/mr/domain/user/config/InstrumentSeeder.java
  • src/main/java/com/mr/domain/user/controller/UserProfileController.java
  • src/main/java/com/mr/domain/user/dto/UserProfileRequestDTO.java
  • src/main/java/com/mr/domain/user/dto/UserProfileResponseDTO.java
  • src/main/java/com/mr/domain/user/exception/StudentErrorStatus.java
  • src/main/java/com/mr/domain/user/exception/UserErrorStatus.java
  • src/main/java/com/mr/domain/user/repository/InstrumentRepository.java
  • src/main/java/com/mr/domain/user/repository/StudentInstrumentRepository.java
  • src/main/java/com/mr/domain/user/repository/StudentRepository.java
  • src/main/java/com/mr/domain/user/repository/UserRepository.java
  • src/main/java/com/mr/domain/user/service/UserProfileService.java

Comment thread src/main/java/com/mr/domain/subscriptions/repository/SubscriptionRepository.java Outdated
Comment thread src/main/java/com/mr/domain/user/config/InstrumentSeeder.java Outdated
Comment thread src/main/java/com/mr/domain/user/repository/UserRepository.java Outdated
Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java
Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java Outdated
Comment thread src/main/java/com/mr/domain/user/repository/UserRepository.java Outdated
Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5d3a2c1 and 8403213.

📒 Files selected for processing (4)
  • src/main/java/com/mr/domain/subscriptions/repository/SubscriptionRepository.java
  • src/main/java/com/mr/domain/user/config/InstrumentSeeder.java
  • src/main/java/com/mr/domain/user/service/UserProfileService.java
  • src/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

Comment thread src/main/java/com/mr/domain/user/config/InstrumentSeeder.java
Comment thread src/main/java/com/mr/domain/user/dto/req/UserProfileRequestDTO.java

@on1yoneprivate on1yoneprivate left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코멘트 확인 부탁드립니다~

Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java Outdated
Comment thread src/main/java/com/mr/domain/user/dto/req/UserProfileRequestDTO.java
@rkdehdrbs7885-oss
rkdehdrbs7885-oss self-requested a review July 25, 2026 14:29

@rkdehdrbs7885-oss rkdehdrbs7885-oss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다!

@ownue ownue left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코멘트 확인 부탁드립니다~~

Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java Outdated
Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java
Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java
Comment thread src/main/java/com/mr/domain/user/service/UserProfileService.java Outdated
Comment thread src/main/java/com/mr/domain/user/config/InstrumentSeeder.java
@p1001q
p1001q requested a review from ownue July 26, 2026 16:47

@ownue ownue left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Feature - 사용자 프로필 조회 및 관리 API 구현

4 participants