Skip to content

Comments

Feat :Club 도메인 및 카테고리/소속 enum 구현#338

Merged
jiyun921 merged 13 commits intodevelopfrom
feat/club-#336
Feb 14, 2026
Merged

Feat :Club 도메인 및 카테고리/소속 enum 구현#338
jiyun921 merged 13 commits intodevelopfrom
feat/club-#336

Conversation

@jiyun921
Copy link
Collaborator

@jiyun921 jiyun921 commented Feb 8, 2026

#️⃣ 이슈

#336

📌 요약

  • Club 도메인 구조를 구현했습니다.
  • 동아리 카테고리 및 소속(enum)을 정의하고 관련 도메인 테스트를 추가했습니다.
  • club 및 club_bookmark 테이블에 대한 Flyway 마이그레이션을 추가하였습니다.

🛠️ 상세

  • Club, ClubBookmark 엔티티 구현
  • club / club_bookmark 테이블 Flyway 마이그레이션 추가
  • ClubCategory, ClubDivision enum 구현
    • 서버 내부 UPPERCASE 관리
    • API 통신 시 lowercase name 사용
    • 테스트 코드 구현
    • enum 변환 로직 및 예외 처리 추가

💬 기타

Summary by CodeRabbit

  • 새로운 기능

    • 동아리 관리 추가: 상세 정보, 카테고리·소속, 위치·모집 기간, 포스터 및 자격요건 등록 가능
    • 동아리 SNS 링크(Instagram, YouTube 등) 등록 지원
    • 동아리 구독(팔로우) 기능 추가
  • 테스트

    • 동아리 카테고리 및 소속 매핑 관련 단위 테스트 추가
  • Chores

    • 동아리 관련 데이터베이스 테이블 생성 마이그레이션 추가

@jiyun921 jiyun921 self-assigned this Feb 8, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 8, 2026

훑어보기

동아리 관리 도메인을 새로 추가합니다: Club 엔티티와 ClubSns·ClubSubscribe 연관, ClubCategory·ClubDivision·ClubSnsType 열거형, 관련 에러 코드, DB 마이그레이션, 그리고 fromName 검증 유닛 테스트가 포함됩니다.

변경 사항

Cohort / File(s) 요약
Club 도메인 엔티티
src/main/java/com/kustacks/kuring/club/domain/Club.java, src/main/java/com/kustacks/kuring/club/domain/ClubSns.java, src/main/java/com/kustacks/kuring/club/domain/ClubSubscribe.java
새로운 JPA 엔티티 Club, ClubSns(클럽별 SNS), ClubSubscribe(클럽-사용자 구독) 추가. 필드와 관계 매핑(일대다/다대일, 제약조건 등) 및 Lombok 어노테이션 적용.
Club 도메인 Enum
src/main/java/com/kustacks/kuring/club/domain/ClubCategory.java, src/main/java/com/kustacks/kuring/club/domain/ClubDivision.java, src/main/java/com/kustacks/kuring/club/domain/ClubSnsType.java
ClubCategory, ClubDivision, ClubSnsType 열거형 추가. 각 열거형에 이름/한글명 필드와 fromName(String) 조회(ClubCategory·ClubDivision) 구현.
에러 코드
src/main/java/com/kustacks/kuring/common/exception/code/ErrorCode.java
새로운 오류 코드 CLUB_CATEGORY_NOT_SUPPORTED, CLUB_DIVISION_NOT_SUPPORTED 추가(HTTP 400, 지원되지 않는 카테고리/소속 메시지).
데이터베이스 스키마 (마이그레이션)
src/main/resources/db/migration/V260206__Create_club_table.sql
club, club_subscribe, club_sns 테이블 생성. 외래키 제약(ON DELETE CASCADE), 유니크 제약(클럽-사용자), 인덱스 및 문자셋 설정 포함.
유닛 테스트
src/test/java/com/kustacks/kuring/club/domain/ClubCategoryTest.java, src/test/java/com/kustacks/kuring/club/domain/ClubDivisionTest.java
fromName() 정상 매핑 검증 및 잘못된 입력에 대한 예외(NotFoundException) 테스트 추가.

🐰 새 둥지에 모여서 노래해요,
카테고리 불러와서 줄 세우고,
구독은 톡톡, SNS로 반짝이며,
데이터는 안전히 땅속에 쌓이고,
작은 발자국으로 도메인이 완성되었네요 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ⚠️ Unable to check for merge conflicts: Invalid branch name format
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경사항의 핵심을 명확하게 요약하고 있으며, Club 도메인, 카테고리/소속 enum 구현이라는 주요 내용을 정확하게 반영합니다.

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

✨ 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 feat/club-#336
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch feat/club-#336
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
src/main/java/com/kustacks/kuring/club/domain/ClubDivision.java (2)

36-56: NAME_MAPMap<String, ClubDivision>으로 변경하면 valueOf() 호출을 제거할 수 있습니다.

현재 NAME_MAPString → String으로 매핑한 후 fromName에서 다시 valueOf()를 호출합니다. Map<String, ClubDivision>으로 직접 enum 상수를 저장하면 조회가 한 단계로 줄어들고, 코드가 더 간결해집니다.

♻️ 개선 제안
-    private static final Map<String, String> NAME_MAP;
+    private static final Map<String, ClubDivision> NAME_MAP;

     static {
         NAME_MAP = Collections.unmodifiableMap(Arrays.stream(values())
-                .collect(Collectors.toMap(ClubDivision::getName, ClubDivision::name))
+                .collect(Collectors.toMap(ClubDivision::getName, v -> v))
         );
     }

     // ...

     public static ClubDivision fromName(String name) {
-        String findName = Optional.ofNullable(NAME_MAP.get(name))
+        return Optional.ofNullable(NAME_MAP.get(name))
                 .orElseThrow(() -> new NotFoundException(CLUB_DIVISION_NOT_SUPPORTED));
-        return ClubDivision.valueOf(findName);
     }

44-44: name 필드가 java.lang.Enum의 내부 name 필드를 섀도잉합니다.

Enum.name()은 enum 상수 이름(예: "CENTRAL")을 반환하고, Lombok @Getter가 생성하는 getName()은 커스텀 필드(예: "central")를 반환합니다. 동작에 문제는 없으나, 유지보수 시 혼동 가능성이 있으므로 apiName 등 좀 더 명확한 필드명을 고려해 보세요.


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.

@jiyun921 jiyun921 changed the title [feat] Club 도메인 및 카테고리/소속 enum 초기 구현 [feat] Club 도메인 및 카테고리/소속 enum 구현 Feb 8, 2026
@github-actions
Copy link

github-actions bot commented Feb 8, 2026

Unit Test Results

  77 files    77 suites   1m 31s ⏱️
554 tests 547 ✔️ 7 💤 0
557 runs  550 ✔️ 7 💤 0

Results for commit b290560.

♻️ This comment has been updated with latest results.

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

🤖 Fix all issues with AI agents
In `@src/main/java/com/kustacks/kuring/club/domain/Club.java`:
- Around line 37-41: The Club entity's enum fields category and division lack
JPA non-null constraints; update the Club class by adding `@Column`(nullable =
false) to the category and division fields (the fields named category and
division in class Club) so the JPA mapping matches the migration schema and
enforces NOT NULL at the entity level.
🧹 Nitpick comments (8)
src/main/resources/db/migration/V260206__Create_club_table.sql (2)

1-22: category, division 컬럼에 인덱스 추가를 고려해 주세요.

동아리 목록을 카테고리나 소속별로 필터링하는 API가 추가될 가능성이 높습니다. 해당 컬럼에 인덱스가 없으면 테이블 풀 스캔이 발생할 수 있습니다.

CREATE INDEX idx_club_category ON club (category);
CREATE INDEX idx_club_division ON club (division);

13-14: x, y 컬럼명이 의미를 전달하지 못합니다.

좌표를 나타내는 컬럼이라면 longitude/latitude 같은 명시적인 이름이 스키마 가독성에 도움이 됩니다. 이미 초기 마이그레이션 단계이므로 지금 변경하는 것이 가장 비용이 적습니다. (JPA 엔티티의 @Column(name = ...) 도 함께 수정 필요)

src/main/java/com/kustacks/kuring/club/domain/ClubCategory.java (2)

22-42: NAME_MAPMap<String, ClubCategory>로 단순화하세요.

현재 Map<String, String>으로 enum 상수 이름(문자열)을 저장한 후 다시 valueOf()를 호출하고 있습니다. 중간 단계 없이 enum 인스턴스를 직접 매핑하면 더 간결하고 타입 안전합니다.

♻️ 제안하는 수정 사항
-    private static final Map<String, String> NAME_MAP;
+    private static final Map<String, ClubCategory> NAME_MAP;

     static {
         NAME_MAP = Collections.unmodifiableMap(Arrays.stream(ClubCategory.values())
-                .collect(Collectors.toMap(ClubCategory::getName, ClubCategory::name))
+                .collect(Collectors.toMap(ClubCategory::getName, cc -> cc))
         );
     }

     ...

     public static ClubCategory fromName(String name) {
-        String findName = Optional.ofNullable(NAME_MAP.get(name))
+        return Optional.ofNullable(NAME_MAP.get(name))
                 .orElseThrow(() -> new NotFoundException(CAT_NOT_EXIST_CLUB_CATEGORY));
-        return ClubCategory.valueOf(findName);
     }

30-30: name 필드가 Enum.name()을 섀도잉합니다.

java.lang.Enumname 필드와 이름이 겹쳐서 name() vs getName() 호출 결과가 달라집니다 (ACADEMIC vs academic). 기능적으로 문제는 없지만, 유지보수 시 혼동의 여지가 있으므로 apiName 등으로 필드명을 변경하는 것을 고려해 주세요.

src/test/java/com/kustacks/kuring/club/domain/ClubCategoryTest.java (1)

10-11: Assertions 대신 AssertionsForClassTypes를 직접 import 하고 있습니다.

일반적으로 org.assertj.core.api.Assertions를 사용하는 것이 더 관용적이며, Iterable/Collection 관련 assertion도 함께 사용할 수 있습니다. 기능상 문제는 없으나 참고 사항입니다.

src/main/java/com/kustacks/kuring/club/domain/Club.java (1)

18-73: public 생성자 또는 팩토리 메서드가 없습니다.

현재 @NoArgsConstructor(access = AccessLevel.PROTECTED)만 존재하여, 애플리케이션 코드에서 Club 인스턴스를 생성할 방법이 없습니다. 초기 구현이므로 이후 기능 개발 시 생성자/빌더 추가가 필요할 것입니다.

src/main/java/com/kustacks/kuring/club/domain/ClubDivision.java (2)

36-56: NAME_MAPMap<String, ClubDivision>으로 변경하여 이중 조회를 제거할 수 있습니다.

현재 NAME_MAPString → String (lowercase name → enum constant name)으로 매핑한 뒤, fromName에서 다시 valueOf로 변환하고 있어 불필요한 이중 조회가 발생합니다. 직접 ClubDivision을 값으로 저장하면 코드가 더 간결해집니다.

♻️ 제안하는 리팩터링
-    private static final Map<String, String> NAME_MAP;
+    private static final Map<String, ClubDivision> NAME_MAP;

     static {
         NAME_MAP = Collections.unmodifiableMap(Arrays.stream(values())
-                .collect(Collectors.toMap(ClubDivision::getName, ClubDivision::name))
+                .collect(Collectors.toMap(ClubDivision::getName, v -> v))
         );
     }

     public static ClubDivision fromName(String name) {
-        String findName = Optional.ofNullable(NAME_MAP.get(name))
+        return Optional.ofNullable(NAME_MAP.get(name))
                 .orElseThrow(() -> new NotFoundException(CAT_NOT_EXIST_CLUB_DIVISION));
-        return ClubDivision.valueOf(findName);
     }

44-44: name 필드가 Enum.name()과 혼동될 수 있습니다.

Java의 Enum 클래스에는 이미 상수 이름을 반환하는 name() 메서드가 있습니다. 필드명으로 name을 사용하면 getName()(Lombok 생성, "central" 반환)과 name()("CENTRAL" 반환)이 공존하게 되어 유지보수 시 혼동을 유발할 수 있습니다. externalName 등 보다 구별되는 이름을 고려해 보세요.

Copy link
Member

@zbqmgldjfh zbqmgldjfh left a comment

Choose a reason for hiding this comment

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

몇부분만 논의 해보면 좋을것 같아요!

@Column(length = 30, nullable = false)
private String summary;

@Lob
Copy link
Member

Choose a reason for hiding this comment

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

이거 Lob 먹일정도로 content가 길어질까요?
내부적으로 CLOB or BLOB의 대용량 오브젝트를 사용하는데 이정도로 긴 내용을 작성하나 해서요~
추가로 Column 에노테이션도 필요해 보입니다~!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@lob가 TEXT로 저장되는줄 알았는데 다시 찾아보니 MariaDB에서는 LONGTEXT로 저장되는군요.. @column(columnDefinition = "TEXT")으로 하면 충분할 것 같습니다!

@Column(length = 30)
private String room;

private Double x;
Copy link
Member

Choose a reason for hiding this comment

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

몰라서 그러는데 x랑 y는 뭔가요?? 위경도 인가요?
지나가는 동료 입장에서 x, y 변수명은 한눈에 이해하기 어려운것 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

위도, 경도인데 x,y는 명확하지 않은것 같아서 lat, lon으로 수정했습니다!

)
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ClubBookmark {
Copy link
Member

Choose a reason for hiding this comment

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

아마 Club 과 Book 간의 중간 테이블을 활용한 N:M 구조 인것 같은데,
북마크에 많은 기능이 있지 않다면, 그냥 Club 에서 userId 를 리스트로 즉, 간접참조를 적용해봐도 좋을것 같은데,
판단은 지윤님이 원하는 방향으로 하는거로!~~
다만 "간접참조"와 "직접참조"에 대하여는 한번 공부해보시고 넘어갔으면 좋겠습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아하 넵!! 생각해봤는데 북마크한 동아리 목록 조회나 모집 일정 푸시 알림처럼 특정 사용자 기준으로 조회하는 기능 때문에 직접참조로 하면 특정 user_id에 해당하는 row를 바로 찾을 수 있는데 간접참조로는 모든 동아리를 순회하면서 userId가 포함되어 있는지 확인하는 구조가 될 것 같아서 직접참조가 좋지 않을까 생각했습니다! 괜찮을까유??

assertThat(result).isEqualTo(clubCategory);
}

@DisplayName("존재하지 않는 name으로 ClubCategory를 찾으려 하면 예외가 발생한다")
Copy link
Member

Choose a reason for hiding this comment

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

테스트 작성 정말 굳굳!!

@rlagkswn00 rlagkswn00 changed the title [feat] Club 도메인 및 카테고리/소속 enum 구현 Feat :Club 도메인 및 카테고리/소속 enum 구현 Feb 12, 2026
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

🤖 Fix all issues with AI agents
In `@src/main/java/com/kustacks/kuring/club/domain/Club.java`:
- Around line 20-77: The Club entity only has a protected no-args constructor so
callers cannot create instances; add a creation API such as Lombok's `@Builder`
(or a public static factory) to the Club class so callers can construct objects
with its many fields. Specifically, annotate the Club class with `@Builder` (and
optionally `@AllArgsConstructor`) or implement a public static factory method
(e.g., Club.create(...)) that accepts the necessary fields (name, summary,
category, division, etc.) and sets defaults like homepageUrls = new
ArrayList<>() and isAlways = false; update places that construct Club (similar
to ClubSubscribe) to use the new builder/factory.

In `@src/main/java/com/kustacks/kuring/club/domain/ClubSubscribe.java`:
- Around line 24-38: ClubSubscribe lacks a public constructor to create and
persist a new subscription: add a public constructor (or static factory) that
accepts Club and User and assigns them to the club and user fields (ensuring
JPA-compatible behavior), e.g. a public ClubSubscribe(Club club, User user) that
sets this.club and this.user; apply the same fix to the ClubSns entity (add a
public constructor or static factory accepting the appropriate associated
entities) so new instances can be created and persisted.
🧹 Nitpick comments (2)
src/main/java/com/kustacks/kuring/club/domain/Club.java (1)

47-48: homepageUrls 필드명이 실제 의미와 맞지 않습니다.

이 필드는 ClubSns (Instagram, YouTube 등) 목록인데, homepageUrls라는 이름은 홈페이지 URL을 의미합니다. snsLinks 또는 snsAccounts 같은 이름이 더 적절합니다.

src/main/resources/db/migration/V260206__Create_club_table.sql (1)

36-39: idx_club_subscribe_club(club_id) 인덱스가 중복됩니다.

uk_club_user(club_id, user_id) 유니크 제약조건이 이미 club_id를 선두 컬럼으로 포함하고 있어, MySQL은 club_id 단독 조회 시 해당 인덱스를 사용할 수 있습니다. idx_club_subscribe_club은 불필요한 중복 인덱스입니다.

♻️ 중복 인덱스 제거 제안
     CONSTRAINT uk_club_user UNIQUE (club_id, user_id),
 
-    INDEX idx_club_subscribe_user (user_id),
-    INDEX idx_club_subscribe_club (club_id)
+    INDEX idx_club_subscribe_user (user_id)

@jiyun921
Copy link
Collaborator Author

리뷰랑 기획 변경 사항 반영했습니다!

  1. 위도/경도 의미를 명확하게 표현하도록 수정
  • x, y → lat, lon 으로 변경
  1. description / qualifications 타입 수정
  • 기존 @lob@column(columnDefinition = "TEXT") 로 변경
  1. Club SNS url 1:N 구조 적용
  • 하나의 동아리가 여러 SNS 링크를 가질 수 있도록 구조 확장
  • ClubSns 엔티티 추가
  • ClubSnsType enum 추가
  1. 즐겨찾기 → 구독으로 명칭 변경
  • 테이블명 및 관련 클래스 이름 변경
  1. DB 스키마 반영
  • club 테이블 변경사항 수정
  • club_sns 테이블 추가

Copy link
Member

@rlagkswn00 rlagkswn00 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다! 한가지 네이밍 관련해서 이야기 드려요!

추가로 코드래빗이 AI툴이긴 하지만,,리뷰 달리면 꼭 건바건 해결해주세요! 나름 답글 달면 답변도 해준답니다? 딱히 대응 안해도 되는거면 그냥 resolve 누르셔도 되구!

그리구 맨날 보고있는게 아니기 때무네,,,수정하시면 디코나 카톡으로 이야기 해주세요!!
기왕이면 소통은 과하게!! 투머치하게!!

Comment on lines 58 to 59
CAT_NOT_EXIST_CLUB_CATEGORY(HttpStatus.BAD_REQUEST, "서버에서 지원하지 않는 동아리 카테고리입니다."),
CAT_NOT_EXIST_CLUB_DIVISION(HttpStatus.BAD_REQUEST, "서버에서 지원하지 않는 동아리 소속입니다."),
Copy link
Member

Choose a reason for hiding this comment

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

아마도 여기 있는 CAT은 공지사항 카테고리(학사, 국제, 장학 등)이런거 같은데 네이밍이 적절치는 않은거 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵 !! Club 관련 이름으로 수정했습니다!

@jiyun921 jiyun921 merged commit ebf153c into develop Feb 14, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants