Skip to content

Conversation

@NCookies
Copy link
Collaborator

@NCookies NCookies commented Dec 20, 2025

작업 내용

  • 이메일 인증 또는 oauth 회원 가입 직후 토큰을 발급받을 수 있도록 함
  • 기존의 회원 데이터 삭제(Soft Delete) 시 이메일 값을 수정하여 재가입 시 충돌이 발생하지 않도록 함
  • 코드 리뷰 에러가 발생하던 문제 해결
    • @Value의 변수 값이 이상하게 설정되어 있었음

문제점:
- 이메일 인증 완료 후 verified=true가 되어도 리뷰 토큰이 없음
- 토큰 값이 하드코딩되어 유지보수 어려움
- setReviewToken(int)로 임의 값 설정 가능하여 오용 위험

변경사항:
- AuthService.verifyEmailCode()에서 인증 완료 시 기본 토큰 발급
- User 엔티티에 토큰 상수 추가 (DEFAULT_REVIEW_TOKEN=20, FULL_WEEK_REVIEW_TOKEN=40)
- setReviewToken(int) 제거하고 의미 있는 메서드로 분리
  - setDefaultReviewToken(): 기본 토큰 설정 (이메일 인증, OAuth 가입 시)
  - setFullWeekReviewToken(): 주간 풀 참여 토큰 설정
- CustomOAuth2UserService에서도 새로운 메서드 사용
- 회원 탈퇴 시 soft delete 처리를 하고 있는데, 추후 같은 이메일로 재가입 시도 시 정상적으로 가입할 수 있도록 함
- 이를 위해 탈퇴한 계정의 이메일 값을 변경함
  - ex) user@example.com -> user@example.com__deleted_20231220
- `setDeleted` 메서드 이름도 `markAsDeleted`로 변경
- 개발 중 IntelliJ 환경변수 사용하던 값을 수정하지 않은 것으로 보임
- application.properties 파일에서 해당 값을 정상적으로 읽어올 수 있도록 수정함
- 그리고 open ai 계정에 API key가 발급되있지 않은 상태였음. 지금까지는 어떻게 동작했는지는 의문...
@NCookies NCookies self-assigned this Dec 20, 2025
@NCookies NCookies added the enhancement New feature or request label Dec 20, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 20, 2025

Walkthrough

이메일 검증 성공 시 기본 리뷰 토큰을 자동 설정하고, 사용자 삭제 메서드명을 변경하며, 리뷰 토큰 상수화 및 에러 처리 로직을 개선하는 변경사항입니다.

Changes

Cohort / File(s) 변경 요약
사용자 삭제 메서드 리팩토링
src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java
src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java
setDeleted()markAsDeleted()로 변경하고, 삭제 시 이메일에 __deleted_<YYYYMMDD> 접미사 추가하여 재가입 시 고유 키 충돌 방지
리뷰 토큰 처리 표준화
src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java
src/main/java/org/ezcode/codetest/domain/user/service/CustomOAuth2UserService.java
src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java
setReviewToken(int) 제거, DEFAULT_REVIEW_TOKEN(20), FULL_WEEK_REVIEW_TOKEN(40) 상수 추가, 전용 메서드 setDefaultReviewToken(), setFullWeekReviewToken() 추가 및 호출 지점 통일
이메일 검증 후 토큰 초기화
src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java
이메일 검증 성공 시 setDefaultReviewToken() 호출하여 사용자 검증 설정과 동시에 토큰 할당
OpenAI 클라이언트 개선
src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java
설정 프로퍼티명 변경(OPEN_API_URL/OPEN_API_KEYopenai.api.url/openai.api.key), 5xx 상태 코드 및 타임아웃 예외에만 재시도 적용, 상세 에러 로깅 추가

Estimated code review effort

🎯 3 (중간) | ⏱️ ~20분

  • 주의 필요 영역:
    • User 엔티티의 public API 변경으로 인한 호출 지점 누락 여부 확인 필요
    • markAsDeleted()의 이메일 변조 로직이 데이터 무결성과 재가입 흐름에 미치는 영향 검토
    • OpenAIReviewClient의 재시도 정책 변경이 의도한 대로 작동하는지 확인 (5xx만 재시도 vs 모든 WebClientResponseException)

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • pokerbearkr
  • Kimminu7
  • thezz9

Poem

🐰 토큰을 정렬하고 삭제를 다듬으면,
사용자 흐름이 더욱 깔끔해져요.
상수들이 모여 일관성을 만들고,
이메일에 날짜 스탬프를 찍으니
재가입도 더 이상 헷갈리지 않네요! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 회원 탈퇴 재가입 처리 개선과 OAuth/AI 리뷰 에러 해결이라는 두 가지 주요 변경사항을 포함하고 있으며, raw_summary의 내용과 일치합니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/user-management

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

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28bff8b and 0ba09d6.

