Skip to content

Applicant 도메인 기반 구조 추가#571

Open
kangkings wants to merge 3 commits into
tmp/devfrom
feat/562-set-up-applicant
Open

Applicant 도메인 기반 구조 추가#571
kangkings wants to merge 3 commits into
tmp/devfrom
feat/562-set-up-applicant

Conversation

@kangkings
Copy link
Copy Markdown
Contributor

@kangkings kangkings commented Jun 1, 2026

#️⃣연관된 이슈

close: #562
Parent: #561

📝 작업 내용

  • 기존 domain.apply.domain.Applicant를 독립 domain.applicant.entity.Applicant로 이동
  • 기존 Member가 가지고 있던 legacy account/applicant 도메인 로직을 Applicant에 병렬 복사
  • Applicant controller, service, repository, query repository, DTO, exception 기반 추가
  • Applicant entity/service/controller/repository 테스트 추가
  • 기존 domain.member controller/service/repository/entity는 유지
  • 지원 흐름 전환, 인증 흐름 전환, DB migration, flyway 일부 수정은 후속 작업에서 순차적으로 진행

🙏 리뷰 요구사항 (선택)

코멘트

  • teamMember 관련 기능은 추후 삭제 예정, 제외하고 applicant로 로직 병렬 복사

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

Warning

Review limit reached

@kangkings, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 31 minutes and 23 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6070cea9-848b-4f63-99a8-726d8858aa68

📥 Commits

Reviewing files that changed from the base of the PR and between 270f16e and 354afff.

📒 Files selected for processing (2)
  • .github/workflows/continuous-intergration.yml
  • src/main/java/org/ject/support/domain/applicant/service/ApplicantService.java

Walkthrough

지원자 도메인(Applicant Domain)의 전체 구현이 추가됩니다. PIN 기반 임시 등록, 초기 프로필 설정, PIN 관리를 제공하는 예외 처리, 엔티티, 저장소, DTO, 서비스, 컨트롤러 계층과 포괄적인 테스트가 포함됩니다. 또한 JWT 토큰 생성 기능 확장과 CI 워크플로 설정 변경이 함께 이루어집니다.

Changes

Applicant 도메인 전체 구현

Layer / File(s) Summary
Exception & Error Handling
src/main/java/org/ject/support/domain/applicant/exception/*
ApplicantErrorCode 열거형으로 NOT_FOUND_APPLICANT, ALREADY_EXIST_APPLICANT, DUPLICATE_EMAIL 등 5가지 오류를 HttpStatus-코드-메시지로 정의합니다. ApplicantException은 BusinessException을 상속하여 ErrorCode를 기반으로 예외를 발생시킵니다.
Entity & Domain Model
src/main/java/org/ject/support/domain/applicant/entity/Applicant.java, ApplicantEditor.java, src/test/java/org/ject/support/domain/applicant/ApplicantTest.java
Applicant 엔티티를 apply.domain에서 applicant.entity로 이동하고, 이메일/이름/전화번호에 검증 제약을 추가합니다. updateNameAndPhoneNumber, updatePin, isInitialed, isProfileComplete 등 도메인 메서드를 제공합니다. ApplicantEditor 레코드는 엔티티 편집 시 필드 집합을 제공합니다.
Repository & Data Access
src/main/java/org/ject/support/domain/applicant/repository/*, src/test/java/org/ject/support/domain/applicant/repository/ApplicantRepositoryTest.java
ApplicantRepository는 JpaRepository와 ApplicantQueryRepository를 상속하며, 이메일/역할 기반 조회를 지원합니다. ApplicantQueryRepositoryImpl은 QueryDSL로 미제출 지원자 이메일 조회와 관리자용 검색 조건 기반 페이징 조회를 구현합니다.
Data Transfer Objects
src/main/java/org/ject/support/domain/applicant/dto/*
ApplicantDto는 RegisterRequest(PIN 포함), InitialProfileRequest(이름/전화번호), UpdatePinRequest(PIN 검증)를 정의합니다. ApplicantProfileResponse는 지원자 프로필 조회용 응답 객체이고, ApplicantAccountProjection은 관리자용 계정 투영입니다.
Service Business Logic
src/main/java/org/ject/support/domain/applicant/service/ApplicantService.java, src/test/java/org/ject/support/domain/applicant/service/ApplicantServiceTest.java
ApplicantService는 이메일 검증 및 PIN 암호화를 거쳐 지원자를 등록하고 JWT Authentication을 반환합니다. 초기 프로필 설정, PIN 갱신, 상태/프로필 조회 메서드를 제공하며, 미존재 지원자나 중복 이메일 시 예외를 발생시킵니다.
API Specification & Controller
src/main/java/org/ject/support/domain/applicant/controller/ApplicantApiSpec.java, ApplicantController.java, src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java
ApplicantApiSpec은 Swagger 메타데이터와 함께 5가지 엔드포인트 계약을 정의합니다. ApplicantController는 POST /apply(임시 등록), PUT /profile/initial(프로필 설정), PUT /pin(PIN 재설정), GET 조회 엔드포인트를 구현하며, 권한 검증과 인증 성공 처리를 조율합니다.
JWT Token Provider Enhancement
src/main/java/org/ject/support/common/security/jwt/JwtTokenProvider.java
Applicant 엔티티 정보로 CustomUserDetails를 생성하고 UsernamePasswordAuthenticationToken을 반환하는 createAuthenticationByApplicant 메서드를 추가합니다.
CI/CD Workflow
.github/workflows/continuous-intergration.yml
pull_request 대상 브랜치 목록에 tmp/dev를 추가하여 임시 개발 브랜치에서도 CI 파이프라인이 실행되도록 구성합니다.

Sequence Diagram(s)

sequenceDiagram
  actor Client
  participant ApplicantController
  participant JwtTokenProvider
  participant ApplicantService
  participant PasswordEncoder
  participant SemesterRepository
  participant ApplicantRepository
  
  Client->>ApplicantController: POST /apply (verification token, PIN)
  ApplicantController->>JwtTokenProvider: parseVerificationToken(token)
  JwtTokenProvider-->>ApplicantController: email
  ApplicantController->>ApplicantService: registerTempApplicant(email, PIN)
  ApplicantService->>ApplicantRepository: findByEmail(email)
  ApplicantRepository-->>ApplicantService: Optional (check duplicate)
  ApplicantService->>PasswordEncoder: encode(PIN)
  PasswordEncoder-->>ApplicantService: encodedPin
  ApplicantService->>SemesterRepository: findRecruitingSemester()
  SemesterRepository-->>ApplicantService: semesterId
  ApplicantService->>ApplicantRepository: save(new Applicant)
  ApplicantRepository-->>ApplicantService: saved applicant
  ApplicantService->>JwtTokenProvider: createAuthenticationByApplicant(applicant)
  JwtTokenProvider-->>ApplicantService: Authentication token
  ApplicantService-->>ApplicantController: Authentication
  ApplicantController->>ApplicantController: CustomSuccessHandler.onAuthenticationSuccess()
  ApplicantController->>ApplicantController: deleteCookie(verificationToken)
  ApplicantController-->>Client: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 깡깡깡, 지원자 도메인이 폭발했어,
PIN과 학기를 암호화해 저장하고,
프로필 설정은 순차적으로 진행돼,
다섯 가지 엔드포인트가 춤을 춘다네! 🎪
테스트는 촘촘하게, 예외는 우아하게,
완성된 도메인이 빛나는구나! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 주요 변경사항인 Applicant 도메인 기반 구조 추가를 명확히 설명하며, 전체 changeset의 핵심을 정확하게 반영하고 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/562-set-up-applicant

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.

@kangkings kangkings self-assigned this Jun 1, 2026
@kangkings kangkings added 👨🏻‍💻backend 백엔드 작업 🪄refactor 리팩토링 labels Jun 1, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Test Results

409 tests   409 ✅  13s ⏱️
 74 suites    0 💤
 74 files      0 ❌

Results for commit 354afff.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

📊테스트 커버리지

Overall Project 80.69% -4.7% 🍏
Files changed 22.53%

File Coverage
ApplicantService.java 84.09% -15.91% 🍏
JwtTokenProvider.java 57.96% -3.98%
Applicant.java 14.58% -85.42%
ApplicantQueryRepositoryImpl.java 0%
ApplicantEditor.java 0%

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

🧹 Nitpick comments (1)
src/main/java/org/ject/support/domain/applicant/entity/Applicant.java (1)

114-116: ⚡ Quick win

isInitialed()isProfileComplete()가 동일 로직으로 중복됩니다.

두 메서드 모두 name != null && phoneNumber != null을 반환합니다. 의미가 같다면 하나로 통합하거나, 한쪽이 다른 쪽을 위임하도록 정리해 향후 조건 변경 시 불일치 위험을 줄이세요.

♻️ 제안
     public boolean isInitialed() {
-        return this.name != null && this.phoneNumber != null;
+        return isProfileComplete();
     }

Also applies to: 165-167

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/ject/support/domain/applicant/entity/Applicant.java` around
lines 114 - 116, Applicant currently has duplicate logic in isInitialed() and
isProfileComplete() (both return name != null && phoneNumber != null); remove
duplication by having one canonical method (choose either isProfileComplete or
isInitialed) implement the actual check and have the other simply delegate to it
(e.g., isInitialed() -> return isProfileComplete()), or delete the redundant
method and update callers to use the remaining method; update any references to
the removed method accordingly (look for Applicant.isInitialed and
Applicant.isProfileComplete).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/org/ject/support/domain/applicant/entity/Applicant.java`:
- Around line 49-50: Applicant.email is limited to `@Column`(length = 30) but
emails extracted in ApplicantController.extractEmailFromVerificationToken and
passed into ApplicantService.registerTempApplicant are not length-validated,
risking DB insert failures; fix by aligning model and validation: either
increase the column length on Applicant.email to a realistic max (e.g., 254) and
update migrations, or add a validation annotation (e.g., `@Size`(max=30) or a
larger agreed limit) plus explicit trimming/validation in
ApplicantService.registerTempApplicant (and ensure controller extraction
enforces same constraint) so DB schema and runtime validation match.

In
`@src/main/java/org/ject/support/domain/applicant/repository/ApplicantQueryRepositoryImpl.java`:
- Around line 29-37: The query in
ApplicantQueryRepositoryImpl.findEmailsByIdsAndNotSubmitted can over-include and
return duplicate emails because apply.status != SUBMITTED matches applicants who
also have submitted rows and multiple non-submitted rows; change the query to
select distinct applicant.email and replace the apply.status != SUBMITTED
predicate with a NOT EXISTS subquery that checks for any SUBMITTED apply for the
same applicant (use apply.member.id.eq(applicant.id) and
apply.status.eq(SUBMITTED) inside the subquery) so only applicants with no
submitted applications are returned.

In
`@src/main/java/org/ject/support/domain/applicant/service/ApplicantService.java`:
- Around line 40-54: registerTempApplicant currently does a check-then-act using
applicantRepository.findByEmail(...) then createTempApplicantWithPin(...) which
allows TOCTOU duplicates; add a unique constraint on Applicant.email (either via
`@Column`(unique=true)/@UniqueConstraint on the Applicant entity and/or a DB
migration) and wrap the save in createTempApplicantWithPin (or the caller) with
a catch for DataIntegrityViolationException (or the specific persistence
exception) to rethrow ApplicantException(ALREADY_EXIST_APPLICANT); also update
the else-branch comment near applicantRepository.findByEmail(...) to reflect
that an exception is thrown rather than “PIN only updated.”

In
`@src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java`:
- Around line 62-66: The test setup in ApplicantControllerTest uses
standaloneSetup(applicantController) but does not register the custom argument
resolver for `@AuthPrincipal`; update the setUp method to register
AuthenticatedMemberIdResolver via
standaloneSetup(applicantController).setCustomArgumentResolvers(new
AuthenticatedMemberIdResolver()) so `@AuthPrincipal` Long applicantId is bound
from SecurityContextHolder's CustomUserDetails (mirror
WebConfig#addArgumentResolvers), or alternatively convert the test to use a
Spring MVC slice (e.g., `@WebMvcTest`) so the WebConfig argument resolvers are
applied; ensure the change targets the setUp method and references
applicantController, standaloneSetup, setCustomArgumentResolvers,
AuthenticatedMemberIdResolver, `@AuthPrincipal` and SecurityContextHolder.

---

Nitpick comments:
In `@src/main/java/org/ject/support/domain/applicant/entity/Applicant.java`:
- Around line 114-116: Applicant currently has duplicate logic in isInitialed()
and isProfileComplete() (both return name != null && phoneNumber != null);
remove duplication by having one canonical method (choose either
isProfileComplete or isInitialed) implement the actual check and have the other
simply delegate to it (e.g., isInitialed() -> return isProfileComplete()), or
delete the redundant method and update callers to use the remaining method;
update any references to the removed method accordingly (look for
Applicant.isInitialed and Applicant.isProfileComplete).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: abf69d8c-597c-4dd2-9312-3a9cbaf3f585

📥 Commits

Reviewing files that changed from the base of the PR and between 387c70c and 270f16e.

📒 Files selected for processing (19)
  • .github/workflows/continuous-intergration.yml
  • src/main/java/org/ject/support/common/security/jwt/JwtTokenProvider.java
  • src/main/java/org/ject/support/domain/applicant/controller/ApplicantApiSpec.java
  • src/main/java/org/ject/support/domain/applicant/controller/ApplicantController.java
  • src/main/java/org/ject/support/domain/applicant/dto/ApplicantAccountProjection.java
  • src/main/java/org/ject/support/domain/applicant/dto/ApplicantDto.java
  • src/main/java/org/ject/support/domain/applicant/dto/ApplicantProfileResponse.java
  • src/main/java/org/ject/support/domain/applicant/entity/Applicant.java
  • src/main/java/org/ject/support/domain/applicant/entity/ApplicantEditor.java
  • src/main/java/org/ject/support/domain/applicant/exception/ApplicantErrorCode.java
  • src/main/java/org/ject/support/domain/applicant/exception/ApplicantException.java
  • src/main/java/org/ject/support/domain/applicant/repository/ApplicantQueryRepository.java
  • src/main/java/org/ject/support/domain/applicant/repository/ApplicantQueryRepositoryImpl.java
  • src/main/java/org/ject/support/domain/applicant/repository/ApplicantRepository.java
  • src/main/java/org/ject/support/domain/applicant/service/ApplicantService.java
  • src/test/java/org/ject/support/domain/applicant/ApplicantTest.java
  • src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java
  • src/test/java/org/ject/support/domain/applicant/repository/ApplicantRepositoryTest.java
  • src/test/java/org/ject/support/domain/applicant/service/ApplicantServiceTest.java

Comment on lines +49 to +50
@Column(length = 30, nullable = false)
private String email;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 기존 member/legacy applicant의 email 컬럼 길이 정의 및 마이그레이션 확인
rg -nP '\bemail\b' --type=java -C2 -g '*Member*.java'
fd -e sql | xargs -r rg -niP 'email' -C1

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 50395


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Applicant.java의 email 컬럼 애노테이션 실제 값 확인
FILE="src/main/java/org/ject/support/domain/applicant/entity/Applicant.java"
if [ -f "$FILE" ]; then
  echo "=== $FILE (lines 35-70) ==="
  nl -ba "$FILE" | sed -n '35,70p'
else
  echo "Missing file: $FILE" >&2
fi

# 2) Applicant 관련 엔티티/쿼리에서 email length(컬럼) 사용처 확인
echo "=== Applicant email length usages (in src/main/java) ==="
rg -n "class Applicant|Applicant.*email|`@Column`\\(.*email|private String email" src/main/java -S || true

# 3) DB 마이그레이션/DDL에서 applicant email 길이 확인 (sql/ddl 관련 폴더를 넓게 보되 검색은 가볍게)
echo "=== DB migration ddl: applicant + email length/definition ==="
for d in . src/main src/main/resources src/main/resources/db src/main/resources/database src/main/resources/migration src/main/resources/db/migration src/main/resources/flyway src/main/resources/liquibase; do
  if [ -d "$d" ]; then
    rg -n --hidden --no-ignore-vcs "(applicant|applicants).*email|email.*(applicant|applicants)|CREATE TABLE.*applicant|ALTER TABLE.*applicant" "$d" -S -g'*.sql' -g'*.ddl' -g'*.txt' 2>/dev/null | head -n 80 || true
  fi
done

# 4) 코드에서 이메일 길이 제한(Bean Validation `@Size`, `@Pattern` 등) 확인
echo "=== Email length validations (`@Size` or similar) ==="
rg -n "`@Size`\\(|`@Length`\\(|`@Pattern`\\(.*email|email.*`@Size`|email.*`@Length`" src/main/java src/test/java -S || true

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 236


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/main/java/org/ject/support/domain/applicant/entity/Applicant.java"
echo "=== $FILE (lines 1-120) preview (for locating email annotation) ==="
if [ -f "$FILE" ]; then
  sed -n '1,120p' "$FILE" | nl -ba 2>/dev/null || sed -n '1,120p' "$FILE"
else
  echo "Missing file: $FILE" >&2
fi

echo "=== Applicant entity email annotation (`@Column`...) ==="
rg -n "`@Column`\\([^\\)]*length\\s*=\\s*([0-9]+)[^\\)]*\\)|private String email" "$FILE" || true

echo "=== Applicant email usages (src/main/java) near email column mappings/validators ==="
rg -n "Applicant.*email|`@Column`\\(.*email|private String email|email.*`@Size`|email.*`@Length`|`@Size`\\(|`@Length`\\(|email.*`@Pattern`" src/main/java -S || true

echo "=== DB DDL/migrations: applicant email column length ==="
# Restrict to migration folders if they exist; otherwise search in resources.
SEARCH_DIRS=(
  "src/main/resources/db"
  "src/main/resources/database"
  "src/main/resources/migration"
  "src/main/resources/flyway"
  "src/main/resources/liquibase"
  "src/main/resources"
)
for d in "${SEARCH_DIRS[@]}"; do
  [ -d "$d" ] || continue
  echo "--- scanning: $d ---"
  rg -n --hidden --no-ignore-vcs "(applicant|applicants).*email|email.*(applicant|applicants)|CREATE TABLE.*applicant|ALTER TABLE.*applicant" "$d" \
    -S -g'*.sql' -g'*.ddl' -g'*.txt' 2>/dev/null | head -n 120 || true
done

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 8350


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Migration에서 applicant.email 컬럼 타입/길이 확인
MIG="src/main/resources/db/migration/V28__create_applicant_and_copy_member.sql"
echo "=== Inspect migration: $MIG (email column definition) ==="
if [ -f "$MIG" ]; then
  # Show around CREATE TABLE applicant ... email
  rg -n "CREATE TABLE applicant|email" "$MIG" || true
  # Print a focused window around the first "email" occurrence
  line=$(rg -n "email" "$MIG" | head -n 1 | cut -d: -f1 || true)
  if [ -n "${line:-}" ]; then
    start=$((line-20)); end=$((line+20))
    sed -n "${start},${end}p" "$MIG" | nl -ba 2>/dev/null || sed -n "${start},${end}p" "$MIG"
  fi
else
  echo "Missing file: $MIG" >&2
fi

# 2) Applicant 관련 DTO/요청에서 email 길이 검증(`@Size/`@Length 등) 있는지 확인
echo "=== Applicant DTO/request email validations ==="
rg -n "package .*applicant|domain\\.applicant" src/main/java/org/ject/support/domain/applicant -S || true
rg -n "@(Size|Length)\\([^\\)]*max\\s*=\\s*[0-9]+|email.*`@Size`|email.*`@Length`|`@Size`\\([^\\)]*max[^\\)]*\\).*email" src/main/java/org/ject/support/domain/applicant -S || true
rg -n "@(Size|Length)\\([^\\)]*max\\s*=\\s*[0-9]+|email" src/main/java/org/ject/support/domain/applicant/dto src/main/java/org/ject/support/domain/applicant/controller src/main/java/org/ject/support/domain/applicant/service  -S || true

# 3) 혹시 applicant와 연결되는 공용 요청(예: register, apply)에서 email 길이 제한이 있는지 추가로 확인
echo "=== Global search: email validations mentioning max length (near applicant flow) ==="
rg -n "`@Size`\\(max\\s*=\\s*[0-9]+[^\\)]*\\).*이메일|이메일.*`@Size`\\(max\\s*=\\s*[0-9]+|email.*`@Size`\\(max\\s*=\\s*[0-9]+" src/main/java/org/ject/support -S || true

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 4376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Applicant flow: find email validations (`@Size/`@Length) ==="
rg -n "@(Size|Length)\\([^\\)]*max\\s*=\\s*[0-9]+" src/main/java/org/ject/support/domain/applicant -S || true
rg -n "email.*@(Size|Length)|@(Size|Length).*email" src/main/java/org/ject/support/domain/applicant -S || true
rg -n "class .*Applicant|RegisterRequest|ApplicantDto|ApplyProfileRequest" src/main/java/org/ject/support/domain/applicant src/main/java/org/ject/support/domain/apply -S || true

echo "=== Applicant request/DTO fields mentioning email ==="
rg -n "(email\\b)" src/main/java/org/ject/support/domain/applicant/dto src/main/java/org/ject/support/domain/applicant/controller src/main/java/org/ject/support/domain/applicant/service -S || true

echo "=== Global search: email max length validators around applicant code paths ==="
rg -n "@(Size|Length)\\([^\\)]*max\\s*=\\s*[0-9]+[^\\)]*\\)\\s*(?:`@Email`\\b)?|`@Email`\\b" src/main/java/org/ject/support/domain/applicant src/main/java/org/ject/support/domain/apply -S || true

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 6740


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find any `@Size/`@Length validators for email (max=30 etc.) across codebase ==="
rg -n "@(Size|Length)\\([^\\)]*max\\s*=\\s*[0-9]+[^\\)]*\\).*이메일|이메일.*@(Size|Length)\\([^\\)]*max\\s*=\\s*[0-9]+|`@Email`\\b" src/main/java -S || true
rg -n "이메일 길이|email length|max = 30|varchar\\(30\\)|length = 30" src/main/java -S || true

echo "=== ApplicantDto.java (to check whether request body contains email with validations) ==="
FILE="src/main/java/org/ject/support/domain/applicant/dto/ApplicantDto.java"
if [ -f "$FILE" ]; then
  sed -n '1,200p' "$FILE" | nl -ba 2>/dev/null || sed -n '1,200p' "$FILE"
fi

echo "=== Find where applicant email token is extracted and generated ==="
rg -n "extractEmailFromVerificationToken|VerificationToken|verification token|email verification" src/main/java -S || true

echo "=== Member entity email column length for consistency (varchar(30) etc.) ==="
FILE="src/main/java/org/ject/support/domain/member/entity/Member.java"
if [ -f "$FILE" ]; then
  rg -n "`@Column`\\([^\\)]*length\\s*=\\s*30[^\\)]*\\)" "$FILE" || true
  sed -n '35,80p' "$FILE" | nl -ba 2>/dev/null || sed -n '35,80p' "$FILE"
fi

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 7921


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Applicant flow: ApplicantController 관련 email 검증/DTO 사용처 확인
rg -n "ApplicantController|registerTempApplicant|createVerificationToken|extractEmailFromVerificationToken" src/main/java/org/ject/support/domain/applicant src/main/java/org/ject/support/common/security/jwt -S

# 2) JwtTokenProvider: createVerificationToken/extractEmailFromVerificationToken 구현에서 email 길이/검증 로직 유무 확인
FILE="src/main/java/org/ject/support/common/security/jwt/JwtTokenProvider.java"
if [ -f "$FILE" ]; then
  rg -n "createVerificationToken\\(|extractEmailFromVerificationToken\\(" "$FILE" -n
  # print around those methods (rough window)
  for m in "createVerificationToken" "extractEmailFromVerificationToken"; do
    line=$(rg -n "$m" "$FILE" | head -n 1 | cut -d: -f1 || true)
    if [ -n "${line:-}" ]; then
      start=$((line-30)); end=$((line+40))
      echo "=== $FILE around $m ($start-$end) ==="
      sed -n "${start},${end}p" "$FILE" | head -n 120
    fi
  done
fi

# 3) 회원/인증에서 email을 받는 DTO/요청에 `@Size`(max=30) 유무 확인(회원/로그인/검증 토큰 생성 전 단계)
rg -n "AuthDto|`@Email`|`@Size`\\(max\\s*=\\s*[0-9]+\\).*email|email.*`@Size`\\(max\\s*=\\s*[0-9]+\\)" src/main/java/org/ject/support/domain/auth src/main/java/org/ject/support/common/security src/main/java/org/ject/support/domain/member src/main/java/org/ject/support/domain/applicant -S || true

# 4) AuthDto 내용 확인
FILE="src/main/java/org/ject/support/domain/auth/dto/AuthDto.java"
if [ -f "$FILE" ]; then
  sed -n '1,120p' "$FILE" | nl -ba 2>/dev/null || sed -n '1,120p' "$FILE"
fi

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 7354


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "`@Size`\\(max\\s*=\\s*30\\)" src/main/java -S || true
rg -n "email.*`@Size`\\(max\\s*=\\s*30\\)|`@Size`\\(max\\s*=\\s*30\\).*email" src/main/java -S || true
rg -n "`@Schema`\\([^\\)]*maxLength\\s*=\\s*30[^\\)]*\\)" src/main/java -S || true
rg -n "`@NotBlank`\\s*`@Email`|`@Email`" src/main/java/org/ject/support/domain -S | head -n 200

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 355


Applicant email 컬럼(30) 길이 제약으로 인한 DB 삽입 실패 가능성

Applicant.email@Column(length = 30)이고 마이그레이션에서도 applicant.email varchar(30) not null로 생성돼 있습니다. 그런데 applicant 등록 흐름은 검증 토큰의 email을 그대로 사용하고(ApplicantControllerextractEmailFromVerificationTokenApplicantService.registerTempApplicant), 이메일 값에 @Size(max=30) 같은 길이 검증이 코드에서 확인되지 않아 30자를 초과하는(표준 범위의) 이메일이 들어오면 DB 삽입 단계에서 실패할 수 있습니다. DB 길이/입력 검증 정책을 명확히 맞추는 조정이 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/ject/support/domain/applicant/entity/Applicant.java` around
lines 49 - 50, Applicant.email is limited to `@Column`(length = 30) but emails
extracted in ApplicantController.extractEmailFromVerificationToken and passed
into ApplicantService.registerTempApplicant are not length-validated, risking DB
insert failures; fix by aligning model and validation: either increase the
column length on Applicant.email to a realistic max (e.g., 254) and update
migrations, or add a validation annotation (e.g., `@Size`(max=30) or a larger
agreed limit) plus explicit trimming/validation in
ApplicantService.registerTempApplicant (and ensure controller extraction
enforces same constraint) so DB schema and runtime validation match.

Comment on lines +29 to +37
public List<String> findEmailsByIdsAndNotSubmitted(List<Long> applicantIds) {
return queryFactory.select(applicant.email)
.from(applicant)
.join(apply)
.on(applicant.id.eq(apply.member.id))
.where(applicant.id.in(applicantIds),
applicant.isDeleted.eq(false),
apply.status.eq(SUBMITTED).not())
.fetch();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

findEmailsByIdsAndNotSubmitted 조건이 의도와 다르게 결과를 과포함할 수 있습니다.

현재 조건(apply.status != SUBMITTED)은 동일 지원자에게 제출/미제출 이력이 함께 있을 때도 미제출 row 때문에 포함되고, 미제출 row가 여러 개면 이메일이 중복 반환됩니다. 메서드 명세(미제출 지원자 이메일)와 불일치할 수 있습니다. distinct + submitted apply 부재(not exists) 조건으로 바꾸는 쪽이 안전합니다.

수정 예시
 `@Override`
 public List<String> findEmailsByIdsAndNotSubmitted(List<Long> applicantIds) {
-    return queryFactory.select(applicant.email)
+    QApply submittedApply = new QApply("submittedApply");
+
+    return queryFactory.selectDistinct(applicant.email)
             .from(applicant)
             .join(apply)
             .on(applicant.id.eq(apply.member.id))
             .where(applicant.id.in(applicantIds),
                     applicant.isDeleted.eq(false),
-                    apply.status.eq(SUBMITTED).not())
+                    apply.status.ne(SUBMITTED),
+                    JPAExpressions.selectOne()
+                            .from(submittedApply)
+                            .where(
+                                    submittedApply.member.id.eq(applicant.id),
+                                    submittedApply.status.eq(SUBMITTED)
+                            )
+                            .notExists())
             .fetch();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/org/ject/support/domain/applicant/repository/ApplicantQueryRepositoryImpl.java`
around lines 29 - 37, The query in
ApplicantQueryRepositoryImpl.findEmailsByIdsAndNotSubmitted can over-include and
return duplicate emails because apply.status != SUBMITTED matches applicants who
also have submitted rows and multiple non-submitted rows; change the query to
select distinct applicant.email and replace the apply.status != SUBMITTED
predicate with a NOT EXISTS subquery that checks for any SUBMITTED apply for the
same applicant (use apply.member.id.eq(applicant.id) and
apply.status.eq(SUBMITTED) inside the subquery) so only applicants with no
submitted applications are returned.

Comment on lines +62 to +66
@BeforeEach
void setUp() {
objectMapper = new ObjectMapper();
mockMvc = standaloneSetup(applicantController).build();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# `@AuthPrincipal` 리졸버 구현 확인
rg -nP 'AuthPrincipal' --type=java -C3
ast-grep --pattern 'class $_ implements HandlerMethodArgumentResolver {
  $$$
}'

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 40446


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) ApplicantControllerTest 관련 코드 확인 (62-66 및 MockMvc perform 부분)
sed -n '1,140p' src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java

# 2) `@AuthPrincipal` 리졸버 구현 확인
sed -n '1,200p' src/main/java/org/ject/support/common/security/AuthenticatedMemberIdResolver.java

# 3) 리졸버가 WebMvcConfigurer에 등록되는지 확인
rg -n "addArgumentResolvers|WebMvcConfigurer|setCustomArgumentResolvers|HandlerMethodArgumentResolver" src/main/java src/test/java -S --type=java

# 4) ApplicantControllerTest에서 standaloneSetup에 setCustomArgumentResolvers를 쓰는지 확인
rg -n "standaloneSetup\\(|setCustomArgumentResolvers|AuthenticatedMemberIdResolver|AuthPrincipal" src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java -S --type=java

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 10519


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# AuthPrincipal 정의 확인
sed -n '1,120p' src/main/java/org/ject/support/common/security/AuthPrincipal.java

# WebConfig에서 addArgumentResolvers 등록 여부 확인
sed -n '1,200p' src/main/java/org/ject/support/common/security/config/WebConfig.java

# ApplicantControllerTest에서 setCustomArgumentResolvers 사용 여부 확인
rg -n "setCustomArgumentResolvers|AuthenticatedMemberIdResolver|AuthPrincipal" \
  src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java -S --type=java

# AuthenticatedMemberIdResolver 외에 `@AuthPrincipal` 대응 리졸버가 더 있는지 확인
rg -n "class .*Resolver.*implements HandlerMethodArgumentResolver|`@AuthPrincipal`" \
  src/main/java/org/ject/support/common/security -S --type=java

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 1050


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# ApplicantControllerTest 내부에서 setCustomArgumentResolvers 사용 여부 확인
rg -n "setCustomArgumentResolvers\\(|AuthenticatedMemberIdResolver|AuthPrincipal" src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java -S --type=java

# setUp() 메서드 주변(약 40~90라인)만 다시 확인
sed -n '40,110p' src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 65


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 파일 전체(대부분 100줄 내외일 가능성) 확인
wc -l src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java
sed -n '1,220p' src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java

# setCustomArgumentResolvers 존재 여부(없으면 아무 출력도 안 나올 수 있으니 ok)
rg -n "setCustomArgumentResolvers\\(" src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java -S --type=java || true

Repository: JECT-Study/JECT-Official-WebSite-Server

Length of output: 10015


standaloneSetup에서 @AuthPrincipalHandlerMethodArgumentResolver 등록 여부를 확인하세요.

  • WebConfig#addArgumentResolvers(...)에서 AuthenticatedMemberIdResolver를 등록하고, AuthenticatedMemberIdResolver@AuthPrincipal이 붙은 Long 파라미터를 SecurityContextHolderCustomUserDetails에서 꺼내 바인딩합니다.
  • 그런데 ApplicantControllerTeststandaloneSetup(applicantController)만 사용하며 setCustomArgumentResolvers(...)AuthenticatedMemberIdResolver를 명시적으로 추가하지 않습니다(62~66의 setUp). 따라서 @AuthPrincipal Long applicantId 바인딩이 실제로 이뤄지는지/인증 경로가 검증되는지 불명확하니, standaloneSetup(...).setCustomArgumentResolvers(new AuthenticatedMemberIdResolver()) 추가 또는 @WebMvcTest 등 스프링 MVC 구성을 포함하는 방식으로 테스트 구성을 정리하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/org/ject/support/domain/applicant/controller/ApplicantControllerTest.java`
around lines 62 - 66, The test setup in ApplicantControllerTest uses
standaloneSetup(applicantController) but does not register the custom argument
resolver for `@AuthPrincipal`; update the setUp method to register
AuthenticatedMemberIdResolver via
standaloneSetup(applicantController).setCustomArgumentResolvers(new
AuthenticatedMemberIdResolver()) so `@AuthPrincipal` Long applicantId is bound
from SecurityContextHolder's CustomUserDetails (mirror
WebConfig#addArgumentResolvers), or alternatively convert the test to use a
Spring MVC slice (e.g., `@WebMvcTest`) so the WebConfig argument resolvers are
applied; ensure the change targets the setUp method and references
applicantController, standaloneSetup, setCustomArgumentResolvers,
AuthenticatedMemberIdResolver, `@AuthPrincipal` and SecurityContextHolder.

@kangkings kangkings requested review from 023-dev and fivedragon5 June 1, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

👨🏻‍💻backend 백엔드 작업 🪄refactor 리팩토링

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant