Skip to content

Commit 42258b9

Browse files
committed
[feat] 일반 구성원 목록 조회 API 추가
1 parent c39eac7 commit 42258b9

5 files changed

Lines changed: 135 additions & 7 deletions

File tree

src/main/java/org/ject/support/admin/member/controller/AdminMemberSemesterApiSpec.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package org.ject.support.admin.member.controller;
22

33
import org.ject.support.admin.member.dto.request.CreateMemberSemesterRequest;
4+
import org.ject.support.admin.member.dto.request.MemberSemesterSearchCondition;
5+
import org.ject.support.admin.member.dto.response.SearchMemberSemesterResponse;
6+
import org.ject.support.common.response.CursorPageResponse;
7+
import org.springframework.web.bind.annotation.ModelAttribute;
48
import org.springframework.web.bind.annotation.RequestBody;
59

610
import io.swagger.v3.oas.annotations.Operation;
@@ -14,4 +18,12 @@ public interface AdminMemberSemesterApiSpec {
1418
description = "일반 구성원을 추가합니다."
1519
)
1620
void createAdminMemberSemester(@RequestBody @Valid CreateMemberSemesterRequest request);
21+
22+
@Operation(
23+
summary = "일반 구성원 리스트 조회",
24+
description = "일반 구성원 목록을 커서기반 페이징과 필터를 적용해 조회합니다."
25+
)
26+
CursorPageResponse<SearchMemberSemesterResponse> searchAdminMemberSemesterList(
27+
@ModelAttribute @Valid MemberSemesterSearchCondition request
28+
);
1729
}

src/main/java/org/ject/support/admin/member/controller/AdminMemberSemesterController.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package org.ject.support.admin.member.controller;
22

33
import org.ject.support.admin.member.dto.request.CreateMemberSemesterRequest;
4+
import org.ject.support.admin.member.dto.request.MemberSemesterSearchCondition;
5+
import org.ject.support.admin.member.dto.response.SearchMemberSemesterResponse;
46
import org.ject.support.admin.member.service.AdminMemberUseCase;
7+
import org.ject.support.common.response.CursorPageResponse;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.ModelAttribute;
510
import org.springframework.web.bind.annotation.PostMapping;
11+
import org.springframework.web.bind.annotation.RequestBody;
612
import org.springframework.web.bind.annotation.RequestMapping;
713
import org.springframework.web.bind.annotation.RestController;
814

15+
import jakarta.validation.Valid;
916
import lombok.RequiredArgsConstructor;
1017

1118
@RestController
@@ -18,7 +25,15 @@ public class AdminMemberSemesterController implements AdminMemberSemesterApiSpec
1825
//일반 구성원 추가
1926
@Override
2027
@PostMapping
21-
public void createAdminMemberSemester(CreateMemberSemesterRequest request) {
28+
public void createAdminMemberSemester(
29+
@RequestBody @Valid CreateMemberSemesterRequest request) {
2230
adminMemberUsecase.createMemberSemester(request);
2331
}
32+
33+
@Override
34+
@GetMapping
35+
public CursorPageResponse<SearchMemberSemesterResponse> searchAdminMemberSemesterList(
36+
@ModelAttribute @Valid MemberSemesterSearchCondition request) {
37+
return adminMemberUsecase.searchMemberSemester(request);
38+
}
2439
}

src/main/java/org/ject/support/admin/member/service/AdminMemberActivityService.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
package org.ject.support.admin.member.service;
22

3+
import org.ject.support.admin.member.dto.projection.SearchMemberSemesterProjection;
34
import org.ject.support.admin.member.dto.request.CreateMemberSemesterRequest;
5+
import org.ject.support.admin.member.dto.request.MemberSemesterSearchCondition;
6+
import org.ject.support.admin.member.dto.result.SearchMemberSemesterPageResult;
47
import org.ject.support.domain.member.MemberType;
58
import org.ject.support.domain.member.entity.MemberActivity;
69
import org.ject.support.domain.member.exception.MemberException;
710
import org.ject.support.domain.member.repository.MemberActivityRepository;
811
import org.springframework.stereotype.Service;
12+
import org.springframework.transaction.annotation.Transactional;
913

1014
import lombok.RequiredArgsConstructor;
1115

1216
import static org.ject.support.domain.member.exception.MemberErrorCode.ALREADY_EXIST_MEMBER_SEMESTER_ACTIVITY;
1317

18+
import java.util.List;
19+
1420
@Service
1521
@RequiredArgsConstructor
1622
public class AdminMemberActivityService {
@@ -37,6 +43,20 @@ public void createMemberSemesterActivity(CreateMemberSemesterRequest request, Lo
3743
memberActivityRepository.save(memberActivity);
3844
}
3945

46+
// 동적 필터로 일반 구성원 목록 조회
47+
public SearchMemberSemesterPageResult searchMemberSemesterList(MemberSemesterSearchCondition condition) {
48+
// size+1로 조회 (다음 페이지 유무 확인용)
49+
List<SearchMemberSemesterProjection> projections =
50+
memberActivityRepository.searchMemberSemesters(condition, condition.getSizeOrDefault()+1);
51+
long totalCount = memberActivityRepository.countMemberSemesters(condition);
52+
return new SearchMemberSemesterPageResult(projections, totalCount);
53+
}
54+
55+
56+
/*
57+
유틸, 검증 함수
58+
*/
59+
4060
// 동일 기수 일반 구성원 활동 중복 검증
4161
private void validateDuplicateSemesterActivity(Long memberId, Long semesterId) {
4262
if (memberActivityRepository.existsSemesterActivity(memberId, MemberType.SEMESTER, semesterId)) {

src/main/java/org/ject/support/admin/member/service/AdminMemberUseCase.java

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
package org.ject.support.admin.member.service;
22

3-
import static org.ject.support.domain.member.exception.MemberErrorCode.NOT_FOUND_TEAM_OF_SEMESTER;
3+
import static org.ject.support.domain.member.exception.MemberErrorCode.*;
44

5+
import java.util.HashSet;
6+
import java.util.List;
7+
import java.util.Set;
8+
import java.util.stream.Collectors;
9+
10+
import org.ject.support.admin.member.dto.projection.SearchMemberSemesterProjection;
511
import org.ject.support.admin.member.dto.request.CreateMemberSemesterRequest;
12+
import org.ject.support.admin.member.dto.request.MemberSemesterSearchCondition;
13+
import org.ject.support.admin.member.dto.response.SearchMemberSemesterResponse;
14+
import org.ject.support.admin.member.dto.result.SearchMemberSemesterPageResult;
15+
import org.ject.support.common.response.CursorPageResponse;
616
import org.ject.support.domain.member.exception.MemberException;
717
import org.ject.support.domain.recruit.service.SemesterInquiryUsecase;
818
import org.springframework.stereotype.Service;
@@ -33,6 +43,56 @@ public void createMemberSemester(CreateMemberSemesterRequest request) {
3343
adminMemberActivityService.createMemberSemesterActivity(request, memberId);
3444
}
3545

46+
47+
// 일반 구성원 목록 조회 (커서 기반 페이징)
48+
@Transactional(readOnly=true)
49+
public CursorPageResponse<SearchMemberSemesterResponse> searchMemberSemester(
50+
MemberSemesterSearchCondition condition
51+
) {
52+
// 기수 유효성 체크
53+
validateSemester(condition.semesterId());
54+
// 팀 유효성 체크
55+
validateTeams(condition.semesterId(), condition.teamIds());
56+
// size만큼 조회 + 전체 행 수 조회
57+
SearchMemberSemesterPageResult pageResult = adminMemberActivityService.searchMemberSemesterList(condition);
58+
// 페이징 값 처리
59+
boolean hasNext = pageResult.content().size() > condition.getSizeOrDefault();
60+
long totalCount = pageResult.totalCount();
61+
List<SearchMemberSemesterProjection> content = hasNext
62+
? pageResult.content().subList(0,condition.getSizeOrDefault())
63+
: pageResult.content();
64+
65+
List<SearchMemberSemesterResponse> responses = content.stream()
66+
.map(SearchMemberSemesterResponse::from)
67+
.toList();
68+
69+
Long nextCursor = hasNext && !content.isEmpty()
70+
? content.get(content.size()-1).memberActivityId()
71+
: null;
72+
73+
// 응답
74+
return CursorPageResponse.of(
75+
responses,
76+
condition.getSizeOrDefault(),
77+
hasNext,
78+
nextCursor,
79+
totalCount
80+
);
81+
}
82+
83+
/*
84+
유틸 함수
85+
*/
86+
87+
// 존재하는 기수인지 검증
88+
private void validateSemester(Long semesterId) {
89+
if(semesterId == null){
90+
return;
91+
}
92+
// 존재하지 않으면 semester 도메인에서 예외 처리
93+
semesterInquiryUsecase.getSemester(semesterId);
94+
}
95+
3696
// 선택한 팀의 기수 소속 검증
3797
private void validateTeam(Long semesterId, Long teamId) {
3898
if (teamId == null) {
@@ -43,4 +103,26 @@ private void validateTeam(Long semesterId, Long teamId) {
43103
throw new MemberException(NOT_FOUND_TEAM_OF_SEMESTER);
44104
}
45105
}
106+
107+
private void validateTeams(Long semesterId, List<Long> teamIds) {
108+
if(teamIds == null || teamIds.isEmpty()){
109+
return;
110+
}
111+
112+
// 팀이 있는데 기수가 없으면 예외
113+
if (semesterId == null) {
114+
throw new MemberException(REQUIRED_SEMESTER_FOR_TEAM_FILTER);
115+
}
116+
117+
// 조회 결과 List에서 바로 containsAll() -> O(N*M)
118+
// Set에 담고 O(1)로 비교시 -> O(N+M)
119+
Set<Long> validTeamIds = new HashSet<>(
120+
adminMemberTeamService.getTeamIdsBySemesterId(semesterId)
121+
);
122+
123+
if(!validTeamIds.containsAll(teamIds)) {
124+
throw new MemberException(NOT_FOUND_TEAM_OF_SEMESTER);
125+
}
126+
}
127+
46128
}

src/main/java/org/ject/support/domain/member/exception/MemberErrorCode.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package org.ject.support.domain.member.exception;
22

3+
import static org.springframework.http.HttpStatus.*;
4+
35
import lombok.AllArgsConstructor;
46
import lombok.Getter;
57
import org.ject.support.common.exception.ErrorCode;
68
import org.springframework.http.HttpStatus;
79

8-
import static org.springframework.http.HttpStatus.CONFLICT;
9-
import static org.springframework.http.HttpStatus.NOT_FOUND;
10-
import static org.springframework.http.HttpStatus.PAYLOAD_TOO_LARGE;
11-
1210
@Getter
1311
@AllArgsConstructor
1412
public enum MemberErrorCode implements ErrorCode {
@@ -18,7 +16,8 @@ public enum MemberErrorCode implements ErrorCode {
1816
DUPLICATE_EMAIL(CONFLICT, "MEMBER-4", "이미 사용 중인 이메일입니다."),
1917
EXCEEDED_INTERESTED_DOMAINS_MAX_SIZE(PAYLOAD_TOO_LARGE, "MEMBER-5", "관심 도메인 목록의 최대 크기를 초과했습니다."),
2018
ALREADY_EXIST_MEMBER_SEMESTER_ACTIVITY(CONFLICT, "MEMBER-6", "해당 기수에 이미 등록된 일반 구성원 활동입니다."),
21-
NOT_FOUND_TEAM_OF_SEMESTER(NOT_FOUND, "MEMBER-7", "해당 기수의 팀을 찾을 수 없습니다.")
19+
NOT_FOUND_TEAM_OF_SEMESTER(NOT_FOUND, "MEMBER-7", "해당 기수의 팀을 찾을 수 없습니다."),
20+
REQUIRED_SEMESTER_FOR_TEAM_FILTER(BAD_REQUEST, "MEMBER-8", "팀 필터를 사용할 때는 기수를 함께 선택해야 합니다."),
2221
;
2322

2423
private final HttpStatus httpStatus;

0 commit comments

Comments
 (0)