Skip to content

Conversation

@lepitaaar
Copy link
Contributor

@lepitaaar lepitaaar commented Dec 23, 2025

#️⃣연관된 이슈

#962

📝작업 내용

동아리 설명을 바뀐 명세에 맞게 수정하였습니다.
PUT /api/club/info

...
"description": {
    "introDescription": "여기까지",
    "activityDescription": "입니다",
    "awards": [
        {
            "semester": "2024학년도 1학기",
            "achievements": [
                "이런거했음"
            ]
        }
    ],
    "idealCandidate": {
        "tags": [
            "핼로"
        ],
        "content": "123"
    },
    "benefits": "123",
    "faqs": [
        {
            "question": "1",
            "answer": "2"
        }
    ]
},
...

또한 외부에있던 faqs 필드를 description 내부로 이동시켰습니다

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

릴리스 노트

  • Refactor

    • 클럽의 소개 데이터를 문자열에서 구조화된 형식으로 전환해 소개, 활동, 수상, 이상적 회원상, 혜택, FAQ 등을 한곳에서 관리합니다.
    • 상세 조회 및 요청 형식이 새 구조를 반영하도록 변경되었습니다.
  • New Features

    • 수상 항목과 이상적 회원 정보 표현이 추가되어 더 풍부한 클럽 정보를 제공합니다.
  • Chores

    • 테스트 픽스처와 요청/응답 생성 로직을 새 구조에 맞게 업데이트했습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@lepitaaar lepitaaar self-assigned this Dec 23, 2025
@lepitaaar lepitaaar added ✨ Feature 기능 개발 💾 BE Backend labels Dec 23, 2025
@vercel
Copy link

vercel bot commented Dec 23, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
moadong Ready Ready Preview, Comment Dec 23, 2025 3:40pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

Caution

Review failed

The pull request is closed.

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

클럽 엔티티의 description과 faqs가 단일 복합 객체 ClubDescription으로 대체되었고, 관련 엔티티/DTO/픽스처들이 이를 반영하도록 추가·변경되었습니다.

Changes

코호트 / 파일(s) 변경 요약
엔티티: Club 변경
backend/src/main/java/moadong/club/entity/Club.java
description:Stringfaqs:List<Faq>clubDescription:ClubDescription으로 통합; 생성자·빌더 초기화 및 update(ClubInfoRequest) 매핑 변경
새로운 엔티티들
backend/src/main/java/moadong/club/entity/ClubDescription.java, .../ClubAward.java, .../ClubIdealCandidate.java
클럽 상세 설명을 위한 ClubDescription, 수상 정보 ClubAward, 이상적 지원자 ClubIdealCandidate 추가 (Lombok 사용)
DTO: Description 관련 추가/변경
backend/src/main/java/moadong/club/payload/dto/ClubDescriptionDto.java, .../ClubAwardDto.java, .../ClubIdealCandidateDto.java, .../FaqDto.java
검증 어노테이션이 포함된 DTO 레코드 추가; from(...)/toEntity() 매핑 메서드 제공
요청/응답 DTO 변경
backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java, backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java
description 타입을 String->ClubDescriptionDto로 변경; faqs 관련 타입/필드 조정 및 매핑 업데이트
테스트 픽스처 업데이트
backend/src/test/java/moadong/fixture/ClubFixture.java, backend/src/test/java/moadong/fixture/ClubRequestFixture.java
픽스처에서 ClubDescriptionDto 사용으로 변경: 일부 메서드 시그니처와 목(mock) 제거/수정

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • PR #963 — 동일한 ClubDescription 전환(엔티티/DTO/매핑) 변경을 다룸.
  • PR #539 — FAQ와 클럽 설명 모델링 변경을 공유함(ClubDetailedResult 등 동일 파일 영향).
  • PR #956 — 백엔드에서 String description → ClubDescription로 데이터 구조 변경과 관련 있음.

Suggested labels

📬 API

Suggested reviewers

  • oesnuj
  • Zepelown
  • seongwon030

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 '[feature] 동아리 소개 필드 추가'는 주요 변경사항(description 필드를 문자열에서 복합 객체로 변경)을 명확하게 설명합니다.
Linked Issues check ✅ Passed PR은 MOA-453의 모든 코딩 요구사항을 충족합니다: description 엔티티(ClubDescription, ClubAward, ClubIdealCandidate) 추가, 명세에 맞는 구조 구현, faqs를 description 내부로 이동 완료.
Out of Scope Changes check ✅ Passed 모든 변경사항이 MOA-453 범위 내입니다. ClubDescription 관련 엔티티와 DTO 추가, 필드 구조화, faqs 이동 등 모두 연관 이슈의 목표와 일치합니다.

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between df505cb and a9b2716.

📒 Files selected for processing (1)
  • backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java

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.

@github-actions
Copy link

Test Results

75 tests   72 ✅  18s ⏱️
14 suites   3 💤
14 files     0 ❌

Results for commit 8f43ad0.

@lepitaaar lepitaaar changed the base branch from develop/be to refactor/#960-recruitment-api-update-MOA-452 December 23, 2025 11:35
Copy link
Contributor

@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)
backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java (1)

76-81: 메서드 이름이 기능과 일치하지 않습니다

updateDescription 메서드는 더 이상 description 필드를 업데이트하지 않고 모집 정보(모집 시작일, 종료일, 대상, URL)만 업데이트합니다. description 필드가 ClubDescription 엔티티로 이동했으므로, 메서드 이름을 updateRecruitmentInfo와 같이 실제 동작을 반영하도록 변경하는 것이 좋습니다.

🔎 메서드 이름 변경 제안
-    public void updateDescription(ClubRecruitmentInfoUpdateRequest request) {
+    public void updateRecruitmentInfo(ClubRecruitmentInfoUpdateRequest request) {
         this.recruitmentStart = request.recruitmentStart();
         this.recruitmentEnd = request.recruitmentEnd();
         this.recruitmentTarget = request.recruitmentTarget();
         this.externalApplicationUrl = request.externalApplicationUrl();
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5c182ca and 8f43ad0.

📒 Files selected for processing (12)
  • backend/src/main/java/moadong/club/entity/Club.java
  • backend/src/main/java/moadong/club/entity/ClubAward.java
  • backend/src/main/java/moadong/club/entity/ClubDescription.java
  • backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java
  • backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java
  • backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java
  • backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java
  • backend/src/main/java/moadong/club/payload/request/ClubRecruitmentInfoUpdateRequest.java
  • backend/src/main/java/moadong/club/repository/ClubSearchRepository.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/test/java/moadong/fixture/ClubFixture.java
  • backend/src/test/java/moadong/fixture/ClubRequestFixture.java
💤 Files with no reviewable changes (2)
  • backend/src/main/java/moadong/club/repository/ClubSearchRepository.java
  • backend/src/test/java/moadong/fixture/ClubFixture.java
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Zepelown
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java:32-33
Timestamp: 2025-05-15T12:03:57.356Z
Learning: 엔티티 클래스는 요청/응답 객체(DTO)에 의존해서는 안 됩니다. 계층 간 의존성 문제를 방지하기 위해 엔티티와 DTO는 분리되어야 합니다. 예를 들어, `ClubApplicationQuestion` 엔티티가 `ClubApplicationRequest.Options`와 같은 요청 객체를 직접 참조하는 대신, 엔티티 패키지 내에 `QuestionOptions`와 같은 별도의 클래스를 정의하고 사용해야 합니다. 이렇게 하면 요청 객체 변경이 데이터베이스 스키마나 엔티티 계층에 영향을 미치지 않습니다.
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 703
File: backend/src/main/java/moadong/club/controller/ClubApplyController.java:84-84
Timestamp: 2025-08-25T14:43:52.320Z
Learning: In the Moadong codebase, questionId and clubId are equivalent identifiers that represent the same entity. The ClubApplicationRepository.findAllByIdInAndQuestionId method correctly uses clubId as the questionId parameter for filtering club applications.

Applied to files:

  • backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/test/java/moadong/fixture/ClubRequestFixture.java
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.

Applied to files:

  • backend/src/main/java/moadong/club/entity/ClubAward.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/main/java/moadong/club/entity/ClubDescription.java
  • backend/src/main/java/moadong/club/entity/Club.java
  • backend/src/test/java/moadong/fixture/ClubRequestFixture.java
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.

Applied to files:

  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/main/java/moadong/club/entity/Club.java
  • backend/src/test/java/moadong/fixture/ClubRequestFixture.java
🧬 Code graph analysis (7)
backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java (3)
backend/src/main/java/moadong/user/entity/User.java (1)
  • Builder (21-99)
backend/src/main/java/moadong/club/entity/ClubDescription.java (1)
  • Getter (10-21)
backend/src/main/java/moadong/club/entity/ClubAward.java (1)
  • Getter (10-17)
backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java (1)
frontend/src/types/club.ts (1)
  • ClubDescription (28-31)
backend/src/main/java/moadong/club/entity/ClubAward.java (1)
backend/src/main/java/moadong/club/entity/ClubDescription.java (1)
  • Getter (10-21)
backend/src/main/java/moadong/club/entity/ClubDescription.java (2)
backend/src/main/java/moadong/club/entity/ClubAward.java (1)
  • Getter (10-17)
backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java (1)
  • Getter (10-17)
backend/src/main/java/moadong/club/entity/Club.java (1)
frontend/src/types/club.ts (1)
  • ClubDescription (28-31)
backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java (1)
frontend/src/types/club.ts (1)
  • ClubDescription (28-31)
backend/src/test/java/moadong/fixture/ClubRequestFixture.java (1)
frontend/src/types/club.ts (1)
  • ClubDescription (28-31)
⏰ 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: test
🔇 Additional comments (13)
backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java (1)

18-22: 임포트 재구성 확인됨

시간 관련 클래스들과 List 임포트가 재구성되었습니다. 기능적 영향은 없습니다.

backend/src/main/java/moadong/club/service/ClubProfileService.java (1)

35-55: 코드 정리가 잘 되었습니다.

사용하지 않는 import와 ClubSearchResult 의존성을 제거하여 코드가 더 깔끔해졌습니다. 로직 변경 없이 단순화가 잘 이루어졌습니다.

backend/src/main/java/moadong/club/entity/ClubAward.java (1)

10-17: 엔티티 구조가 올바르게 작성되었습니다.

Lombok을 사용한 간단한 데이터 클래스로 잘 구성되어 있습니다. ClubDescription에 임베드되어 사용되므로 MongoDB의 기본 매핑으로 충분합니다.

backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java (1)

10-17: 엔티티 구조가 올바르게 작성되었습니다.

이상적인 지원자 정보를 담는 간단한 데이터 클래스로 잘 구성되어 있습니다.

backend/src/main/java/moadong/club/entity/Club.java (2)

49-50: 엔티티 필드 추가가 올바르게 구현되었습니다.

ClubDescription 필드가 적절하게 추가되었으며:

  • MongoDB 매핑을 위한 @Field("description") 어노테이션 사용 ✓
  • 모든 생성자에서 NPE 방지를 위해 초기화 ✓
  • update(ClubInfoRequest) 메서드에서 올바르게 할당 ✓

Also applies to: 54-92, 104-104


125-127: updateDescription 메서드는 현재 요청 구조와 호환됩니다.

ClubRecruitmentInformation.updateDescription() 메서드를 확인했으며, 해당 메서드는 recruitmentStart, recruitmentEnd, recruitmentTarget, externalApplicationUrl만 접근하고 있습니다. 현재 ClubRecruitmentInfoUpdateRequest가 이 모든 필드를 포함하고 있으므로 호환성 문제는 없습니다.

backend/src/main/java/moadong/club/payload/request/ClubRecruitmentInfoUpdateRequest.java (1)

5-10: 요청 객체 단순화가 적절하게 이루어졌습니다.

descriptionfaqs 필드를 ClubDescription으로 이동시키면서, 이 요청 객체는 모집 시간과 대상에만 집중하게 되었습니다. 명확한 책임 분리가 잘 되었습니다.

backend/src/main/java/moadong/club/entity/ClubDescription.java (1)

10-21: 엔티티 구조가 명세에 맞게 잘 작성되었습니다.

ClubDescription 엔티티가 PR 목표에 명시된 구조를 정확하게 반영하고 있습니다:

  • introDescription, activityDescription, benefits ✓
  • awards (List) ✓
  • idealCandidate (ClubIdealCandidate) ✓
  • faqs 필드가 외부에서 내부로 이동 ✓
backend/src/test/java/moadong/fixture/ClubRequestFixture.java (2)

15-27: 테스트 픽스처가 새로운 구조에 맞게 업데이트되었습니다.

ClubInfoRequestClubDescription.builder().build()를 추가하여 새 생성자 시그니처와 일치시켰습니다. 빈 객체를 사용하는 것은 기본 테스트 픽스처로는 적절합니다.


30-37: 요청 객체 단순화가 올바르게 반영되었습니다.

ClubRecruitmentInfoUpdateRequest의 매개변수가 4개로 줄어든 것이 정확하게 반영되었습니다. descriptionfaqs 제거가 올바르게 적용되었습니다.

backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java (3)

5-5: LGTM: 필요한 import 추가

ClubDescription 엔티티 import가 올바르게 추가되었습니다.


70-70: description 필드의 null 가능성 확인

club.getClubDescription()이 null을 반환할 수 있는지 확인이 필요합니다. 만약 null이 가능하다면 API 응답에서 description이 null로 전달되는데, 이것이 의도된 동작인지 확인하세요.

다른 필드들은 대부분 null일 때 빈 문자열이나 빈 컬렉션으로 기본값을 설정하는 패턴을 따르고 있습니다.


38-38: 팩토리 메서드 시그니처 변경이 모든 호출부에 반영되었습니다

recommendClubs 파라미터 제거에 따른 시그니처 변경이 ClubProfileService의 호출부에 올바르게 적용되었습니다.

List<String> feeds,
String introduction,
String description,
ClubDescription description,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find ClubDetailedResult.java and ClubDescription class
fd -t f "ClubDetailedResult.java" backend/
fd -t f "ClubDescription.java" backend/

Repository: Moadong/moadong

Length of output: 192


🏁 Script executed:

# Search for ClubDescription in frontend
fd -t f -name "*.ts" -o -name "*.tsx" frontend/ | head -20
rg -t ts "ClubDescription" frontend/src --max-count=5

Repository: Moadong/moadong

Length of output: 1587


🏁 Script executed:

# Read ClubDetailedResult.java to see the structure around line 23
cat -n backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java

Repository: Moadong/moadong

Length of output: 5157


🏁 Script executed:

# Read ClubDescription backend entity
cat -n backend/src/main/java/moadong/club/entity/ClubDescription.java

Repository: Moadong/moadong

Length of output: 678


🏁 Script executed:

# Read frontend ClubDescription type definition
cat -n frontend/src/types/club.ts

Repository: Moadong/moadong

Length of output: 936


🏁 Script executed:

# Check how ClubDetail is used in frontend, particularly the description field
rg -t ts "ClubDetail" frontend/src --max-count=20 -A2

Repository: Moadong/moadong

Length of output: 18142


🏁 Script executed:

# Check API response handling for club detail
fd -t f "*.ts" frontend/src/apis | xargs grep -l -i "club.*detail" | head -5

Repository: Moadong/moadong

Length of output: 342


🏁 Script executed:

# Search for API calls that fetch club details
rg -t ts "getClubDetail\|fetchClubDetail\|club.*detail" frontend/src/apis --max-count=10

Repository: Moadong/moadong

Length of output: 41


🏁 Script executed:

# Check the API client code for club detail fetching
cat -n frontend/src/apis/getClubDetail.ts

Repository: Moadong/moadong

Length of output: 692


🏁 Script executed:

# Check the hook that processes the API response
cat -n frontend/src/hooks/queries/club/useGetClubDetail.ts

Repository: Moadong/moadong

Length of output: 872


🏁 Script executed:

# Check for any transformation or mapping logic
rg -t ts "description" frontend/src/hooks/queries/club/useGetClubDetail.ts -B3 -A3

Repository: Moadong/moadong

Length of output: 41


🏁 Script executed:

# Verify what pages/components actually use the description field
rg -t ts "clubDetail\.description\|description:" frontend/src/pages/ClubDetailPage --max-count=10

Repository: Moadong/moadong

Length of output: 41


🏁 Script executed:

# Check IntroduceBox component which likely uses description
fd -t f "IntroduceBox.tsx" frontend/src
cat frontend/src/pages/ClubDetailPage/components/IntroduceBox/IntroduceBox.tsx

Repository: Moadong/moadong

Length of output: 1911


🏁 Script executed:

# Final verification: Check if there's any serializer or JSON adapter that might transform ClubDescription
fd -t f "JsonSerializer\|@JsonProperty" backend/src/main/java/moadong/club/payload/dto | head -5
rg -t java "@JsonProperty\|@JsonSerialize" backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java

Repository: Moadong/moadong

Length of output: 41


🏁 Script executed:

# Check for Jackson annotations in ClubDetailedResult
rg -t java "Jackson\|@Json\|Serializable" backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java

Repository: Moadong/moadong

Length of output: 41


🏁 Script executed:

# Look for serialization configuration
fd -t f "WebConfig\|JsonConfig\|JacksonConfig" backend/src

Repository: Moadong/moadong

Length of output: 41


🏁 Script executed:

# Find the controller that returns ClubDetailedResult
rg -t java "ClubDetailedResult" backend/src/main/java --max-count=10

Repository: Moadong/moadong

Length of output: 895


🏁 Script executed:

# Check the actual controller method that returns the club detail
fd -t f "ClubController.java" backend/src

Repository: Moadong/moadong

Length of output: 41


description 필드 타입 변경으로 인한 API 호환성 문제

description 필드가 String에서 ClubDescription 객체로 변경되었으나, 프론트엔드는 여전히 문자열을 기대합니다. IntroduceBox 컴포넌트가 description을 마크다운 문자열로 렌더링하려 하므로 런타임 에러가 발생합니다.

프론트엔드의 ClubDetail 타입을 업데이트하거나, 백엔드에서 적절한 필드(introDescription 등)를 문자열로 매핑해야 합니다.

🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java around
line 23, the description field was changed from String to ClubDescription which
breaks the frontend expecting a markdown string; restore API compatibility by
adding a new String field (e.g., introDescription or descriptionMarkdown) and
populate it from ClubDescription (e.g., description.toMarkdown() or
description.getContent()) in the DTO mapping or factory method so the existing
frontend can continue to receive a plain markdown string; alternatively, if you
prefer not to add a field, revert the type back to String and ensure the service
layer maps ClubDescription -> String before constructing the DTO.

Copy link
Member

@seongwon030 seongwon030 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다

Copy link
Contributor

@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: 3

♻️ Duplicate comments (1)
backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java (1)

84-85: NullPointerException 위험: getClubDescription() null 체크 필요

club.getClubDescription()이 null인 경우 .getFaqs() 호출 시 NPE가 발생합니다. 이전 리뷰에서도 동일한 문제가 지적되었습니다.

🔎 NPE 방지를 위한 수정 제안
-                .faqs(club.getClubDescription().getFaqs() == null ? List.of()
-                        : club.getClubDescription().getFaqs().stream().map(FaqDto::from).toList())
+                .faqs(club.getClubDescription() == null || club.getClubDescription().getFaqs() == null 
+                        ? List.of() 
+                        : club.getClubDescription().getFaqs().stream().map(FaqDto::from).toList())
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8f43ad0 and df505cb.

📒 Files selected for processing (11)
  • backend/src/main/java/moadong/club/entity/Club.java
  • backend/src/main/java/moadong/club/entity/ClubAward.java
  • backend/src/main/java/moadong/club/entity/ClubDescription.java
  • backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java
  • backend/src/main/java/moadong/club/payload/dto/ClubAwardDto.java
  • backend/src/main/java/moadong/club/payload/dto/ClubDescriptionDto.java
  • backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java
  • backend/src/main/java/moadong/club/payload/dto/ClubIdealCandidateDto.java
  • backend/src/main/java/moadong/club/payload/dto/FaqDto.java
  • backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java
  • backend/src/test/java/moadong/fixture/ClubRequestFixture.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/src/main/java/moadong/club/entity/ClubAward.java
  • backend/src/test/java/moadong/fixture/ClubRequestFixture.java
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: Zepelown
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java:32-33
Timestamp: 2025-05-15T12:03:57.356Z
Learning: 엔티티 클래스는 요청/응답 객체(DTO)에 의존해서는 안 됩니다. 계층 간 의존성 문제를 방지하기 위해 엔티티와 DTO는 분리되어야 합니다. 예를 들어, `ClubApplicationQuestion` 엔티티가 `ClubApplicationRequest.Options`와 같은 요청 객체를 직접 참조하는 대신, 엔티티 패키지 내에 `QuestionOptions`와 같은 별도의 클래스를 정의하고 사용해야 합니다. 이렇게 하면 요청 객체 변경이 데이터베이스 스키마나 엔티티 계층에 영향을 미치지 않습니다.
Learnt from: seongwon030
Repo: Moadong/moadong PR: 548
File: frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx:17-57
Timestamp: 2025-07-19T05:05:10.196Z
Learning: ClubDetailPage.tsx에서 notJoinedClubNames 배열의 하드코딩은 의도적인 설계 결정입니다. 개발자가 명시적으로 하드코딩을 선택했으므로 이에 대한 리팩토링 제안을 하지 않아야 합니다.
Learnt from: Zepelown
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java:35-36
Timestamp: 2025-05-15T12:03:57.833Z
Learning: 엔티티 클래스에서 요청 객체(DTO)를 직접 참조하지 않고, 대신 도메인 모델에 해당하는 별도의 클래스를 엔티티 패키지에 정의하여 계층 간 분리를 유지해야 합니다. 이는 Options, QuestionItem과 같은 클래스에도 적용됩니다.
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.

Applied to files:

  • backend/src/main/java/moadong/club/entity/Club.java
  • backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java
  • backend/src/main/java/moadong/club/entity/ClubDescription.java
  • backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.

Applied to files:

  • backend/src/main/java/moadong/club/entity/Club.java
  • backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java
  • backend/src/main/java/moadong/club/entity/ClubDescription.java
  • backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 703
File: backend/src/main/java/moadong/club/controller/ClubApplyController.java:84-84
Timestamp: 2025-08-25T14:43:52.320Z
Learning: In the Moadong codebase, questionId and clubId are equivalent identifiers that represent the same entity. The ClubApplicationRepository.findAllByIdInAndQuestionId method correctly uses clubId as the questionId parameter for filtering club applications.

Applied to files:

  • backend/src/main/java/moadong/club/entity/Club.java
  • backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java
  • backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java
📚 Learning: 2025-07-19T05:05:10.196Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 548
File: frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx:17-57
Timestamp: 2025-07-19T05:05:10.196Z
Learning: ClubDetailPage.tsx에서 notJoinedClubNames 배열의 하드코딩은 의도적인 설계 결정입니다. 개발자가 명시적으로 하드코딩을 선택했으므로 이에 대한 리팩토링 제안을 하지 않아야 합니다.

Applied to files:

  • backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java
  • backend/src/main/java/moadong/club/entity/ClubDescription.java
📚 Learning: 2025-05-15T12:03:57.356Z
Learnt from: Zepelown
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java:32-33
Timestamp: 2025-05-15T12:03:57.356Z
Learning: 엔티티 클래스는 요청/응답 객체(DTO)에 의존해서는 안 됩니다. 계층 간 의존성 문제를 방지하기 위해 엔티티와 DTO는 분리되어야 합니다. 예를 들어, `ClubApplicationQuestion` 엔티티가 `ClubApplicationRequest.Options`와 같은 요청 객체를 직접 참조하는 대신, 엔티티 패키지 내에 `QuestionOptions`와 같은 별도의 클래스를 정의하고 사용해야 합니다. 이렇게 하면 요청 객체 변경이 데이터베이스 스키마나 엔티티 계층에 영향을 미치지 않습니다.

Applied to files:

  • backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java
📚 Learning: 2025-05-15T12:03:57.833Z
Learnt from: Zepelown
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java:35-36
Timestamp: 2025-05-15T12:03:57.833Z
Learning: 엔티티 클래스에서 요청 객체(DTO)를 직접 참조하지 않고, 대신 도메인 모델에 해당하는 별도의 클래스를 엔티티 패키지에 정의하여 계층 간 분리를 유지해야 합니다. 이는 Options, QuestionItem과 같은 클래스에도 적용됩니다.

Applied to files:

  • backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java
🧬 Code graph analysis (3)
backend/src/main/java/moadong/club/entity/Club.java (1)
frontend/src/types/club.ts (1)
  • ClubDescription (28-31)
backend/src/main/java/moadong/club/entity/ClubDescription.java (3)
backend/src/main/java/moadong/club/entity/ClubAward.java (1)
  • Getter (10-19)
backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java (1)
  • Getter (10-19)
backend/src/main/java/moadong/club/entity/Faq.java (1)
  • NoArgsConstructor (7-13)
backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java (4)
backend/src/main/java/moadong/user/entity/User.java (1)
  • Builder (21-99)
backend/src/main/java/moadong/club/entity/ClubAward.java (1)
  • Getter (10-19)
backend/src/main/java/moadong/club/entity/ClubDescription.java (1)
  • Getter (10-27)
backend/src/main/java/moadong/club/entity/Faq.java (1)
  • NoArgsConstructor (7-13)
🔇 Additional comments (10)
backend/src/main/java/moadong/club/payload/dto/ClubAwardDto.java (1)

8-25: LGTM! DTO 구조가 적절하게 설계되었습니다.

엔티티와 DTO 간의 변환 메서드(from, toEntity)가 깔끔하게 구현되어 있고, @Size 제약 조건도 적절히 적용되어 있습니다.

backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java (1)

4-4: LGTM! DTO 계층 분리가 올바르게 적용되었습니다.

ClubDescriptionDto를 사용하여 엔티티(ClubDescription)와 요청 객체 간의 의존성을 적절히 분리했습니다. 이전 리뷰에서 지적된 계층 간 의존성 문제가 해결되었습니다. Based on learnings, 엔티티와 DTO 분리 원칙을 준수하고 있습니다.

Also applies to: 20-20

backend/src/main/java/moadong/club/payload/dto/ClubIdealCandidateDto.java (1)

8-25: LGTM! ClubAwardDto와 일관된 패턴으로 구현되었습니다.

from()toEntity() 메서드가 적절하게 구현되어 있으며, null 체크도 포함되어 있습니다.

backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java (1)

10-19: LGTM! 엔티티 구조가 적절합니다.

Lombok 어노테이션을 사용하여 보일러플레이트 코드를 줄였고, ClubAward 엔티티와 일관된 패턴을 따르고 있습니다.

backend/src/main/java/moadong/club/entity/Club.java (1)

49-50: LGTM! 생성자에서 clubDescription 초기화가 잘 되어 있습니다.

모든 생성자에서 clubDescription을 빈 빌더로 초기화하여 null 상태를 방지하고 있습니다. 이는 다른 필드들(clubRecruitmentInformation)과 일관된 패턴입니다.

Also applies to: 54-61, 63-71, 73-82, 84-92

backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java (1)

21-21: LGTM! DTO 타입 변경이 적절합니다.

descriptionfaqs 필드가 DTO 타입으로 변경되었고, ClubDescriptionDto.from()은 null을 안전하게 처리합니다.

Also applies to: 32-32, 68-68

backend/src/main/java/moadong/club/entity/ClubDescription.java (1)

10-27: LGTM! 명세에 맞는 복합 객체 구조가 잘 설계되었습니다.

PR 목표에 따라 description을 단순 문자열에서 복합 객체로 변경했습니다. ClubAward, ClubIdealCandidate, Faq 등 중첩 엔티티 참조가 적절하게 구성되어 있습니다.

backend/src/main/java/moadong/club/payload/dto/ClubDescriptionDto.java (1)

9-50: LGTM! DTO 매핑 로직이 잘 구현되었습니다.

from()toEntity() 메서드 모두 null 안전 처리가 적절히 적용되어 있고, @Valid 어노테이션으로 중첩 DTO의 유효성 검사가 전파됩니다. 컬렉션 타입에 대한 스트림 매핑도 깔끔합니다.

backend/src/main/java/moadong/club/payload/dto/FaqDto.java (2)

1-4: LGTM!

패키지 구조와 import가 적절합니다. DTO가 엔티티를 참조하는 것은 올바른 의존성 방향입니다.


13-16: LGTM!

엔티티에서 DTO로의 변환 로직이 명확하고, null 처리도 적절합니다.

this.clubRecruitmentInformation.update(request);
this.description = request.description();
this.faqs = request.faqs();
this.clubDescription = request.description().toEntity();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

NullPointerException 위험: description null 체크 필요

request.description()이 null인 경우 toEntity() 호출 시 NPE가 발생합니다. 방어적 null 체크를 추가하세요.

🔎 NPE 방지를 위한 수정 제안
-        this.clubDescription = request.description().toEntity();
+        this.clubDescription = request.description() == null 
+                ? ClubDescription.builder().build() 
+                : request.description().toEntity();
📝 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
this.clubDescription = request.description().toEntity();
this.clubDescription = request.description() == null
? ClubDescription.builder().build()
: request.description().toEntity();
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/entity/Club.java around line 104, calling
request.description().toEntity() can throw a NullPointerException if
request.description() is null; add a defensive null check and set
this.clubDescription to either null or a default/empty description entity when
request.description() is null (e.g., use a conditional/Optional to call
toEntity() only when description is non-null), ensuring the assignment never
invokes toEntity() on null.

Comment on lines +6 to +12
public record FaqDto(
@Size(max = 100)
String question,

@Size(max = 500)
String answer
) {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

필수 필드 검증 추가를 권장합니다.

FAQ의 질문과 답변은 사용자에게 표시되는 중요한 콘텐츠이므로, @NotBlank 검증을 추가하는 것이 좋습니다. 현재 @Size만으로는 null이나 빈 문자열을 허용하게 됩니다.

🔎 제안하는 수정사항
+import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.Size;
 import moadong.club.entity.Faq;
 
 public record FaqDto(
+    @NotBlank
     @Size(max = 100)
     String question,
     
+    @NotBlank
     @Size(max = 500)
     String answer
 ) {
📝 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
public record FaqDto(
@Size(max = 100)
String question,
@Size(max = 500)
String answer
) {
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import moadong.club.entity.Faq;
public record FaqDto(
@NotBlank
@Size(max = 100)
String question,
@NotBlank
@Size(max = 500)
String answer
) {
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/payload/dto/FaqDto.java around lines 6 to
12, the DTO currently only uses @Size which allows null or blank values; add
@NotBlank to both question and answer to enforce non-null/non-empty input (place
@NotBlank before @Size), and add the corresponding import for
javax.validation.constraints.NotBlank so validation fails for null or blank FAQ
fields.

Comment on lines +18 to +20
public Faq toEntity() {
return new Faq(question, answer);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd -e java -t f "Faq.java" | head -5

Repository: Moadong/moadong

Length of output: 109


🏁 Script executed:

cat -n backend/src/main/java/moadong/club/entity/Faq.java

Repository: Moadong/moadong

Length of output: 377


🏁 Script executed:

cat -n backend/src/main/java/moadong/club/payload/dto/FaqDto.java

Repository: Moadong/moadong

Length of output: 656


🏁 Script executed:

# 검증을 위해 FaqDto 컨텍스트 재확인
cd backend && find . -name "*.java" -type f | xargs rg "FaqDto" | head -20

Repository: Moadong/moadong

Length of output: 1015


FaqDto의 question과 answer 필드에 null 체크 추가

toEntity() 메서드가 questionanswer 필드의 null 검증 없이 엔티티를 생성합니다. FaqDto의 필드에는 @Size 어노테이션만 있고 @NotBlank@NotNull이 없으므로 null 값이 전달될 수 있습니다.

question과 answer가 null이 될 수 없다면, FaqDto 필드에 @NotBlank 어노테이션을 추가하거나 toEntity() 메서드에서 null 체크를 수행해야 합니다.

🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/payload/dto/FaqDto.java around lines
18-20, the toEntity() method constructs a Faq without validating question and
answer which may be null; update the DTO by adding @NotBlank to the question and
answer fields to enforce non-null/non-empty at validation time, and also add
defensive null checks in toEntity() that throw an IllegalArgumentException (or
IllegalStateException) with a clear message if question or answer is null/blank
before constructing and returning the Faq entity.

@lepitaaar lepitaaar merged commit 61872ed into refactor/#960-recruitment-api-update-MOA-452 Dec 23, 2025
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💾 BE Backend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants