Skip to content
12 changes: 7 additions & 5 deletions backend/src/main/java/moadong/club/entity/Club.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ public class Club implements Persistable<String> {
@Field("recruitmentInformation")
private ClubRecruitmentInformation clubRecruitmentInformation;

private String description;

private List<Faq> faqs;
@Field("description")
private ClubDescription clubDescription;

@Version
private Long version;
Expand All @@ -58,6 +57,7 @@ public Club() {
this.division = "";
this.state = ClubState.UNAVAILABLE;
this.clubRecruitmentInformation = ClubRecruitmentInformation.builder().build();
this.clubDescription = ClubDescription.builder().build();
}

public Club(String userId) {
Expand All @@ -67,6 +67,7 @@ public Club(String userId) {
this.state = ClubState.UNAVAILABLE;
this.clubRecruitmentInformation = ClubRecruitmentInformation.builder().build();
this.userId = userId;
this.clubDescription = ClubDescription.builder().build();
}

public Club(String id, String userId) {
Expand All @@ -77,6 +78,7 @@ public Club(String id, String userId) {
this.state = ClubState.UNAVAILABLE;
this.clubRecruitmentInformation = ClubRecruitmentInformation.builder().build();
this.userId = userId;
this.clubDescription = ClubDescription.builder().build();
}

@Builder
Expand All @@ -86,6 +88,7 @@ public Club(String name, String category, String division,
this.category = category;
this.division = division;
this.clubRecruitmentInformation = clubRecruitmentInformation;
this.clubDescription = ClubDescription.builder().build();
}

public void update(ClubInfoRequest request) {
Expand All @@ -98,8 +101,7 @@ public void update(ClubInfoRequest request) {
this.state = ClubState.AVAILABLE;
this.socialLinks = request.socialLinks();
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.

}

private void validateTags(List<String> tags) {
Expand Down
19 changes: 19 additions & 0 deletions backend/src/main/java/moadong/club/entity/ClubAward.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package moadong.club.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ClubAward {

private String semester;

private List<String> achievements;
}
27 changes: 27 additions & 0 deletions backend/src/main/java/moadong/club/entity/ClubDescription.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package moadong.club.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ClubDescription {

private String introDescription;

private String activityDescription;

private List<ClubAward> awards;

private ClubIdealCandidate idealCandidate;

private String benefits;

private List<Faq> faqs;
}
19 changes: 19 additions & 0 deletions backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package moadong.club.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ClubIdealCandidate {

private List<String> tags;

private String content;
}
25 changes: 25 additions & 0 deletions backend/src/main/java/moadong/club/payload/dto/ClubAwardDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package moadong.club.payload.dto;

import jakarta.validation.constraints.Size;
import moadong.club.entity.ClubAward;

import java.util.List;

public record ClubAwardDto(
@Size(max = 50)
String semester,

List<@Size(max = 100) String> achievements
) {
public static ClubAwardDto from(ClubAward clubAward) {
if (clubAward == null) return null;
return new ClubAwardDto(clubAward.getSemester(), clubAward.getAchievements());
}

public ClubAward toEntity() {
return ClubAward.builder()
.semester(semester)
.achievements(achievements)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package moadong.club.payload.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import moadong.club.entity.ClubDescription;

import java.util.List;

public record ClubDescriptionDto(
@Size(max = 500)
String introDescription,

@Size(max = 1000)
String activityDescription,

@Valid
List<ClubAwardDto> awards,

@Valid
ClubIdealCandidateDto idealCandidate,

@Size(max = 1000)
String benefits,

@Valid
List<FaqDto> faqs
) {
public static ClubDescriptionDto from(ClubDescription description) {
if (description == null) return null;
return new ClubDescriptionDto(
description.getIntroDescription(),
description.getActivityDescription(),
description.getAwards() == null ? null : description.getAwards().stream().map(ClubAwardDto::from).toList(),
ClubIdealCandidateDto.from(description.getIdealCandidate()),
description.getBenefits(),
description.getFaqs() == null ? null : description.getFaqs().stream().map(FaqDto::from).toList()
);
}

public ClubDescription toEntity() {
return ClubDescription.builder()
.introDescription(introDescription)
.activityDescription(activityDescription)
.awards(awards == null ? null : awards.stream().map(ClubAwardDto::toEntity).toList())
.idealCandidate(idealCandidate == null ? null : idealCandidate.toEntity())
.benefits(benefits)
.faqs(faqs == null ? null : faqs.stream().map(FaqDto::toEntity).toList())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import lombok.Builder;
import moadong.club.entity.Club;
import moadong.club.entity.ClubRecruitmentInformation;
import moadong.club.entity.Faq;

import java.time.format.DateTimeFormatter;
import java.util.List;
Expand All @@ -19,7 +18,7 @@ public record ClubDetailedResult(
String state,
List<String> feeds,
String introduction,
String description,
ClubDescriptionDto description,
String presidentName,
String presidentPhoneNumber,
String recruitmentStart,
Expand All @@ -30,7 +29,6 @@ public record ClubDetailedResult(
Map<String, String> socialLinks,
String category,
String division,
List<Faq> faqs,
String lastModifiedDate
) {

Expand Down Expand Up @@ -66,8 +64,7 @@ public static ClubDetailedResult of(Club club) {
.division(club.getDivision() == null ? "" : club.getDivision())
.introduction(clubRecruitmentInformation.getIntroduction() == null ? ""
: clubRecruitmentInformation.getIntroduction())
.description(club.getDescription() == null ? ""
: club.getDescription())
.description(ClubDescriptionDto.from(club.getClubDescription()))
.presidentName(clubRecruitmentInformation.getPresidentName() == null ? ""
: clubRecruitmentInformation.getPresidentName())
.presidentPhoneNumber(
Expand All @@ -83,8 +80,6 @@ public static ClubDetailedResult of(Club club) {
club.getClubRecruitmentInformation().getExternalApplicationUrl())
.socialLinks(club.getSocialLinks() == null ? Map.of()
: club.getSocialLinks())
.faqs(club.getFaqs() == null ? List.of()
: club.getFaqs())
.lastModifiedDate(lastModifiedDate)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package moadong.club.payload.dto;

import jakarta.validation.constraints.Size;
import moadong.club.entity.ClubIdealCandidate;

import java.util.List;

public record ClubIdealCandidateDto(
List<@Size(max = 10) String> tags,

@Size(max = 700)
String content
) {
public static ClubIdealCandidateDto from(ClubIdealCandidate candidate) {
if (candidate == null) return null;
return new ClubIdealCandidateDto(candidate.getTags(), candidate.getContent());
}

public ClubIdealCandidate toEntity() {
return ClubIdealCandidate.builder()
.tags(tags)
.content(content)
.build();
}
}
21 changes: 21 additions & 0 deletions backend/src/main/java/moadong/club/payload/dto/FaqDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package moadong.club.payload.dto;

import jakarta.validation.constraints.Size;
import moadong.club.entity.Faq;

public record FaqDto(
@Size(max = 100)
String question,

@Size(max = 500)
String answer
) {
Comment on lines +6 to +12
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.

public static FaqDto from(Faq faq) {
if (faq == null) return null;
return new FaqDto(faq.getQuestion(), faq.getAnswer());
}

public Faq toEntity() {
return new Faq(question, answer);
}
Comment on lines +18 to +20
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.

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package moadong.club.payload.request;

import jakarta.validation.constraints.NotBlank;
import java.util.List;
import java.util.Map;

import moadong.club.entity.Faq;
import moadong.club.payload.dto.ClubDescriptionDto;
import moadong.club.enums.ClubCategory;
import moadong.club.enums.ClubDivision;
import moadong.global.annotation.PhoneNumber;

import java.util.List;
import java.util.Map;

public record ClubInfoRequest(
@NotBlank
String name,
Expand All @@ -17,8 +17,7 @@ public record ClubInfoRequest(
List<String> tags,
String introduction,
String presidentName,
String description,
List<Faq> faqs,
ClubDescriptionDto description,
@PhoneNumber
String presidentPhoneNumber,
Map<String, String> socialLinks
Expand Down
2 changes: 0 additions & 2 deletions backend/src/test/java/moadong/fixture/ClubFixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public static ClubRecruitmentInformation createRecruitmentInfo(
String id,
String logo,
String introduction,
String description,
String presidentName,
String presidentTelephoneNumber,
LocalDateTime recruitmentStart,
Expand All @@ -34,7 +33,6 @@ public static ClubRecruitmentInformation createRecruitmentInfo(
when(clubRecruitmentInfo.getId()).thenReturn(id);
when(clubRecruitmentInfo.getLogo()).thenReturn(logo);
when(clubRecruitmentInfo.getIntroduction()).thenReturn(introduction);
when(clubRecruitmentInfo.getDescription()).thenReturn(description);
when(clubRecruitmentInfo.getPresidentName()).thenReturn(presidentName);
when(clubRecruitmentInfo.getPresidentTelephoneNumber()).thenReturn(presidentTelephoneNumber);
when(clubRecruitmentInfo.getRecruitmentStart()).thenReturn(ZonedDateTime.from(recruitmentStart));
Expand Down
8 changes: 4 additions & 4 deletions backend/src/test/java/moadong/fixture/ClubRequestFixture.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package moadong.fixture;

import moadong.club.entity.ClubDescription;
import moadong.club.enums.ClubCategory;
import moadong.club.enums.ClubDivision;
import moadong.club.payload.dto.ClubDescriptionDto;
import moadong.club.payload.request.ClubInfoRequest;
import moadong.club.payload.request.ClubRecruitmentInfoUpdateRequest;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
Expand All @@ -20,6 +21,7 @@ public static ClubInfoRequest createValidClubInfoRequest() {
List.of("개발", "스터디"),
"동아리 소개입니다.",
"홍길동",
ClubDescriptionDto.from(ClubDescription.builder().build()),
"010-1234-5678",
Map.of("insta", "https://test")
);
Expand All @@ -31,9 +33,7 @@ public static ClubRecruitmentInfoUpdateRequest defaultRequest() {
Instant.now(),
Instant.now().plus(7, ChronoUnit.DAYS),
"테스트 대상",
"테스트 설명",
"https://fake-url.com",
List.of()
"https://fake-url.com"
);
}
//ToDo: 시간 계산법을 LocalDateTime에서 Instant로 변경 후에 활성화할 것
Expand Down