Skip to content

refactor: 특정 유저 프로필 조회 수정#140

Merged
xoruddl merged 2 commits into
developfrom
138-특정-프로필-조회-수정
Aug 27, 2025

Hidden character warning

The head ref may contain hidden characters: "138-\ud2b9\uc815-\ud504\ub85c\ud544-\uc870\ud68c-\uc218\uc815"
Merged

refactor: 특정 유저 프로필 조회 수정#140
xoruddl merged 2 commits into
developfrom
138-특정-프로필-조회-수정

Conversation

@xoruddl
Copy link
Copy Markdown
Member

@xoruddl xoruddl commented Aug 27, 2025

📝 개요
이번 PR의 핵심 내용을 한 줄로 요약해 주세요.

💻 작업 내용
이번 PR에서 작업한 내용을 상세히 설명해 주세요.

작업 내용 1
작업 내용 2
...

✅ PR 체크리스트
PR을 보내기 전에 아래 체크리스트를 확인해 주세요.

커밋 메시지는 포맷에 맞게 작성했나요?
스스로 코드를 다시 한번 검토했나요?
관련 이슈를 연결했나요?
빌드 및 테스트가 로컬에서 성공했나요?

🔗 관련 이슈
이번 PR과 관련된 이슈 번호를 기재해 주세요. 예: Closes #138

스크린샷 (선택)
UI 변경 사항이 있다면 스크린샷을 첨부해 주세요.

Summary by CodeRabbit

  • 새로운 기능
    • 프로필 대표 이미지가 보유한 정원의 가장 이른 슬롯 아바타 이미지로 자동 선택되며, 없으면 기존 프로필 이미지를 사용합니다.
  • 버그 수정
    • 존재하지 않는 사용자에 대한 경험치 추가 시, 예외가 명확한 오류 메시지로 안전하게 처리됩니다.
  • 리팩터링
    • 현재 사용자 조회 흐름을 단순화하고 가독성과 안정성을 개선했습니다.
    • 프로필 응답 구성 로직을 정리하여 향후 확장성과 유지보수성을 향상했습니다.

@xoruddl xoruddl linked an issue Aug 27, 2025 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 27, 2025

Walkthrough

프로필 조회 로직(getUserProfile)에 프로필 이미지 결정 규칙을 추가해 정렬된 정원 아바타 이미지를 우선 사용하고, 없을 경우 기존 프로필 이미지를 사용하도록 변경. addExperience의 사용자 미존재 처리 강화 및 getCurrentUser의 instanceof 패턴 매칭 리팩터링 포함.

Changes

Cohort / File(s) Change Summary
User profile image resolution + service hardening
src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java
- addExperience: 사용자 미존재 시 UserNotFoundException 처리 추가
- getCurrentUser: 문자열 UUID 처리에 instanceof 패턴 매칭 적용
- getUserProfile: 가장 작은 slotNumber의 Garden 아바타 이미지(URL) 우선 사용, 없으면 기존 profileImageUrl 사용. Comparator/Objects 등 유틸 사용 추가.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as Client
  participant S as UserService
  participant R as UserRepository
  participant G as Gardens (from User)
  participant A as Avatar/AvatarMaster

  U->>S: getUserProfile(profileUserId, currentUserId)
  S->>R: findById(profileUserId)
  R-->>S: profileUser
  S->>R: findById(currentUserId)
  R-->>S: currentUser

  rect rgba(230,245,255,0.6)
    S->>G: profileUser.getGardens()
    Note right of S: slotNumber 오름차순 최솟값 선택
    S->>S: select min slotNumber garden
    alt 아바타 및 기본 이미지 존재
      S->>A: garden.getAvatar().getAvatarMaster().getDefaultImageUrl()
      A-->>S: avatarImageUrl
      S->>S: profileImageUrl = avatarImageUrl
    else 미존재
      S->>S: profileImageUrl = profileUser.getProfileImageUrl()
    end
  end

  S-->>U: UserProfileResponse(profileImageUrl 포함)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
특정 프로필 조회 수정 (#138)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
addExperience에서 사용자 미존재 시 예외 처리 추가 (src/.../UserService.java) 프로필 조회 수정(#138)와 직접 관련 없음. 경험치 추가 흐름의 예외 처리 개선은 범위 외로 보임.
getCurrentUser의 instanceof 패턴 매칭 리팩터링 (src/.../UserService.java) 프로필 조회 기능 수정과 무관한 코드 품질/리팩터링 변경으로 보이며 이슈 범위에 명시되지 않음.

Poem

초록 정원 사이로 귀 쫑긋 토끼가 말하네,
“첫 번째 슬롯의 빛, 새 프로필을 골라보세!” 🥕
없으면 예전 길로, 조용히 돌아오고,
길 잃은 사용자? 이제 딱 부러지게 알리지요.
코드 들풀 사이 바람 솔솔, 리뷰는 금세 끝!

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 138-특정-프로필-조회-수정

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@xoruddl xoruddl merged commit e6610af into develop Aug 27, 2025
10 of 11 checks passed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a 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 (2)
src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java (2)

121-126: anonymousUser 등 UUID 아님 문자열 처리 누락 → 500 위험

Spring Security 기본 익명 principal("anonymousUser")가 들어오면 UUID.fromString(...)에서 IllegalArgumentException이 발생하여 500으로 이어질 수 있습니다. try/catch로 감싸 401/적절한 에러로 변환하세요.

-    if (principal instanceof String uuidString) {
-      UUID userUuid = UUID.fromString(uuidString);
-      return userRepository
-          .findByUuid(userUuid)
-          .orElseThrow(() -> new IllegalArgumentException("현재 로그인한 사용자를 찾을 수 없습니다."));
-    }
+    if (principal instanceof String uuidString) {
+      try {
+        UUID userUuid = UUID.fromString(uuidString);
+        return userRepository
+            .findByUuid(userUuid)
+            .orElseThrow(() -> new IllegalArgumentException("현재 로그인한 사용자를 찾을 수 없습니다."));
+      } catch (IllegalArgumentException e) {
+        // e.g. "anonymousUser" 등 UUID가 아닌 문자열인 경우
+        throw new IllegalArgumentException("인증되지 않은 사용자입니다.");
+      }
+    }

198-203: 정원 아바타 null 시 NPE

일부 정원에 아바타가 없을 수 있습니다. 현재는 garden.getAvatar().get...로 바로 접근하여 NPE 위험이 있습니다. null-safe로 빌드하세요.

-                  HomeResponseDto.AvatarInfo avatarInfoForGarden =
-                      HomeResponseDto.AvatarInfo.builder()
-                          .avatarName(garden.getAvatar().getNickname())
-                          .avatarImageUrl(garden.getAvatar().getAvatarMaster().getDefaultImageUrl())
-                          .build();
+                  Avatar avatar = garden.getAvatar();
+                  String avatarName = (avatar != null) ? avatar.getNickname() : null;
+                  String avatarImageUrl =
+                      (avatar != null && avatar.getAvatarMaster() != null)
+                          ? avatar.getAvatarMaster().getDefaultImageUrl()
+                          : null;
+
+                  HomeResponseDto.AvatarInfo avatarInfoForGarden =
+                      HomeResponseDto.AvatarInfo.builder()
+                          .avatarName(avatarName)
+                          .avatarImageUrl(avatarImageUrl)
+                          .build();
🧹 Nitpick comments (2)
src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java (2)

45-49: Optional.get() 회피는 👍; 예외 타입/메시지 일관성 검토 제안

동일 서비스 내에서 CustomApiException(ErrorCode.NOT_FOUND)/UserNotFoundException("...")/UserNotFoundException(ErrorCode.USER_NOT_FOUND.getMessage())가 혼재합니다. 컨트롤러/글로벌 핸들러 기준으로 하나의 예외/코드 체계로 통일하는 것을 권장합니다.


175-180: 남에게 물 주기 남은 횟수 음수 가능성 클램프 필요

DB/비즈니스 규칙에 따라 당일 횟수가 3 초과일 수 있다면 음수 응답이 나옵니다. 0으로 클램프하세요.

-    Long leftWaterCountForOthers =
-        (long) (3 - todayWateringCountForOthers); // MAX_FRIEND_WATERING_PER_DAY = 3
+    Long leftWaterCountForOthers =
+        Math.max(0L, 3L - (long) todayWateringCountForOthers); // MAX_FRIEND_WATERING_PER_DAY = 3
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8849039 and af78709.

📒 Files selected for processing (1)
  • src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java (6 hunks)
🔇 Additional comments (2)
src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java (2)

5-5: 필요한 import 추가 적절

새로운 로직에서 Garden/Comparator/Objects가 실제로 사용되고 있어 적합합니다.

Also applies to: 22-22, 24-24


216-224: 동적 프로필 이미지 적용 변경 사항 LGTM

가든 기반 대표 이미지 선택 후 기존 프로필 이미지로 폴백하는 전략이 명확합니다.

Comment on lines +148 to +156
// [추가] 프로필 이미지 URL을 첫 번째 텃밭의 아바타 이미지로 설정
String profileImageUrl =
profileUser.getGardens().stream()
.min(Comparator.comparing(Garden::getSlotNumber)) // 슬롯 번호가 가장 낮은 텃밭 찾기
.map(Garden::getAvatar) // 해당 텃밭의 아바타 가져오기
.filter(Objects::nonNull) // 아바타가 null이 아닌 경우 필터링
.map(avatar -> avatar.getAvatarMaster().getDefaultImageUrl()) // 아바타의 이미지 URL 가져오기
.orElse(profileUser.getProfileImageUrl()); // 텃밭/아바타가 없으면 기존 프로필 이미지 사용

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

프로필 이미지 URL 계산 시 NPE 가능성(avatarMaster/defaultImageUrl null)

avatar.getAvatarMaster().getDefaultImageUrl() 체인이 null을 전제하지 않습니다. null-safe 필터를 추가해 안전하게 처리하세요. (slotNumber가 null일 수 있다면 Comparator.nullsLast(...)도 고려)

-            .map(avatar -> avatar.getAvatarMaster().getDefaultImageUrl()) // 아바타의 이미지 URL 가져오기
-            .orElse(profileUser.getProfileImageUrl()); // 텃밭/아바타가 없으면 기존 프로필 이미지 사용
+            .map(Avatar::getAvatarMaster) // 마스터 객체 추출
+            .filter(Objects::nonNull) // null 안전
+            .map(master -> master.getDefaultImageUrl()) // 기본 이미지 URL
+            .filter(Objects::nonNull) // null URL 방지
+            .orElse(profileUser.getProfileImageUrl()); // 텃밭/아바타/이미지 없으면 기존 프로필 이미지

추가로(선택): slotNumber null 방지를 위해

-            .min(Comparator.comparing(Garden::getSlotNumber))
+            .min(Comparator.comparing(Garden::getSlotNumber, Comparator.nullsLast(Comparator.naturalOrder())))
📝 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.

Suggested change
// [추가] 프로필 이미지 URL을 첫 번째 텃밭의 아바타 이미지로 설정
String profileImageUrl =
profileUser.getGardens().stream()
.min(Comparator.comparing(Garden::getSlotNumber)) // 슬롯 번호가 가장 낮은 텃밭 찾기
.map(Garden::getAvatar) // 해당 텃밭의 아바타 가져오기
.filter(Objects::nonNull) // 아바타가 null이 아닌 경우 필터링
.map(avatar -> avatar.getAvatarMaster().getDefaultImageUrl()) // 아바타의 이미지 URL 가져오기
.orElse(profileUser.getProfileImageUrl()); // 텃밭/아바타가 없으면 기존 프로필 이미지 사용
// [추가] 프로필 이미지 URL을 첫 번째 텃밭의 아바타 이미지로 설정
String profileImageUrl =
profileUser.getGardens().stream()
.min(Comparator.comparing(
Garden::getSlotNumber,
Comparator.nullsLast(Comparator.naturalOrder())
)) // 슬롯 번호가 가장 낮은 텃밭 찾기 (null-safe)
.map(Garden::getAvatar) // 해당 텃밭의 아바타 가져오기
.filter(Objects::nonNull) // 아바타가 null이 아닌 경우 필터링
- .map(avatar -> avatar.getAvatarMaster().getDefaultImageUrl()) // 아바타의 이미지 URL 가져오기
.map(Avatar::getAvatarMaster) // 마스터 객체 추출
.filter(Objects::nonNull) // null 안전
.map(master -> master.getDefaultImageUrl()) // 기본 이미지 URL
.filter(Objects::nonNull) // null URL 방지
.orElse(profileUser.getProfileImageUrl()); // 텃밭/아바타/이미지 없으면 기존 프로필 이미지
🤖 Prompt for AI Agents
In
src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java
around lines 148 to 156, the stream chain
avatar.getAvatarMaster().getDefaultImageUrl() can throw NPE if avatarMaster or
its defaultImageUrl is null (and Comparator may throw if slotNumber is null);
fix by adding null-safe checks: when mapping from Garden to avatar, filter out
null avatars, then filter avatar.getAvatarMaster() != null and
avatar.getAvatarMaster().getDefaultImageUrl() != null before mapping to the URL,
and use Comparator.nullsLast(Comparator.comparing(Garden::getSlotNumber)) (or
Comparator.comparing(g -> g.getSlotNumber(), Comparator.nullsLast(...))) to
handle null slotNumber values so the code safely falls back to
profileUser.getProfileImageUrl().

@xoruddl xoruddl deleted the 138-특정-프로필-조회-수정 branch August 27, 2025 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

특정 프로필 조회 수정

1 participant