Skip to content

Conversation

@Shinjongyun
Copy link
Contributor

@Shinjongyun Shinjongyun commented Sep 30, 2025

Related issue 🛠

Work Description 📝

  • 마이페이지 조회 기능
  • 마이페이지 수정 기능

Screenshot 📸

Uncompleted Tasks 😅

  • Task1

To Reviewers 📢

Summary by CodeRabbit

  • 신기능
    • 이메일 인증 코드 발송·검증 기능 추가
    • 비밀번호 재설정 이메일 발송 및 임시 비밀번호 발급
    • 내 정보(My Page) 조회·수정: 닉네임 변경 및 소속 동아리 관리
    • 내 비밀번호 변경 기능 추가
  • 개선
    • 토큰 재발급·로그아웃 흐름의 요청/응답 일관성 향상
    • 인증 코드/임시 비밀번호용 이메일 템플릿 추가로 메일 가독성 개선

@coderabbitai
Copy link

coderabbitai bot commented Sep 30, 2025

Walkthrough

인증/토큰 재발급 흐름을 요청 본문 DTO 기반으로 변경하고, 이메일 인증/임시비밀번호 발송 기능을 추가했습니다. 마이페이지 조회/수정 API와 관련 DTO/레포지토리를 도입했습니다. 에러코드/예외/리졸버를 보강했고, 메일 설정과 .env 로드, 템플릿, .gitignore를 갱신했습니다.

Changes

Cohort / File(s) Summary
Auth API 리팩터링/확장
src/.../auth/controller/AuthController.java
리프레시 토큰을 요청 본문(RefreshTokenRequest)으로 처리하도록 변경, 로그아웃/재발급 서명 변경, 이메일 인증(전송/검증) 및 비밀번호 찾기 엔드포인트 추가
Auth DTO 추가
src/.../auth/dto/request/MailRequest.java, src/.../auth/dto/request/CodeCheckRequest.java, src/.../auth/dto/request/PasswordRequest.java, src/.../auth/dto/request/RefreshTokenRequest.java, src/.../auth/dto/response/ReissueResponse.java
메일/코드/비밀번호/리프레시토큰 요청 DTO 및 재발급 응답 DTO 추가
JWT 서비스 변경
src/.../auth/service/JwtService.java, src/.../auth/handler/success/CustomAuthenticationSuccessHandler.java
토큰 직접 헤더 전송 로직 제거, reissue/logout 메서드 시그니처 변경(요청 DTO 사용), 재발급 결과를 ReissueResponse로 반환
메일 서비스/템플릿
src/.../auth/service/MailService.java, src/main/resources/templates/AuthCode-email.html, src/main/resources/templates/Password-email.html
인증 코드 생성/저장(Redis)/검증, 임시비밀번호 생성 및 메일 전송, Thymeleaf 이메일 템플릿 추가
마이페이지 기능
src/.../user/controller/UserController.java, src/.../user/service/UserService.java, src/.../user/dto/request/MyPageUpdateRequest.java, src/.../user/dto/response/MyPageResponse.java, src/.../user/dto/response/ClubResponse.java
내 정보 조회/수정 API 추가, 닉네임 중복 검사·최적화, 클럽 멤버십 동기화 로직 및 응답 매핑 DTO 추가
레포지토리 추가
src/.../club/repository/ClubRepository.java, src/.../member/repository/MemberRepository.java
Club 기본 CRUD 리포지토리 추가, Member용 조회·클럽ID 조회·일괄 삭제 쿼리 추가
공통 인프라
src/.../global/common/response/ErrorCode.java, src/.../global/common/exception/BusinessException.java, src/.../global/common/resolver/CurrentUserIdArgumentResolver.java
에러코드 추가(CLUB_NOT_FOUND, INVALID_PASSWORD), BusinessException 메시지 초기화 변경, 리졸버가 UserPrincipal에서 userId 직접 추출하도록 변경
환경설정/기타
src/main/resources/application.yml, .gitignore
.env 파일 optional import 추가 및 메일 설정 활성화, VS Code 섹션에 *.env 무시 규칙 추가
테스트
src/test/.../UserServiceTest.java
마이페이지 updateMyPage 관련 단위테스트 추가 (모킹된 레포지토리 사용)

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor C as Client
  participant AC as AuthController
  participant JS as JwtService
  participant Store as RefreshTokenStore

  Note over C,AC: 리프레시 토큰 재발급 (요청 본문)
  C->>AC: POST /api/auth/reissue { refreshToken }
  AC->>JS: reissueTokens(RefreshTokenRequest)
  JS->>Store: refreshToken 검증(유효성, 타입)
  Store-->>JS: 검증결과
  JS-->>AC: ReissueResponse{accessToken, refreshToken}
  AC-->>C: BaseResponse(ReissueResponse)
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant AC as AuthController
  participant MS as MailService
  participant RD as Redis

  Note over U,AC: 이메일 인증 코드 발송
  U->>AC: POST /api/auth/email/send {email}
  AC->>MS: sendMail(MailRequest)
  MS->>RD: 코드 저장 (5분 TTL)
  MS-->>AC: void
  AC-->>U: BaseResponse(void)

  Note over U,AC: 인증 코드 검증
  U->>AC: POST /api/auth/email/validation {email, authCode}
  AC->>MS: checkAuthCode(CodeCheckRequest)
  MS->>RD: 코드 조회/검증 → VERIFIED로 설정(30분)
  MS-->>AC: void
  AC-->>U: BaseResponse(void)
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant UC as UserController
  participant US as UserService
  participant MR as MemberRepository
  participant CR as ClubRepository

  U->>UC: PATCH /api/users/myPage/update {nickName, clubList}
  UC->>US: updateMyPage(userId, request)
  US->>MR: findClubIdsByUserId(userId)
  MR-->>US: 현재 클럽 ID 목록
  US->>MR: deleteByUserIdAndClubIdIn(userId, removedIds)
  US->>CR: findAllById(request.clubList) // 존재 검증
  CR-->>US: Club 엔티티 목록
  US->>MR: saveAll(new Members)
  US-->>UC: MyPageResponse
  UC-->>U: BaseResponse(MyPageResponse)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Suggested reviewers

  • yskim6772

Poem

새 비번은 메일로, 코드는 살짝 쿵 ✉️
리프레시 토큰은 바디로 건네고, 응답은 DTO로 뿅
마이페이지 닉네임 반짝, 클럽은 정갈히 정렬
레디스가 속삭인다: "VERIFIED"
서버는 담백하게 대답한다: "OK"

Pre-merge checks and finishing touches

