Skip to content

Conversation

@xoruddl
Copy link
Member

@xoruddl xoruddl commented Sep 5, 2025

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

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

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

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

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

🔗 관련 이슈

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

Summary by CodeRabbit

  • Refactor
    • 물주기 기준 시각을 정오(한국 표준시)로 표준화하여 물주기/활동 상태 표시의 일관성을 개선했습니다. 시간대에 따른 표시 차이가 줄어 더 예측 가능한 경험을 제공합니다.
    • 사용자 프로필 조회가 인증된 사용자 컨텍스트를 활용하도록 정리되어, 로그인된 사용자 기준의 프로필 접근 동작이 더 명확하고 안정적으로 동작합니다.

@coderabbitai
Copy link

coderabbitai bot commented Sep 5, 2025

Walkthrough

인증 사용자 기반으로 UserController의 프로필 조회 흐름을 수정하고, UserService의 급수일 계산 로직을 전역 유틸 TimeUtil로 이관했습니다. Asia/Seoul 기준 정오 시작 규칙을 TimeUtil에 추가했으며, 불필요한 예외/헬퍼 메서드와 관련 import를 정리했습니다.

Changes

Cohort / File(s) Change Summary
User profile controller flow
src/main/java/com/example/cp_main_be/domain/member/user/presentation/UserController.java
getUserInfo@AuthenticationPrincipal User를 추가로 받아 현재 사용자 ID와 targetUserIduserService.getUserProfile에 전달. 리포지토리 조회 및 UserNotFoundException 의존 제거. 메서드 시그니처 변경.
UserService time calc refactor
src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java
급수일 계산을 로컬 메서드에서 TimeUtil.getStartOfCurrentWateringDay() 사용으로 변경. 불필요한 ZoneId import 및 개인 헬퍼 제거. 주석 보강(동작 변화 없음). 공개 API 변경 없음.
New time utility
src/main/java/com/example/cp_main_be/global/util/TimeUtil.java
신규 유틸 클래스 추가. Asia/Seoul 기준 정오(12:00)를 급수일 시작점으로 계산하는 public static LocalDateTime getStartOfCurrentWateringDay() 제공. 싱글톤 불가(final 클래스, private 생성자).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Controller as UserController
  participant Security as SecurityContext
  participant Service as UserService
  participant Time as TimeUtil

  Client->>Controller: GET /users/{userId}
  Controller->>Security: 인증 사용자 조회
  Security-->>Controller: Authenticated User
  Controller->>Service: getUserProfile(currentUserId, targetUserId)
  Service->>Time: getStartOfCurrentWateringDay()
  Time-->>Service: LocalDateTime(startAtNoonKST)
  Service-->>Controller: UserProfileResponse
  Controller-->>Client: 200 OK (ApiResponse)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

새벽 정오 종이 울리네, 토끼는 시계를 본다 ⏰
오늘의 물길은 KST 정오에서 시작된다
컨트롤러는 나를 알고, 프로필로 길을 튼다
서비스는 시간을 묻고, 유틸은 답해준다
깡총—리팩터 한 발짝, 버그는 뒷걸음친다 🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch leetk/refactor

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 or @coderabbit 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 e4aea9c into develop Sep 5, 2025
5 of 6 checks passed
@xoruddl xoruddl deleted the leetk/refactor branch September 5, 2025 00:46
Copy link

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/main/java/com/example/cp_main_be/domain/member/user/presentation/UserController.java (1)

45-49: 경로 변수 바인딩 오류(@RequestParam@PathVariable).

매핑은 /me/{avatarId}인데 파라미터는 @RequestParam("avatarId")로 선언되어 있어 400 바인딩 오류가 발생합니다. @PathVariable로 수정하세요.

-  public ResponseEntity<ApiResponse<Void>> updateAvatar(
-      @AuthenticationPrincipal User user,
-      @RequestBody @Valid AvatarChangeRequest request,
-      @RequestParam("avatarId") Long avatarId) {
+  public ResponseEntity<ApiResponse<Void>> updateAvatar(
+      @AuthenticationPrincipal User user,
+      @RequestBody @Valid AvatarChangeRequest request,
+      @PathVariable("avatarId") Long avatarId) {
src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java (3)

73-81: 영속 엔티티가 아닌 파라미터 객체를 save 하고 있습니다.

managedUser를 갱신해놓고 userRepository.save(user)를 호출합니다. 영속 상태 보장 및 변경감지 일관성을 위해 managedUser를 저장하세요.

-    managedUser.updateProfile(newNickname, null);
-    userRepository.save(user);
+    managedUser.updateProfile(newNickname, null);
+    userRepository.save(managedUser);

51-71: 권한 체크 부재: 타인의 아바타 수정 가능성.

avatarId만으로 조회 후 수정하면 소유자 검증이 없어 타인의 아바타가 수정될 수 있습니다. 소유자 제약으로 조회하거나, 조회 후 소유자 일치 여부를 검증하세요.

두 가지 중 택1:

  • 옵션 A(권장): 리포지토리 제약 조회 메서드 사용
Avatar avatar = avatarRepository.findByIdAndUserId(avatarId, user.getId())
    .orElseThrow(() -> new CustomApiException(ErrorCode.AVATAR_NOT_FOUND));
  • 옵션 B: 조회 후 소유자 검증(도메인 모델에 맞춰 필드/메서드명 조정)
 Avatar avatar =
     avatarRepository
         .findById(avatarId)
         .orElseThrow(() -> new CustomApiException(ErrorCode.AVATAR_NOT_FOUND));

+ // TODO: 프로젝트의 ErrorCode(예: FORBIDDEN/ACCESS_DENIED)에 맞춰 교체
+ if (!avatar.belongsTo(user)) {
+   throw new CustomApiException(ErrorCode.FORBIDDEN, "본인 아바타만 수정할 수 있습니다.");
+ }

247-253: NPE 위험: avatar가 null일 수 있는 케이스를 빌더에서 직접 역참조합니다.

avatarName, avatarImageUrl에서 garden.getAvatar()를 바로 역참조하고 있어 NPE가 납니다. 로컬 변수로 안전하게 처리하세요.

-                  HomeResponseDto.AvatarInfo avatarInfoForGarden =
-                      HomeResponseDto.AvatarInfo.builder()
-                          // 아바타가 없는 텃밭이 있을 수 있는 예외 케이스를 방어합니다.
-                          .avatarId(garden.getAvatar() != null ? garden.getAvatar().getId() : null)
-                          .avatarName(garden.getAvatar().getNickname())
-                          .avatarImageUrl(garden.getAvatar().getAvatarMaster().getDefaultImageUrl())
-                          .build();
+                  var avatar = garden.getAvatar();
+                  String avatarName =
+                      (avatar != null) ? avatar.getNickname() : null;
+                  String avatarImageUrl =
+                      (avatar == null)
+                          ? null
+                          : (avatar.getImageUrl() != null && !avatar.getImageUrl().isBlank()
+                                ? avatar.getImageUrl()
+                                : avatar.getAvatarMaster().getDefaultImageUrl());
+
+                  HomeResponseDto.AvatarInfo avatarInfoForGarden =
+                      HomeResponseDto.AvatarInfo.builder()
+                          .avatarId(avatar != null ? avatar.getId() : null)
+                          .avatarName(avatarName)
+                          .avatarImageUrl(avatarImageUrl)
+                          .build();
🧹 Nitpick comments (3)
src/main/java/com/example/cp_main_be/global/util/TimeUtil.java (1)

20-29: 정오 경계 처리는 적절합니다. 테스트 용이성과 가독성 개선 제안(Clock 주입, LocalTime.NOON 사용).

동작은 맞습니다. 다만 테스트(경계 시각, 11:59:59/12:00:00) 용이성을 위해 Clock 주입 오버로드를 추가하고, NOON 상수를 사용하면 의도가 더 분명합니다.

변경 제안(diff: 기존 메서드 본문만 최소 수정):

   public static LocalDateTime getStartOfCurrentWateringDay() {
-    LocalDateTime now = LocalDateTime.now(KST);
-    LocalDateTime todayNoon = now.toLocalDate().atTime(12, 0);
-
-    if (now.isBefore(todayNoon)) {
-      return todayNoon.minusDays(1); // 아직 정오가 안 지났으면, 어제 정오가 시작 시간
-    } else {
-      return todayNoon; // 정오가 지났으면, 오늘 정오가 시작 시간
-    }
+    return getStartOfCurrentWateringDay(java.time.Clock.system(KST));
   }

오버로드 추가(선택):

public static LocalDateTime getStartOfCurrentWateringDay(java.time.Clock clock) {
  var now = java.time.ZonedDateTime.now(clock);
  var todayNoon = now.toLocalDate().atTime(java.time.LocalTime.NOON).atZone(KST);
  return now.isBefore(todayNoon) ? todayNoon.minusDays(1).toLocalDateTime()
                                 : todayNoon.toLocalDateTime();
}
src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java (2)

167-173: 읽기 전용 트랜잭션으로 명시해 불필요한 쓰기 플러시를 방지하세요.

getUserProfile는 조회 전용이므로 @Transactional(readOnly = true) 부여를 권장합니다.

-  public UserProfileResponse getUserProfile(Long currentUserId, Long profileUserId) {
+  @Transactional(readOnly = true)
+  public UserProfileResponse getUserProfile(Long currentUserId, Long profileUserId) {

Also applies to: 213-214


242-246: 주석과 변수 의미가 불일치합니다(가독성).

주석은 “아직 안 줬고 횟수 남으면 true”를 설명하지만, 바로 아래 alreadyWateredByMe는 반대 의미입니다. 혼동 방지를 위해 주석을 isWateringAbleByMe에 맞춰 이동/수정하세요.

📜 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 2911465 and f1698c7.

📒 Files selected for processing (3)
  • src/main/java/com/example/cp_main_be/domain/member/user/presentation/UserController.java (1 hunks)
  • src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java (4 hunks)
  • src/main/java/com/example/cp_main_be/global/util/TimeUtil.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java (1)
src/main/java/com/example/cp_main_be/global/util/TimeUtil.java (1)
  • TimeUtil (7-33)
🔇 Additional comments (1)
src/main/java/com/example/cp_main_be/domain/member/user/presentation/UserController.java (1)

96-99: 서비스 위임 및 인증 사용자 활용으로 책임 분리가 좋아졌습니다.

현재 사용자와 대상 사용자 ID를 분리해 전달하는 변경은 명확하고 안전합니다.

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.

2 participants