Skip to content

[USER] 닉네임 자동 생성 및 프로필 편집 기능 구현#45

Merged
usn757 merged 4 commits intoPETTY-HUB:mainfrom
taehyun32:feat/auth
May 31, 2025
Merged

[USER] 닉네임 자동 생성 및 프로필 편집 기능 구현#45
usn757 merged 4 commits intoPETTY-HUB:mainfrom
taehyun32:feat/auth

Conversation

@taehyun32
Copy link
Copy Markdown
Member

📜 PR 내용 요약

  • 로컬 및 소셜 로그인/가입 시 사용자의 닉네임(displayName)을 자동으로 생성하는 기능을 구현했습니다.
  • 이메일 인증 메일의 HTML 구조 및 발신 정보를 개선했습니다.
  • 사용자 프로필 수정 기능을 완성하고, 닉네임 중복 검사 기능을 추가했습니다.
  • 사용자 엔티티에 이름(name) 필드를 추가하고 닉네임(displayName)과 분리했습니다.

⚒️ 작업 및 변경 내용(상세하게)

1. 로그인/가입 시 자동 닉네임 생성 기능 구현

  • DisplayNameGenerator 유틸리티 클래스 추가:
    • "형용사 + 동물 + 3자리 숫자" 형태의 유니크한 닉네임을 생성하는 로직을 구현했습니다.
    • 생성된 닉네임이 이미 사용 중인지 UsersRepository.existsByDisplayName 메서드를 통해 확인하고, 중복 시 최대 50회까지 재시도합니다.
    • 재시도 후에도 유니크한 닉네임을 생성하지 못할 경우를 대비하여 UUID 기반의 폴백(fallback) 닉네임 생성 로직을 추가했습니다.
  • 로컬 로그인/가입 플로우 수정:
    • 로컬 회원 가입 시 사용자가 입력하는 필드를 username, password, name, phone으로 변경했습니다 (JoinDTO에서 displayName 필드를 제거하고 name 필드를 추가).
    • JoinService.joinProcess 메서드에서 DisplayNameGenerator를 주입받아 사용자의 displayName을 자동으로 생성하여 저장하도록 로직을 추가했습니다. 사용자가 입력한 namephone도 함께 저장합니다.
  • 소셜 로그인/가입 플로우 수정:
    • CustomOAuth2UserServiceDisplayNameGenerator를 주입받도록 수정했습니다.
    • saveOrUpdate 메서드에서 소셜 로그인 성공 시 DisplayNameGenerator를 사용하여 사용자의 displayName을 자동으로 생성하여 저장하도록 로직을 추가했습니다.
    • 제공자로부터 받은 사용자 정보(name, nickname, login 등)는 사용자의 name 필드에 설정하도록 변경했습니다. 기존에는 이 정보가 displayName으로 설정되었습니다.
  • Users 엔티티 및 UsersRepository 수정:
    • Users 엔티티에 사용자의 실제 이름 또는 소셜 제공자 이름에 해당하는 name 필드를 추가했습니다.
    • 기존의 displayName 필드는 닉네임으로 사용되며, unique = true 속성을 추가하여 중복을 허용하지 않도록 변경했습니다.
    • UsersRepositoryBoolean existsByDisplayName(String displayName); 메서드를 추가했습니다.

2. 이메일 인증 메일 구조 개선

  • EmailService.sendEmail 메서드 수정:
    • 이메일 발신자 정보를 krpetty54@gmail.com과 함께 "Petty Team"이라는 표시 이름을 사용하여 설정하도록 변경했습니다.
    • 이메일 제목을 "[Petty] 이메일 인증 코드입니다."에서 "Petty 인증 코드"로 변경했습니다.
    • HTML 본문의 구조와 내용을 더 명확하고 보기 좋게 개선했습니다. 인증 코드 유효 시간 및 발신 전용 정보 등을 추가했습니다.
    • sendVerificationCode 메서드에서 이메일 전송 실패 시 MessagingException 뿐만 아니라 UnsupportedEncodingException도 함께 처리하도록 예외 처리를 보강했습니다.

