diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java index cd1e21db..742a7c3f 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java @@ -51,6 +51,9 @@ public interface ClubApi { - isRecruiting가 true일 경우, 모집 중인 동아리만 조회하며 모집일(마감일)이 빠른 순으로 정렬됩니다. - isRecruiting가 false일 경우, 전체 동아리를 조회하되 모집 중인 동아리를 먼저 보여줍니다. - status은 BEFORE(모집 전), ONGOING(모집 중), CLOSED(모집 마감)으로 반환됩니다. + - isAlwaysRecruiting는 상시 모집 여부입니다. + - applicationDeadline는 모집 마감일(endDate)이며, 모집 공고가 없거나 상시 모집이면 null로 반환됩니다. + - isPendingApproval은 가입 승인 대기 중 여부이며, 지원 내역이 존재하지만 아직 멤버가 아닌 경우 true로 반환됩니다. """) @GetMapping ResponseEntity getClubs( diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubsResponse.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubsResponse.java index 42ffd2ee..ebeec3e0 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubsResponse.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubsResponse.java @@ -1,11 +1,16 @@ package gg.agit.konect.domain.club.dto; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; +import java.time.LocalDate; import java.util.List; +import java.util.Set; import org.springframework.data.domain.Page; +import com.fasterxml.jackson.annotation.JsonFormat; + import gg.agit.konect.domain.club.enums.RecruitmentStatus; import gg.agit.konect.domain.club.model.ClubSummaryInfo; import io.swagger.v3.oas.annotations.media.Schema; @@ -45,10 +50,20 @@ public record InnerClubResponse( @Schema(description = "동아리 모집 상태", example = "ONGOING", requiredMode = REQUIRED) RecruitmentStatus status, + @Schema(description = "가입 승인 대기 중 여부", example = "false", requiredMode = REQUIRED) + Boolean isPendingApproval, + + @Schema(description = "상시 모집 여부", example = "false", requiredMode = REQUIRED) + Boolean isAlwaysRecruiting, + + @Schema(description = "지원 마감일(상시 모집이거나 모집 공고가 없으면 null)", example = "2025.12.31", requiredMode = NOT_REQUIRED) + @JsonFormat(pattern = "yyyy.MM.dd") + LocalDate applicationDeadline, + @Schema(description = "동아리 태그 리스트", example = "[\"IT\", \"프로그래밍\"]", requiredMode = REQUIRED) List tags ) { - public static InnerClubResponse from(ClubSummaryInfo clubSummaryInfo) { + public static InnerClubResponse from(ClubSummaryInfo clubSummaryInfo, boolean isPendingApproval) { return new InnerClubResponse( clubSummaryInfo.id(), clubSummaryInfo.name(), @@ -56,19 +71,26 @@ public static InnerClubResponse from(ClubSummaryInfo clubSummaryInfo) { clubSummaryInfo.categoryName(), clubSummaryInfo.description(), clubSummaryInfo.status(), + isPendingApproval, + clubSummaryInfo.isAlwaysRecruiting(), + clubSummaryInfo.applicationDeadline(), clubSummaryInfo.tags() ); } } public static ClubsResponse of(Page page) { + return of(page, Set.of()); + } + + public static ClubsResponse of(Page page, Set pendingApprovalClubIds) { return new ClubsResponse( page.getTotalElements(), page.getNumberOfElements(), page.getTotalPages(), page.getNumber() + 1, page.stream() - .map(InnerClubResponse::from) + .map(club -> InnerClubResponse.from(club, pendingApprovalClubIds.contains(club.id()))) .toList() ); } diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubSummaryInfo.java b/src/main/java/gg/agit/konect/domain/club/model/ClubSummaryInfo.java index 099c3c4f..9cc4b8ca 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubSummaryInfo.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubSummaryInfo.java @@ -1,5 +1,6 @@ package gg.agit.konect.domain.club.model; +import java.time.LocalDate; import java.util.List; import gg.agit.konect.domain.club.enums.RecruitmentStatus; @@ -11,6 +12,8 @@ public record ClubSummaryInfo( String categoryName, String description, RecruitmentStatus status, + Boolean isAlwaysRecruiting, + LocalDate applicationDeadline, List tags ) { diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java index fc71a0c4..7324ea4b 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java @@ -16,6 +16,17 @@ public interface ClubApplyRepository extends Repository { boolean existsByClubIdAndUserId(Integer clubId, Integer userId); + @Query(""" + SELECT ca.club.id + FROM ClubApply ca + WHERE ca.user.id = :userId + AND ca.club.id IN :clubIds + """) + List findClubIdsByUserIdAndClubIdIn( + @Param("userId") Integer userId, + @Param("clubIds") List clubIds + ); + ClubApply save(ClubApply clubApply); void deleteByUserId(Integer userId); diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java index b37b0927..16f375f6 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubMemberRepository.java @@ -109,6 +109,17 @@ default ClubMember getByClubIdAndUserId(Integer clubId, Integer userId) { boolean existsByClubIdAndUserId(Integer clubId, Integer userId); + @Query(""" + SELECT cm.id.clubId + FROM ClubMember cm + WHERE cm.id.userId = :userId + AND cm.id.clubId IN :clubIds + """) + List findClubIdsByUserIdAndClubIdIn( + @Param("userId") Integer userId, + @Param("clubIds") List clubIds + ); + List findByUserId(Integer userId); @Query(""" diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubQueryRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubQueryRepository.java index fc8a7f2f..6f6f6484 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubQueryRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubQueryRepository.java @@ -203,6 +203,12 @@ private List convertToSummaryInfo(List clubs, Map convertToSummaryInfo(List clubs, Map pendingApprovalClubIds = findPendingApprovalClubIds(clubSummaryInfoPage, userId); + return ClubsResponse.of(clubSummaryInfoPage, pendingApprovalClubIds); + } + + private Set findPendingApprovalClubIds(Page clubSummaryInfoPage, Integer userId) { + List clubIds = clubSummaryInfoPage.getContent().stream() + .map(ClubSummaryInfo::id) + .filter(Objects::nonNull) + .toList(); + + if (clubIds.isEmpty()) { + return Set.of(); + } + + List appliedClubIds = clubApplyRepository.findClubIdsByUserIdAndClubIdIn(userId, clubIds); + if (appliedClubIds.isEmpty()) { + return Set.of(); + } + + Set pendingClubIds = new HashSet<>(appliedClubIds); + List memberClubIds = clubMemberRepository.findClubIdsByUserIdAndClubIdIn(userId, clubIds); + pendingClubIds.removeAll(memberClubIds); + + return pendingClubIds; } public ClubDetailResponse getClubDetail(Integer clubId, Integer userId) {