Skip to content

Fix#78#79

Merged
Hgyeol merged 2 commits intodevelopfrom
fix#78
Sep 9, 2025
Merged

Fix#78#79
Hgyeol merged 2 commits intodevelopfrom
fix#78

Conversation

@Hgyeol
Copy link
Member

@Hgyeol Hgyeol commented Sep 9, 2025

[#78] 회원가입 로직 수정


✅ 작업 내용

  • 회원가입 로직 수정

🔗 관련 이슈

Close #78


📌 체크리스트

  • 코드 컨벤션을 지켰나요?
  • 커밋 메시지 컨벤션을 지켰나요?
  • 테스트를 완료했나요?

Summary by CodeRabbit

  • 신기능

    • 첫 로그인 시 자동으로 회원가입 페이지로 이동하며, 세션에 저장된 사용자 정보로 기본값이 미리 채워집니다.
    • 최초 가입 정보를 조회할 수 있는 화면 진입이 추가되었습니다.
  • 변경 사항

    • 회원가입 절차가 간소화되어 클래스 코드만 입력하면 완료됩니다.
    • 회원가입 요청 성공 시 생성됨(201) 상태로 응답합니다.

@coderabbitai
Copy link

coderabbitai bot commented Sep 9, 2025

Walkthrough

사용자 회원가입 흐름이 개편됨. 로그인 성공 시 세션에 firstUser(UserDto)를 저장하고 /register로 리다이렉트. GET /first-register가 세션 기반 DefaultRegisterUserDto를 반환. POST /register는 RegisterUserDto의 classCode와 세션 firstUser를 사용해 UserService.registerUser(UserDto, classCode) 호출 후 201 Created 반환. DTO/서비스 시그니처 정리.

Changes

Cohort / File(s) Summary
Controller: Registration flow
src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java
기존 POST 등록 메서드 제거. GET /first-register 추가(세션의 firstUser 기반 DTO 반환). POST /register 추가(세션의 firstUser + 요청의 classCode로 등록, 201 반환). 불필요 import 정리.
DTOs: Payload 재구성
.../domain/user/presentation/dto/DefaultRegisterUserDto.java, .../domain/user/presentation/dto/RegisterUserDto.java
DefaultRegisterUserDto에서 classCode 필드 제거, @Builder/@NoArgsConstructor/@AllArgsConstructor 추가. RegisterUserDto에서 email/username/role 제거하고 classCode만 유지.
Service: API 시그니처 변경
src/main/java/hello/cluebackend/domain/user/service/UserService.java
registerUser(DefaultRegisterUserDto)registerUser(UserDto userDto, int classCode)로 변경. UserEntity 생성 시 userDto의 사용자 정보와 분리된 classCode 사용.
Security: Success handler 세션 설정
src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java
classCode == -1인 경우 세션에 firstUser(UserDto) 저장 후 /register로 리다이렉트. 나머지 로직 동일.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant U as User
    participant S as Spring Security<br/>CustomSuccessHandler
    participant Sess as HttpSession
    participant C as RegisterController
    participant SV as UserService

    U->>S: 로그인 성공
    S->>S: 사용자 classCode 확인
    alt classCode == -1 (미등록)
        S->>Sess: setAttribute("firstUser", UserDto)
        S-->>U: 302 Redirect to /register
    else 등록됨
        S-->>U: 토큰 발급 후 홈으로 리다이렉트
    end

    U->>C: GET /first-register
    C->>Sess: getAttribute("firstUser")
    C-->>U: 200 DefaultRegisterUserDto (email, username, role)

    U->>C: POST /register { classCode }
    C->>Sess: getAttribute("firstUser")
    C->>Sess: removeAttribute("firstUser")
    C->>SV: registerUser(UserDto, classCode)
    SV-->>C: 완료
    C-->>U: 201 Created
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks (3 passed, 1 warning, 1 inconclusive)

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
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.
Title Check ❓ Inconclusive 현재 PR 제목인 “Fix#78”은 이슈 번호만을 참조할 뿐 변경된 회원가입 로직의 주요 내용을 요약하지 않아 PR의 목적을 파악하기 어렵고 구체성이 부족합니다. PR 제목을 이슈 번호 대신 주요 변경 사항을 명확히 나타내도록 수정해주세요. 예를 들어 “회원가입 로직 개편: /first-register 및 /register 엔드포인트 추가”와 같이 구체적으로 요약할 수 있습니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed 링크된 이슈 #78의 목표인 “회원가입 수정”에 맞추어 컨트롤러 엔드포인트, DTO 구조, UserService 시그니처 및 인증 핸들러까지 일관되게 수정되었으므로 이슈의 요구 사항을 충족합니다.
Out of Scope Changes Check ✅ Passed 모든 변경 사항이 회원가입 로직 개편과 직접 연관되어 있으며 그 외 무관한 코드나 파일 수정이 포함되어 있지 않습니다.
Description Check ✅ Passed 현재 PR 설명은 “회원가입 로직 수정”으로 변경된 기능의 범위를 직접 언급하여 PR 본문과 코드 변경 내용이 일치하므로 변경 내역을 적절히 설명하고 있습니다.

Poem

토끼는 폴짝, 세션에 살짝
firstUser 품고 길을 떠나네
GET으로 인사, POST로 마무리
코드엔 클래스코드, 마음엔 등록의 춤
201 불빛 아래, 귀 쫑긋 박수! 🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix#78

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands 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: 3

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/hello/cluebackend/global/config/CustomSuccessHandler.java (1)

70-76: 쿠키 보안 속성 누락: Secure + SameSite(None) 설정 필요

refresh_token은 크로스 사이트(프론트: localhost:3000, 백엔드: 다른 포트/도메인) 전송이므로 기본 Lax에서는 전송되지 않거나 브라우저에 의해 차단됩니다. SameSite=None; Secure를 명시해야 합니다. HttpOnly만으로는 불충분합니다. 개발/운영 프로필별로 Secure 분기 처리하세요.

-    private Cookie createCookie(String key, String value) {
-        Cookie cookie = new Cookie(key, value);
-        cookie.setMaxAge(7 * 24  * 60 * 60);
-        // cookie.setSecure(true);
-        cookie.setPath("/");
-        cookie.setHttpOnly(true);
-
-        return cookie;
-    }
+    private void addCookie(HttpServletResponse response, String key, String value) {
+        // Spring 6+ ResponseCookie를 사용하면 SameSite 설정 가능
+        var cookie = org.springframework.http.ResponseCookie.from(key, value)
+                .httpOnly(true)
+                .secure(true) // dev 프로필에서는 환경변수로 false 전환 고려
+                .sameSite("None")
+                .path("/")
+                .maxAge(7 * 24 * 60 * 60)
+                .build();
+        response.addHeader(org.springframework.http.HttpHeaders.SET_COOKIE, cookie.toString());
+    }

그리고 호출부도 아래처럼 교체하세요.

- response.addCookie(createCookie("refresh_token", refresh));
+ addCookie(response, "refresh_token", refresh);
🧹 Nitpick comments (4)
src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java (3)

43-49: 세션 키 하드코딩 제거 및 상수화 권장

"firstUser" 문자열을 여러 곳에서 직접 사용하면 오타/변경 시 오류 위험이 큽니다. 공용 상수(예: SessionAttributes.FIRST_USER)로 추출해 컨트롤러/핸들러 간 일관성을 보장하세요.

- request.getSession().setAttribute("firstUser", userDTO);
+ request.getSession().setAttribute(SessionAttributes.FIRST_USER, userDTO);

51-58: 권한 컬렉션 비어있을 가능성 가드

iterator.next()는 권한이 비어있으면 NoSuchElementException을 유발합니다. 방어 코드를 한 줄 추가하세요.

- Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
- GrantedAuthority auth = iterator.next();
+ Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
+ if (!iterator.hasNext()) throw new IllegalStateException("권한이 없습니다.");
+ GrantedAuthority auth = iterator.next();

59-61: 토큰/쿠키 TTL 매직 넘버 상수화

가독성과 유지보수를 위해 액세스/리프레시 만료시간을 상수로 추출하세요.

src/main/java/hello/cluebackend/domain/user/service/UserService.java (1)

28-31: Optional: 반환값 제공 고려

등록 결과(예: 생성된 userId)를 반환하면 호출부에서 후속 처리(로그, 리다이렉트 파라미터)가 편해집니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4474ae2 and cf9c5c8.

📒 Files selected for processing (5)
  • src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/user/presentation/dto/DefaultRegisterUserDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/user/presentation/dto/RegisterUserDto.java (0 hunks)
  • src/main/java/hello/cluebackend/domain/user/service/UserService.java (1 hunks)
  • src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/hello/cluebackend/domain/user/presentation/dto/RegisterUserDto.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-08T23:38:29.680Z
Learnt from: fixgramwork
PR: EscapeFrame/clueBackend#23
File: src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java:17-23
Timestamp: 2025-07-08T23:38:29.680Z
Learning: This project uses OAuth2 authentication and does not require traditional password storage or validation. The RegisterUserService in src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java should not store passwords since OAuth2 handles authentication externally.

Applied to files:

  • src/main/java/hello/cluebackend/domain/user/service/UserService.java
  • src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java
🧬 Code graph analysis (4)
src/main/java/hello/cluebackend/global/config/CustomSuccessHandler.java (2)
src/main/java/hello/cluebackend/global/config/SecurityConfig.java (2)
  • Configuration (22-140)
  • Bean (36-139)
src/main/java/hello/cluebackend/domain/user/service/CustomOAuth2UserService.java (1)
  • CustomOAuth2UserService (16-70)
src/main/java/hello/cluebackend/domain/user/service/UserService.java (2)
src/main/java/hello/cluebackend/domain/user/presentation/dto/UserDto.java (2)
  • Getter (10-41)
  • UserDto (23-29)
src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java (1)
  • UserEntity (47-52)
src/main/java/hello/cluebackend/domain/user/presentation/dto/DefaultRegisterUserDto.java (3)
src/main/java/hello/cluebackend/domain/user/presentation/dto/RegisterUserDto.java (1)
  • RegisterUserDto (7-14)
src/main/java/hello/cluebackend/domain/user/presentation/dto/UserDto.java (3)
  • Getter (10-41)
  • UserDto (23-29)
  • toEntity (31-40)
src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java (1)
  • ToString (11-64)
src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java (2)
src/main/java/hello/cluebackend/domain/user/presentation/dto/RegisterUserDto.java (1)
  • RegisterUserDto (7-14)
src/main/java/hello/cluebackend/domain/user/service/CustomOAuth2UserService.java (1)
  • CustomOAuth2UserService (16-70)
🔇 Additional comments (2)
src/main/java/hello/cluebackend/domain/user/presentation/dto/DefaultRegisterUserDto.java (1)

8-15: 역할 분리 명확—구조 적절

classCode를 분리하고 email/username/role만 노출하는 DTO로 재구성된 점 좋습니다. 빌더 추가도 적절합니다.

src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java (1)

23-42: 세션 쿠키 SameSite/Secure 설정 추가 필요
application*.yml/properties에서 server.servlet.session.cookie.same-site 및 server.servlet.session.cookie.secure 설정이 발견되지 않습니다.

  • server.servlet.session.cookie.same-site=None
  • server.servlet.session.cookie.secure=true (HTTPS)
    를 프로필별로 추가해 FE(localhost:3000)→BE API 호출 시 JSESSIONID가 전송되도록 설정하세요.
    대안으로 세션 대신 단회용 등록 토큰(JWT, 짧은 TTL)을 리다이렉트 시 쿼리/프래그먼트로 전달하는 방식을 고려할 수 있습니다.

Comment on lines +23 to 32
@GetMapping("/first-register")
public DefaultRegisterUserDto showRegistrationForm(HttpServletRequest request) {
HttpSession session = request.getSession();
UserDto userDto = (UserDto) session.getAttribute("firstUser");
return DefaultRegisterUserDto.builder()
.username(userDto.getUsername())
.email(userDto.getEmail())
.role(userDto.getRole())
.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

NPE 및 세션 오용 가드: getSession(false)와 401/410 처리

세션이 없거나 firstUser가 없으면 현재 코드는 NPE가 납니다(getUsername 호출). 세션을 새로 만들지 말고(getSession(false)) 없으면 401(또는 410 Gone)로 응답하세요.

-    public DefaultRegisterUserDto showRegistrationForm(HttpServletRequest request) {
-        HttpSession session = request.getSession();
-        UserDto userDto = (UserDto) session.getAttribute("firstUser");
+    public DefaultRegisterUserDto showRegistrationForm(HttpServletRequest request) {
+        HttpSession session = request.getSession(false);
+        if (session == null) {
+            throw new org.springframework.web.server.ResponseStatusException(HttpStatus.UNAUTHORIZED, "세션이 없습니다.");
+        }
+        UserDto userDto = (UserDto) session.getAttribute("firstUser");
+        if (userDto == null) {
+            throw new org.springframework.web.server.ResponseStatusException(HttpStatus.UNAUTHORIZED, "등록 세션이 만료되었습니다.");
+        }
         return DefaultRegisterUserDto.builder()
                 .username(userDto.getUsername())
                 .email(userDto.getEmail())
                 .role(userDto.getRole())
                 .build();
     }
📝 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
@GetMapping("/first-register")
public DefaultRegisterUserDto showRegistrationForm(HttpServletRequest request) {
HttpSession session = request.getSession();
UserDto userDto = (UserDto) session.getAttribute("firstUser");
return DefaultRegisterUserDto.builder()
.username(userDto.getUsername())
.email(userDto.getEmail())
.role(userDto.getRole())
.build();
}
@GetMapping("/first-register")
public DefaultRegisterUserDto showRegistrationForm(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
throw new org.springframework.web.server.ResponseStatusException(
HttpStatus.UNAUTHORIZED, "세션이 없습니다."
);
}
UserDto userDto = (UserDto) session.getAttribute("firstUser");
if (userDto == null) {
throw new org.springframework.web.server.ResponseStatusException(
HttpStatus.UNAUTHORIZED, "등록 세션이 만료되었습니다."
);
}
return DefaultRegisterUserDto.builder()
.username(userDto.getUsername())
.email(userDto.getEmail())
.role(userDto.getRole())
.build();
}
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java
around lines 23-32, the method currently calls request.getSession() and
dereferences a possibly null session/attribute causing NPE; change to
request.getSession(false), check if the session is null or
session.getAttribute("firstUser") is null (and assert the attribute type), and
if missing throw a ResponseStatusException with HttpStatus.UNAUTHORIZED (or
HttpStatus.GONE if you prefer 410) so no new session is created and a proper
401/410 is returned; otherwise safely cast the attribute to UserDto and
build/return the DefaultRegisterUserDto.

Comment on lines +34 to 42
@PostMapping("/register")
public ResponseEntity<?> processRegistration(HttpServletRequest request,
RegisterUserDto registerUserDto) {
HttpSession session = request.getSession();
UserDto dto = (UserDto) session.getAttribute("firstUser");
UserDto userDto = (UserDto) session.getAttribute("firstUser");
session.removeAttribute("firstUser");
return dto;
}

@PostMapping(
value = "/register",
consumes = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<?> processRegistration(@RequestBody DefaultRegisterUserDto defaultRegisterUserDTO) {
log.info("ClassCode 1 : " + defaultRegisterUserDTO.getClassCode());
userService.registerUser(defaultRegisterUserDTO);
log.info("ClassCode 1 : " + registerUserDto.getClassCode());
userService.registerUser(userDto, registerUserDto.getClassCode());
return new ResponseEntity<>(HttpStatus.CREATED);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

JSON 바인딩 깨짐(@RequestBody 누락) + NPE/세션 제거 시점

  • @RequestBody가 없어 프런트에서 JSON 전송 시 classCode가 바인딩되지 않습니다.
  • firstUser가 없을 때 NPE 발생.
  • removeAttribute를 저장 성공 후로 이동해야 재시도 가능.
-    public ResponseEntity<?> processRegistration(HttpServletRequest request,
-                                                 RegisterUserDto registerUserDto) {
-        HttpSession session = request.getSession();
-        UserDto userDto = (UserDto) session.getAttribute("firstUser");
-        session.removeAttribute("firstUser");
-        log.info("ClassCode 1 : " + registerUserDto.getClassCode());
-        userService.registerUser(userDto, registerUserDto.getClassCode());
+    public ResponseEntity<Void> processRegistration(
+            HttpServletRequest request,
+            @org.springframework.web.bind.annotation.RequestBody
+            @jakarta.validation.Valid RegisterUserDto registerUserDto) {
+        HttpSession session = request.getSession(false);
+        if (session == null) {
+            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
+        }
+        UserDto userDto = (UserDto) session.getAttribute("firstUser");
+        if (userDto == null) {
+            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
+        }
+        log.info("ClassCode 1 : {}", registerUserDto.getClassCode());
+        userService.registerUser(userDto, registerUserDto.getClassCode());
+        session.removeAttribute("firstUser");
-        return new ResponseEntity<>(HttpStatus.CREATED);
+        return new ResponseEntity<>(HttpStatus.CREATED);
     }
📝 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
@PostMapping("/register")
public ResponseEntity<?> processRegistration(HttpServletRequest request,
RegisterUserDto registerUserDto) {
HttpSession session = request.getSession();
UserDto dto = (UserDto) session.getAttribute("firstUser");
UserDto userDto = (UserDto) session.getAttribute("firstUser");
session.removeAttribute("firstUser");
return dto;
}
@PostMapping(
value = "/register",
consumes = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<?> processRegistration(@RequestBody DefaultRegisterUserDto defaultRegisterUserDTO) {
log.info("ClassCode 1 : " + defaultRegisterUserDTO.getClassCode());
userService.registerUser(defaultRegisterUserDTO);
log.info("ClassCode 1 : " + registerUserDto.getClassCode());
userService.registerUser(userDto, registerUserDto.getClassCode());
return new ResponseEntity<>(HttpStatus.CREATED);
@PostMapping("/register")
public ResponseEntity<Void> processRegistration(
HttpServletRequest request,
@org.springframework.web.bind.annotation.RequestBody
@jakarta.validation.Valid RegisterUserDto registerUserDto) {
HttpSession session = request.getSession(false);
if (session == null) {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
UserDto userDto = (UserDto) session.getAttribute("firstUser");
if (userDto == null) {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
log.info("ClassCode 1 : {}", registerUserDto.getClassCode());
userService.registerUser(userDto, registerUserDto.getClassCode());
session.removeAttribute("firstUser");
return new ResponseEntity<>(HttpStatus.CREATED);
}
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java
around lines 34 to 42, the controller is missing @RequestBody on the
RegisterUserDto so JSON classCode won't bind, it dereferences session attribute
firstUser without null-check (NPE risk), and it removes the session attribute
before ensuring registration succeeded. Add @RequestBody to the RegisterUserDto
parameter, check session.getAttribute("firstUser") for null and return an
appropriate error response if absent, call userService.registerUser(...) only
after confirming firstUser is present, and move
session.removeAttribute("firstUser") to after a successful registration (or only
remove when registerUser returns/does not throw) so failed attempts can retry.

Comment on lines +18 to 26
public void registerUser(UserDto userDto, int classCode) {
UserEntity userEntity = new UserEntity(
userDTO.getClassCode(),
userDTO.getUsername(),
userDTO.getEmail(),
userDTO.getRole()
classCode,
userDto.getUsername(),
userDto.getEmail(),
userDto.getRole()
);
userRepository.save(userEntity);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

트랜잭션과 중복/입력 검증 추가 필요

  • 트랜잭션 경계가 없어 예외 시 일관성 보장이 약합니다.
  • 이메일 중복(경합 포함)과 classCode 유효성 검증이 없습니다.
+import org.springframework.transaction.annotation.Transactional;
+
-    public void registerUser(UserDto userDto, int classCode) {
+    @Transactional
+    public void registerUser(UserDto userDto, int classCode) {
+        if (userDto == null) throw new IllegalArgumentException("userDto is null");
+        if (classCode < 0) throw new IllegalArgumentException("invalid classCode");
+        userRepository.findByEmail(userDto.getEmail()).ifPresent(u -> {
+            throw new IllegalStateException("이미 등록된 이메일입니다.");
+        });
         UserEntity userEntity = new UserEntity(
                 classCode,
                 userDto.getUsername(),
                 userDto.getEmail(),
                 userDto.getRole()
         );
         userRepository.save(userEntity);
     }

원하시면 예외를 도메인 예외로 치환해 드리겠습니다.

📝 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
public void registerUser(UserDto userDto, int classCode) {
UserEntity userEntity = new UserEntity(
userDTO.getClassCode(),
userDTO.getUsername(),
userDTO.getEmail(),
userDTO.getRole()
classCode,
userDto.getUsername(),
userDto.getEmail(),
userDto.getRole()
);
userRepository.save(userEntity);
}
import org.springframework.transaction.annotation.Transactional;
public class UserService {
// … other fields and constructors …
@Transactional
public void registerUser(UserDto userDto, int classCode) {
if (userDto == null) {
throw new IllegalArgumentException("userDto is null");
}
if (classCode < 0) {
throw new IllegalArgumentException("invalid classCode");
}
userRepository.findByEmail(userDto.getEmail()).ifPresent(u -> {
throw new IllegalStateException("이미 등록된 이메일입니다.");
});
UserEntity userEntity = new UserEntity(
classCode,
userDto.getUsername(),
userDto.getEmail(),
userDto.getRole()
);
userRepository.save(userEntity);
}
// … other methods …
}
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/user/service/UserService.java around
lines 18 to 26, the registerUser method lacks a transaction boundary, does not
validate classCode, and does not guard against duplicate emails (including race
conditions). Make the method transactional (e.g., @Transactional at class or
method level), validate classCode against allowed values and throw a
domain-specific validation exception if invalid, check for existing email via
the repository (existsByEmail) and throw a domain duplicate-email exception
before saving, and additionally handle database-level unique constraint races by
catching DataIntegrityViolationException (or the persistence exception) and
translating it into the same domain duplicate-email exception so concurrent
inserts are handled consistently. Ensure all thrown exceptions are domain
exceptions defined in the user domain.

@Hgyeol Hgyeol merged commit 0f2276b into develop Sep 9, 2025
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Sep 17, 2025
4 tasks
@fixgramwork fixgramwork deleted the fix#78 branch November 19, 2025 06:49
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.

[REFACTOR] 회원가입 수정

1 participant