📒 Files selected for processing (6)
  • src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java (1 hunks)
  • src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java (1 hunks)
  • src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java (3 hunks)
  • src/main/java/org/ezcode/codetest/domain/user/service/CustomOAuth2UserService.java (2 hunks)
  • src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java (1 hunks)
  • src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java (3 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: thezz9
Repo: ezcode-my/backend PR: 75
File: src/main/java/org/ezcode/codetest/application/submission/service/SubmissionService.java:142-147
Timestamp: 2025-06-19T07:00:53.895Z
Learning: SubmissionService.getCodeReview() 메서드에서 토큰 차감을 리뷰 요청 전에 수행하는 이유: OpenAI 서버 장애보다 유저 토큰 부족 상황이 더 빈번하므로, 확률이 높은 실패 케이스를 먼저 체크하여 불필요한 처리를 방지함. OpenAI 서버 장애 시에는 OpenAIClient에서 트랜잭션 롤백으로 토큰 복원이 처리됨.
📚 Learning: 2025-05-31T10:49:44.725Z
Learnt from: minjee2758
Repo: ezcode-my/backend PR: 9
File: src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java:100-102
Timestamp: 2025-05-31T10:49:44.725Z
Learning: User prefers boolean deletion state with a parameterless setDeleted() method that always sets isDeleted to true, supporting only soft deletion functionality. This design choice makes the API clearer by removing parameter ambiguity.

Applied to files:

  • src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java
📚 Learning: 2025-06-27T06:03:05.761Z
Learnt from: minjee2758
Repo: ezcode-my/backend PR: 110
File: src/main/java/org/ezcode/codetest/domain/user/service/CustomOAuth2UserService.java:102-112
Timestamp: 2025-06-27T06:03:05.761Z
Learning: `CustomOAuth2UserService`의 `updateGithubUrl` 메서드는 새로운 유저 생성 시나 기존 유저가 새로운 AuthType을 추가할 때만 호출되므로, `UserGithubInfo` 중복 생성 가능성이 없다. 별도의 중복 체크 로직이 불필요하다.

Applied to files:

  • src/main/java/org/ezcode/codetest/domain/user/service/CustomOAuth2UserService.java
📚 Learning: 2025-06-21T21:24:57.979Z
Learnt from: chat26666
Repo: ezcode-my/backend PR: 88
File: src/main/java/org/ezcode/codetest/domain/game/model/character/GameCharacterMatchTokenBucket.java:0-0
Timestamp: 2025-06-21T21:24:57.979Z
Learning: In the GameCharacterMatchTokenBucket class, the user prefers the current "full refill after interval" approach over gradual token refilling. When REFILL_INTERVAL (6 hours) passes, all tokens should be reset to maximum values rather than being refilled progressively.

Applied to files:

  • src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java
📚 Learning: 2025-06-18T11:42:03.701Z
Learnt from: thezz9
Repo: ezcode-my/backend PR: 73
File: src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java:52-64
Timestamp: 2025-06-18T11:42:03.701Z
Learning: OpenAI API 클라이언트에서는 두 가지 재시도 전략을 사용한다: 1) WebClient의 retryWhen은 네트워크/HTTP 에러 및 타임아웃에 대한 재시도, 2) 외부 for 루프는 API 응답 포맷 검증 실패에 대한 재시도. 이는 서로 다른 실패 상황을 처리하는 독립적인 메커니즘이다.

Applied to files:

  • src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java
📚 Learning: 2025-06-30T10:10:07.449Z
Learnt from: thezz9
Repo: ezcode-my/backend PR: 120
File: src/main/java/org/ezcode/codetest/infrastructure/judge0/Judge0Client.java:58-64
Timestamp: 2025-06-30T10:10:07.449Z
Learning: Judge0Client에서 Reactor의 retryWhen() 재시도가 모두 실패했을 때 발생하는 RetryExhaustedException은 package-private 클래스라서 직접 import할 수 없으므로, 부모 클래스인 IllegalStateException을 catch해서 COMPILE_TIMEOUT으로 매핑하는 것이 필요한 해결책이다.

Applied to files:

  • src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java
📚 Learning: 2025-06-19T07:00:53.895Z
Learnt from: thezz9
Repo: ezcode-my/backend PR: 75
File: src/main/java/org/ezcode/codetest/application/submission/service/SubmissionService.java:142-147
Timestamp: 2025-06-19T07:00:53.895Z
Learning: SubmissionService.getCodeReview() 메서드에서 토큰 차감을 리뷰 요청 전에 수행하는 이유: OpenAI 서버 장애보다 유저 토큰 부족 상황이 더 빈번하므로, 확률이 높은 실패 케이스를 먼저 체크하여 불필요한 처리를 방지함. OpenAI 서버 장애 시에는 OpenAIClient에서 트랜잭션 롤백으로 토큰 복원이 처리됨.

Applied to files:

  • src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java
📚 Learning: 2025-06-30T10:10:07.449Z
Learnt from: thezz9
Repo: ezcode-my/backend PR: 120
File: src/main/java/org/ezcode/codetest/infrastructure/judge0/Judge0Client.java:58-64
Timestamp: 2025-06-30T10:10:07.449Z
Learning: Reactor의 retryWhen()에서 재시도가 모두 실패했을 때 발생하는 retry exhausted exception은 reactor-core에서 package-private으로 구현되어 있어서 직접 import하거나 catch할 수 없다. 따라서 부모 클래스를 catch하거나 onRetryExhaustedThrow()를 사용해서 커스텀 예외를 던지는 방식으로 처리해야 한다.

Applied to files:

  • src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (8)
src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java (1)

129-136: LGTM!

이메일 인증 성공 시 기본 리뷰 토큰을 자동으로 설정하는 변경사항이 적절합니다. @Transactional 내에서 setVerified()setDefaultReviewToken()이 함께 호출되어 원자성이 보장됩니다.

src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java (1)

115-118: LGTM!

하드코딩된 매직 넘버(40, 20)를 User.FULL_WEEK_REVIEW_TOKENUser.DEFAULT_REVIEW_TOKEN 상수로 대체한 리팩토링이 적절합니다. 토큰 값의 단일 소스(Single Source of Truth)가 보장됩니다.

src/main/java/org/ezcode/codetest/domain/user/service/CustomOAuth2UserService.java (2)

86-95: LGTM!

setReviewToken(20)setDefaultReviewToken()으로 변경하여 토큰 초기화 로직을 중앙 집중화했습니다. 새 사용자 생성 시 일관된 방식으로 기본 토큰이 설정됩니다.


97-106: LGTM!

기존 사용자 업데이트 경로에서도 동일하게 setDefaultReviewToken()을 사용하여 일관성이 유지됩니다. 검증되지 않은 기존 사용자가 새로운 AuthType을 추가할 때 올바르게 토큰이 설정됩니다.

src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java (2)

34-38: LGTM!

설정 키 이름이 OPEN_API_URL/OPEN_API_KEY에서 openai.api.url/openai.api.key로 변경되어 Spring Boot의 일반적인 프로퍼티 네이밍 컨벤션(소문자 + 점 구분)을 따릅니다.


87-101: 재시도 정책 개선 확인

5xx 서버 오류와 TimeoutException에만 재시도하도록 필터를 제한한 것은 올바른 접근입니다. 4xx 클라이언트 오류(예: 401 Unauthorized, 400 Bad Request)는 재시도해도 결과가 동일하므로 재시도하지 않는 것이 적절합니다.

src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java (1)

153-164: 구현이 올바르게 처리됩니다.

markAsDeleted() 메서드는 isDeleted 플래그를 true로 설정하고 이메일에 __deleted_<YYYYMMDD> 접미사를 추가하여 탈퇴 후 동일 이메일 재가입 시 유니크 키 충돌을 방지합니다. 구현이 올바릅니다.

src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java (1)

231-246: 리뷰 토큰 상수화 및 setter 메서드 추가 - LGTM!

매직 넘버를 상수로 추출하고 명확한 의미의 setter 메서드를 제공하여 가독성과 유지보수성이 향상되었습니다. 기존 학습에서 확인된 것처럼 setReviewToken(int) 대신 파라미터 없는 명시적 메서드를 사용하여 API가 더 명확해졌습니다.

Comment on lines +176 to 188
/**
* 회원 탈퇴 시 호출되는 메서드
* - isDeleted 플래그를 true로 설정
* - 이메일을 변경하여 동일 이메일 재가입 시 unique key 충돌 방지
* (예: user@example.com -> user@example.com__deleted_20231220)
*/
public void markAsDeleted() {
this.isDeleted = true;
if (this.email != null && !this.email.isBlank()) {
String deletedDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
this.email = this.email + "__deleted_" + deletedDate;
}
}
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

동일 이메일 재가입 후 같은 날 탈퇴 시 unique constraint 충돌 가능성

날짜 기반 suffix만 사용하면 같은 날 동일 이메일로 재가입 후 탈퇴하는 경우 unique key 충돌이 발생할 수 있습니다:

  1. user@example.com → 탈퇴 → user@example.com__deleted_20231220
  2. 같은 이메일로 재가입 → user@example.com
  3. 같은 날 탈퇴 시도 → user@example.com__deleted_20231220 (충돌!)

또한 markAsDeleted()가 중복 호출되면 suffix가 계속 추가됩니다.

🔎 UUID 또는 고유 식별자 추가를 권장합니다
 public void markAsDeleted() {
     this.isDeleted = true;
-    if (this.email != null && !this.email.isBlank()) {
+    if (this.email != null && !this.email.isBlank() && !this.email.contains("__deleted_")) {
         String deletedDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
-        this.email = this.email + "__deleted_" + deletedDate;
+        String uniqueSuffix = java.util.UUID.randomUUID().toString().substring(0, 8);
+        this.email = this.email + "__deleted_" + deletedDate + "_" + uniqueSuffix;
     }
 }
📝 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
/**
* 회원 탈퇴 호출되는 메서드
* - isDeleted 플래그를 true로 설정
* - 이메일을 변경하여 동일 이메일 재가입 unique key 충돌 방지
* (: user@example.com -> user@example.com__deleted_20231220)
*/
public void markAsDeleted() {
this.isDeleted = true;
if (this.email != null && !this.email.isBlank()) {
String deletedDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
this.email = this.email + "__deleted_" + deletedDate;
}
}
/**
* 회원 탈퇴 호출되는 메서드
* - isDeleted 플래그를 true로 설정
* - 이메일을 변경하여 동일 이메일 재가입 unique key 충돌 방지
* (: user@example.com -> user@example.com__deleted_20231220)
*/
public void markAsDeleted() {
this.isDeleted = true;
if (this.email != null && !this.email.isBlank() && !this.email.contains("__deleted_")) {
String deletedDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String uniqueSuffix = java.util.UUID.randomUUID().toString().substring(0, 8);
this.email = this.email + "__deleted_" + deletedDate + "_" + uniqueSuffix;
}
}
🤖 Prompt for AI Agents
In src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java around
lines 176 to 188, the current markAsDeleted() appends a date-only suffix which
can collide if a user re-registers and is deleted the same day and also keeps
appending on repeated calls; change the suffix to include a unique component
(e.g., a short UUID or nano timestamp) so each deleted email is globally unique,
and make the method idempotent by first checking if isDeleted is already true or
if the email already contains your deleted-marker (skip modification if so).
Ensure the generated suffix format remains stable and human-readable (e.g.,
"__deleted_yyyyMMdd_<uuid>") and only modify the email when email is non-empty
and the account is not already marked deleted.

Comment on lines +103 to 113
.onErrorMap(WebClientResponseException.class, ex -> {
HttpStatusCode statusCode = ex.getStatusCode();
String responseBody = ex.getResponseBodyAsString();
log.error("OpenAI API 호출 실패 - Status: {}, Response Body: {}", statusCode, responseBody, ex);
return new CodeReviewException(CodeReviewExceptionCode.REVIEW_SERVER_ERROR);
})
.onErrorMap(TimeoutException.class, ex -> {
log.error("OpenAI API 호출 타임아웃", ex);
return new CodeReviewException(CodeReviewExceptionCode.REVIEW_TIMEOUT);
})
.block();
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

네트워크 연결 오류 처리 누락

WebClientResponseExceptionTimeoutException 외의 네트워크 연결 오류(예: ConnectException, UnknownHostException 등)가 발생할 경우 처리되지 않고 상위로 전파될 가능성이 있습니다. 이러한 예외들을 처리하기 위해 추가 onErrorMap() 핸들러를 추가하거나 .block() 호출을 try-catch로 감싸는 것을 고려하세요.

🤖 Prompt for AI Agents
In
src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIReviewClient.java
around lines 103 to 113, the reactive chain only maps WebClientResponseException
and TimeoutException but will let other network errors (e.g., ConnectException,
UnknownHostException, generic IOException) propagate; add an additional
onErrorMap handler (or wrap block() in try-catch) to catch these
connection-related exceptions and map them to an appropriate CodeReviewException
(e.g., REVIEW_NETWORK_ERROR) with a clear log entry including the exception;
specifically, add an onErrorMap for
ConnectException/UnknownHostException/IOException (or a broad Throwable fallback
after the specific handlers) that logs the error and returns the
CodeReviewException so network errors are standardized and don’t leak up
unhandled.

@NCookies NCookies merged commit c991519 into dev Dec 20, 2025
2 checks passed
@NCookies NCookies deleted the refactor/user-management branch December 20, 2025 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants