Skip to content

✨ Feat: 동호회 가입 신청서 작성 기능 추가 및 신청 조회 API 개선#59

Merged
angoroa merged 4 commits intodevelopfrom
feat/join-match-message
Feb 28, 2026
Merged

✨ Feat: 동호회 가입 신청서 작성 기능 추가 및 신청 조회 API 개선#59
angoroa merged 4 commits intodevelopfrom
feat/join-match-message

Conversation

@angoroa
Copy link
Copy Markdown
Member

@angoroa angoroa commented Feb 26, 2026

#️⃣ Issue Number

  • closed #

📝 요약(Summary)

  • JoinClubRequest 엔티티에 message(TEXT, NOT NULL) 필드 추가
  • 가입 신청 요청 DTO JoinClubApplicationRequest 신규 생성 (@notblank, @SiZe(max=200) 유효성 검사 포함)
  • JoinClubRequestResponse에 message 및 신청자 한줄 소개(userIntroduce) 필드 추가 — 동호회장이 신청 목록 조회 시 확인 가능
  • POST /api/clubs/{clubId}/join API가 RequestBody로 message를 필수 수신하도록 변경
  • notifications 테이블에 잔존하던 미사용 message 컬럼(NOT NULL) 제거 — 엔티티-스키마 불일치로 인한 INSERT 오류 수정
  • GET /api/clubs/join-requests/{requestId} 가입 신청 단건 조회 API 추가 — 동호회장 또는 신청자 본인만 조회 가능
  • 가입 신청 목록 조회(GET /{clubId}/join-requests) 응답을 경량 DTO(JoinClubRequestSummaryResponse)로 분리 — 목록에는 {id, userId, userNickname, clubId, clubName}만 반환

🛠️ PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • CSS 등 사용자 UI 디자인 변경
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📸스크린샷 (선택)

image

💬 공유사항 to 리뷰어

  • join-request API가 아예 알림창에서 " (안훈기)님이 (적시타)에 가입 신청을 하였습니다"라고 뜨게끔 바꿔줌
  • 해당 건 클릭시 가입신청 단건 조회 API 호출 (마이페이지 프로필 내용 + 자기소개서 작성한 내용) 보여줌

✅ PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

Summary by CodeRabbit

  • New Features

    • 클럽 가입 신청 시 최대 200자 메시지 입력 기능 추가
    • 가입 신청 단건 상세 조회 기능 추가
  • Improvements

    • 클럽 리더용 대기 중 가입 신청 목록을 요약형 응답으로 간소화
    • 가입 신청 응답에 신청자 소개 및 메시지 노출 추가

@angoroa angoroa requested a review from imjuyongp February 26, 2026 07:28
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9a5cdc and 585be7a.

📒 Files selected for processing (1)
  • src/main/resources

Walkthrough

클럽 가입 신청에 사용자 메시지 저장을 추가하고, 단일 신청 조회 엔드포인트를 추가하며 대기 중 신청 목록 응답을 요약 DTO로 변경했습니다. 관련 엔티티, 서비스, 컨트롤러 및 DTO들이 이에 맞춰 수정되었습니다.

Changes

Cohort / File(s) Summary
Controller
src/main/java/com/be/sportizebe/domain/notification/controller/JoinClubRequestController.java
requestJoin@Valid @RequestBody JoinClubApplicationRequest 파라미터 추가 및 message 전달; 새로운 GET /join-requests/{requestId} 엔드포인트 추가; getPendingRequests 반환 타입을 요약 DTO 리스트로 변경.
Entity
src/main/java/com/be/sportizebe/domain/notification/entity/JoinClubRequest.java
message 필드 추가 (@Column(nullable = false, columnDefinition = "TEXT")).
Service
src/main/java/com/be/sportizebe/domain/notification/service/...
requestJoin 시그니처에 String message 추가 및 저장; 새로운 getJoinRequest(Long, Long) 메서드 추가(권한 체크 포함); getPendingRequests 반환을 JoinClubRequestSummaryResponse 리스트로 변경 및 매핑 업데이트.
DTOs (request & response)
src/main/java/com/be/sportizebe/domain/notification/dto/request/JoinClubApplicationRequest.java, .../dto/response/JoinClubRequestSummaryResponse.java, .../dto/response/JoinClubRequestResponse.java
신규 요청 DTO JoinClubApplicationRequest 추가(메시지, 검증 어노테이션); 신규 요약 응답 JoinClubRequestSummaryResponse 추가; 상세 응답 JoinClubRequestResponseuserIntroducemessage 필드 추가 및 매핑 반영.
Service Impl
src/main/java/com/be/sportizebe/domain/notification/service/JoinClubRequestServiceImpl.java
엔티티 생성 시 .message(message) 설정; getJoinRequest 구현으로 리더/신청자 접근 제한 처리; pending 목록 매핑을 Summary DTO로 변경.
Resources
src/main/resources
서브프로젝트 커밋 메타데이터 업데이트(비기능적).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant Repository
    participant Entity

    rect rgba(100, 200, 100, 0.5)
    Note over Client,Entity: 메시지 포함 가입 신청
    Client->>Controller: POST /clubs/{clubId}/join-requests\n(JoinClubApplicationRequest)
    Controller->>Service: requestJoin(clubId, userId, message)
    Service->>Repository: save(JoinClubRequest with message)
    Repository->>Entity: persist entity
    Service->>Service: JoinClubRequestResponse.from(entity)
    Service-->>Controller: JoinClubRequestResponse
    Controller-->>Client: BaseResponse<JoinClubRequestResponse>
    end

    rect rgba(150, 150, 200, 0.5)
    Note over Client,Entity: 특정 신청 조회
    Client->>Controller: GET /join-requests/{requestId}
    Controller->>Service: getJoinRequest(requestId, userId)
    Service->>Repository: findById(requestId)
    Repository-->>Service: JoinClubRequest entity
    Service->>Service: 권한 검증(리더 또는 신청자)
    Service->>Service: JoinClubRequestResponse.from(entity)
    Service-->>Controller: JoinClubRequestResponse
    Controller-->>Client: BaseResponse<JoinClubRequestResponse>
    end

    rect rgba(200, 150, 100, 0.5)
    Note over Client,Entity: 대기 중인 신청 목록 조회
    Client->>Controller: GET /clubs/{clubId}/join-requests
    Controller->>Service: getPendingRequests(clubId, leaderId)
    Service->>Repository: findPendingByClubId(clubId)
    Repository-->>Service: List<JoinClubRequest>
    Service->>Service: map to JoinClubRequestSummaryResponse.from(...)
    Service-->>Controller: List<JoinClubRequestSummaryResponse>
    Controller-->>Client: BaseResponse<List<JoinClubRequestSummaryResponse>>
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

👀 review needed

Poem

🐰 작은 메시지 품어 문을 두드렸네,
새로 만든 창에 이름과 마음 남기고,
요약된 목록 속엔 소중한 발자국,
리더와 신청자 사이 다리 놓으며,
클럽의 한 켠에 따뜻함 한 움큼. 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 풀 리퀘스트의 주요 변경사항을 명확하게 반영합니다. 동호회 가입 신청서 작성 기능 추가 및 신청 조회 API 개선이라는 두 가지 핵심 변화를 정확히 요약하고 있습니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/join-match-message

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

🧹 Nitpick comments (1)
src/main/java/com/be/sportizebe/domain/notification/service/JoinClubRequestServiceImpl.java (1)

38-67: 서비스 계층에서도 message 방어 검증을 두는 것을 권장합니다.

현재는 컨트롤러 검증 경로를 우회하는 호출에서 null/blank/길이 초과가 DB 제약 예외로 전파될 수 있습니다. 서비스에서 한 번 더 검증(및 trim 저장)하면 장애 형태를 더 예측 가능하게 만들 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/be/sportizebe/domain/notification/service/JoinClubRequestServiceImpl.java`
around lines 38 - 67, In requestJoin within JoinClubRequestServiceImpl, add
defensive validation for the incoming message before building JoinClubRequest:
trim the message, check for null or blank and enforce a maximum length
(introduce a MAX_MESSAGE_LENGTH constant in the class), and if invalid throw a
CustomException (e.g., JoinClubRequestErrorCode.INVALID_MESSAGE or an
appropriate existing error code); finally pass the trimmed message to
JoinClubRequest.builder().message(...) so stored values are normalized and
service-only callers cannot bypass controller validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/com/be/sportizebe/domain/notification/entity/JoinClubRequest.java`:
- Around line 40-41: The JoinClubRequest entity declares the field message as
`@Column`(nullable = false) but there is no DB migration framework or scripts; set
up a migration tool (Flyway or Liquibase), then add a migration that (1) adds
the message column to the join_club_request table (allow NULL initially or add
with DEFAULT ''), (2) backfill existing rows with a safe default (e.g. update
message = '' where message IS NULL), and (3) alter the column to NOT NULL (or
create it with NOT NULL only after backfill); ensure the migration file is
committed and the application configured to run migrations on deploy so
JoinClubRequest.message matches the DB schema.