3. 프로필 수정 기능 완성

  • 프로필 정보 조회 및 수정 백엔드 구현:
    • UserProfileEditDTOname 필드를 추가하고 displayName 필드를 닉네임으로 명시하도록 수정했습니다.
    • UserService에 사용자 ID(UUID)로 프로필 정보 (name, displayName, phone)를 조회하는 getUserById 메서드를 추가했습니다.
    • UserService에 프로필 정보 (name, displayName, phone)를 받아 사용자 엔티티를 업데이트하는 updateUserProfile 메서드를 추가했습니다.
    • updateUserProfile 메서드 내에서 수정하려는 닉네임(newDisplayName)이 현재 사용자의 닉네임과 다를 경우에만 usersRepository.existsByDisplayName를 호출하여 중복 검사를 수행하고, 중복 시 예외를 발생시키도록 로직을 추가했습니다.
  • 닉네임 중복 검사 API 구현:
    • UserService에 특정 닉네임의 사용 가능 여부를 확인하는 checkDisplayName 메서드를 추가했습니다. 이 메서드는 해당 닉네임이 이미 존재하는지 확인하되, 현재 사용자의 닉네임인 경우는 사용 가능한 것으로 처리하는 isCurrentUserDisplayName 헬퍼 메서드를 활용합니다. 결과는 available 상태와 메시지를 담은 Map 형태로 반환합니다.
    • UsersApiController/api/users/check-displayname GET 엔드포인트를 추가하여 클라이언트에서 닉네임 중복 검사를 요청할 수 있도록 했습니다. 이 API는 인증된 사용자만 호출할 수 있습니다.
  • 프로필 수정 프론트엔드 및 컨트롤러 연동:
    • UsersController/profile/edit GET 엔드포인트에서 인증 상태를 확인하고, userService.getUserById를 통해 현재 사용자의 프로필 정보를 불러와 userProfile 모델 속성으로 profile_edit.html 뷰에 전달하도록 수정했습니다. 프로필 정보를 가져오는 중 오류 발생 시 예외를 처리합니다.
    • UsersController/profile/update POST 엔드포인트에서 @ModelAttribute로 받은 UserProfileEditDTO 객체를 userService.updateUserProfile 메서드에 전달하여 프로필 업데이트를 수행하도록 수정했습니다.
    • 프로필 업데이트 성공 시 성공 메시지를, 실패 시 에러 메시지를 RedirectAttributes에 담아 메인 페이지 또는 프로필 수정 페이지로 리다이렉트하도록 했습니다. 실패 시 입력값을 유지하기 위해 userProfileEditDTO를 플래시 속성으로 추가했습니다.
    • profile_edit.html 뷰에 이름, 닉네임, 전화번호 입력 필드와 저장 버튼을 구현하고 디자인을 개선했습니다.
    • 닉네임 입력 필드 변경 시 /api/users/check-displayname API를 호출하여 닉네임 중복 여부를 실시간으로 확인하고 사용자에게 피드백을 제공하는 클라이언트 측 JavaScript 로직을 추가했습니다. 페이지 로드 시 초기 유효성 검사를 수행합니다.

📚 기타 참고 사항

  • 사용자의 고유한 식별 값인 닉네임(displayName)과 사용자의 실제 이름 또는 제공자 이름인 name 필드가 목적에 맞게 분리되어 사용됩니다.
  • 자동 생성되는 닉네임은 "형용사 + 동물 + 3자리 숫자" 패턴을 따르며, 중복 시 재시도 메커니즘을 통해 유니크함을 보장합니다.
  • 프로필 수정 시 닉네임 변경은 현재 사용자의 기존 닉네임을 제외하고 중복 검사를 수행합니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2025

Summary by CodeRabbit

  • 신규 기능

    • 닉네임(별명) 중복 확인 API가 추가되어, 프로필 수정 시 닉네임 중복 여부를 실시간으로 확인할 수 있습니다.
    • 닉네임은 자동 생성되며, 회원가입 및 소셜 로그인 시 고유한 닉네임이 부여됩니다.
  • 개선 사항

    • 프로필 수정 페이지가 새롭게 디자인되고, 입력값에 대한 실시간 유효성 검사 및 피드백이 제공됩니다.
    • 닉네임, 이름, 전화번호 입력 시 즉각적인 유효성 메시지와 스타일이 반영됩니다.
    • 닉네임 변경 시 중복 확인 버튼이 추가되어, 중복 여부를 직접 확인할 수 있습니다.
    • 프로필 수정 시 오류 발생 시 입력값이 보존되고, 적절한 안내 메시지와 함께 원래 페이지로 이동합니다.
  • 버그 수정

    • 프로필 수정 및 회원정보 처리 시 예외 상황에 대한 안내 메시지와 리다이렉트 처리가 개선되었습니다.
  • 기타

    • 이메일 인증 메일의 제목과 본문이 변경되고, 발신자명이 "Petty Team"으로 표시됩니다.
    • 데이터 구조상 이름(name)과 닉네임(displayName)이 분리되어 관리됩니다.

Walkthrough

이 변경 사항은 사용자 이름(name)과 닉네임(displayName)을 분리하여 관리하도록 전체 사용자 도메인을 리팩터링하고, 닉네임 중복 확인 API 및 프론트엔드 실시간 중복 체크 기능을 추가합니다. 또한 프로필 수정 시 예외 처리와 사용자 피드백이 강화되었으며, 닉네임 자동 생성 로직이 도입되었습니다.

Changes

파일/경로 요약 변경 내용 요약
.../UsersApiController.java UserService 의존성 주입, 닉네임 중복 확인 GET API(/api/check-displayname) 추가, 불필요한 import 제거
.../UsersController.java 프로필 수정/업데이트 시 try-catch로 예외 처리 강화, Redirect 경로 및 사용자 입력 보존 개선
.../dto/JoinDTO.java
.../dto/UserProfileEditDTO.java
.../entity/Users.java
displayName 필드를 name으로 변경, 별도 displayName(닉네임) 필드 추가 및 유니크 제약, DTO/엔티티/폼 구조 일관화
.../oauth2/CustomOAuth2UserService.java
.../service/JoinService.java
닉네임 자동 생성(DisplayNameGenerator) 도입, OAuth2/회원가입 시 닉네임을 자동 생성하여 저장
.../repository/UsersRepository.java existsByDisplayName(String displayName) 중복 확인용 메서드 추가
.../service/UserService.java 프로필 DTO 반환 시 name/displayName 모두 포함, 닉네임 중복 검사 및 본인 닉네임 확인 메서드 추가, 프로필 업데이트 시 닉네임 중복 검사 로직 추가
.../service/EmailService.java 이메일 발송자명 추가, 제목/본문/푸터 텍스트 개선, 예외 처리 범위 확장
.../util/DisplayNameGenerator.java 닉네임 자동 생성기 신규 클래스 추가(형용사+동물+숫자 조합, 최대 50회 중복 검사, 실패 시 UUID fallback)
.../templates/join.html 가입 폼에서 displayNamename으로 필드 바인딩 변경
.../templates/profile_edit.html 프로필 수정 UI/UX 전면 개선: 닉네임 중복 체크 버튼 및 API 연동, 입력 실시간 검증, 토큰 만료 처리, 스타일링 강화, 에러/성공 메시지 표시 등

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant UsersApiController
    participant UserService
    participant UsersRepository

    User->>Frontend: 닉네임 입력 및 중복 확인 버튼 클릭
    Frontend->>UsersApiController: GET /api/check-displayname?displayName=xxx (with token)
    UsersApiController->>UserService: checkDisplayName(currentUserId, displayName)
    UserService->>UsersRepository: existsByDisplayName(displayName)
    UsersRepository-->>UserService: true/false
    UserService-->>UsersApiController: { available: true/false, message: ... }
    UsersApiController-->>Frontend: JSON 응답 반환
    Frontend-->>User: 중복 결과 메시지 및 입력 상태 반영
Loading
sequenceDiagram
    participant User
    participant Frontend
    participant UsersController
    participant UserService
    participant UsersRepository

    User->>Frontend: 프로필 수정 폼 제출
    Frontend->>UsersController: POST /profile/edit
    UsersController->>UserService: updateUserProfile(userId, userProfileEditDTO)
    UserService->>UsersRepository: existsByDisplayName(newDisplayName)
    alt 중복 O & 본인 아님
        UserService-->>UsersController: Exception 발생
        UsersController-->>Frontend: 에러 메시지 및 입력값 복구
    else 중복 X 또는 본인
        UserService->>UsersRepository: save(user)
        UsersController-->>Frontend: 성공 리다이렉트
    end
Loading

Possibly related PRs

  • PETTY-HUB/PETTY-BACK#25: UserService 클래스 도입 및 OAuth2 로그인/프로필 편집 엔드포인트 추가. 이번 PR과 사용자 프로필 관리 및 서비스 계층에서 연관.

Poem

🐰
닉네임은 이제 두 개,
이름과 별명, 깔끔하게!
중복 체크 버튼 콕,
자동 생성 닉네임 똑똑!
토끼도 깡총, 유저도 방긋—
프로필 수정, 한결 더 똑똑!
🌸

✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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.

@taehyun32 taehyun32 changed the title [USER] 사용자 닉네임 관리 시스템 구현 [USER] 닉네임 자동 생성 및 프로필 편집 기능 구현 May 31, 2025
@taehyun32
Copy link
Copy Markdown
Member Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (10)
src/main/java/io/github/petty/users/util/DisplayNameGenerator.java (1)

14-14: Random 인스턴스의 스레드 안전성을 개선할 수 있습니다.

현재 Random 인스턴스가 인스턴스 필드로 선언되어 있어 멀티스레드 환경에서 성능 저하가 발생할 수 있습니다.

더 나은 성능을 위해 ThreadLocalRandom을 사용하는 것을 권장합니다:

-import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;

-private final Random random = new Random();

그리고 사용 부분에서:

-String adjective = ADJECTIVES[random.nextInt(ADJECTIVES.length)];
-String animal = ANIMALS[random.nextInt(ANIMALS.length)];
-String number = String.format("%03d", random.nextInt(1000));
+String adjective = ADJECTIVES[ThreadLocalRandom.current().nextInt(ADJECTIVES.length)];
+String animal = ANIMALS[ThreadLocalRandom.current().nextInt(ANIMALS.length)];
+String number = String.format("%03d", ThreadLocalRandom.current().nextInt(1000));
src/main/java/io/github/petty/users/oauth2/CustomOAuth2UserService.java (1)

99-112: 이메일 폴백 로직의 보안성을 검토해주세요.

