From 3380f76077476980d88a3164125adad13d314307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Mon, 2 Mar 2026 15:34:43 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=EC=8A=B9=EC=9D=B8=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EC=A7=80=EC=9B=90=EC=84=9C=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=B0=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClubMemberApplicationAnswersResponse.java | 22 +++++++++++++++++++ .../repository/ClubApplyQueryRepository.java | 20 +++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/main/java/gg/agit/konect/domain/club/dto/ClubMemberApplicationAnswersResponse.java diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubMemberApplicationAnswersResponse.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubMemberApplicationAnswersResponse.java new file mode 100644 index 00000000..33d16523 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubMemberApplicationAnswersResponse.java @@ -0,0 +1,22 @@ +package gg.agit.konect.domain.club.dto; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record ClubMemberApplicationAnswersResponse( + @Schema(description = "지원서 총 개수", example = "3", requiredMode = REQUIRED) + Long totalCount, + + @Schema(description = "승인된 회원 지원서 목록", requiredMode = REQUIRED) + List applications +) { + public static ClubMemberApplicationAnswersResponse from(List applications) { + return new ClubMemberApplicationAnswersResponse( + (long)applications.size(), + applications + ); + } +} diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java index 002f5435..a8f2d250 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java @@ -193,6 +193,26 @@ public Page findApprovedMemberApplicationsByClubId( return new PageImpl<>(content, pageable, total != null ? total : 0L); } + public List findAllApprovedMemberApplicationsByClubId(Integer clubId) { + BooleanExpression isClubMember = isAlreadyClubMember(clubId); + BooleanExpression activeUserOnly = user.deletedAt.isNull(); + BooleanExpression approvedOnly = clubApply.status.eq(ClubApplyStatus.APPROVED); + BooleanExpression latestApprovedApplicationOnly = isLatestApprovedApplicationByUser(clubId); + + return jpaQueryFactory + .selectFrom(clubApply) + .join(clubApply.user, user).fetchJoin() + .where( + clubApply.club.id.eq(clubId), + activeUserOnly, + isClubMember, + approvedOnly, + latestApprovedApplicationOnly + ) + .orderBy(clubApply.createdAt.asc(), clubApply.id.asc()) + .fetch(); + } + private BooleanExpression isAlreadyClubMember(Integer clubId) { return JPAExpressions .selectOne() From 0a5a3f854435e6f0dc503f19d2a3bcab03030ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Mon, 2 Mar 2026 15:34:53 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=EC=8A=B9=EC=9D=B8=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EC=A7=80=EC=9B=90=EC=84=9C=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/controller/ClubApplicationApi.java | 15 ++++++++++ .../controller/ClubApplicationController.java | 11 ++++++++ .../club/service/ClubApplicationService.java | 28 +++++++++++++++---- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationApi.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationApi.java index 6c5b0e11..5eb0a947 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationApi.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationApi.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestParam; import gg.agit.konect.domain.club.dto.ClubApplicationAnswersResponse; +import gg.agit.konect.domain.club.dto.ClubMemberApplicationAnswersResponse; import gg.agit.konect.domain.club.dto.ClubApplicationsResponse; import gg.agit.konect.domain.club.dto.ClubApplyQuestionsReplaceRequest; import gg.agit.konect.domain.club.dto.ClubApplyQuestionsResponse; @@ -115,6 +116,20 @@ ResponseEntity getApprovedMemberApplicationAnswe @UserId Integer requesterId ); + @Operation(summary = "승인된 회원들의 지원서를 리스트로 조회한다.", description = """ + - 동아리 관리자만 해당 동아리의 승인된 회원 지원서를 리스트로 조회할 수 있습니다. + - 승인된 회원별 최신 지원서 답변을 리스트로 반환합니다. + + ## 에러 + - FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다. + - NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다. + """) + @GetMapping("/{clubId}/member-applications/answers") + ResponseEntity getApprovedMemberApplicationAnswersList( + @PathVariable(name = "clubId") Integer clubId, + @UserId Integer requesterId + ); + @Operation(summary = "동아리 지원 답변을 조회한다.", description = """ - 동아리 관리자만 해당 동아리의 지원 답변을 조회할 수 있습니다. diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationController.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationController.java index 3518d079..be3a7420 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationController.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationController.java @@ -11,6 +11,7 @@ import gg.agit.konect.domain.club.dto.ClubApplicationAnswersResponse; import gg.agit.konect.domain.club.dto.ClubApplicationCondition; +import gg.agit.konect.domain.club.dto.ClubMemberApplicationAnswersResponse; import gg.agit.konect.domain.club.dto.ClubApplicationsResponse; import gg.agit.konect.domain.club.dto.ClubApplyQuestionsReplaceRequest; import gg.agit.konect.domain.club.dto.ClubApplyQuestionsResponse; @@ -87,6 +88,16 @@ public ResponseEntity getApprovedMemberApplicati return ResponseEntity.ok(response); } + @Override + public ResponseEntity getApprovedMemberApplicationAnswersList( + @PathVariable(name = "clubId") Integer clubId, + @UserId Integer requesterId + ) { + ClubMemberApplicationAnswersResponse response = + clubApplicationService.getApprovedMemberApplicationAnswersList(clubId, requesterId); + return ResponseEntity.ok(response); + } + @Override public ResponseEntity getClubApplicationAnswers( @PathVariable(name = "clubId") Integer clubId, diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java index 2ff1dfda..4d671da3 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java @@ -21,6 +21,7 @@ import gg.agit.konect.domain.chat.service.ChatRoomMembershipService; import gg.agit.konect.domain.club.dto.ClubApplicationAnswersResponse; import gg.agit.konect.domain.club.dto.ClubApplicationCondition; +import gg.agit.konect.domain.club.dto.ClubMemberApplicationAnswersResponse; import gg.agit.konect.domain.club.dto.ClubApplicationsResponse; import gg.agit.konect.domain.club.dto.ClubAppliedClubsResponse; import gg.agit.konect.domain.club.dto.ClubApplyQuestionsReplaceRequest; @@ -118,11 +119,24 @@ public ClubApplicationAnswersResponse getApprovedMemberApplicationAnswers( clubMemberRepository.getByClubIdAndUserId(clubId, targetUserId); ClubApply clubApply = clubApplyRepository.getLatestApprovedByClubIdAndUserId(clubId, targetUserId); - List questions = - clubApplyQuestionRepository.findAllVisibleAtApplyTime(clubId, clubApply.getCreatedAt()); - List answers = clubApplyAnswerRepository.findAllByApplyIdWithQuestion(clubApply.getId()); + return toClubApplicationAnswersResponse(clubId, clubApply); + } - return ClubApplicationAnswersResponse.of(clubApply, questions, answers); + public ClubMemberApplicationAnswersResponse getApprovedMemberApplicationAnswersList( + Integer clubId, + Integer requesterId + ) { + clubRepository.getById(clubId); + + clubPermissionValidator.validateManagerAccess(clubId, requesterId); + + List approvedApplications = + clubApplyQueryRepository.findAllApprovedMemberApplicationsByClubId(clubId); + List responses = approvedApplications.stream() + .map(application -> toClubApplicationAnswersResponse(clubId, application)) + .toList(); + + return ClubMemberApplicationAnswersResponse.from(responses); } public ClubApplicationAnswersResponse getClubApplicationAnswers( @@ -135,9 +149,13 @@ public ClubApplicationAnswersResponse getClubApplicationAnswers( clubPermissionValidator.validateManagerAccess(clubId, userId); ClubApply clubApply = clubApplyRepository.getByIdAndClubId(applicationId, clubId); + return toClubApplicationAnswersResponse(clubId, clubApply); + } + + private ClubApplicationAnswersResponse toClubApplicationAnswersResponse(Integer clubId, ClubApply clubApply) { List questions = clubApplyQuestionRepository.findAllVisibleAtApplyTime(clubId, clubApply.getCreatedAt()); - List answers = clubApplyAnswerRepository.findAllByApplyIdWithQuestion(applicationId); + List answers = clubApplyAnswerRepository.findAllByApplyIdWithQuestion(clubApply.getId()); return ClubApplicationAnswersResponse.of(clubApply, questions, answers); } From 953c1663f12a3c6df69fc73d8af6c514f922f0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Mon, 2 Mar 2026 15:44:11 +0900 Subject: [PATCH 03/10] =?UTF-8?q?perf:=20=EC=A7=80=EC=9B=90=EC=84=9C=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=EB=B0=B0=EC=B9=98=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EB=A1=9C=20N+1=20=EA=B0=90=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ClubApplyAnswerRepository.java | 9 ++++++ .../club/service/ClubApplicationService.java | 28 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyAnswerRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyAnswerRepository.java index acc9fdb4..b71c5466 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyAnswerRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyAnswerRepository.java @@ -20,4 +20,13 @@ public interface ClubApplyAnswerRepository extends Repository findAllByApplyIdWithQuestion(@Param("applyId") Integer applyId); + + @Query(""" + SELECT answer + FROM ClubApplyAnswer answer + JOIN FETCH answer.question question + WHERE answer.apply.id IN :applyIds + ORDER BY answer.apply.id ASC, question.displayOrder ASC, question.id ASC + """) + List findAllByApplyIdsWithQuestion(@Param("applyIds") List applyIds); } diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java index 4d671da3..69da6e8c 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java @@ -132,8 +132,24 @@ public ClubMemberApplicationAnswersResponse getApprovedMemberApplicationAnswersL List approvedApplications = clubApplyQueryRepository.findAllApprovedMemberApplicationsByClubId(clubId); + if (approvedApplications.isEmpty()) { + return ClubMemberApplicationAnswersResponse.from(List.of()); + } + + List applyIds = approvedApplications.stream() + .map(ClubApply::getId) + .toList(); + Map> answersByApplyId = clubApplyAnswerRepository + .findAllByApplyIdsWithQuestion(applyIds) + .stream() + .collect(Collectors.groupingBy(answer -> answer.getApply().getId())); + List responses = approvedApplications.stream() - .map(application -> toClubApplicationAnswersResponse(clubId, application)) + .map(application -> toClubApplicationAnswersResponse( + clubId, + application, + answersByApplyId.getOrDefault(application.getId(), List.of()) + )) .toList(); return ClubMemberApplicationAnswersResponse.from(responses); @@ -153,9 +169,17 @@ public ClubApplicationAnswersResponse getClubApplicationAnswers( } private ClubApplicationAnswersResponse toClubApplicationAnswersResponse(Integer clubId, ClubApply clubApply) { + List answers = clubApplyAnswerRepository.findAllByApplyIdWithQuestion(clubApply.getId()); + return toClubApplicationAnswersResponse(clubId, clubApply, answers); + } + + private ClubApplicationAnswersResponse toClubApplicationAnswersResponse( + Integer clubId, + ClubApply clubApply, + List answers + ) { List questions = clubApplyQuestionRepository.findAllVisibleAtApplyTime(clubId, clubApply.getCreatedAt()); - List answers = clubApplyAnswerRepository.findAllByApplyIdWithQuestion(clubApply.getId()); return ClubApplicationAnswersResponse.of(clubApply, questions, answers); } From 6d1204b718889e5fee54968102918014b1f8d36b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Mon, 2 Mar 2026 16:52:04 +0900 Subject: [PATCH 04/10] =?UTF-8?q?refactor:=20=EA=B3=B5=ED=86=B5=EB=90=9C?= =?UTF-8?q?=20=EC=8A=B9=EC=9D=B8=20=ED=9A=8C=EC=9B=90=20=EC=A7=80=EC=9B=90?= =?UTF-8?q?=EC=84=9C=20=EC=A1=B0=ED=9A=8C=20=EC=A1=B0=EA=B1=B4=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ClubApplyQueryRepository.java | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java index a8f2d250..df0df31d 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java @@ -16,6 +16,7 @@ import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import gg.agit.konect.domain.club.dto.ClubApplicationCondition; @@ -158,21 +159,7 @@ public Page findApprovedMemberApplicationsByClubId( condition.sortDirection() ); - BooleanExpression isClubMember = isAlreadyClubMember(clubId); - BooleanExpression activeUserOnly = user.deletedAt.isNull(); - BooleanExpression approvedOnly = clubApply.status.eq(ClubApplyStatus.APPROVED); - BooleanExpression latestApprovedApplicationOnly = isLatestApprovedApplicationByUser(clubId); - - List content = jpaQueryFactory - .selectFrom(clubApply) - .join(clubApply.user, user).fetchJoin() - .where( - clubApply.club.id.eq(clubId), - activeUserOnly, - isClubMember, - approvedOnly, - latestApprovedApplicationOnly - ) + List content = approvedMemberApplicationBaseQuery(clubId) .orderBy(orderSpecifier) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) @@ -181,36 +168,33 @@ public Page findApprovedMemberApplicationsByClubId( Long total = jpaQueryFactory .select(clubApply.count()) .from(clubApply) - .where( - clubApply.club.id.eq(clubId), - activeUserOnly, - isClubMember, - approvedOnly, - latestApprovedApplicationOnly - ) + .where(approvedMemberApplicationPredicates(clubId)) .fetchOne(); return new PageImpl<>(content, pageable, total != null ? total : 0L); } public List findAllApprovedMemberApplicationsByClubId(Integer clubId) { - BooleanExpression isClubMember = isAlreadyClubMember(clubId); - BooleanExpression activeUserOnly = user.deletedAt.isNull(); - BooleanExpression approvedOnly = clubApply.status.eq(ClubApplyStatus.APPROVED); - BooleanExpression latestApprovedApplicationOnly = isLatestApprovedApplicationByUser(clubId); + return approvedMemberApplicationBaseQuery(clubId) + .orderBy(clubApply.createdAt.asc(), clubApply.id.asc()) + .fetch(); + } + private JPAQuery approvedMemberApplicationBaseQuery(Integer clubId) { return jpaQueryFactory .selectFrom(clubApply) .join(clubApply.user, user).fetchJoin() - .where( - clubApply.club.id.eq(clubId), - activeUserOnly, - isClubMember, - approvedOnly, - latestApprovedApplicationOnly - ) - .orderBy(clubApply.createdAt.asc(), clubApply.id.asc()) - .fetch(); + .where(approvedMemberApplicationPredicates(clubId)); + } + + private BooleanExpression[] approvedMemberApplicationPredicates(Integer clubId) { + return new BooleanExpression[] { + clubApply.club.id.eq(clubId), + user.deletedAt.isNull(), + isAlreadyClubMember(clubId), + clubApply.status.eq(ClubApplyStatus.APPROVED), + isLatestApprovedApplicationByUser(clubId) + }; } private BooleanExpression isAlreadyClubMember(Integer clubId) { From 8c36560bfced830ad4cc19d64c888cbb2d67a50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Mon, 2 Mar 2026 16:58:40 +0900 Subject: [PATCH 05/10] =?UTF-8?q?perf:=20=EC=8A=B9=EC=9D=B8=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EC=A7=80=EC=9B=90=EC=84=9C=20=EB=8B=B5=EB=B3=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/controller/ClubApplicationApi.java | 9 ++++++++ .../controller/ClubApplicationController.java | 7 +++++- .../ClubMemberApplicationAnswersResponse.java | 22 +++++++++++++++++-- .../club/service/ClubApplicationService.java | 13 ++++++----- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationApi.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationApi.java index 5eb0a947..b6acaab5 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationApi.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationApi.java @@ -119,6 +119,9 @@ ResponseEntity getApprovedMemberApplicationAnswe @Operation(summary = "승인된 회원들의 지원서를 리스트로 조회한다.", description = """ - 동아리 관리자만 해당 동아리의 승인된 회원 지원서를 리스트로 조회할 수 있습니다. - 승인된 회원별 최신 지원서 답변을 리스트로 반환합니다. + - 정렬 기준: APPLIED_AT(신청 일시), STUDENT_NUMBER(학번), NAME(이름) + - 정렬 방향: ASC(오름차순), DESC(내림차순) + - 기본 정렬: 신청 일시 오래된 순 (APPLIED_AT ASC) ## 에러 - FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다. @@ -127,6 +130,12 @@ ResponseEntity getApprovedMemberApplicationAnswe @GetMapping("/{clubId}/member-applications/answers") ResponseEntity getApprovedMemberApplicationAnswersList( @PathVariable(name = "clubId") Integer clubId, + @Min(value = 1, message = "페이지 번호는 1 이상이어야 합니다.") + @RequestParam(defaultValue = "1") Integer page, + @Min(value = 1, message = "페이지 당 항목 수는 1 이상이어야 합니다.") + @RequestParam(defaultValue = "10") Integer limit, + @RequestParam(defaultValue = "APPLIED_AT") ClubApplicationSortBy sortBy, + @RequestParam(defaultValue = "ASC") Sort.Direction sortDirection, @UserId Integer requesterId ); diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationController.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationController.java index be3a7420..188c042a 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationController.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubApplicationController.java @@ -91,10 +91,15 @@ public ResponseEntity getApprovedMemberApplicati @Override public ResponseEntity getApprovedMemberApplicationAnswersList( @PathVariable(name = "clubId") Integer clubId, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer limit, + @RequestParam(defaultValue = "APPLIED_AT") ClubApplicationSortBy sortBy, + @RequestParam(defaultValue = "ASC") Sort.Direction sortDirection, @UserId Integer requesterId ) { + ClubApplicationCondition condition = new ClubApplicationCondition(page, limit, sortBy, sortDirection); ClubMemberApplicationAnswersResponse response = - clubApplicationService.getApprovedMemberApplicationAnswersList(clubId, requesterId); + clubApplicationService.getApprovedMemberApplicationAnswersList(clubId, requesterId, condition); return ResponseEntity.ok(response); } diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubMemberApplicationAnswersResponse.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubMemberApplicationAnswersResponse.java index 33d16523..6fa44d5b 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubMemberApplicationAnswersResponse.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubMemberApplicationAnswersResponse.java @@ -4,18 +4,36 @@ import java.util.List; +import org.springframework.data.domain.Page; + +import gg.agit.konect.domain.club.model.ClubApply; import io.swagger.v3.oas.annotations.media.Schema; public record ClubMemberApplicationAnswersResponse( @Schema(description = "지원서 총 개수", example = "3", requiredMode = REQUIRED) Long totalCount, + @Schema(description = "현재 페이지에서 조회된 지원서 개수", example = "3", requiredMode = REQUIRED) + Integer currentCount, + + @Schema(description = "최대 페이지", example = "2", requiredMode = REQUIRED) + Integer totalPage, + + @Schema(description = "현재 페이지", example = "1", requiredMode = REQUIRED) + Integer currentPage, + @Schema(description = "승인된 회원 지원서 목록", requiredMode = REQUIRED) List applications ) { - public static ClubMemberApplicationAnswersResponse from(List applications) { + public static ClubMemberApplicationAnswersResponse from( + Page page, + List applications + ) { return new ClubMemberApplicationAnswersResponse( - (long)applications.size(), + page.getTotalElements(), + page.getNumberOfElements(), + page.getTotalPages(), + page.getNumber() + 1, applications ); } diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java index 69da6e8c..8b930e92 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java @@ -124,16 +124,19 @@ public ClubApplicationAnswersResponse getApprovedMemberApplicationAnswers( public ClubMemberApplicationAnswersResponse getApprovedMemberApplicationAnswersList( Integer clubId, - Integer requesterId + Integer requesterId, + ClubApplicationCondition condition ) { clubRepository.getById(clubId); clubPermissionValidator.validateManagerAccess(clubId, requesterId); - List approvedApplications = - clubApplyQueryRepository.findAllApprovedMemberApplicationsByClubId(clubId); + Page approvedApplicationsPage = + clubApplyQueryRepository.findApprovedMemberApplicationsByClubId(clubId, condition); + List approvedApplications = approvedApplicationsPage.getContent(); + if (approvedApplications.isEmpty()) { - return ClubMemberApplicationAnswersResponse.from(List.of()); + return ClubMemberApplicationAnswersResponse.from(approvedApplicationsPage, List.of()); } List applyIds = approvedApplications.stream() @@ -152,7 +155,7 @@ public ClubMemberApplicationAnswersResponse getApprovedMemberApplicationAnswersL )) .toList(); - return ClubMemberApplicationAnswersResponse.from(responses); + return ClubMemberApplicationAnswersResponse.from(approvedApplicationsPage, responses); } public ClubApplicationAnswersResponse getClubApplicationAnswers( From 1d399167acdc8f632b4ec2b9f81a27a299acb0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 3 Mar 2026 09:00:34 +0900 Subject: [PATCH 06/10] =?UTF-8?q?refactor:=20=EC=A7=80=EC=9B=90=EC=84=9C?= =?UTF-8?q?=20=EB=8B=B5=EB=B3=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20appliedAt=EB=B3=84=20=EB=AC=B8=ED=95=AD?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BA=90=EC=8B=9C=EB=A1=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/service/ClubApplicationService.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java index 8b930e92..aef31c9a 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java @@ -146,11 +146,18 @@ public ClubMemberApplicationAnswersResponse getApprovedMemberApplicationAnswersL .findAllByApplyIdsWithQuestion(applyIds) .stream() .collect(Collectors.groupingBy(answer -> answer.getApply().getId())); + Map> questionsByAppliedAt = approvedApplications.stream() + .map(ClubApply::getCreatedAt) + .distinct() + .collect(Collectors.toMap( + appliedAt -> appliedAt, + appliedAt -> clubApplyQuestionRepository.findAllVisibleAtApplyTime(clubId, appliedAt) + )); List responses = approvedApplications.stream() .map(application -> toClubApplicationAnswersResponse( - clubId, application, + questionsByAppliedAt.getOrDefault(application.getCreatedAt(), List.of()), answersByApplyId.getOrDefault(application.getId(), List.of()) )) .toList(); @@ -184,6 +191,14 @@ private ClubApplicationAnswersResponse toClubApplicationAnswersResponse( List questions = clubApplyQuestionRepository.findAllVisibleAtApplyTime(clubId, clubApply.getCreatedAt()); + return toClubApplicationAnswersResponse(clubApply, questions, answers); + } + + private ClubApplicationAnswersResponse toClubApplicationAnswersResponse( + ClubApply clubApply, + List questions, + List answers + ) { return ClubApplicationAnswersResponse.of(clubApply, questions, answers); } From cb277b6a0d4b168f69eabb140db7c04a2a79a4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 3 Mar 2026 09:41:20 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor:=20=EC=A7=80=EC=9B=90=EC=84=9C?= =?UTF-8?q?=20=EB=8B=B5=EB=B3=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=EB=A6=BD=20=EC=8B=9C=20=EB=AC=B8=ED=95=AD=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EB=A5=BC=20=EA=B5=AC=EA=B0=84=20=EC=82=AC=EC=A0=84=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EB=A1=9C=20=ED=86=B5=ED=95=A9=ED=95=B4=20N+1=EC=9D=84?= =?UTF-8?q?=20=EC=99=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClubApplyQuestionRepository.java | 14 ++++++++++ .../club/service/ClubApplicationService.java | 28 ++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.java index 076a56d8..747c3bfc 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.java @@ -33,6 +33,20 @@ List findAllVisibleAtApplyTime( @Param("appliedAt") LocalDateTime appliedAt ); + @Query(""" + SELECT question + FROM ClubApplyQuestion question + WHERE question.club.id = :clubId + AND question.createdAt <= :maxAppliedAt + AND (question.deletedAt IS NULL OR question.deletedAt > :minAppliedAt) + ORDER BY question.displayOrder ASC, question.id ASC + """) + List findAllCandidatesVisibleBetweenApplyTimes( + @Param("clubId") Integer clubId, + @Param("minAppliedAt") LocalDateTime minAppliedAt, + @Param("maxAppliedAt") LocalDateTime maxAppliedAt + ); + ClubApplyQuestion save(ClubApplyQuestion question); List saveAll(Iterable questions); diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java index aef31c9a..3bed05bd 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java @@ -142,16 +142,30 @@ public ClubMemberApplicationAnswersResponse getApprovedMemberApplicationAnswersL List applyIds = approvedApplications.stream() .map(ClubApply::getId) .toList(); + List appliedAts = approvedApplications.stream() + .map(ClubApply::getCreatedAt) + .distinct() + .toList(); + + LocalDateTime minAppliedAt = appliedAts.stream() + .min(LocalDateTime::compareTo) + .orElseThrow(() -> new IllegalStateException("지원서 신청 시점이 비어 있습니다.")); + LocalDateTime maxAppliedAt = appliedAts.stream() + .max(LocalDateTime::compareTo) + .orElseThrow(() -> new IllegalStateException("지원서 신청 시점이 비어 있습니다.")); + Map> answersByApplyId = clubApplyAnswerRepository .findAllByApplyIdsWithQuestion(applyIds) .stream() .collect(Collectors.groupingBy(answer -> answer.getApply().getId())); - Map> questionsByAppliedAt = approvedApplications.stream() - .map(ClubApply::getCreatedAt) - .distinct() + List questionCandidates = clubApplyQuestionRepository + .findAllCandidatesVisibleBetweenApplyTimes(clubId, minAppliedAt, maxAppliedAt); + Map> questionsByAppliedAt = appliedAts.stream() .collect(Collectors.toMap( appliedAt -> appliedAt, - appliedAt -> clubApplyQuestionRepository.findAllVisibleAtApplyTime(clubId, appliedAt) + appliedAt -> questionCandidates.stream() + .filter(question -> isVisibleAtApplyTime(question, appliedAt)) + .toList() )); List responses = approvedApplications.stream() @@ -202,6 +216,12 @@ private ClubApplicationAnswersResponse toClubApplicationAnswersResponse( return ClubApplicationAnswersResponse.of(clubApply, questions, answers); } + private boolean isVisibleAtApplyTime(ClubApplyQuestion question, LocalDateTime appliedAt) { + LocalDateTime deletedAt = question.getDeletedAt(); + return !question.getCreatedAt().isAfter(appliedAt) + && (deletedAt == null || deletedAt.isAfter(appliedAt)); + } + @Transactional public void approveClubApplication(Integer clubId, Integer applicationId, Integer userId) { Club club = clubRepository.getById(clubId); From 4d81d3f12f4dda021e75bf1d96fe3c0c7fd250ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 3 Mar 2026 09:59:22 +0900 Subject: [PATCH 08/10] =?UTF-8?q?chore:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/club/repository/ClubApplyQueryRepository.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java index df0df31d..2951150a 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java @@ -174,12 +174,6 @@ public Page findApprovedMemberApplicationsByClubId( return new PageImpl<>(content, pageable, total != null ? total : 0L); } - public List findAllApprovedMemberApplicationsByClubId(Integer clubId) { - return approvedMemberApplicationBaseQuery(clubId) - .orderBy(clubApply.createdAt.asc(), clubApply.id.asc()) - .fetch(); - } - private JPAQuery approvedMemberApplicationBaseQuery(Integer clubId) { return jpaQueryFactory .selectFrom(clubApply) From e6de9e307ce60d25616650697caa525734ae45ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 3 Mar 2026 10:14:34 +0900 Subject: [PATCH 09/10] =?UTF-8?q?chore:=20JPQL=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=EB=AC=B8=EA=B3=BC=20=EC=9D=BC=EA=B4=80=EC=84=B1=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konect/domain/club/service/ClubApplicationService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java index 3bed05bd..b48bae89 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java @@ -217,8 +217,9 @@ private ClubApplicationAnswersResponse toClubApplicationAnswersResponse( } private boolean isVisibleAtApplyTime(ClubApplyQuestion question, LocalDateTime appliedAt) { + LocalDateTime createdAt = question.getCreatedAt(); LocalDateTime deletedAt = question.getDeletedAt(); - return !question.getCreatedAt().isAfter(appliedAt) + return (createdAt.isBefore(appliedAt) || createdAt.isEqual(appliedAt)) && (deletedAt == null || deletedAt.isAfter(appliedAt)); } From e7f57b35697cb80875371ab5d3b247541523098a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 3 Mar 2026 10:32:34 +0900 Subject: [PATCH 10/10] =?UTF-8?q?fix:=20=EC=8A=B9=EC=9D=B8=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=8B=A0=EC=B2=AD=20count=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=EC=97=90=20user=20=EC=A1=B0=EC=9D=B8=EC=9D=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=B4=20predicate=20=ED=95=B4=EC=84=9D=20=EC=9D=BC?= =?UTF-8?q?=EA=B4=80=EC=84=B1=EC=9D=84=20=EB=B3=B4=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konect/domain/club/repository/ClubApplyQueryRepository.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java index 2951150a..69dd1281 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java @@ -168,6 +168,7 @@ public Page findApprovedMemberApplicationsByClubId( Long total = jpaQueryFactory .select(clubApply.count()) .from(clubApply) + .join(clubApply.user, user) .where(approvedMemberApplicationPredicates(clubId)) .fetchOne();