---

Nitpick comments:
In
`@src/main/java/com/be/sportizebe/domain/notification/service/JoinClubRequestServiceImpl.java`:
- Around line 38-67: In requestJoin within JoinClubRequestServiceImpl, add
defensive validation for the incoming message before building JoinClubRequest:
trim the message, check for null or blank and enforce a maximum length
(introduce a MAX_MESSAGE_LENGTH constant in the class), and if invalid throw a
CustomException (e.g., JoinClubRequestErrorCode.INVALID_MESSAGE or an
appropriate existing error code); finally pass the trimmed message to
JoinClubRequest.builder().message(...) so stored values are normalized and
service-only callers cannot bypass controller validation.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c85bac5 and d9a5cdc.

📒 Files selected for processing (7)
  • src/main/java/com/be/sportizebe/domain/notification/controller/JoinClubRequestController.java
  • src/main/java/com/be/sportizebe/domain/notification/dto/request/JoinClubApplicationRequest.java
  • src/main/java/com/be/sportizebe/domain/notification/dto/response/JoinClubRequestResponse.java
  • src/main/java/com/be/sportizebe/domain/notification/dto/response/JoinClubRequestSummaryResponse.java
  • src/main/java/com/be/sportizebe/domain/notification/entity/JoinClubRequest.java
  • src/main/java/com/be/sportizebe/domain/notification/service/JoinClubRequestService.java
  • src/main/java/com/be/sportizebe/domain/notification/service/JoinClubRequestServiceImpl.java

Comment on lines +40 to +41
@Column(nullable = false, columnDefinition = "TEXT")
private String message; // 가입 신청서
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:

# Find migration-related directories and files
find . -type d \( -name migrations -o -name db -o -name liquibase -o -name flyway -o -name sql \) 2>/dev/null | head -20

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Look for migration file patterns in common locations
find . -name "*.sql" -o -name "*.xml" | grep -i -E "(migration|flyway|liquibase|db)" | head -30

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Check for Flyway or Liquibase configuration in build files and properties
find . -name "*.gradle" -o -name "*.gradle.kts" -o -name "application*.yml" -o -name "application*.yaml" -o -name "application*.properties" | xargs grep -l "flyway\|liquibase" 2>/dev/null

Repository: Sportize/be

Length of output: 39


🏁 Script executed:

# Search for join_club_request related code and migrations
rg -i "join_club_request" --type-list | head -20

Repository: Sportize/be

Length of output: 444


🏁 Script executed:

# Get repository structure overview
ls -la

Repository: Sportize/be

Length of output: 1171


🏁 Script executed:

# Explore src directory structure
find src -type f -name "*.sql" -o -name "*.xml" | head -30

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Check for Flyway or Liquibase configuration in gradle and properties files
cat build.gradle

Repository: Sportize/be

Length of output: 1969


🏁 Script executed:

# Look for application properties/yml files
find src -name "application*.yml" -o -name "application*.properties" | head -10

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Search for join_club_request in the codebase
rg "join_club_request" -i

Repository: Sportize/be

Length of output: 399


🏁 Script executed:

# Check the JoinClubRequest entity file to understand the current state
cat -n src/main/java/com/be/sportizebe/domain/notification/entity/JoinClubRequest.java

Repository: Sportize/be

Length of output: 1917


🏁 Script executed:

# Check if there's a migrations directory or database schema directory
find src -type d -name "*migration*" -o -type d -name "*db*" -o -type d -name "*sql*" -o -type d -name "*schema*"

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Look for all SQL files in the project
find . -name "*.sql" -type f

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Check application.yml/properties in resources directory
find src -path "*/resources/*" -name "application*" -type f

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Check for database configuration in resources
ls -la src/main/resources/ 2>/dev/null || echo "Resources directory not found or empty"

Repository: Sportize/be

Length of output: 161


🏁 Script executed:

# Search for any references to Flyway, Liquibase, or migration in all files
rg -i "flyway|liquibase|migration" --type-list

Repository: Sportize/be

Length of output: 444


🏁 Script executed:

# Try a simpler search for migration configuration
rg "flyway|liquibase|migration" -i

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Check complete src directory structure
tree src -L 5 --dirsfirst

Repository: Sportize/be

Length of output: 277


🏁 Script executed:

# Check if there's a resources directory with application properties
find src/main -type f -name "*" | grep -i -E "(application|config|properties|yml|yaml)" | head -20

Repository: Sportize/be

Length of output: 690


🏁 Script executed:

# Look for any database initialization or schema files
find . -type f \( -name "*.sql" -o -name "schema.*" -o -name "data.*" \) 2>/dev/null

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Check git history for any migration-related commits or files
git log --name-only --oneline | grep -i -E "(migration|flyway|liquibase|\.sql)" | head -20

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Verify if there's any Spring Boot auto-ddl or hibernate configuration
rg "spring.jpa.hibernate|spring.jpa.database|ddl-auto" -i

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Check if Hibernate is configured to auto-create tables
find src/main -name "*.java" | xargs rg "spring.jpa|hibernate|ddl-auto" 2>/dev/null | head -20

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Verify if application properties exist anywhere in the project
find . -name "application*" -o -name "application*.yml" -o -name "application*.properties" 2>/dev/null

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Check if there's any database configuration in the codebase
rg "@Configuration|@EnableAutoConfiguration" -A 10 | grep -i "database\|datasource\|jpa\|hibernate" | head -20

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Final comprehensive check - list all directories in src/main
find src/main -type d | sort

Repository: Sportize/be

Length of output: 6104


🏁 Script executed:

# Check git history to see if message field is newly added
git log -p -- src/main/java/com/be/sportizebe/domain/notification/entity/JoinClubRequest.java | head -100

Repository: Sportize/be

Length of output: 37


🏁 Script executed:

# Check the current status of the file in git
git show HEAD:src/main/java/com/be/sportizebe/domain/notification/entity/JoinClubRequest.java

Repository: Sportize/be

Length of output: 1518


🏁 Script executed:

# Check if there are any unstaged changes to this file
git diff src/main/java/com/be/sportizebe/domain/notification/entity/JoinClubRequest.java

Repository: Sportize/be

Length of output: 37


데이터베이스 마이그레이션 프레임워크 및 스크립트가 누락되었습니다.

코드상 message 필드는 nullable = false로 정의되어 있으나, 프로젝트에 Flyway나 Liquibase 같은 마이그레이션 도구가 설정되어 있지 않으며 마이그레이션 스크립트도 없습니다.

운영 데이터베이스에 기존 join_club_request 데이터가 있을 경우, 스키마 변경 시 다음 순서로 진행해야 합니다:

  1. 마이그레이션 프레임워크(Flyway/Liquibase) 설정
  2. 마이그레이션 스크립트 작성: message 컬럼 추가 → 기존 행 백필(DEFAULT '' 등) → NOT NULL 제약 적용
  3. 마이그레이션 파일을 통해 안전하게 배포
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/be/sportizebe/domain/notification/entity/JoinClubRequest.java`
around lines 40 - 41, The JoinClubRequest entity declares the field message as
`@Column`(nullable = false) but there is no DB migration framework or scripts; set
up a migration tool (Flyway or Liquibase), then add a migration that (1) adds
the message column to the join_club_request table (allow NULL initially or add
with DEFAULT ''), (2) backfill existing rows with a safe default (e.g. update
message = '' where message IS NULL), and (3) alter the column to NOT NULL (or
create it with NOT NULL only after backfill); ensure the migration file is
committed and the application configured to run migrations on deploy so
JoinClubRequest.message matches the DB schema.

@angoroa angoroa merged commit b3a3917 into develop Feb 28, 2026
1 check was pending
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.

1 participant