이메일 주소에서 @ 앞부분을 사용자 이름으로 사용하는 폴백 로직이 있습니다. 이는 예측 가능한 패턴을 만들 수 있어 보안상 고려가 필요합니다.

더 안전한 폴백 전략을 고려해보세요:

} else {
-    user.setName(email.split("@")[0]);
+    user.setName("User_" + UUID.randomUUID().toString().substring(0, 8));
}

또는 기본값을 설정하되 사용자가 나중에 수정할 수 있도록 유도하는 방식도 고려할 수 있습니다.

src/main/java/io/github/petty/users/controller/UsersApiController.java (1)

107-128: 닉네임 중복 체크 API 구현이 적절합니다.

API 설계와 보안 처리가 잘 되어 있습니다:

  • 인증된 사용자만 접근 가능
  • 현재 사용자 ID를 통한 자신의 닉네임 제외 로직
  • 적절한 HTTP 상태 코드 반환

다만 몇 가지 개선사항을 제안합니다:

입력 검증과 에러 처리를 강화할 수 있습니다:

 @GetMapping("/check-displayname")
 public ResponseEntity<Map<String, Object>> checkDisplayName(
-        @RequestParam String displayName) {
+        @RequestParam @NotBlank(message = "닉네임을 입력해주세요") String displayName) {

     Authentication auth = SecurityContextHolder.getContext().getAuthentication();
     if (auth == null || !auth.isAuthenticated() || auth instanceof AnonymousAuthenticationToken) {
         return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
     }

+    try {
         // 현재 사용자 ID 가져오기
         UUID currentUserId = userService.getCurrentUserId(auth.getPrincipal());

         // 한 번의 서비스 호출로 모든 정보 가져오기
         Map<String, Object> result = userService.checkDisplayName(currentUserId, displayName);

         Map<String, Object> response = new HashMap<>();
         response.put("available", result.get("available"));
         response.put("message", result.get("message"));

         return ResponseEntity.ok(response);
+    } catch (Exception e) {
+        Map<String, Object> errorResponse = new HashMap<>();
+        errorResponse.put("available", false);
+        errorResponse.put("message", "서버 오류가 발생했습니다.");
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
+    }
 }
src/main/java/io/github/petty/users/service/EmailService.java (1)

60-75: HTML 구조 개선이 필요합니다.

이메일 콘텐츠가 개선되었지만, 접근성과 보안 측면에서 몇 가지 개선사항을 제안합니다:

-<div style='display:block;margin-bottom:20px;text-decoration:none;color:#000000;width:5.5rem;font-weight:bold' target='_blank'>
+<div style='display:block;margin-bottom:20px;color:#000000;font-weight:bold'>
 <h2 style='color:#f39c12'>Petty</h2>
 </div>

추가로 이메일 템플릿을 별도 파일로 분리하는 것을 고려해보세요:

// resources/templates/email-verification.html 파일 생성 후
private String loadEmailTemplate(String code) {
    // 템플릿 엔진 사용하여 코드 바인딩
    return templateEngine.process("email-verification", 
        Map.of("verificationCode", code));
}
src/main/java/io/github/petty/users/controller/UsersController.java (1)

94-112: 프로필 업데이트 에러 핸들링이 크게 개선되었습니다.

사용자 경험 향상을 위한 여러 개선사항이 적용되었습니다:

  • 입력값 유지 로직으로 재입력 불편함 해소
  • 명확한 에러 메시지 제공
  • 프로필 편집 페이지로 다시 리다이렉트하여 수정 가능

예외 처리를 더 세분화하여 구체적인 에러 메시지를 제공할 수 있습니다:

 } catch (Exception e) {
-    // 모든 에러를 하나로 처리
-    String errorMessage = e.getMessage();
-    if (errorMessage == null || errorMessage.trim().isEmpty()) {
-        errorMessage = "프로필 수정 중 오류가 발생했습니다.";
-    }
+    String errorMessage;
+    if (e instanceof IllegalArgumentException) {
+        errorMessage = e.getMessage(); // 닉네임 중복 등 비즈니스 로직 오류
+    } else if (e instanceof DataAccessException) {
+        errorMessage = "데이터베이스 오류가 발생했습니다.";
+    } else {
+        errorMessage = "프로필 수정 중 오류가 발생했습니다.";
+        log.error("프로필 업데이트 중 예상치 못한 오류", e); // 디버깅용 로그
+    }

     redirectAttributes.addFlashAttribute("errorMessage", errorMessage);
     redirectAttributes.addFlashAttribute("userProfile", userProfileEditDTO);
     return "redirect:/profile/edit";
 }
src/main/resources/templates/profile_edit.html (4)

6-143: CSS 스타일링이 크게 개선되었습니다.

사용자 인터페이스가 훨씬 직관적이고 사용하기 쉬워졌습니다:

  • 입력 상태별 시각적 피드백 (valid/invalid 클래스)
  • 일관된 색상 체계와 브랜딩
  • 반응형 디자인 고려

CSS를 별도 파일로 분리하여 유지보수성을 높이는 것을 고려해보세요:

-    <style>
-        /* 모든 CSS 코드 */
-    </style>
+    <link rel="stylesheet" th:href="@{/css/profile-edit.css}">

203-243: 사용자 프로필 로드 로직이 견고합니다.

토큰 만료 시 자동 갱신을 포함한 인증 처리가 잘 구현되어 있습니다.

에러 처리를 더 세분화할 수 있습니다:

         } catch (error) {
-            console.error('프로필 정보 조회 실패:', error);
-            redirectToLogin();
+            console.error('프로필 정보 조회 실패:', error);
+            if (error.name === 'TypeError' && !navigator.onLine) {
+                alert('네트워크 연결을 확인해주세요.');
+            } else {
+                alert('프로필 정보를 불러올 수 없습니다.');
+            }
+            redirectToLogin();
         }

306-366: 닉네임 중복 체크 로직이 잘 구현되었습니다.

실시간 중복 체크와 토큰 갱신 처리가 포함된 견고한 구현입니다.

중복된 토큰 갱신 로직을 공통 함수로 추출할 수 있습니다:

+    // 공통 API 호출 함수 (토큰 갱신 포함)
+    async function apiCall(url, options = {}) {
+        let response = await fetch(url, options);
+        
+        if (response.status === 401) {
+            const refreshResponse = await fetch('/api/auth/refresh', { method: 'POST' });
+            if (refreshResponse.ok) {
+                response = await fetch(url, options);
+            } else {
+                redirectToLogin();
+                return null;
+            }
+        }
+        return response;
+    }

     // 닉네임 중복 확인에서 사용
-    const response = await fetch(`/api/check-displayname?displayName=${encodeURIComponent(displayName)}`);
-    
-    if (response.ok) {
-        const data = await response.json();
-        handleDisplayNameCheckResult(data);
-    } else if (response.status === 401) {
-        // 토큰 갱신 로직...
-    }
+    const response = await apiCall(`/api/check-displayname?displayName=${encodeURIComponent(displayName)}`);
+    if (response && response.ok) {
+        const data = await response.json();
+        handleDisplayNameCheckResult(data);
+    }

404-440: 폼 검증과 제출 로직이 안전하게 구현되었습니다.

모든 필드의 검증 상태를 확인하고 제출 버튼 활성화를 제어하는 로직이 잘 구현되어 있습니다.

초기 검증 수행 부분을 더 간결하게 작성할 수 있습니다:

-        // 페이지 로드 시 초기 검증 수행
-        if (nameInput.value.trim()) {
-            nameInput.dispatchEvent(new Event('input'));
-        }
-        if (displayNameInput.value.trim()) {
-            displayNameInput.dispatchEvent(new Event('input'));
-        }
-        if (phoneInput.value.trim()) {
-            phoneInput.dispatchEvent(new Event('input'));
-        }
+        // 페이지 로드 시 초기 검증 수행
+        [nameInput, displayNameInput, phoneInput].forEach(input => {
+            if (input.value.trim()) {
+                input.dispatchEvent(new Event('input'));
+            }
+        });
src/main/java/io/github/petty/users/service/UserService.java (1)

66-72: 닉네임 중복 검사 로직을 개선해주세요.

현재 구현은 기본적으로 올바르지만 예외 처리 방식을 개선할 수 있습니다:

  1. RuntimeException 대신 더 구체적인 예외 타입 사용을 고려해보세요
  2. 현재 닉네임과 새 닉네임이 같은 경우를 올바르게 처리하고 있습니다

더 구체적인 예외 처리를 위해 다음과 같이 개선할 수 있습니다:

-                throw new RuntimeException("이미 사용 중인 닉네임입니다.");
+                throw new IllegalArgumentException("이미 사용 중인 닉네임입니다.");

또는 비즈니스 로직에 특화된 커스텀 예외를 정의하는 것을 고려해보세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82ff5b0 and 090d240.

📒 Files selected for processing (13)
  • src/main/java/io/github/petty/users/controller/UsersApiController.java (3 hunks)
  • src/main/java/io/github/petty/users/controller/UsersController.java (2 hunks)
  • src/main/java/io/github/petty/users/dto/JoinDTO.java (1 hunks)
  • src/main/java/io/github/petty/users/dto/UserProfileEditDTO.java (1 hunks)
  • src/main/java/io/github/petty/users/entity/Users.java (1 hunks)
  • src/main/java/io/github/petty/users/oauth2/CustomOAuth2UserService.java (3 hunks)
  • src/main/java/io/github/petty/users/repository/UsersRepository.java (1 hunks)
  • src/main/java/io/github/petty/users/service/EmailService.java (3 hunks)
  • src/main/java/io/github/petty/users/service/JoinService.java (3 hunks)
  • src/main/java/io/github/petty/users/service/UserService.java (3 hunks)
  • src/main/java/io/github/petty/users/util/DisplayNameGenerator.java (1 hunks)
  • src/main/resources/templates/join.html (1 hunks)
  • src/main/resources/templates/profile_edit.html (2 hunks)
🔇 Additional comments (29)
src/main/java/io/github/petty/users/repository/UsersRepository.java (1)

13-13: 닉네임 중복 체크를 위한 메서드 추가가 적절합니다.

Spring Data JPA 명명 규칙을 올바르게 따르고 있으며, 닉네임 유일성 검증 기능에 필요한 메서드입니다.

src/main/java/io/github/petty/users/dto/JoinDTO.java (1)

13-13: 실명과 닉네임을 구분하는 리팩터링이 잘 적용되었습니다.

필드명을 displayName에서 name으로 변경하여 사용자의 실제 이름을 나타내도록 명확히 했습니다. 닉네임은 시스템에서 자동 생성되므로 회원가입 시에는 실명만 입력받는 것이 적절합니다.

src/main/resources/templates/join.html (1)

253-254: 백엔드 DTO 변경에 맞춰 템플릿 바인딩이 올바르게 업데이트되었습니다.

th:field="*{name}"th:errors="*{name}"으로 변경하여 JoinDTO의 필드명 변경사항과 일치시켰습니다.

src/main/java/io/github/petty/users/dto/UserProfileEditDTO.java (1)

13-15: 실명과 닉네임을 명확히 구분한 DTO 구조가 훌륭합니다.

name(실명)과 displayName(닉네임)을 분리하여 관리할 수 있도록 구조를 개선했고, 한국어 주석을 통해 각 필드의 용도를 명확히 했습니다. 프로필 편집 기능에서 두 필드를 독립적으로 관리할 수 있게 되었습니다.

src/main/java/io/github/petty/users/service/JoinService.java (3)

7-7: DisplayNameGenerator 의존성 주입이 적절하게 구현되었습니다.

새로운 유틸리티 컴포넌트가 올바르게 주입되어 유니크한 닉네임 생성 기능을 지원합니다.

Also applies to: 18-18


23-23: 사용자 이름과 닉네임 분리가 올바르게 구현되었습니다.

DTO에서 name 필드를 사용하여 실제 이름을 저장하도록 변경된 것이 적절합니다. 이는 닉네임과 실명을 분리하는 전체적인 리팩터링과 일치합니다.

Also applies to: 35-35


40-41: 자동 닉네임 생성 로직이 적절히 통합되었습니다.

DisplayNameGenerator를 사용하여 유니크한 닉네임을 자동 생성하는 로직이 깔끔하게 구현되었습니다. 사용자 가입 시 중복 없는 닉네임이 보장됩니다.

src/main/java/io/github/petty/users/util/DisplayNameGenerator.java (3)

29-46: 유니크 닉네임 생성 로직이 견고하게 구현되었습니다.

50회 재시도 로직과 UUID 폴백 메커니즘이 잘 설계되어 있어 유니크성을 보장하면서도 무한루프를 방지합니다. 데이터베이스 조회를 통한 중복 체크도 적절합니다.


17-26: 한국어 형용사와 동물 이름 배열이 적절하게 정의되었습니다.

사용자 친화적인 한국어 닉네임 생성을 위한 단어 선택이 적절하며, 반려동물 테마와 잘 어울립니다.


49-55: 닉네임 형식 생성이 일관성 있게 구현되었습니다.

'형용사 + 동물 + 3자리 숫자' 형식으로 생성하는 로직이 명확하고 예측 가능한 패턴을 제공합니다. String.format을 사용한 숫자 포맷팅도 적절합니다.

src/main/java/io/github/petty/users/entity/Users.java (3)

27-28: 실제 이름을 위한 name 필드가 적절하게 정의되었습니다.

사용자의 실명 또는 소셜 로그인 제공자의 이름을 저장하는 필드로서 적절한 제약조건(non-null, 길이 50)이 설정되었습니다.


30-31: 닉네임을 위한 displayName 필드가 올바르게 구현되었습니다.

자동 생성되는 유니크 닉네임을 위한 필드로서 unique = true 제약조건이 적절하게 설정되어 중복을 방지합니다. 주석도 명확하게 작성되었습니다.


27-31:

Details

❓ Verification inconclusive

데이터베이스 마이그레이션 계획을 확인해주세요.

기존 displayName 필드를 namedisplayName으로 분리하는 변경사항은 기존 데이터에 영향을 줄 수 있습니다.

데이터베이스 마이그레이션 전략이 준비되어 있는지 확인하기 위해 다음 스크립트를 실행해보세요:


🏁 Script executed:

#!/bin/bash
# 데이터베이스 마이그레이션 파일 확인
fd -e sql . | head -10
echo "--- Liquibase/Flyway 마이그레이션 파일 확인 ---"
fd "migration\|changelog" .
echo "--- application.yml DDL 설정 확인 ---"
rg -A 3 -B 3 "ddl-auto\|hibernate\.ddl"

Length of output: 258


데이터베이스 마이그레이션 준비 여부 확인 필요

스크립트 실행 결과 리포지토리 내에 아래 항목이 전혀 발견되지 않았습니다.
기존 displayName 필드를 namedisplayName으로 분리하는 변경은 기존 데이터를 손상시킬 수 있으므로, 아래 사항들을 직접 점검해 주세요:

  • .sql 마이그레이션 스크립트 파일 유무 확인
  • Liquibase/Flyway용 changelog·migration 파일 위치 확인
  • application.yml 또는 application.propertiesddl-auto/hibernate.ddl-auto 설정 검토
  • 기존 데이터 마이그레이션 전략(기본값 설정, 데이터 복사·이동 로직 등) 수립 여부 확인
src/main/java/io/github/petty/users/oauth2/CustomOAuth2UserService.java (3)

6-6: DisplayNameGenerator 의존성이 적절하게 추가되었습니다.

OAuth2 사용자 생성 시에도 일관된 유니크 닉네임 생성을 위해 동일한 컴포넌트를 사용하는 것이 좋은 설계입니다.

Also applies to: 25-25


92-94: 자동 닉네임 생성이 OAuth2 플로우에 잘 통합되었습니다.

소셜 로그인 사용자도 동일한 유니크 닉네임 생성 로직을 사용하여 일관성을 유지합니다. 주석도 명확하게 작성되었습니다.


97-112: OAuth2 제공자별 이름 처리 로직이 올바르게 구현되었습니다.

GitHub와 Kakao 각각의 속성 구조에 맞게 사용자 이름을 추출하여 name 필드에 설정하는 로직이 적절합니다. 각 제공자에서 데이터가 없을 경우의 폴백 로직도 잘 구현되어 있습니다.

src/main/java/io/github/petty/users/controller/UsersApiController.java (1)

33-38: 의존성 주입 구조가 올바르게 구현되었습니다.

생성자 주입 패턴을 사용하여 UserService를 안전하게 주입하고 있으며, 불변성을 보장하는 좋은 구조입니다.

src/main/java/io/github/petty/users/service/EmailService.java (4)

39-39: 예외 처리 개선이 적절합니다.

UnsupportedEncodingException을 추가하여 인코딩 관련 오류를 포함한 더 포괄적인 예외 처리가 구현되었습니다.

Also applies to: 50-50


54-54: 발신자 정보 개선이 좋습니다.

"Petty Team"이라는 표시 이름을 추가하여 사용자에게 더 친숙하고 전문적인 이메일 발신자 정보를 제공하고 있습니다.


56-56: 이메일 제목이 더 간결해졌습니다.

"Petty 인증 코드"로 간소화된 제목이 사용자에게 더 명확하고 직관적입니다.


81-81: 로그 메시지 개선이 적절합니다.

인증 코드 값을 로그에서 제거하여 보안을 강화한 것이 좋은 개선사항입니다.

src/main/java/io/github/petty/users/controller/UsersController.java (1)

68-81: 프로필 로드 시 예외 처리가 잘 구현되었습니다.

try-catch 블록을 추가하여 프로필 정보 조회 실패 시 사용자에게 적절한 피드백을 제공하고 안전하게 홈페이지로 리다이렉트하는 로직이 좋습니다.

src/main/resources/templates/profile_edit.html (2)

150-187: HTML 구조가 잘 구성되었습니다.

폼 구조와 사용자 피드백 영역이 체계적으로 구성되어 있습니다:

  • 로딩 상태 표시
  • 에러/성공 메시지 영역
  • 입력 그룹과 검증 메시지 배치

386-401: 전화번호 검증 로직이 적절합니다.

한국 휴대폰 번호 패턴에 맞는 정규식을 사용하여 올바른 검증이 이루어지고 있습니다.

src/main/java/io/github/petty/users/service/UserService.java (5)

12-13: import 문 추가가 적절합니다.

새로 추가된 checkDisplayName 메서드에서 사용하는 HashMap과 Map 인터페이스를 위한 import 문이 정확하게 추가되었습니다.


57-57: DTO 생성자 변경이 올바르게 적용되었습니다.

namedisplayName을 분리한 새로운 엔티티 구조에 맞게 UserProfileEditDTO 생성자가 정확하게 수정되었습니다. 사용자의 실제 이름과 닉네임을 구분하여 반환하는 로직이 적절합니다.


74-74: name 필드 설정이 적절하게 추가되었습니다.

사용자의 실제 이름(name)과 닉네임(displayName)을 분리한 새로운 구조에 맞게 name 필드 설정이 올바르게 추가되었습니다.


81-99: 닉네임 유효성 검사 메서드가 잘 구현되었습니다.

checkDisplayName 메서드의 로직이 올바릅니다:

  • 현재 사용자의 닉네임인 경우를 적절히 처리
  • 사용 가능 여부와 메시지를 명확하게 반환
  • API 응답에 적합한 Map 구조 사용

메서드의 책임이 명확하고 재사용 가능한 구조입니다.


101-105: 헬퍼 메서드가 적절하게 구현되었습니다.

isCurrentUserDisplayName 메서드가 null 처리를 포함하여 안전하게 구현되었습니다. 사용자가 존재하지 않는 경우를 적절히 처리하고 있으며, 닉네임 비교 로직도 정확합니다.

@s0ooo0k
Copy link
Copy Markdown
Member

s0ooo0k commented May 31, 2025

  • 프로필 수정, 닉네임 자동 생성 등 사용자를 고려한 기능이 돋보였습니다!
  • 추후 유저 인증 부분만 타 기능과 잘 연동되면 좋을 거 같습니다
  • 끝까지 너무 고생하셨습니다 💯

@usn757
Copy link
Copy Markdown
Contributor

usn757 commented May 31, 2025

이번 PR은 사용자 ID 관리, 프로필 기능 및 사용자 경험 측면에서 상당한 개선을 이룬 포괄적이고 완성도 높은 작업으로 판단됩니다.

  • 자동 닉네임 생성 (DisplayNameGenerator):

    • displayName에 '형용사 + 동물 + 숫자' 패턴을 적용한 것은 창의적이면서 사용자 친화적인 접근 방식으로 보입니다.
    • generateUniqueDisplayNameMAX_ATTEMPTS를 활용한 고유성 검사 루프 및 UUID 기반 폴백 메커니즘은 시스템의 충돌 방지 견고성을 높이는 데 기여합니다.
    • 추가 제언: 향후 확장성 또는 현지화 필요시 ADJECTIVESANIMALS 배열의 외부화(예: 설정 파일)를 고려할 수 있겠습니다. 다만, 현재의 정적 배열 방식도 충분히 적절합니다.
  • namedisplayName 분리:

    • Users.name(실제 사용자 이름 또는 소셜 로그인 제공자 이름)과 Users.displayName(고유하며, 시스템 자동 생성 또는 사용자 설정 닉네임)을 구분한 것은 각 필드의 역할을 명확히 하는 좋은 결정입니다.
    • Users 엔티티의 displayNameunique = true 제약 조건을 설정하고 UsersRepositoryexistsByDisplayName 메서드를 추가한 것은 데이터 무결성 확보에 중요한 부분입니다.
  • 프로필 수정 (UsersController, UserService, UsersApiController):

    • 실시간 닉네임 사용 가능 여부 확인을 위한 /api/users/check-displayname 엔드포인트 신설은 프로필 수정 페이지의 사용자 경험(UX)을 효과적으로 개선할 것으로 기대됩니다.
    • UserService.checkDisplayName에서 현재 사용자의 기존 displayName을 '사용 가능'으로 처리하는 로직은 적절하게 구현되었습니다.
    • UserService.updateUserProfile에서 displayName 변경 시에만 중복 검사를 수행하도록 개선한 점은 효율성을 높이는 데 기여합니다.
    • UsersController에서 try-catch 블록 및 RedirectAttributes를 활용하여 프로필 로딩/업데이트 오류 처리와 사용자 피드백을 개선한 점이 긍정적입니다. 유효성 검사 실패 시 입력 값 유지(redirectAttributes.addFlashAttribute("userProfile", userProfileEditDTO)) 기능 또한 사용자 편의를 고려한 좋은 변경입니다.
  • 이메일 서비스 (EmailService):

    • 이메일 HTML 구조, 발신자명('Petty Team') 변경 및 제목 명확화는 모두 긍정적인 개선 사항입니다.
    • sendVerificationCode의 예외 처리에 UnsupportedEncodingException을 추가한 것은 안정성을 높이는 적절한 조치입니다.
  • DTO 및 엔티티 (JoinDTO, UserProfileEditDTO, Users):

    • namedisplayName 분리에 따른 변경 사항이 관련 DTO 및 엔티티에 일관되게 적용되었습니다. JoinDTOname을 입력받고 displayName은 자동 생성되도록 변경된 점이 확인됩니다.
  • OAuth2 및 로컬 회원가입 (CustomOAuth2UserService, JoinService):

    • 로컬 회원가입(JoinService)과 소셜 로그인(CustomOAuth2UserService) 양쪽에 DisplayNameGenerator를 일관되게 통합하여 모든 사용자에게 고유 displayName이 부여되도록 한 점은 바람직합니다.
  • 코드 및 PR 품질:

    • PR 설명이 상세하여 변경 내용과 그 배경을 명확히 파악하는 데 도움이 됩니다.
    • 커밋 메시지 또한 이해하기 쉽게 작성되었습니다.
    • 변경 범위가 넓음에도 불구하고 체계적으로 잘 정리된 것으로 판단됩니다.

총평하자면, 이번 변경 사항들은 플랫폼 기능과 사용자 경험을 의미있게 향상시킬 것으로 기대됩니다. 수고하셨습니다.

@usn757 usn757 merged commit e82967d into PETTY-HUB:main May 31, 2025
2 checks passed
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.

3 participants