Skip to content

Commit e896008

Browse files
Merge pull request #190 from prgrms-web-devcourse-final-project/feature/EA3-168-study-application
[EA3-168] feature: 스터디 신청 목록 페이지네이션 및 필터링
2 parents dfc982e + 7db8183 commit e896008

File tree

5 files changed

+117
-13
lines changed

5 files changed

+117
-13
lines changed

src/main/java/grep/neogul_coder/domain/studyapplication/controller/ApplicationController.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package grep.neogul_coder.domain.studyapplication.controller;
22

3+
import grep.neogul_coder.domain.study.enums.Category;
4+
import grep.neogul_coder.domain.studyapplication.ApplicationStatus;
35
import grep.neogul_coder.domain.studyapplication.controller.dto.request.ApplicationCreateRequest;
4-
import grep.neogul_coder.domain.studyapplication.controller.dto.response.MyApplicationResponse;
6+
import grep.neogul_coder.domain.studyapplication.controller.dto.response.MyApplicationPagingResponse;
57
import grep.neogul_coder.domain.studyapplication.service.ApplicationService;
68
import grep.neogul_coder.global.auth.Principal;
79
import grep.neogul_coder.global.response.ApiResponse;
810
import jakarta.validation.Valid;
911
import lombok.RequiredArgsConstructor;
12+
import org.springframework.data.domain.Pageable;
13+
import org.springframework.data.web.PageableDefault;
1014
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1115
import org.springframework.web.bind.annotation.*;
1216

13-
import java.util.List;
14-
1517
@RequestMapping("/api/recruitment-posts")
1618
@RequiredArgsConstructor
1719
@RestController
@@ -20,9 +22,11 @@ public class ApplicationController implements ApplicationSpecification {
2022
private final ApplicationService applicationService;
2123

2224
@GetMapping("/{recruitment-post-id}/applications")
23-
public ApiResponse<List<MyApplicationResponse>> getMyStudyApplications(@PathVariable("recruitment-post-id") Long recruitmentPostId,
25+
public ApiResponse<MyApplicationPagingResponse> getMyStudyApplications(@PathVariable("recruitment-post-id") Long recruitmentPostId,
26+
@PageableDefault(size = 12) Pageable pageable,
27+
@RequestParam(required = false) ApplicationStatus status,
2428
@AuthenticationPrincipal Principal userDetails) {
25-
return ApiResponse.success(applicationService.getMyStudyApplications(userDetails.getUserId()));
29+
return ApiResponse.success(applicationService.getMyStudyApplicationsPaging(pageable, userDetails.getUserId(), status));
2630
}
2731

2832
@PostMapping("/{recruitment-post-id}/applications")

src/main/java/grep/neogul_coder/domain/studyapplication/controller/ApplicationSpecification.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
package grep.neogul_coder.domain.studyapplication.controller;
22

3+
import grep.neogul_coder.domain.study.enums.Category;
4+
import grep.neogul_coder.domain.studyapplication.ApplicationStatus;
35
import grep.neogul_coder.domain.studyapplication.controller.dto.request.ApplicationCreateRequest;
6+
import grep.neogul_coder.domain.studyapplication.controller.dto.response.MyApplicationPagingResponse;
47
import grep.neogul_coder.domain.studyapplication.controller.dto.response.MyApplicationResponse;
58
import grep.neogul_coder.global.auth.Principal;
69
import grep.neogul_coder.global.response.ApiResponse;
710
import io.swagger.v3.oas.annotations.Operation;
811
import io.swagger.v3.oas.annotations.tags.Tag;
12+
import org.springframework.data.domain.Pageable;
913

1014
import java.util.List;
1115

1216
@Tag(name = "StudyApplication", description = "스터디 신청 API")
1317
public interface ApplicationSpecification {
1418

1519
@Operation(summary = "내 스터디 신청 목록 조회", description = "내가 신청한 스터디의 목록을 조회합니다.")
16-
ApiResponse<List<MyApplicationResponse>> getMyStudyApplications(Long recruitmentPostId, Principal userDetails);
20+
ApiResponse<MyApplicationPagingResponse> getMyStudyApplications(Long recruitmentPostId, Pageable pageable, ApplicationStatus status, Principal userDetails);
1721

1822
@Operation(summary = "스터디 신청 생성", description = "스터디를 신청합니다.")
1923
ApiResponse<Long> createApplication(Long recruitmentPostId, ApplicationCreateRequest request, Principal userDetails);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package grep.neogul_coder.domain.studyapplication.controller.dto.response;
2+
3+
import grep.neogul_coder.domain.study.controller.dto.response.StudyItemPagingResponse;
4+
import grep.neogul_coder.domain.study.controller.dto.response.StudyItemResponse;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
import org.springframework.data.domain.Page;
9+
10+
import java.util.List;
11+
12+
@Getter
13+
public class MyApplicationPagingResponse {
14+
15+
@Schema(
16+
description = "내가 신청한 스터디 목록",
17+
example = "[{" +
18+
"\"applicationId\": 1," +
19+
"\"name\": \"자바 스터디\"," +
20+
"\"leaderNickname\": \"너굴\"," +
21+
"\"capacity\": 4," +
22+
"\"currentCount\": 3," +
23+
"\"startDate\": \"2025-07-15T00:00:00\"," +
24+
"\"imageUrl\": \"http://localhost:8083/image.jpg\"," +
25+
"\"introduction\": \"자바 스터디입니다.\"," +
26+
"\"category\": \"IT\"," +
27+
"\"studyType\": \"ONLINE\"," +
28+
"\"isRead\": true," +
29+
"\"status\": \"PENDING\"" +
30+
"}]"
31+
)
32+
private List<MyApplicationResponse> applications;
33+
34+
@Schema(description = "총 페이지 수", example = "2")
35+
private int totalPage;
36+
37+
@Schema(description = "총 요소 개수", example = "10")
38+
private int totalElementCount;
39+
40+
@Schema(example = "false", description = "다음 페이지 여부")
41+
private boolean hasNext;
42+
43+
@Builder
44+
private MyApplicationPagingResponse(Page<MyApplicationResponse> page) {
45+
this.applications = page.getContent();
46+
this.totalPage = page.getTotalPages();
47+
this.totalElementCount = (int) page.getTotalElements();
48+
this.hasNext = page.hasNext();
49+
}
50+
51+
public static MyApplicationPagingResponse of(Page<MyApplicationResponse> page) {
52+
return new MyApplicationPagingResponse(page);
53+
}
54+
}

src/main/java/grep/neogul_coder/domain/studyapplication/repository/ApplicationQueryRepository.java

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
package grep.neogul_coder.domain.studyapplication.repository;
22

3+
import com.querydsl.core.BooleanBuilder;
4+
import com.querydsl.core.types.dsl.BooleanExpression;
35
import com.querydsl.jpa.impl.JPAQueryFactory;
6+
import grep.neogul_coder.domain.study.enums.Category;
7+
import grep.neogul_coder.domain.studyapplication.ApplicationStatus;
48
import grep.neogul_coder.domain.studyapplication.controller.dto.response.MyApplicationResponse;
59
import grep.neogul_coder.domain.studyapplication.controller.dto.response.QMyApplicationResponse;
610
import jakarta.persistence.EntityManager;
11+
import org.springframework.data.domain.Page;
12+
import org.springframework.data.domain.PageImpl;
13+
import org.springframework.data.domain.Pageable;
714
import org.springframework.stereotype.Repository;
815

916
import java.util.List;
17+
import java.util.function.Supplier;
1018

1119
import static grep.neogul_coder.domain.recruitment.post.QRecruitmentPost.recruitmentPost;
1220
import static grep.neogul_coder.domain.study.QStudy.study;
@@ -24,8 +32,8 @@ public ApplicationQueryRepository(EntityManager em) {
2432
this.queryFactory = new JPAQueryFactory(em);
2533
}
2634

27-
public List<MyApplicationResponse> findMyApplications(Long userId) {
28-
return queryFactory
35+
public Page<MyApplicationResponse> findMyStudyApplicationsPaging(Pageable pageable, Long userId, ApplicationStatus status) {
36+
List<MyApplicationResponse> applications = queryFactory
2937
.select(new QMyApplicationResponse(
3038
studyApplication.id,
3139
study.name,
@@ -45,7 +53,38 @@ public List<MyApplicationResponse> findMyApplications(Long userId) {
4553
.join(study).on(study.id.eq(recruitmentPost.studyId))
4654
.join(studyMember).on(studyMember.study.id.eq(study.id), studyMember.role.eq(LEADER))
4755
.join(user).on(user.id.eq(studyMember.userId))
48-
.where(studyApplication.userId.eq(userId))
56+
.where(
57+
studyApplication.userId.eq(userId),
58+
equalsApplicationStatus(status)
59+
)
60+
.offset(pageable.getOffset())
61+
.limit(pageable.getPageSize())
4962
.fetch();
63+
64+
Long total = queryFactory
65+
.select(studyApplication.count())
66+
.from(studyApplication)
67+
.join(recruitmentPost).on(recruitmentPost.id.eq(studyApplication.recruitmentPostId))
68+
.join(study).on(study.id.eq(recruitmentPost.studyId))
69+
.join(studyMember).on(studyMember.study.id.eq(study.id), studyMember.role.eq(LEADER))
70+
.where(
71+
studyApplication.userId.eq(userId),
72+
equalsApplicationStatus(status)
73+
)
74+
.fetchOne();
75+
76+
return new PageImpl<>(applications, pageable, total == null ? 0 : total);
77+
}
78+
79+
private BooleanBuilder equalsApplicationStatus(ApplicationStatus status) {
80+
return nullSafeBuilder(() -> studyApplication.status.eq(status));
81+
}
82+
83+
private BooleanBuilder nullSafeBuilder(Supplier<BooleanExpression> supplier) {
84+
try {
85+
return new BooleanBuilder(supplier.get());
86+
} catch (Exception e) {
87+
return new BooleanBuilder();
88+
}
5089
}
5190
}

src/main/java/grep/neogul_coder/domain/studyapplication/service/ApplicationService.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,25 @@
44
import grep.neogul_coder.domain.recruitment.post.repository.RecruitmentPostRepository;
55
import grep.neogul_coder.domain.study.Study;
66
import grep.neogul_coder.domain.study.StudyMember;
7+
import grep.neogul_coder.domain.study.enums.Category;
78
import grep.neogul_coder.domain.study.enums.StudyMemberRole;
89
import grep.neogul_coder.domain.study.repository.StudyMemberRepository;
910
import grep.neogul_coder.domain.study.repository.StudyRepository;
1011
import grep.neogul_coder.domain.studyapplication.ApplicationStatus;
1112
import grep.neogul_coder.domain.studyapplication.StudyApplication;
1213
import grep.neogul_coder.domain.studyapplication.controller.dto.request.ApplicationCreateRequest;
14+
import grep.neogul_coder.domain.studyapplication.controller.dto.response.MyApplicationPagingResponse;
1315
import grep.neogul_coder.domain.studyapplication.controller.dto.response.MyApplicationResponse;
1416
import grep.neogul_coder.domain.studyapplication.repository.ApplicationQueryRepository;
1517
import grep.neogul_coder.domain.studyapplication.repository.ApplicationRepository;
1618
import grep.neogul_coder.global.exception.business.BusinessException;
1719
import grep.neogul_coder.global.exception.business.NotFoundException;
1820
import lombok.RequiredArgsConstructor;
21+
import org.springframework.data.domain.Page;
22+
import org.springframework.data.domain.Pageable;
1923
import org.springframework.stereotype.Service;
2024
import org.springframework.transaction.annotation.Transactional;
2125

22-
import java.util.List;
23-
2426
import static grep.neogul_coder.domain.recruitment.RecruitmentErrorCode.NOT_FOUND;
2527
import static grep.neogul_coder.domain.study.exception.code.StudyErrorCode.STUDY_NOT_FOUND;
2628
import static grep.neogul_coder.domain.studyapplication.exception.code.ApplicationErrorCode.*;
@@ -36,8 +38,9 @@ public class ApplicationService {
3638
private final StudyMemberRepository studyMemberRepository;
3739
private final StudyRepository studyRepository;
3840

39-
public List<MyApplicationResponse> getMyStudyApplications(Long userId) {
40-
return applicationQueryRepository.findMyApplications(userId);
41+
public MyApplicationPagingResponse getMyStudyApplicationsPaging(Pageable pageable, Long userId, ApplicationStatus status) {
42+
Page<MyApplicationResponse> page = applicationQueryRepository.findMyStudyApplicationsPaging(pageable, userId, status);
43+
return MyApplicationPagingResponse.of(page);
4144
}
4245

4346
@Transactional

0 commit comments

Comments
 (0)