❌ Failed checks (4 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning PR 제목 “[Feat[ 마이페이지 기능”은 잘못된 괄호 사용으로 형식이 일관되지 않고 가독성이 떨어지며, 변경 내용의 주제와도 완전히 일치하지 않습니다. 제목을 “[Feat] 마이페이지 기능”과 같이 올바른 브라켓 형식과 일관된 레이블로 수정하고, 주요 변경 사항을 명확히 드러내도록 다듬어주세요.
Linked Issues Check ⚠️ Warning Issue #10은 마이페이지 조회 및 수정 기능만을 요구하는데, PR에는 이메일 인증 · JWT 재발급 · MailService 등 MyPage와 직접 관련 없는 기능 구현이 대거 포함되어 있어 요구사항을 충족하지 못합니다. MyPage 조회·수정 기능 구현에만 집중하고 기타 기능은 별도 PR로 분리해 이슈 요구사항을 명확히 따르도록 수정해주세요.
Out of Scope Changes Check ⚠️ Warning PR에는 MyPage 기능 외에도 .gitignore 업데이트, AuthController 이메일 엔드포인트, MailService, JwtService, repository 추가, 템플릿 파일 등 이슈 범위를 벗어난 변경 사항이 다수 포함되어 있습니다. MyPage 기능 구현과 직접 관련 없는 모든 변경 사항을 분리하여 별도 PR로 관리하고, 본 PR은 MyPage 조회·수정 기능 구현만 포함하도록 정리해주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#10-myPage

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
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

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

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: 10

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/WhoIsRoom/WhoIs_Server/global/common/resolver/CurrentUserIdArgumentResolver.java (1)

20-20: 사용되지 않는 UserRepository 필드를 제거하세요.

UserRepository가 더 이상 resolveArgument 메서드에서 사용되지 않으므로 필드와 생성자 의존성을 제거하는 것이 좋습니다.

 @Component
-@RequiredArgsConstructor
 public class CurrentUserIdArgumentResolver implements HandlerMethodArgumentResolver {
 
-    private final UserRepository userRepository;
-
🧹 Nitpick comments (15)
src/main/resources/templates/AuthCode-email.html (1)

1-20: HTML 헤드 섹션이 누락되었습니다.

이메일 템플릿에 <head> 섹션과 문자 인코딩 선언이 없습니다. 일부 이메일 클라이언트에서 한글이 깨질 수 있습니다.

다음 diff를 적용하여 헤드 섹션을 추가하세요:

 <!DOCTYPE html>
 <html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta charset="UTF-8">
+    <title>회원가입 인증번호</title>
+</head>
 
 <body>
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/request/MyPageUpdateRequest.java (1)

9-14: 검증 어노테이션 추가를 권장합니다.

DTO 구조는 좋습니다만, 필드 검증을 위한 어노테이션을 추가하면 더 안전합니다.

다음과 같이 검증 어노테이션을 추가하는 것을 고려해보세요:

+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
 @Getter
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
 public class MyPageUpdateRequest {
+    @NotBlank(message = "닉네임은 필수입니다")
+    @Size(max = 20, message = "닉네임은 20자 이하여야 합니다")
     String nickName;
     List<Long> clubList;
 }

컨트롤러에서 @Valid와 함께 사용하면 자동으로 검증됩니다.

src/main/resources/templates/Password-email.html (1)

5-15: 인라인 스타일 대신 <style> 태그 사용을 고려해보세요.

현재 인라인 스타일로 작성되어 있는데, <head> 내부에 <style> 블록을 사용하면 유지보수가 더 편해집니다. 특히 여러 이메일 템플릿에서 공통 스타일을 재사용할 때 유리합니다. 다만 일부 구형 이메일 클라이언트는 CSS 지원이 제한적이니 테스트는 필수입니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/response/ReissueResponse.java (2)

9-10: 필드를 final로 선언하면 불변성이 보장됩니다.

@Builder를 사용하는 DTO의 필드를 final로 선언하면 빌더 패턴의 불변 객체 의도가 더 명확해집니다.

 @Getter
 @Builder
 public class ReissueResponse {
-    private String accessToken;
-    private String refreshToken;
+    private final String accessToken;
+    private final String refreshToken;
 }

6-11: 공개 API에 JavaDoc을 추가하면 좋습니다.

토큰 재발급 응답 DTO로 사용되는 공개 클래스이므로, 클래스와 필드에 대한 간단한 JavaDoc을 추가하면 API 문서화와 유지보수에 도움이 됩니다.

예시:

/**
 * 토큰 재발급 응답 DTO
 */
@Getter
@Builder
public class ReissueResponse {
    /** 새로 발급된 액세스 토큰 */
    private final String accessToken;
    /** 새로 발급된 리프레시 토큰 */
    private final String refreshToken;
}
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/PasswordRequest.java (1)

10-10: prePassword 대신 currentPassword가 더 명확합니다.

"pre"보다는 "current"가 일반적인 명명 관례이고, 의미도 더 직관적입니다.

-    private String prePassword;
+    private String currentPassword;
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/MailRequest.java (2)

3-4: 사용하지 않는 import 제거 필요

SchemaNotNull 임포트가 코드에서 사용되지 않고 있습니다.

다음 diff를 적용하여 미사용 import를 정리하세요:

-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;

12-12: 이메일 필드에 유효성 검증 추가 권장

email 필드에 @NotNull이나 @Email 같은 validation 어노테이션이 없어서 null이나 잘못된 형식의 이메일이 서비스 레이어까지 전달될 수 있습니다.

다음과 같이 validation을 추가하는 것을 고려해보세요:

+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+
 @Getter
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
 public class MailRequest {
+    @NotBlank
+    @Email
     private String email;
 }
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/CodeCheckRequest.java (2)

3-4: 사용하지 않는 import 제거 필요

SchemaNotNull을 임포트했지만 실제로 사용하지 않고 있습니다.

다음 diff를 적용하세요:

-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;

12-13: 필드 유효성 검증 추가 권장

emailauthCode 필드 모두 validation 어노테이션이 없어서 잘못된 값이 서비스 레이어에 전달될 수 있습니다.

다음과 같이 validation을 추가하세요:

+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
 @Getter
 public class CodeCheckRequest {
+    @NotBlank
+    @Email
     private String email;
+    @NotBlank
     private String authCode;
 }
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java (1)

47-47: 사용되지 않는 필드가 있습니다.

ObjectMapper가 주입되고 있지만 현재 코드에서는 사용되지 않는 것으로 보입니다. 이전 구현의 잔여물이거나 향후 사용 예정이라면 괜찮지만, 그렇지 않다면 제거하는 것이 좋습니다.

-    private final ObjectMapper objectMapper;
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java (3)

76-85: 인증 코드 생성 시 보안 강화를 고려해보세요.

현재 java.util.Random을 사용하고 있는데, 인증 코드의 경우 java.security.SecureRandom을 사용하는 것이 예측 불가능성을 높여 보안에 더 좋습니다. 다만 6자리 숫자 코드에 짧은 TTL이 설정되어 있어 현재 구현도 실용적으로는 큰 문제는 없습니다.

-    public String createCode() {
-        Random random = new Random();
+    public String createCode() {
+        SecureRandom random = new SecureRandom();
         StringBuilder key = new StringBuilder();
 
         for (int i = 0; i < 6; i++) {
             key.append(random.nextInt(10)); // 0~9 숫자
         }
 
         return key.toString();
     }

관련 import도 추가해주세요:

+import java.security.SecureRandom;

88-102: 임시 비밀번호 생성 로직에 몇 가지 개선점이 있습니다.

  1. 보안 강화: 비밀번호 생성에는 java.security.SecureRandom을 사용하는 것이 필수적입니다.
  2. 로직 오류: switch 문에서 case 2default가 모두 숫자를 생성하므로, 의도한 문자 분포(소문자/대문자/숫자)가 제대로 구현되지 않았습니다.
  3. 레거시 클래스: StringBuffer 대신 StringBuilder를 사용하는 것이 더 적절합니다(단일 스레드 컨텍스트).
-    public String createNewPassword() {
-        Random random = new Random();
-        StringBuffer key = new StringBuffer();
+    public String createNewPassword() {
+        SecureRandom random = new SecureRandom();
+        StringBuilder key = new StringBuilder();
 
         for (int i = 0; i < 8; i++) {
-            int index = random.nextInt(4);
+            int index = random.nextInt(3);
 
             switch (index) {
                 case 0: key.append((char) ((int) random.nextInt(26) + 97)); break;
                 case 1: key.append((char) ((int) random.nextInt(26) + 65)); break;
-                default: key.append(random.nextInt(9));
+                case 2: key.append(random.nextInt(10)); break;
             }
         }
         return key.toString();
     }

관련 import:

+import java.security.SecureRandom;

134-149: 인증 코드 검증 로직이 적절하게 구현되었습니다.

만료, 중복 검증, 코드 비교, VERIFIED 상태 전환까지의 흐름이 명확합니다. 다만 Line 141에서 getStoredCode를 한 번 더 호출하는 부분은 Line 135의 결과를 재사용할 수 있어 약간의 최적화가 가능합니다.

     public void checkAuthCode(CodeCheckRequest request) {
         String storedCode = getStoredCode(request.getEmail());
         if (storedCode == null) {
             throw new BusinessException(ErrorCode.EXPIRED_EMAIL_CODE);
         }
 
         // 인증 번호가 이미 인증된 상태인 경우 그냥 리턴
-        if ("VERIFIED".equals(getStoredCode(request.getEmail()))){return;};
+        if ("VERIFIED".equals(storedCode)) {
+            return;
+        }
 
         // 입력 코드와 Redis 코드가 다르면 에러
         if (!String.valueOf(request.getAuthCode()).equals(storedCode)) {
             throw new BusinessException(ErrorCode.INVALID_EMAIL_CODE);
         }
         // 인증 성공: 값 변경 + TTL 재설정
         redisService.setValues(EMAIL_KEY_PREFIX + request.getEmail(), "VERIFIED", Duration.ofSeconds(VERIFIED_TTL_SECONDS));
     }
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (1)

118-165: 클럽 멤버십 동기화 로직이 매우 잘 구현되었습니다.

추가/삭제 집합을 계산하여 변경사항만 처리하는 효율적인 로직입니다. 클럽 존재성 검증, 중복 방지, 순서 유지 등이 모두 고려되어 있습니다.

다만 몇 가지 선택적 개선사항이 있습니다:

  1. null 처리 의미: Line 123에서 newClubIdList가 null이면 모든 클럽을 탈퇴하도록 처리되는데, null을 "변경 없음"으로 해석하는 것이 더 직관적일 수 있습니다. Line 120의 주석처럼 null이면 early return하는 것도 고려해보세요.

  2. 에러 메시지 개선: Line 149-151에서 존재하지 않는 클럽 ID가 있을 때, 어떤 ID가 문제인지 로그나 예외 메시지에 포함하면 디버깅에 도움이 됩니다.

1. null을 "변경 없음"으로 처리하려면:

 private void updateUserClubs(User user, List<Long> newClubIdList) {
-    // 요청이 null이면 "변경 없음"으로 보고 그대로 리턴하고 싶다면 아래 주석 해제
-    // if (newClubIdList == null) return;
+    // null이면 변경 없음으로 간주
+    if (newClubIdList == null) return;
 
-    // null이면 빈 리스트로 간주 => 모두 탈퇴 처리
-    Set<Long> requested = newClubIdList == null ? Set.of()
-            : newClubIdList.stream()
+    Set<Long> requested = newClubIdList.stream()
             .filter(Objects::nonNull)
             .collect(Collectors.toCollection(LinkedHashSet::new));

2. 에러 메시지 개선:

     if (!toAdd.isEmpty()) {
         List<Club> clubs = clubRepository.findAllById(toAdd);
 
         if (clubs.size() != toAdd.size()) {
-            // 어떤 ID는 존재X
+            Set<Long> foundIds = clubs.stream().map(Club::getId).collect(Collectors.toSet());
+            Set<Long> missingIds = new LinkedHashSet<>(toAdd);
+            missingIds.removeAll(foundIds);
+            log.warn("Club IDs not found: {}", missingIds);
             throw new BusinessException(ErrorCode.CLUB_NOT_FOUND);
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f1645b6 and cf3fd82.

📒 Files selected for processing (23)
  • .gitignore (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (2 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/CodeCheckRequest.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/MailRequest.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/PasswordRequest.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/RefreshTokenRequest.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/response/ReissueResponse.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java (0 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java (4 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/club/repository/ClubRepository.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/member/repository/MemberRepository.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (2 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/request/MyPageUpdateRequest.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/ClubResponse.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/MyPageResponse.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (4 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/exception/BusinessException.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/resolver/CurrentUserIdArgumentResolver.java (2 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/response/ErrorCode.java (2 hunks)
  • src/main/resources/application.yml (2 hunks)
  • src/main/resources/templates/AuthCode-email.html (1 hunks)
  • src/main/resources/templates/Password-email.html (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java
🧰 Additional context used
🧬 Code graph analysis (11)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (4)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (1)
  • Slf4j (13-39)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (1)
  • Slf4j (18-65)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java (1)
  • Slf4j (26-169)
src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/redis/RedisService.java (1)
  • Service (11-39)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/MailRequest.java (1)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/CodeCheckRequest.java (1)
  • NoArgsConstructor (9-14)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/MyPageResponse.java (1)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/ClubResponse.java (1)
  • Getter (7-21)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/PasswordRequest.java (1)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/CodeCheckRequest.java (1)
  • NoArgsConstructor (9-14)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java (3)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (1)
  • Slf4j (30-166)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (1)
  • Slf4j (18-65)
src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/redis/RedisService.java (1)
  • Service (11-39)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (3)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (1)
  • Slf4j (30-166)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java (1)
  • Slf4j (24-113)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java (1)
  • Slf4j (26-169)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/RefreshTokenRequest.java (1)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/CodeCheckRequest.java (1)
  • NoArgsConstructor (9-14)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java (4)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (1)
  • Slf4j (18-65)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java (1)
  • Slf4j (22-63)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/util/JwtUtil.java (1)
  • Slf4j (18-134)
src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/redis/RedisService.java (1)
  • Service (11-39)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/ClubResponse.java (1)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/MyPageResponse.java (1)
  • Getter (9-28)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/request/MyPageUpdateRequest.java (1)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/CodeCheckRequest.java (1)
  • NoArgsConstructor (9-14)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (1)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (1)
  • Slf4j (30-166)
🔇 Additional comments (28)
.gitignore (1)

38-41: 환경 변수 파일 보호 설정 추가 - 좋습니다!

.env 파일을 .gitignore에 추가한 것은 보안 모범 사례입니다. 환경 변수 파일에는 일반적으로 API 키, 데이터베이스 자격 증명, 비밀번호 등 민감한 정보가 포함되므로 버전 관리에서 제외하는 것이 중요합니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/club/repository/ClubRepository.java (1)

1-8: LGTM!

표준 Spring Data JPA 리포지토리 인터페이스가 정확하게 선언되었습니다. 기본 CRUD 작업을 위한 깔끔한 구현입니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/RefreshTokenRequest.java (1)

1-12: LGTM!

Lombok 어노테이션과 접근 제어자가 올바르게 사용되었습니다. CodeCheckRequest와 일관된 구조를 따르고 있네요.

다만, refreshToken 필드에 대한 검증 어노테이션(예: @NotBlank)이 필요한지 확인해보세요. 컨트롤러 레벨에서 @Valid를 사용한다면 추가하는 것이 좋습니다.

src/main/resources/application.yml (2)

66-74: 메일 설정이 활성화되었습니다.

Gmail SMTP 설정이 올바르게 구성되었습니다. 포트 587과 STARTTLS 조합이 적절합니다.

환경 변수 MAIL_ADDRESSMAIL_PASSWORD가 제대로 설정되어 있는지 확인하세요. Gmail의 경우 2단계 인증 사용 시 앱 비밀번호가 필요합니다. 참고: 환경 변수 이름이 MAIL_USERNAME에서 MAIL_ADDRESS로 변경되었으니, 기존 설정을 업데이트해야 합니다.


62-62: LGTM — .env 파일이 .gitignore에 포함되어 있습니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/exception/BusinessException.java (1)

10-13: 생성자 파라미터명 변경이 적절합니다.

errorCode에서 code로 파라미터명을 변경했지만, 내부 필드명(this.errorCode)과 동작은 그대로 유지되어 있어서 하위 호환성에 문제가 없습니다. 코드가 간결해지고 가독성도 괜찮네요.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/ClubResponse.java (1)

7-18: 구현 깔끔합니다!

간결한 DTO 설계와 정적 팩토리 메서드 패턴 사용이 좋네요. 코드가 명확하고 읽기 쉽습니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/response/ErrorCode.java (2)

26-28: 새로운 에러 코드 추가 확인

CLUB_NOT_FOUND 에러 코드가 깔끔하게 추가되었습니다. 코드 번호(300)와 메시지가 기존 패턴과 일관성 있게 구성되어 있네요.


47-48: 비밀번호 검증 에러 코드 추가 확인

INVALID_PASSWORD 에러 코드가 추가되었고, enum 구문(쉼표/세미콜론)도 올바르게 수정되었습니다. 마이페이지 비밀번호 수정 기능에 필요한 에러 처리가 준비된 것 같습니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (2)

27-31: 마이페이지 조회 엔드포인트 구현 잘 되었습니다.

@CurrentUserId 리졸버를 통해 인증된 사용자 ID를 자동으로 주입받고, 서비스 레이어에 위임하는 구조가 깔끔합니다.


33-38: 마이페이지 수정 엔드포인트 구현 잘 되었습니다.

@CurrentUserId@RequestBody를 통해 인증과 요청 데이터를 깔끔하게 처리하고 있습니다. 서비스 레이어에 비즈니스 로직을 위임하는 패턴도 적절합니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/member/repository/MemberRepository.java (3)

13-13: 표준 Spring Data JPA 쿼리 메서드입니다.

네이밍 컨벤션에 따라 자동으로 쿼리가 생성됩니다.


15-16: ID만 프로젝션하는 쿼리로 성능 최적화가 잘 되어 있습니다.

전체 엔티티를 조회하지 않고 필요한 ID만 가져오므로, 멤버십 동기화 로직에서 효율적으로 사용될 수 있습니다.


18-20: 벌크 삭제 쿼리가 적절하게 구현되었습니다.

clearAutomaticallyflushAutomatically 플래그를 모두 활성화하여 영속성 컨텍스트와의 동기화 문제를 방지하고 있습니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java (3)

49-62: 로그아웃 로직에 토큰 타입 검증이 추가되어 보안이 강화되었습니다.

리프레시 토큰을 요청 본문에서 받아 검증하고, 타입이 "refresh"인지 확인하는 로직이 추가되어 토큰 혼용을 방지할 수 있습니다. 액세스 토큰을 블랙리스트에 추가하는 처리도 적절합니다.


64-71: 토큰 재발급 로직이 DTO 기반으로 깔끔하게 리팩토링되었습니다.

HTTP 응답 직접 조작 대신 ReissueResponse를 반환하도록 변경되어, 컨트롤러 레이어와의 책임 분리가 명확해졌습니다. 토큰 타입 검증도 일관성 있게 적용되어 있습니다.


96-112: 토큰 재발급 및 응답 구성 로직이 잘 구현되었습니다.

새로운 토큰을 발급하고, 기존 리프레시 토큰을 폐기하는 로테이션 로직이 적절합니다. 빌더 패턴으로 ReissueResponse를 구성하여 가독성도 좋습니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java (3)

46-62: 이메일 인증 코드 발송 로직이 잘 구현되었습니다.

중복 이메일 검사, 코드 생성, 메일 발송, Redis 저장까지의 흐름이 명확하고, 예외 처리도 적절합니다. TTL을 5분으로 설정하여 보안도 고려되어 있습니다.


64-73: 임시 비밀번호 발송 로직이 적절합니다.

임시 비밀번호를 생성하고 메일로 발송한 뒤 반환하여, 호출자(UserService)가 암호화하여 저장할 수 있도록 하는 구조가 합리적입니다.


157-168: Thymeleaf 템플릿 렌더링이 적절하게 구현되었습니다.

인증 코드와 임시 비밀번호용 템플릿을 각각 처리하는 메서드가 명확하게 분리되어 있습니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (5)

48-50: 회원가입에 이메일 인증 검증이 추가되어 보안이 강화되었습니다.

MailService의 인증 상태를 확인하여, 인증되지 않은 이메일로는 가입할 수 없도록 처리되었습니다.


61-67: 비밀번호 재설정 로직이 잘 구현되었습니다.

임시 비밀번호를 메일로 발송하고, 암호화하여 저장하는 흐름이 적절합니다.


69-78: 비밀번호 변경 로직이 안전하게 구현되었습니다.

기존 비밀번호를 검증한 후 새 비밀번호로 변경하는 흐름이 적절하며, 암호화도 올바르게 적용되어 있습니다.


80-87: 마이페이지 조회 로직이 깔끔하게 구현되었습니다.

읽기 전용 트랜잭션으로 최적화되어 있고, 사용자 정보와 멤버십 목록을 조회하는 로직이 명확합니다.


89-101: 마이페이지 수정 로직이 잘 구조화되었습니다.

닉네임과 클럽 멤버십 업데이트를 헬퍼 메서드로 분리하여 가독성과 유지보수성이 좋습니다. 트랜잭션 처리도 적절합니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (3)

3-11: 임포트 구조 깔끔합니다.

새로 추가된 DTO와 서비스 임포트가 모두 실제로 사용되고 있고, 불필요한 임포트가 없네요.

Also applies to: 16-16


25-26: 의존성 주입 패턴 좋습니다.

새로운 서비스들이 생성자 주입 방식으로 깔끔하게 추가되었네요.


29-33: 기존 클라이언트 영향 및 헤더 기반 로직 검토 필요

  1. logout 엔드포인트의 리프레시 토큰 전달 방식(header→body)이 배포된 클라이언트에 영향이 없는지 프론트엔드 팀과 협의하거나 마이그레이션 계획을 수립해주세요.
  2. JwtUtil#extractRefreshToken에 남아 있는 헤더 기반 추출 로직이 실제로 사용되는지 확인하고, 더 이상 필요 없다면 제거해 호출 흐름을 명확히 해주세요.
  3. logout 엔드포인트에 Rate Limiting이 적용되어 있는지도 점검 바랍니다.

Comment on lines 36 to 39
public BaseResponse<ReissueResponse> reissueTokens(@RequestBody RefreshTokenRequest tokenRequest) {
ReissueResponse response = jwtService.reissueTokens(tokenRequest);
return BaseResponse.ok(response);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

❓ Verification inconclusive

토큰 재발급 엔드포인트에 rate limiting이 필수적입니다.

토큰 재발급은 보안에 민감한 엔드포인트라서 rate limiting이나 throttling이 적용되어 있는지 확인이 필요합니다. 공격자가 무한정 토큰 재발급을 시도하는 것을 막아야 합니다.


🏁 Script executed:

#!/bin/bash
# Rate limiting 관련 어노테이션이나 필터 설정 확인
rg -nP --type=java '@RateLimit|RateLimiter|Throttle|bucket4j' -C3

# Spring Security 설정에서 /api/auth/reissue 엔드포인트 보호 확인
fd -e java SecurityConfig | xargs rg -nP 'reissue' -C5

Length of output: 0


토큰 재발급 엔드포인트에 Rate Limiting 적용 필요
현재 AuthController.reissueTokens 메서드 및 SecurityConfig 설정에서 Rate Limiter 어노테이션이나 필터가 확인되지 않아, 무차별 토큰 재발급 시도를 방지할 수 없습니다. 적절한 Rate Limiter(@ratelimiter 등) 또는 Bucket4j 기반 throttling을 구현하세요.

🤖 Prompt for AI Agents
In
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java
around lines 36-39, the reissueTokens endpoint lacks any rate limiting which
allows brute-force token reissue attempts; add a rate limiting mechanism (either
annotate the method with a Resilience4j @RateLimiter or protect it via a
Bucket4j-based servlet/filter/Reactive filter) and update SecurityConfig to
register that filter or enable the annotation support. Specifically: apply a
limiter that restricts requests per client (IP or authenticated user) to a safe
rate, configure a bean with the chosen limiter policy, ensure limiter failure
maps to HTTP 429 with a clear error body, and add relevant exception handling so
exceeded limits return 429 instead of 500.

Comment on lines 41 to 45
@PostMapping("/email/send")
public BaseResponse<Void> sendAuthCodeMail(@RequestBody MailRequest request) {
mailService.sendMail(request);
return BaseResponse.ok(null);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

이메일 전송 엔드포인트에 rate limiting이 반드시 필요합니다.

인증 코드 발송 엔드포인트는 악용될 경우 메일 폭탄 공격에 취약합니다. IP 기반 또는 이메일 주소 기반 rate limiting을 적용해서 짧은 시간 내 반복 요청을 차단해야 합니다. 예를 들어, 동일 IP는 1분에 3회, 동일 이메일은 5분에 1회 정도로 제한하는 것을 권장합니다.

#!/bin/bash
# Rate limiting 구현 확인
rg -nP --type=java '@RateLimit|RateLimiter|Bucket4j|Resilience4j' -C3

# 필터나 인터셉터에서 rate limiting 로직 확인
fd -e java Filter Interceptor | xargs rg -nP 'rate|limit|throttle' -C3
🤖 Prompt for AI Agents
In
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java
around lines 41 to 45, the POST /email/send endpoint lacks rate limiting and is
vulnerable to mail-bombing; add a rate limit that blocks repeated requests by IP
and by email (suggest: same IP max 3 requests per minute, same email max 1
request per 5 minutes). Implement this as a reusable Filter or
HandlerInterceptor (or integrate Bucket4j/Resilience4j) that extracts client IP
and target email from the request, tracks counts in a thread-safe store
(in-memory cache like Caffeine for single-instance or Redis for multi-instance),
enforces the two limits before calling mailService.sendMail, and returns HTTP
429 with a clear message when limits are exceeded; ensure limits are
configurable via properties and registered in Spring configuration so the
controller method itself remains unchanged.

Comment on lines 47 to 51
@PostMapping("/email/validation")
public BaseResponse<Void> checkAuthCode(@RequestBody CodeCheckRequest request) {
mailService.checkAuthCode(request);
return BaseResponse.ok(null);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

인증 코드 검증 엔드포인트에 brute force 방어가 필요합니다.

6자리 숫자 코드는 최대 100만 가지 조합밖에 없어서, rate limiting이 없으면 brute force 공격에 취약할 수 있습니다. 동일 이메일에 대해 1분당 5회 정도로 시도 횟수를 제한하거나, 일정 횟수 실패 시 계정을 일시적으로 잠그는 방식을 고려해보세요.

또한 Redis에 저장된 코드의 TTL이 5분이라서 그나마 공격 시간이 제한적이긴 하지만, 추가 보호 장치가 있으면 더 안전합니다.

Comment on lines 53 to 57
@PostMapping("/email/find-password")
public BaseResponse<Void> findPassword(@RequestBody MailRequest request) {
userService.sendNewPassword(request);
return BaseResponse.ok(null);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

비밀번호 찾기 엔드포인트의 보안 흐름을 재검토해주세요.

현재 구현에서 몇 가지 보안 우려사항이 있습니다:

  1. Rate limiting 부재: 공격자가 무작위 이메일로 반복 요청해서 계정 존재 여부를 확인(account enumeration)하거나 메일 폭탄을 날릴 수 있습니다.

  2. 인증 없이 즉시 비밀번호 변경: 일반적인 비밀번호 재설정 플로우는 (1) 이메일로 재설정 링크 발송 → (2) 링크 클릭 후 본인 인증 → (3) 새 비밀번호 설정 순서인데, 현재는 바로 임시 비밀번호를 생성해서 DB를 업데이트하고 있네요. 만약 공격자가 다른 사람의 이메일 주소를 알고 있다면, 실제 사용자의 비밀번호를 강제로 변경시킬 수 있습니다.

개선 방안:

  • Rate limiting 적용 (IP당 5분에 1회 등)
  • 임시 비밀번호를 바로 DB에 저장하지 않고, 이메일에 포함된 링크를 통해 본인 인증 후 재설정하도록 변경
  • 또는 최소한 기존 /email/validation 플로우를 먼저 거치도록 강제
🤖 Prompt for AI Agents
In
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java
around lines 53-57, the current findPassword endpoint immediately generates and
saves a new password and sends email, which allows account takeover and
enumeration; change it to a secure reset flow: stop updating the DB directly in
this endpoint, instead generate a cryptographically secure one-time token with
expiry, persist that token (linked to user) and send an email containing a
password-reset link with the token; enforce rate-limiting on this endpoint
(e.g., per IP and/or per account) to prevent abuse; implement a separate
endpoint that validates the token and accepts a new password (or reuse existing
/email/validation) which verifies the token, updates the password only after
successful validation, and ensure responses do not reveal whether an account
exists (always return a generic success message).

Comment on lines 59 to 64
@PatchMapping("/password")
public BaseResponse<Void> updatePassword(@CurrentUserId Long userId,
@RequestBody PasswordRequest request) {
userService.updateMyPassword(userId, request);
return BaseResponse.ok(null);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

비밀번호 변경 로직에 몇 가지 개선사항을 제안합니다.

현재 구현은 인증된 사용자만 접근 가능하고 기존 비밀번호를 검증하는 부분은 잘 되어 있습니다. 추가로 고려하면 좋을 부분들:

  1. Rate limiting: 기존 비밀번호 검증 시 실패하면 사용자당 시도 횟수를 제한해서 brute force 공격을 방어하세요.

  2. 새 비밀번호 검증: PasswordRequest DTO나 서비스 레이어에서 다음 사항들을 확인하고 있는지 체크해보세요:

    • 새 비밀번호가 기존 비밀번호와 동일하지 않은지
    • 비밀번호 강도 요구사항 (최소 길이, 복잡도 등)

🏁 Script executed:

#!/bin/bash
# PasswordRequest DTO의 validation 어노테이션 확인
ast-grep --pattern 'class PasswordRequest {
  $$$
}'

# 비밀번호 validation 로직 확인
rg -nP --type=java 'password.*valid|validatePassword' -C3

Length of output: 0


PasswordRequest DTO 파일과 validation 어노테이션을 찾기 위해 좀 더 구체적인 스크립트를 실행해볼게요.


🏁 Script executed:

#!/bin/bash
# 1. DTO 클래스 파일 찾기
rg -l 'class PasswordRequest' -g '*.java'

# 2. DTO 클래스 내용 및 validation 어노테이션 확인
rg -n 'class PasswordRequest' -C5 -g '*.java'
rg -n '@(NotBlank|NotNull|Size|Pattern)' -C2 -g '*PasswordRequest.java'

Length of output: 1092


비밀번호 변경 로직 개선 제안

  • 잘못된 기존 비밀번호 검증 실패 시 brute-force 방어를 위해 rate limiting 적용
  • PasswordRequest DTO에 @notblank, @SiZe 등 validation 어노테이션 추가
  • 새 비밀번호가 기존 비밀번호와 동일하지 않도록 검증
🤖 Prompt for AI Agents
In
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java
around lines 59-64, the password update endpoint lacks validation and
brute-force protections; update it to (1) validate PasswordRequest with javax
validation annotations (@NotBlank, @Size, @Pattern as needed) and annotate the
controller or method with @Validated so Spring validates the DTO, (2) in the
service layer check that the provided current password matches and if it fails
increment/consult a rate-limiter or lockout counter (e.g., per-user in-memory or
Redis) to throttle repeated failures and return a clear error, and (3) verify
the new password is not identical to the current password and return a
validation error if it is; ensure proper error responses and logging are used
for each failure path.

Comment on lines 10 to 11
private String prePassword;
private String newPassword;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

비밀번호 필드에 유효성 검증이 누락되었습니다.

prePasswordnewPassword 필드에 @NotBlank, @Pattern, @Size 등의 검증 어노테이션이 없어서, 빈 값이나 부적절한 형식의 비밀번호가 서비스 레이어까지 전달될 수 있습니다. 특히 비밀번호 관련 입력은 보안상 반드시 검증이 필요합니다.

Jakarta Validation 어노테이션을 추가하세요:

+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+
 @Getter
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
 public class PasswordRequest {
+    @NotBlank(message = "현재 비밀번호를 입력해주세요")
     private String prePassword;
+    
+    @NotBlank(message = "새 비밀번호를 입력해주세요")
+    @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,}$",
+             message = "비밀번호는 8자 이상, 영문/숫자/특수문자를 포함해야 합니다")
     private String newPassword;
 }

컨트롤러에서 @Valid 또는 @Validated도 함께 사용해야 검증이 동작합니다.

📝 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
private String prePassword;
private String newPassword;
// add these imports
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PasswordRequest {
@NotBlank(message = "현재 비밀번호를 입력해주세요")
private String prePassword;
@NotBlank(message = "새 비밀번호를 입력해주세요")
@Pattern(
regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,}$",
message = "비밀번호는 8자 이상, 영문/숫자/특수문자를 포함해야 합니다"
)
private String newPassword;
}

Comment on lines 15 to 27
public static MyPageResponse from(String nickname, List<Member> memberList) {

List<ClubResponse> clubList = memberList.stream()
.map(Member::getClub)
.distinct()
.map(ClubResponse::from)
.toList();

return MyPageResponse.builder()
.nickname(nickname)
.clubList(clubList)
.build();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Club 엔티티의 equals/hashCode 구현 확인 필요

Line 19에서 .distinct()를 사용해 중복 제거를 시도하는데, Club 엔티티가 equals()hashCode()를 올바르게 오버라이드하지 않으면 참조 동등성 비교가 되어서 중복 제거가 제대로 작동하지 않을 수 있습니다.

다음 스크립트로 Club 엔티티의 equals/hashCode 구현 여부를 확인해주세요:


🏁 Script executed:

#!/bin/bash
# Description: Check if Club entity implements equals/hashCode

# Search for Club class definition and equals/hashCode methods
ast-grep --pattern $'class Club {
  $$$
}'

echo "---"
echo "Checking for equals/hashCode methods in Club:"
rg -A 5 "public boolean equals|public int hashCode" --type java -g "**/Club.java"

Length of output: 49


#!/bin/bash

Club 엔티티 equals()/hashCode() 구현 여부 재확인

Club.java 파일 경로 확인

club_file=$(rg -l "class Club\b" -t java)
if [ -z "$club_file" ]; then
echo "Club.java 파일을 찾을 수 없습니다."
exit 0
fi
echo "Club.java 경로: $club_file"

클래스 선언부 출력

echo "--- 클래스 선언부 ---"
rg -n "class Club\b" $club_file

equals()/hashCode() 메서드 구현 여부 확인

echo "--- equals()/hashCode() 메서드 ---"
rg -nP "^(public|protected|private)\s+(boolean equals|int hashCode)\s*(" -C2 $club_file


🏁 Script executed:

#!/bin/bash
# Club.java 파일 경로 찾기
club_file=$(find src -type f -name 'Club.java' | head -n 1)
if [ -z "$club_file" ]; then
  echo "Club.java 파일을 찾을 수 없습니다."
  exit 0
fi
echo "Club.java 경로: $club_file"

# 클래스 선언부 출력
echo "--- 클래스 선언부 ---"
rg -n "class Club\b" "$club_file"

# equals()/hashCode() 메서드 구현 여부 확인
echo "--- equals()/hashCode() 구현 확인 ---"
rg -nP "^(public|protected|private)\s+(boolean\s+equals|int\s+hashCode)\s*\(" -C2 "$club_file"

Length of output: 174


Club 엔티티가 BaseEntity로부터 equals()/hashCode()를 상속받는지 확인하기 위해 BaseEntity.java를 검사해주세요. 아래 스크립트를 실행해주세요.


🏁 Script executed:

#!/bin/bash
# BaseEntity.java 파일 경로 찾기
base_file=$(find src -type f -name 'BaseEntity.java' | head -n 1)
if [ -z "$base_file" ]; then
  echo "BaseEntity.java 파일을 찾을 수 없습니다."
  exit 0
fi
echo "BaseEntity.java 경로: $base_file"

# 클래스 선언부
echo "--- 클래스 선언부 ---"
rg -n "class BaseEntity\b" "$base_file"

# Lombok @EqualsAndHashCode 어노테이션 확인
echo "--- Lombok EqualsAndHashCode 어노테이션 확인 ---"
rg -n "@EqualsAndHashCode" "$base_file"

# equals()/hashCode() 메서드 구현 여부
echo "--- equals()/hashCode() 구현 여부 확인 ---"
rg -nP "^(public|protected|private)\s+(boolean\s+equals|int\s+hashCode)\s*\(" -C2 "$base_file"

Length of output: 229


Club 엔티티에 equals()/hashCode() 오버라이드 필요
현재 src/main/java/com/WhoIsRoom/WhoIs_Server/domain/club/model/Club.java 및 BaseEntity에는 equals()/hashCode() 구현이나 Lombok 어노테이션이 없어 .distinct()가 참조 동등성으로만 동작합니다. ID나 고유 비즈니스 키를 기반으로 equals()와 hashCode()를 구현해 중복 제거가 제대로 이루어지도록 수정하세요.

🤖 Prompt for AI Agents
In
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/MyPageResponse.java
around lines 15–27, .distinct() on member.getClub() relies on Club's
equals()/hashCode(), but Club (and BaseEntity) lack an implementation so
distinct uses reference equality; implement equals() and hashCode() on the Club
entity (or in BaseEntity if you want reuse) based on the entity identifier or an
immutable business key: add a correct equals/hashCode pair (or use Lombok
@EqualsAndHashCode) that uses the non-null id (and fallbacks if id can be null
before persistence) to ensure logical equality and allow stream().distinct() to
remove duplicates properly.

Comment on lines +103 to +116
private void updateUserNickName(User user, String newNickName) {

// 변경 사항이 없으면 아무것도 하지 않음 (최적화)
if (user.getNickName().equals(newNickName)) {
return;
}

// 닉네임 중복 검사 (자기 자신은 제외되므로 안전함)
if (userRepository.existsByNickName(newNickName)) {
throw new BusinessException(ErrorCode.USER_DUPLICATE_NICKNAME);
}

user.setNickName(newNickName);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

닉네임 업데이트 로직에 null 안전성 검증이 필요합니다.

Line 106에서 user.getNickName().equals(newNickName)을 호출하는데, newNickName이 null이면 정상 동작하지만 user.getNickName()이 null일 경우 NPE가 발생할 수 있습니다. 엔티티 필드가 non-null이라면 괜찮지만, 명시적인 null 검증이나 @NotNull 밸리데이션을 추가하는 것이 안전합니다.

컨트롤러나 DTO 레벨에서 @NotNull 또는 @NotBlank 검증 추가:

// MyPageUpdateRequest에서
@NotBlank(message = "닉네임은 필수입니다")
private String nickName;

또는 서비스 메서드에서 명시적 null 체크:

 private void updateUserNickName(User user, String newNickName) {
+    if (newNickName == null || newNickName.trim().isEmpty()) {
+        throw new BusinessException(ErrorCode.INVALID_INPUT);
+    }
 
     // 변경 사항이 없으면 아무것도 하지 않음 (최적화)
     if (user.getNickName().equals(newNickName)) {
         return;
     }

Comment on lines +33 to +34
UserPrincipal principal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return principal.getUserId();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

인증 객체와 타입 검증이 누락되었습니다.

SecurityContextHolder에서 Authenticationprincipal을 가져올 때 null 체크나 타입 검증이 없어서, 인증되지 않은 요청이나 예상치 못한 principal 타입이 들어오면 NullPointerException 또는 ClassCastException이 발생할 수 있습니다.

다음과 같이 안전하게 처리하는 것을 권장합니다:

-    UserPrincipal principal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
-    return principal.getUserId();
+    var authentication = SecurityContextHolder.getContext().getAuthentication();
+    if (authentication == null || !(authentication.getPrincipal() instanceof UserPrincipal)) {
+        throw new BusinessException(ErrorCode.UNAUTHORIZED);
+    }
+    UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+    return principal.getUserId();
📝 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
UserPrincipal principal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return principal.getUserId();
var authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !(authentication.getPrincipal() instanceof UserPrincipal)) {
throw new BusinessException(ErrorCode.UNAUTHORIZED);
}
UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
return principal.getUserId();

Comment on lines 1 to 3
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

섹션과 문자 인코딩 선언이 누락되었습니다.

이메일 템플릿에 <head> 섹션과 <meta charset="UTF-8">가 없어서 일부 이메일 클라이언트에서 한글이 깨질 수 있습니다. 또한 <html> 태그에 lang="ko" 속성을 추가하면 접근성도 개선됩니다.

다음과 같이 수정하는 것을 권장합니다:

 <!DOCTYPE html>
-<html xmlns:th="http://www.thymeleaf.org">
+<html xmlns:th="http://www.thymeleaf.org" lang="ko">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>임시 비밀번호 안내</title>
+</head>
 
 <body>
📝 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
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>임시 비밀번호 안내</title>
</head>
<body>
🤖 Prompt for AI Agents
In src/main/resources/templates/Password-email.html around lines 1 to 3, the
template is missing a <head> section with character encoding and a language
declaration on the <html> tag; add lang="ko" to the <html> element and insert a
<head> that includes <meta charset="UTF-8"> (and optionally a <title> or
viewport) so Korean characters render correctly across email clients and
accessibility is improved.

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

♻️ Duplicate comments (7)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (4)

35-39: 토큰 재발급 로직이 DTO 기반으로 잘 정리되었습니다.

RefreshTokenRequest를 받아서 ReissueResponse를 반환하는 구조로 변경되었고, 코드가 명확해졌습니다. 다만 이전 리뷰에서 언급된 rate limiting 적용 필요성은 여전히 유효합니다.


41-45: 이메일 인증 코드 발송 엔드포인트가 추가되었습니다.

구현은 깔끔하지만, 이전 리뷰에서 지적된 rate limiting 미적용 이슈가 여전히 해결되지 않았습니다. 메일 폭탄 공격 방어를 위해 IP 및 이메일 기반 rate limiting을 적용하는 것을 권장합니다.


47-51: 인증 코드 검증 엔드포인트가 추가되었습니다.

구현 자체는 문제없지만, 이전 리뷰에서 언급된 brute force 공격 방어가 필요합니다. 동일 이메일에 대한 시도 횟수 제한이나 실패 시 잠금 기능을 고려해보세요.


53-57: 비밀번호 찾기 엔드포인트가 추가되었습니다.

이전 리뷰에서 지적된 보안 이슈들(계정 열거 공격 가능성, 본인 인증 없이 즉시 비밀번호 변경, rate limiting 부재)이 여전히 존재합니다. 토큰 기반 재설정 링크 방식으로 개선하는 것을 권장합니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/MyPageResponse.java (1)

15-27: 마이페이지 응답 DTO의 팩토리 메서드가 잘 구현되었습니다.

전반적인 구조는 깔끔하지만, Line 19의 .distinct()가 제대로 작동하려면 Club 엔티티에 equals()hashCode()가 구현되어 있어야 합니다. 이전 리뷰에서 지적된 내용이므로 확인 부탁드립니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (1)

41-46: 비밀번호 변경 엔드포인트가 추가되었습니다.

구현은 깔끔하지만, 이전 리뷰에서 지적된 내용(PasswordRequest DTO validation 추가, 잘못된 비밀번호 반복 입력 시 rate limiting 적용)이 여전히 해결되지 않았습니다. 보안 강화를 위해 해당 사항들을 고려해보세요.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (1)

103-116: 이전 리뷰에서 제기된 NPE 우려 사항

Line 106에서 user.getNickName().equals(newNickName) 호출 시 user.getNickName()이 null이면 NPE가 발생할 수 있습니다.

User 엔티티의 nickName 필드가 @Column(nullable = false) 등으로 non-null이 보장되는지 확인하거나, DTO 레벨에서 @NotBlank 검증을 추가하는 것이 좋습니다.

#!/bin/bash
# Description: User 엔티티의 nickName 필드 정의 확인

ast-grep --pattern $'class User {
  $$$
  nickName
  $$$
}'
🧹 Nitpick comments (6)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/filter/JwtAuthenticationFilter.java (1)

42-44: 포맷팅 변경이 PR 범위와 무관해 보입니다.

PASS_URIS 배열의 두 URI를 한 줄로 합친 순수 포맷팅 변경입니다. 기능적으로는 아무 영향이 없지만, 이 PR의 목적(마이페이지 기능)과는 관련이 없어 보입니다. 의도하지 않은 커밋일 수 있으니 확인해 주세요.

참고: 코드 자체는 문제없습니다.

src/test/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserServiceTest.java (2)

45-66: 테스트 셋업이 잘 구성되어 있습니다.

테스트 데이터 준비가 명확하고 완전합니다. 다만 Line 47, 62-65의 System.out.println은 테스트 출력을 지저분하게 만들 수 있으니, 필요하다면 log.debug()로 대체하거나 제거하는 것도 고려해보세요.

필요하다면 다음과 같이 정리할 수 있습니다:

-        System.out.println("\n[TEST] ========== setUp ==========");
+        log.debug("\n[TEST] ========== setUp ==========");
         // ... (중간 코드 동일)
-        System.out.println("[TEST] userId=" + user.getId() + ", nick=" + user.getNickName());
-        System.out.println("[TEST] clubs=" + clubs.stream()
-                .map(c -> c.getId() + ":" + c.getName()).toList());
-        System.out.println("[TEST] =============================\n");
+        log.debug("[TEST] userId={}, nick={}", user.getId(), user.getNickName());
+        log.debug("[TEST] clubs={}", clubs.stream()
+                .map(c -> c.getId() + ":" + c.getName()).toList());
+        log.debug("[TEST] =============================\n");

68-140: 테스트 로직이 잘 작성되었습니다.

Mock 설정과 검증 로직이 올바르게 구현되어 있고, updateMyPage 플로우를 제대로 테스트하고 있습니다. 다만 테스트 코드 내 System.out.println 로깅(Lines 81-83, 87-89, 96-103, 109-112, 116-122, 126-132)이 많아서 테스트 출력이 장황해질 수 있습니다. 디버깅이 끝났다면 제거하거나 log.debug()로 전환하는 것을 고려해보세요.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/request/MyPageUpdateRequest.java (1)

7-14: DTO 구조가 깔끔하게 구성되었습니다.

Lombok 어노테이션 사용이 적절합니다. 필요에 따라 nickName@NotBlank@Size 같은 validation 어노테이션을 추가하면 서비스 레이어 진입 전에 입력값을 검증할 수 있습니다. 현재는 서비스 레이어에서 처리하고 있어 큰 문제는 없지만, 컨트롤러 레벨에서 빠른 실패(fail-fast)를 원한다면 고려해보세요.

예시:

 @Getter
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
 @Builder
 @AllArgsConstructor(access = AccessLevel.PRIVATE)
 public class MyPageUpdateRequest {
+    @NotBlank(message = "닉네임은 필수입니다")
+    @Size(min = 1, max = 20, message = "닉네임은 1~20자여야 합니다")
     String nickName;
     List<Long> clubList;
 }
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (1)

34-39: 마이페이지 수정 엔드포인트가 잘 구현되었습니다.

로직이 명확하고 구조가 깔끔합니다. 필요하다면 @Validated@Valid 어노테이션을 @RequestBody 앞에 추가해서 DTO validation을 활성화할 수 있습니다.

예시:

     @PatchMapping("/myPage/update")
     public BaseResponse<MyPageResponse> updateMyPage(@CurrentUserId Long userId,
-                                                     @RequestBody MyPageUpdateRequest request) {
+                                                     @Valid @RequestBody MyPageUpdateRequest request) {
         MyPageResponse response = userService.updateMyPage(userId, request);
         return BaseResponse.ok(response);
     }
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (1)

118-163: 클럽 목록 업데이트 로직의 견고함

클럽 가입/탈퇴를 처리하는 로직이 체계적입니다:

  • null 안전성 처리 (line 121-124)
  • 현재 상태와 요청의 차이를 계산 (lines 132-136)
  • 삭제 먼저, 추가는 존재성 검증 후 (lines 139-161)
  • 순서 유지를 위해 LinkedHashSet 사용

Line 147-149의 존재성 검증에서 어떤 clubId가 존재하지 않는지 로그를 남기면 디버깅에 도움이 될 수 있습니다:

             if (clubs.size() != toAdd.size()) {
-                // 어떤 ID는 존재X
+                Set<Long> foundIds = clubs.stream().map(Club::getId).collect(Collectors.toSet());
+                Set<Long> missingIds = new LinkedHashSet<>(toAdd);
+                missingIds.removeAll(foundIds);
+                log.warn("존재하지 않는 클럽 ID: {}", missingIds);
                 throw new BusinessException(ErrorCode.CLUB_NOT_FOUND);
             }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf3fd82 and 9e4b0ef.

📒 Files selected for processing (7)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (2 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/filter/JwtAuthenticationFilter.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (2 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/request/MyPageUpdateRequest.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/MyPageResponse.java (1 hunks)
  • src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (4 hunks)
  • src/test/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserServiceTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (3)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (1)
  • Slf4j (18-58)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/filter/JwtAuthenticationFilter.java (1)
  • Slf4j (34-108)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (1)
  • Slf4j (30-164)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (4)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (1)
  • Slf4j (14-47)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (1)
  • Slf4j (30-164)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java (1)
  • Slf4j (24-113)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java (1)
  • Slf4j (26-169)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/request/MyPageUpdateRequest.java (1)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/MyPageResponse.java (1)
  • Getter (9-28)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/MyPageResponse.java (2)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/request/MyPageUpdateRequest.java (1)
  • Getter (7-14)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/dto/response/ClubResponse.java (1)
  • Getter (7-21)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (5)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (1)
  • Slf4j (18-58)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (1)
  • Slf4j (14-47)
src/test/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserServiceTest.java (1)
  • Slf4j (31-141)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java (1)
  • Slf4j (26-169)
src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/redis/RedisService.java (1)
  • Service (11-39)
src/test/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserServiceTest.java (1)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (1)
  • Slf4j (30-164)
🔇 Additional comments (8)
src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java (1)

29-33: 로그아웃 로직이 새로운 DTO 기반 흐름과 잘 맞춰졌네요.

RefreshTokenRequest를 요청 본문으로 받아서 JwtService에 전달하는 방식으로 변경되었고, 구조가 깔끔합니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/controller/UserController.java (1)

28-32: 마이페이지 조회 엔드포인트가 잘 구현되었습니다.

@CurrentUserId 리졸버를 사용해서 인증된 사용자 ID를 주입받는 방식이 깔끔하고, 서비스 레이어로의 위임도 명확합니다.

src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java (6)

36-38: 새로운 의존성 추가 확인

마이페이지 및 이메일 인증 기능을 위한 의존성이 추가되었습니다. 구조가 깔끔하네요!


70-78: 비밀번호 변경 로직 구현

기존 비밀번호를 검증한 후 새 비밀번호로 변경하는 로직이 정확합니다.


81-87: 마이페이지 조회 기능 구현

사용자의 닉네임과 가입한 클럽 목록을 조회하는 로직입니다. 간결하고 명확하네요!


90-101: 마이페이지 업데이트 로직

닉네임과 클럽 목록을 업데이트한 후 최신 정보를 반환하는 구조가 잘 설계되었습니다.


48-50: getValues 미존재 시 예외 발생 정상 동작 확인
RedisService.getValues가 키가 없을 때 "false"를 반환하므로, !"VERIFIED".equals(mailService.getStoredCode(...)) 조건에서 예외가 정상적으로 발생합니다.


62-67: 리뷰 코멘트 오류: 메일 전송 후에만 DB 업데이트됩니다.
sendPasswordMail 호출에서 예외 발생 시 user.setPassword가 실행되지 않으므로, 메일 전송 실패 시에는 DB에 저장된 비밀번호가 변경되지 않습니다. 해당 우려는 사실이 아닙니다.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@yskim6772 yskim6772 left a comment

Choose a reason for hiding this comment

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

👍🏻👍🏻👍🏻

@yskim6772 yskim6772 merged commit 3dfcee4 into develop Oct 1, 2025
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Oct 12, 2025
1 task
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.

[Feat] 마이페이지 기능

3 participants