Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions backend/src/main/java/moadong/club/entity/Club.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package moadong.club.entity;

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

import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.Message;
Expand All @@ -22,6 +19,9 @@
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

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

@Slf4j
@Document("clubs")
@AllArgsConstructor
Expand All @@ -46,6 +46,9 @@ public class Club implements Persistable<String> {
@Field("recruitmentInformation")
private ClubRecruitmentInformation clubRecruitmentInformation;

@Field("description")
private ClubDescription clubDescription;

@Version
private Long version;
public Club() {
Expand All @@ -54,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 @@ -63,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 @@ -73,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 @@ -82,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 @@ -94,6 +101,7 @@ public void update(ClubInfoRequest request) {
this.state = ClubState.AVAILABLE;
this.socialLinks = request.socialLinks();
this.clubRecruitmentInformation.update(request);
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

🧩 Analysis chain

🏁 Script executed:

# Find and examine ClubInfoRequest.java
find backend -name "ClubInfoRequest.java" -type f

Repository: Moadong/moadong

Length of output: 130


🏁 Script executed:

# Check if the file exists and read it
cat backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java

Repository: Moadong/moadong

Length of output: 689


🏁 Script executed:

# Also check the Club.java file around line 104 for context
sed -n '95,115p' backend/src/main/java/moadong/club/entity/Club.java

Repository: Moadong/moadong

Length of output: 820


🏁 Script executed:

# Check for any validation annotations on description field
rg -n '@NotNull|@Nullable|@Valid' backend/src/main/java/moadong/club/payload/request/

Repository: Moadong/moadong

Length of output: 1951


null 안전성 검증 필요 - 논증이 타당합니다.

ClubInfoRequestdescription 필드에는 검증 애노테이션이 없어 null이 될 수 있습니다. 현재 코드에서 request.description().toEntity()를 직접 호출하면 null인 경우 NPE가 발생합니다. @NotNull 애노테이션을 추가하거나, 호출 전에 null 체크를 추가해야 합니다.

🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/entity/Club.java around line 104, the code
directly calls request.description().toEntity() which can throw an NPE because
ClubInfoRequest.description may be null; either enforce non-null by adding
@NotNull on the description field in ClubInfoRequest (and validate incoming
requests) or add a null-check here and handle the null case (e.g., assign a
default/empty ClubDescription entity or throw a clear IllegalArgumentException)
before calling toEntity().

}

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;
}
Comment on lines +10 to +27
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

null 안전성과 불변성을 강화해주세요.

ClubDescription 엔티티의 모든 필드가 nullable이며, List 필드들에 대한 방어적 복사가 없습니다. 이는 다음과 같은 문제를 야기할 수 있습니다:

  1. NPE 위험: null 필드 접근 시 NullPointerException 발생 가능
  2. 외부 수정 가능: List 필드들이 외부에서 수정될 수 있음

다음 위치에서 null 처리를 확인해주세요:

#!/bin/bash
# ClubDescription을 사용하는 코드에서 null 체크 확인
rg -A 5 'clubDescription\.(get|awards|faqs|idealCandidate)' --type java
🔎 방어적 복사 및 null 안전성 강화 제안
+import java.util.ArrayList;
+import java.util.Collections;
+
 @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;
+    
+    @Builder
+    public ClubDescription(
+            String introDescription,
+            String activityDescription,
+            List<ClubAward> awards,
+            ClubIdealCandidate idealCandidate,
+            String benefits,
+            List<Faq> faqs) {
+        this.introDescription = introDescription;
+        this.activityDescription = activityDescription;
+        this.awards = awards != null ? new ArrayList<>(awards) : new ArrayList<>();
+        this.idealCandidate = idealCandidate;
+        this.benefits = benefits;
+        this.faqs = faqs != null ? new ArrayList<>(faqs) : new ArrayList<>();
+    }
+    
+    public List<ClubAward> getAwards() {
+        return awards != null ? Collections.unmodifiableList(awards) : Collections.emptyList();
+    }
+    
+    public List<Faq> getFaqs() {
+        return faqs != null ? Collections.unmodifiableList(faqs) : Collections.emptyList();
+    }
 }
🤖 Prompt for AI Agents
backend/src/main/java/moadong/club/entity/ClubDescription.java lines 10-27: all
fields are nullable and List fields lack defensive copies; change the design to
enforce null-safety and immutability by making fields non-null where possible,
initialize List fields to empty immutable collections (e.g.,
Collections.emptyList()) via Lombok @Builder.Default or constructor defaults,
remove or avoid exposing setters/NoArgsConstructor for mutability, and ensure
getters return unmodifiable copies (or use ImmutableList) so callers cannot
modify internal lists; update constructors/builders to defensively copy incoming
lists (e.g., new ArrayList<>(input) wrapped with Collections.unmodifiableList)
and validate/replace null scalar fields with empty strings or Optional as
appropriate.

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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,21 @@
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import moadong.club.enums.ClubRecruitmentStatus;
import moadong.club.payload.request.ClubInfoRequest;
import moadong.club.payload.request.ClubRecruitmentInfoUpdateRequest;
import moadong.global.RegexConstants;
import org.checkerframework.common.aliasing.qual.Unique;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;

@AllArgsConstructor
@Getter
@Builder(toBuilder = true)
Expand All @@ -41,9 +40,6 @@ public class ClubRecruitmentInformation {
@Column(length = 30)
private String introduction;

@Column(length = 20000)
private String description;

@Column(length = 5)
private String presidentName;

Expand All @@ -63,8 +59,6 @@ public class ClubRecruitmentInformation {

private List<String> tags;

private List<Faq> faqs;

@Enumerated(EnumType.STRING)
@NotNull
private ClubRecruitmentStatus clubRecruitmentStatus;
Expand All @@ -80,12 +74,10 @@ public void updateRecruitmentStatus(ClubRecruitmentStatus status) {
}

public void updateDescription(ClubRecruitmentInfoUpdateRequest request) {
this.description = request.description();
this.recruitmentStart = request.recruitmentStart();
this.recruitmentEnd = request.recruitmentEnd();
this.recruitmentTarget = request.recruitmentTarget();
this.externalApplicationUrl = request.externalApplicationUrl();
this.faqs = request.faqs();
}

public boolean hasRecruitmentPeriod() {
Expand Down
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
@@ -1,12 +1,12 @@
package moadong.club.payload.dto;

import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
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;
import java.util.Map;

@Builder
public record ClubDetailedResult(
Expand All @@ -18,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 @@ -29,12 +29,10 @@ public record ClubDetailedResult(
Map<String, String> socialLinks,
String category,
String division,
List<Faq> faqs,
String lastModifiedDate,
List<ClubSearchResult> recommendClubs
String lastModifiedDate
) {

public static ClubDetailedResult of(Club club, List<ClubSearchResult> recommendClubs) {
public static ClubDetailedResult of(Club club) {
ClubRecruitmentInformation clubRecruitmentInformation = club.getClubRecruitmentInformation();

String start = "미정";
Expand Down Expand Up @@ -66,8 +64,7 @@ public static ClubDetailedResult of(Club club, List<ClubSearchResult> recommendC
.division(club.getDivision() == null ? "" : club.getDivision())
.introduction(clubRecruitmentInformation.getIntroduction() == null ? ""
: clubRecruitmentInformation.getIntroduction())
.description(clubRecruitmentInformation.getDescription() == null ? ""
: clubRecruitmentInformation.getDescription())
.description(ClubDescriptionDto.from(club.getClubDescription()))
.presidentName(clubRecruitmentInformation.getPresidentName() == null ? ""
: clubRecruitmentInformation.getPresidentName())
.presidentPhoneNumber(
Expand All @@ -83,10 +80,7 @@ public static ClubDetailedResult of(Club club, List<ClubSearchResult> recommendC
club.getClubRecruitmentInformation().getExternalApplicationUrl())
.socialLinks(club.getSocialLinks() == null ? Map.of()
: club.getSocialLinks())
.faqs(club.getClubRecruitmentInformation().getFaqs() == null ? List.of()
: club.getClubRecruitmentInformation().getFaqs())
.lastModifiedDate(lastModifiedDate)
.recommendClubs(recommendClubs)
.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
) {
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);
}
}
Loading
Loading