Skip to content

[WTH-198] 동아리 개설 가입시 기수 입력 및 초기화#28

Merged
hyxklee merged 12 commits intodevfrom
feat/WTH-198-동아리-개설-가입시-기수-입력-및-초기화
Mar 19, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/WTH-198-\ub3d9\uc544\ub9ac-\uac1c\uc124-\uac00\uc785\uc2dc-\uae30\uc218-\uc785\ub825-\ubc0f-\ucd08\uae30\ud654"
Merged

[WTH-198] 동아리 개설 가입시 기수 입력 및 초기화#28
hyxklee merged 12 commits intodevfrom
feat/WTH-198-동아리-개설-가입시-기수-입력-및-초기화

Conversation

@hyxklee
Copy link
Contributor

@hyxklee hyxklee commented Mar 18, 2026

📌 Summary

어떤 작업인지 한 줄 요약해 주세요.

동아리 개설시 가장 최신 기수를 입력받아 1기부터 초기화하는 로직을 추가했습니다.
동아리 부원이 본인의 활동 기수를 마이페이지에서 설정한 경우 기수를 저장하고, 출석을 초기화하는 로직을 추가했습니다.

📝 Changes

변경사항을 what, why, how로 구분해 작성해 주세요.

What

""

Why

동아리 별 기수 관리를 명확하게 하기 위함

How

동아리 개설 Form에 최신 기수 필드 추가 및 1부터 반복적으로 기수를 초기화하는 로직 추가
동아리 부원 활동정보 저장시 기수를 저장하고, 출석을 초기화하는 로직 추가 - 이때 출석 정보는 이미 0 0 0 일 것이므로 명시적으로 출석 정보를 초기화하진 않음 (기수 입력은 기능 사용을 위해 필수기 때문)

📸 Screenshots / Logs

필요시 스크린샷 or 로그를 첨부해주세요.

💡 Reviewer 참고사항

리뷰에 참고할 내용을 작성해주세요.

getActiveMemberWithLock 메서드는 WTH-200 이슈에서 사용하고, 사용처 등록할 예정입니당

프론트 분들은 추가된 API 확인하고, 입력값 확인해주세옹
또한 기수 정보도 활동에 필수입니다. 모든 기능을 사용할 때 필수이므로 지연입력 될 수 있도록 처리해주세오
지연입력시 필요한 정보, API가 있다면 금일 회의 때 말해주세용 (프로필 정보가 완료되었음을 프론트에서 확인할 수 있는 방법)

✅ Checklist

  • PR 제목 설정 완료 (WTH-123 인증 필터 설정)
  • 테스트 구현 완료
  • 리뷰어 등록 완료
  • 자체 코드 리뷰 완료

Summary by CodeRabbit

  • 새로운 기능

    • 멤버가 자신의 초기 활동 기수를 설정하는 새 API 엔드포인트 추가
    • 동아리 생성 시 1부터 현재 기수까지 기수들이 자동으로 생성되어 초기화됨
    • 설정된 기수에 따라 출석 기록이 자동 생성되는 초기화 로직 추가
  • 버그 수정

    • 이미 기수가 설정된 멤버의 재설정 시 명확한 충돌 응답 제공 및 동시성 안전성 강화
  • 테스트

    • 초기 기수 설정 및 생성 흐름에 대한 단위/통합 테스트 추가 및 보강

@hyxklee hyxklee self-assigned this Mar 18, 2026
@hyxklee hyxklee added ✨ Feature 새로운 기능 추가 🔨 Refactor 코드 구조 개선 및 리팩토링 labels Mar 18, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

클럽 생성 시 초기 활동 기수(currentCardinal)를 자동 생성하고, 회원이 자신의 초기 활동 기수 목록을 설정할 수 있는 엔드포인트와 관련 비즈니스 로직(중복/존재 검사, 락 조회, 출석 초기화 등)을 추가합니다.

Changes

Cohort / File(s) Summary
API 설계 문서
\.claude/rules/api-design.md
Club 스코프 API 주석 권장사항을 세 개에서 두 개로 단순화(IDE 경고 억제 어노테이션 제거).
요청 DTO
src/main/kotlin/com/weeth/domain/club/application/dto/request/ClubCreateRequest.kt, src/main/kotlin/com/weeth/domain/club/application/dto/request/ClubMemberCardinalSetRequest.kt
ClubCreateRequest에 currentCardinal: Int(@Positive) 추가. 새로운 ClubMemberCardinalSetRequest DTO(기수 목록, NotEmpty + 요소별 Positive) 추가.
예외 및 에러 코드
src/main/kotlin/com/weeth/domain/club/application/exception/CardinalAlreadySetException.kt, src/main/kotlin/com/weeth/domain/club/application/exception/ClubErrorCode.kt
CardinalAlreadySetException 추가 및 ClubErrorCodeCARDINAL_ALREADY_SET(21109, CONFLICT) 열거형 추가.
Repository / Reader / Policy
src/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberReader.kt, src/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberRepository.kt, src/main/kotlin/com/weeth/domain/club/domain/service/ClubMemberPolicy.kt, src/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberCardinalRepository.kt
ClubMemberReader에 findByClubIdAndUserIdWithLock 시그니처 추가. ClubMemberRepository에 PESSIMISTIC_WRITE 락 및 타임아웃 힌트가 포함된 findByClubIdAndUserIdWithLock 구현 추가. ClubMemberPolicy에 락 조회용 getActiveMemberWithLock 추가. ClubMemberCardinalRepositoryexistsByClubMember 추가.
Use Case — 클럽 생성
src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCase.kt
클럽 생성 흐름에 currentCardinal 기반 Cardinal 자동 생성(1..currentCardinal) 및 최신 Cardinal에 IN_PROGRESS 상태 부여, LEAD 멤버에 ClubMemberCardinal 생성. 생성 관련 Repository 의존성(cardinalRepository, clubMemberCardinalRepository) 추가.
Use Case — 멤버 기수 설정
src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt
setInitialCardinals 공개 메서드 추가: 락 조회, 이미 설정 여부 검사(CARDINAL_ALREADY_SET), Cardinal 존재 검증, ClubMemberCardinal 저장, 관련 세션에 대한 Attendance 초기화 및 저장. 생성된 private initializeAttendances 헬퍼 추가. 생성자에 관련 리포지토리/리더/리포지토리 의존성 추가.
Admin UseCase 주석
src/main/kotlin/com/weeth/domain/club/application/usecase/command/AdminClubMemberUseCase.kt
출석 초기화 관련 경쟁 상태 가능성에 대한 TODO 주석 추가(로직 변경 없음).
Controller 및 응답 코드
src/main/kotlin/com/weeth/domain/club/presentation/ClubController.kt, src/main/kotlin/com/weeth/domain/club/presentation/ClubResponseCode.kt
POST /{clubId}/members/me/cardinals 엔드포인트 추가(ClubMemberCardinalSetRequest 처리). 응답 코드 MEMBER_CARDINAL_SET_SUCCESS(11116, CREATED) 추가.
테스트
src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.kt, src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.kt
ManageClubMemberUsecase의 setInitialCardinals 시나리오 테스트 추가(복수/중복/비존재/재설정 금지 등). ManageClubUseCaseTest에 Cardinal 자동 생성 및 ClubMemberCardinal 연결 검증 테스트 추가.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as ClubController
    participant UseCase as ManageClubMemberUsecase
    participant Policy as ClubMemberPolicy
    participant CardinalReader as CardinalReader
    participant MemberCardinalRepo as ClubMemberCardinalRepository
    participant SessionReader as SessionReader
    participant AttendanceRepo as AttendanceRepository

    Client->>Controller: POST /clubs/{clubId}/members/me/cardinals\n(ClubMemberCardinalSetRequest)
    Controller->>UseCase: setInitialCardinals(clubId, userId, request)
    UseCase->>Policy: getActiveMemberWithLock(clubId, userId)
    Policy-->>UseCase: ClubMember (locked)
    alt Member already has cardinals
        UseCase-->>Controller: throw CardinalAlreadySetException
    else proceed
        UseCase->>CardinalReader: validate requested cardinals exist
        CardinalReader-->>UseCase: Cardinals found
        UseCase->>MemberCardinalRepo: saveAll(ClubMemberCardinals)
        MemberCardinalRepo-->>UseCase: saved
        UseCase->>SessionReader: find sessions by cardinals
        SessionReader-->>UseCase: sessions list
        UseCase->>AttendanceRepo: saveAll(Attendances)
        AttendanceRepo-->>UseCase: saved
        UseCase-->>Controller: success
    end
    Controller-->>Client: 201 MEMBER_CARDINAL_SET_SUCCESS
Loading
sequenceDiagram
    participant Client
    participant Controller as ClubController
    participant UseCase as ManageClubUseCase
    participant ClubRepo as ClubRepository
    participant MemberRepo as ClubMemberRepository
    participant CardinalRepo as CardinalRepository
    participant MemberCardinalRepo as ClubMemberCardinalRepository

    Client->>Controller: POST /clubs (ClubCreateRequest with currentCardinal)
    Controller->>UseCase: create(request)
    UseCase->>ClubRepo: save(Club)
    ClubRepo-->>UseCase: Club created
    UseCase->>MemberRepo: save(ClubMember - LEAD)
    MemberRepo-->>UseCase: LEAD member saved
    loop i from 1 to currentCardinal
        alt i < currentCardinal
            UseCase->>CardinalRepo: save(Cardinal number i, status DONE)
        else i == currentCardinal
            UseCase->>CardinalRepo: save(Cardinal number i, status IN_PROGRESS)
        end
    end
    CardinalRepo-->>UseCase: all Cardinals saved
    UseCase->>MemberCardinalRepo: save(ClubMemberCardinal LEAD→latest)
    MemberCardinalRepo-->>UseCase: saved
    UseCase-->>Controller: created with initialized cardinals
    Controller-->>Client: success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75분

Possibly related PRs

Suggested reviewers

  • soo0711
  • JIN921
  • dalzzy
  • woneeeee

Poem

🐰 새 기수 하나씩 줄 지어 서고
멤버는 자기 자리를 조심스레 골라요
락으로 잠근 문 사이로 불안은 사라지고
출석 기록은 조용히 자리를 채우네
함께 뛰는 클럽, 토끼도 축하해요 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% 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
Title check ✅ Passed PR 제목이 변경사항의 핵심 내용을 명확하게 요약하고 있습니다. 동아리 개설 및 가입 시 기수 입력과 초기화라는 주요 기능이 간결하게 표현되었습니다.
Description check ✅ Passed PR 설명이 필수 섹션(Summary, Changes, Reviewer 참고사항)을 포함하고 있으며, What/Why/How 구조로 변경사항을 설명하고 있습니다. 다만 What 섹션이 공백으로 남겨져 있습니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/WTH-198-동아리-개설-가입시-기수-입력-및-초기화
📝 Coding Plan
  • Generate coding plan for human review comments

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

🧹 Nitpick comments (2)
src/main/kotlin/com/weeth/domain/club/application/dto/request/ClubCreateRequest.kt (1)

17-25: TODO 항목 처리 및 검증 불일치 확인 필요

Line 23의 TODO는 contactPhoneNumber가 필수라고 명시하지만, 실제 구현은 String? = null로 선택 필드입니다. 의도된 동작과 불일치합니다.

🔧 contactPhoneNumber를 필수로 변경하는 수정안
-    // TODO: 얘는 필수
-    `@field`:Schema(description = "연락 전화번호", example = "010-1234-5678")
-    val contactPhoneNumber: String? = null,
+    `@field`:Schema(description = "연락 전화번호", example = "010-1234-5678")
+    `@field`:NotBlank
+    val contactPhoneNumber: String,

TODO 항목들(길이 제한 추가, 필수/선택 필드 검증)을 구현하는 코드를 생성하거나, 별도 이슈로 등록하는 것을 도와드릴까요?

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

In
`@src/main/kotlin/com/weeth/domain/club/application/dto/request/ClubCreateRequest.kt`
around lines 17 - 25, The TODOs indicate missing validation and a mismatch for
contactPhoneNumber: change contactPhoneNumber from nullable String? = null to a
non-null String and apply appropriate validation annotations (e.g.,
`@field`:NotBlank / `@field`:NotNull and a `@field`:Pattern or `@field`:Size for phone
format/length) on contactPhoneNumber in ClubCreateRequest; also add length/size
constraints for description and contactEmail (e.g., `@field`:Size(max=...) and
`@field`:Email for contactEmail) so all three fields (description, contactEmail,
contactPhoneNumber) enforce the intended required/optional and length rules at
DTO level.
src/main/kotlin/com/weeth/domain/club/application/usecase/command/AdminClubMemberUseCase.kt (1)

67-97: TODO 주석은 적절하나, 경쟁 조건(race condition)의 실제 심각도를 고려해야 합니다.

setInitialCardinalsgetActiveMemberWithLock을 사용하여 비관적 락을 획득하지만, applyObgetMemberInClub을 사용하여 락 없이 멤버를 조회합니다. 동일 멤버에 대해 두 메서드가 동시에 실행되면 initializeAttendances가 중복 호출되어 중복 출석 레코드가 생성될 수 있습니다.

MVP 이후 개선 시, applyOb에서도 멤버 단위 락을 획득하거나, Attendance 엔티티에 (session_id, club_member_id) 유니크 제약을 추가하는 방안을 검토하세요.

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

In
`@src/main/kotlin/com/weeth/domain/club/application/usecase/command/AdminClubMemberUseCase.kt`
around lines 67 - 97, applyOb currently fetches members without a lock (using
getMemberInClub) which allows a race with setInitialCardinals (which uses
getActiveMemberWithLock) and can cause initializeAttendances to run twice; fix
by fetching the member under the same pessimistic/member-level lock (replace
getMemberInClub with getActiveMemberWithLock or call the existing locking method
before mutate operations in applyOb) so duplicate attendance creation is
prevented, and/or add a DB-level unique constraint on Attendance (session_id,
club_member_id) to enforce idempotency as a safety net; ensure applyOb remains
`@Transactional` and handle any lock-related exceptions consistently.
🤖 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/kotlin/com/weeth/domain/club/domain/repository/ClubMemberRepository.kt`:
- Around line 35-41: The query in findByClubIdAndUserIdWithLock can return
multiple rows because it lacks a memberStatus filter and also doesn't fetch the
user like findByIdWithLock; update the method and its `@Query` to include a
memberStatus condition (e.g., AND cm.memberStatus = :memberStatus) and add JOIN
FETCH cm.user so the locked entity includes the user, and add a corresponding
method parameter `@Param`("memberStatus") memberStatus: MemberStatus to ensure a
single, eagerly-fetched result under the pessimistic lock.

In `@src/main/kotlin/com/weeth/domain/club/presentation/ClubController.kt`:
- Line 26: Remove the unused import statement for PathVariable from
ClubController.kt: delete the line importing
org.springframework.web.bind.annotation.PathVariable since ClubController (the
class in this file) does not reference PathVariable anywhere; ensure no other
references to PathVariable remain before committing.

---

Nitpick comments:
In
`@src/main/kotlin/com/weeth/domain/club/application/dto/request/ClubCreateRequest.kt`:
- Around line 17-25: The TODOs indicate missing validation and a mismatch for
contactPhoneNumber: change contactPhoneNumber from nullable String? = null to a
non-null String and apply appropriate validation annotations (e.g.,
`@field`:NotBlank / `@field`:NotNull and a `@field`:Pattern or `@field`:Size for phone
format/length) on contactPhoneNumber in ClubCreateRequest; also add length/size
constraints for description and contactEmail (e.g., `@field`:Size(max=...) and
`@field`:Email for contactEmail) so all three fields (description, contactEmail,
contactPhoneNumber) enforce the intended required/optional and length rules at
DTO level.

In
`@src/main/kotlin/com/weeth/domain/club/application/usecase/command/AdminClubMemberUseCase.kt`:
- Around line 67-97: applyOb currently fetches members without a lock (using
getMemberInClub) which allows a race with setInitialCardinals (which uses
getActiveMemberWithLock) and can cause initializeAttendances to run twice; fix
by fetching the member under the same pessimistic/member-level lock (replace
getMemberInClub with getActiveMemberWithLock or call the existing locking method
before mutate operations in applyOb) so duplicate attendance creation is
prevented, and/or add a DB-level unique constraint on Attendance (session_id,
club_member_id) to enforce idempotency as a safety net; ensure applyOb remains
`@Transactional` and handle any lock-related exceptions consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4456792-859a-44cf-9c22-e34167dbc4fa

📥 Commits

Reviewing files that changed from the base of the PR and between 76eee80 and 6bb9d04.

📒 Files selected for processing (15)
  • .claude/rules/api-design.md
  • src/main/kotlin/com/weeth/domain/club/application/dto/request/ClubCreateRequest.kt
  • src/main/kotlin/com/weeth/domain/club/application/dto/request/ClubMemberCardinalSetRequest.kt
  • src/main/kotlin/com/weeth/domain/club/application/exception/CardinalAlreadySetException.kt
  • src/main/kotlin/com/weeth/domain/club/application/exception/ClubErrorCode.kt
  • src/main/kotlin/com/weeth/domain/club/application/usecase/command/AdminClubMemberUseCase.kt
  • src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt
  • src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCase.kt
  • src/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberReader.kt
  • src/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberRepository.kt
  • src/main/kotlin/com/weeth/domain/club/domain/service/ClubMemberPolicy.kt
  • src/main/kotlin/com/weeth/domain/club/presentation/ClubController.kt
  • src/main/kotlin/com/weeth/domain/club/presentation/ClubResponseCode.kt
  • src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.kt

Copy link
Collaborator

@soo0711 soo0711 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~~! 코멘트 하나만 확인 부탁드립니당!

Comment on lines +95 to +97
if (clubMemberCardinalRepository.findAllByClubMember(member).isNotEmpty()) {
throw CardinalAlreadySetException()
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

find는 모든 레코드를 가져오니까 존재여부를 확인 하기 위해서 existsByClubMember를 사용하는 건 어떨까요???

Copy link
Contributor Author

Choose a reason for hiding this comment

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

가볍게 검증하도록 수정할게요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

수정햇숩니다

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.

🧹 Nitpick comments (1)
src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt (1)

99-103: 기수 조회 시 N+1 쿼리 패턴 고려

현재 request.cardinals의 각 요소에 대해 개별적으로 findByClubIdAndCardinalNumber를 호출하고 있습니다. 기수 수가 적다면 큰 문제가 되지 않지만, 향후 요청 기수 수가 많아질 경우 findAllByClubIdAndCardinalNumberIn(clubId, numbers) 같은 배치 조회로 개선을 고려해볼 수 있습니다.

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

In
`@src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt`
around lines 99 - 103, 현재 ManageClubMemberUsecase의 request.cardinals를 순회하며
cardinalReader.findByClubIdAndCardinalNumber를 개별 호출해 N+1 쿼리 패턴을 유발하므로,
request.cardinals.distinct()로 얻은 숫자 목록을 한 번에 조회하는 배치 메서드(예:
cardinalReader.findAllByClubIdAndCardinalNumberIn(clubId, numbers) 또는
repository의 유사 메서드)를 구현·사용하도록 변경하고, 반환된 엔티티들을 cardinal number 기준으로 매핑하여 누락된 항목은
CardinalNotFoundException으로 처리하도록 수정하세요 (참조 심볼: request.cardinals,
cardinalReader.findByClubIdAndCardinalNumber,
cardinalReader.findAllByClubIdAndCardinalNumberIn).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt`:
- Around line 99-103: 현재 ManageClubMemberUsecase의 request.cardinals를 순회하며
cardinalReader.findByClubIdAndCardinalNumber를 개별 호출해 N+1 쿼리 패턴을 유발하므로,
request.cardinals.distinct()로 얻은 숫자 목록을 한 번에 조회하는 배치 메서드(예:
cardinalReader.findAllByClubIdAndCardinalNumberIn(clubId, numbers) 또는
repository의 유사 메서드)를 구현·사용하도록 변경하고, 반환된 엔티티들을 cardinal number 기준으로 매핑하여 누락된 항목은
CardinalNotFoundException으로 처리하도록 수정하세요 (참조 심볼: request.cardinals,
cardinalReader.findByClubIdAndCardinalNumber,
cardinalReader.findAllByClubIdAndCardinalNumberIn).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dad8459e-f337-4083-944d-462de5e9f9f0

📥 Commits

Reviewing files that changed from the base of the PR and between 6bb9d04 and 0a6c351.

📒 Files selected for processing (4)
  • src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt
  • src/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberCardinalRepository.kt
  • src/main/kotlin/com/weeth/domain/club/presentation/ClubController.kt
  • src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.kt

@hyxklee hyxklee merged commit e2d0673 into dev Mar 19, 2026
2 checks passed
@hyxklee hyxklee deleted the feat/WTH-198-동아리-개설-가입시-기수-입력-및-초기화 branch March 19, 2026 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 추가 🔨 Refactor 코드 구조 개선 및 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants