Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
12 changes: 9 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,10 @@ public class Club implements Persistable<String> {
@Field("recruitmentInformation")
private ClubRecruitmentInformation clubRecruitmentInformation;

private String description;

private List<Faq> faqs;

@Version
private Long version;
public Club() {
Expand Down Expand Up @@ -94,6 +98,8 @@ 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();
}

private void validateTags(List<String> tags) {
Expand Down
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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(
String id,
Expand All @@ -30,11 +31,10 @@ public record ClubDetailedResult(
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 +66,8 @@ 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(club.getDescription() == null ? ""
: club.getDescription())
.presidentName(clubRecruitmentInformation.getPresidentName() == null ? ""
: clubRecruitmentInformation.getPresidentName())
.presidentPhoneNumber(
Expand All @@ -83,10 +83,9 @@ 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())
.faqs(club.getFaqs() == null ? List.of()
: club.getFaqs())
.lastModifiedDate(lastModifiedDate)
.recommendClubs(recommendClubs)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import jakarta.validation.constraints.NotBlank;
import java.util.List;
import java.util.Map;

import moadong.club.entity.Faq;
import moadong.club.enums.ClubCategory;
import moadong.club.enums.ClubDivision;
import moadong.global.annotation.PhoneNumber;
Expand All @@ -15,6 +17,8 @@ public record ClubInfoRequest(
List<String> tags,
String introduction,
String presidentName,
String description,
List<Faq> faqs,
@PhoneNumber
String presidentPhoneNumber,
Map<String, String> socialLinks
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package moadong.club.payload.request;

import java.time.Instant;
import java.time.LocalDateTime;
import java.util.List;
import moadong.club.entity.Faq;

public record ClubRecruitmentInfoUpdateRequest(
Instant recruitmentStart,
Instant recruitmentEnd,
String recruitmentTarget,
String description,
String externalApplicationUrl,
List<Faq> faqs
String externalApplicationUrl
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -67,140 +67,6 @@ public List<ClubSearchResult> searchClubsByKeyword(String keyword, String recrui
return results.getMappedResults();
}

public List<ClubSearchResult> searchRecommendClubs(String category, String excludeClubId) {
Set<String> excludeIds = new HashSet<>();
if (excludeClubId != null) {
excludeIds.add(excludeClubId);
}

List<ClubSearchResult> result = new ArrayList<>();

// 1. 같은 카테고리 모집중 + (모집마감 포함) 동아리 최대 4개 추출 (모집상태 우선)
int maxCategoryCount = 4;
List<ClubSearchResult> categoryClubs = findClubsByCategoryAndState(category, excludeIds, true, maxCategoryCount);
addClubs(result, excludeIds, categoryClubs);

int remainCount = maxCategoryCount - categoryClubs.size();

// 2. 부족하면 마감 동아리로 채우기
if (remainCount > 0) {
List<ClubSearchResult> categoryClosedClubs = findClubsByCategoryAndState(category, excludeIds, false, remainCount);
addClubs(result, excludeIds, categoryClosedClubs);
}

// 3. 나머지 전체 랜덤 2개(모집상태 우선)로 채우기
int totalNeeded = 6;
int randomNeeded = totalNeeded - result.size();

if (randomNeeded > 0) {
List<ClubSearchResult> randomPool = findRandomClubs(excludeIds, 10);

List<ClubSearchResult> selectedRandomClubs = selectClubsByStatePriority(randomPool, randomNeeded);
addClubs(result, excludeIds, selectedRandomClubs);
}

return result.isEmpty() ? Collections.emptyList() : result;
}

// 같은 카테고리 & 주어진 모집 상태별 랜덤 n개 동아리 조회
private List<ClubSearchResult> findClubsByCategoryAndState(String category, Set<String> excludeIds,
boolean onlyRecruitAvailable, int limit) {
List<AggregationOperation> ops = new ArrayList<>();

Criteria criteria = Criteria.where("category").is(category)
.and("_id").nin(excludeIds);

if (onlyRecruitAvailable) {
criteria = criteria.and("recruitmentInformation.clubRecruitmentStatus")
.in(
ClubRecruitmentStatus.ALWAYS.toString(),
ClubRecruitmentStatus.OPEN.toString()
);
}

ops.add(Aggregation.match(criteria));
ops.add(Aggregation.sample((long) limit));

// searchClubsByKeyword 와 동일한 project 단계 적용
ops.add(
Aggregation.project("name", "state", "category", "division")
.and("recruitmentInformation.introduction").as("introduction")
.and("recruitmentInformation.clubRecruitmentStatus").as("recruitmentStatus")
.and(ConditionalOperators.ifNull("$recruitmentInformation.logo").then(""))
.as("logo")
.and(ConditionalOperators.ifNull("$recruitmentInformation.tags").then(Collections.emptyList()))
.as("tags")
);

return mongoTemplate.aggregate(Aggregation.newAggregation(ops), "clubs", ClubSearchResult.class)
.getMappedResults();
}

// 중복 ID 추적하며 클럽 리스트에 추가
private void addClubs(List<ClubSearchResult> result, Set<String> excludeIds, List<ClubSearchResult> clubs) {
for (ClubSearchResult club : clubs) {
if (!excludeIds.contains(club.id())) {
result.add(club);
excludeIds.add(club.id());
}
}
}

// 전체 랜덤 풀에서 모집중 우선으로 n개, 부족하면 마감 동아리로 채움
private boolean isRecruiting(ClubSearchResult club) {
String status = club.recruitmentStatus();
return ClubRecruitmentStatus.ALWAYS.toString().equals(status) || ClubRecruitmentStatus.OPEN.toString().equals(status);
}

private List<ClubSearchResult> selectClubsByStatePriority(List<ClubSearchResult> pool, int maxCount) {
List<ClubSearchResult> selected = new ArrayList<>();
Set<String> ids = new HashSet<>();

// 모집중 우선 선택
for (ClubSearchResult club : pool) {
if (selected.size() >= maxCount) break;
if (isRecruiting(club) && !ids.contains(club.id())) {
selected.add(club);
ids.add(club.id());
}
}

// 부족하면 모집 마감 동아리 추가
if (selected.size() < maxCount) {
for (ClubSearchResult club : pool) {
if (selected.size() >= maxCount) break;
if (!isRecruiting(club) && !ids.contains(club.id())) {
selected.add(club);
ids.add(club.id());
}
}
}

return selected;
}

// 전체 클럽에서 랜덤 n개 뽑기 (중복 제거용 excludeIds는 외부에서 처리)
private List<ClubSearchResult> findRandomClubs(Set<String> excludeIds, int sampleSize) {
List<AggregationOperation> ops = new ArrayList<>();
ops.add(Aggregation.match(Criteria.where("_id").nin(excludeIds)));
ops.add(Aggregation.sample((long) sampleSize));

ops.add(
Aggregation.project("name", "state", "category", "division")
.and("recruitmentInformation.introduction").as("introduction")
.and("recruitmentInformation.clubRecruitmentStatus").as("recruitmentStatus")
.and(ConditionalOperators.ifNull("$recruitmentInformation.logo").then(""))
.as("logo")
.and(ConditionalOperators.ifNull("$recruitmentInformation.tags").then(Collections.emptyList()))
.as("tags")
);

return mongoTemplate.aggregate(Aggregation.newAggregation(ops), "clubs", ClubSearchResult.class)
.getMappedResults();
}



private Criteria getMatchedCriteria(String recruitmentStatus, String division,
String category) {
List<Criteria> criteriaList = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package moadong.club.service;

import java.time.LocalDateTime;
import java.util.List;
import lombok.AllArgsConstructor;
import moadong.club.entity.Club;
import moadong.club.payload.dto.ClubDetailedResult;
import moadong.club.payload.dto.ClubSearchResult;
import moadong.club.payload.request.ClubInfoRequest;
import moadong.club.payload.request.ClubRecruitmentInfoUpdateRequest;
import moadong.club.payload.response.ClubDetailedResponse;
Expand Down Expand Up @@ -36,9 +33,9 @@ public void updateClubInfo(ClubInfoRequest request, CustomUserDetails user) {
}

public void updateClubRecruitmentInfo(ClubRecruitmentInfoUpdateRequest request,
CustomUserDetails user) {
CustomUserDetails user) {
Club club = clubRepository.findClubByUserId(user.getId())
.orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
.orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
club.update(request);
RecruitmentStateCalculator.calculate(
club,
Expand All @@ -54,10 +51,8 @@ public ClubDetailedResponse getClubDetail(String clubId) {
Club club = clubRepository.findClubById(objectId)
.orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));

List<ClubSearchResult> clubSearchResults = clubSearchRepository.searchRecommendClubs(club.getCategory(), clubId);

ClubDetailedResult clubDetailedResult = ClubDetailedResult.of(
club,clubSearchResults
club
);
return new ClubDetailedResponse(clubDetailedResult);
}
Expand Down
Loading