Skip to content

Commit 9e5fd42

Browse files
authored
Merge pull request #105 from TaskFlow-CLAP/CLAP-118
CLAP-118 feat:전체 회원 조회 API 구현
2 parents 0716af2 + b53aad5 commit 9e5fd42

12 files changed

Lines changed: 288 additions & 1 deletion

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package clap.server.adapter.inbound.web.admin;
2+
3+
import clap.server.adapter.inbound.web.dto.admin.FindMemberRequest;
4+
import clap.server.adapter.inbound.web.dto.admin.RetrieveAllMemberResponse;
5+
import clap.server.application.mapper.RetrieveAllMemberMapper;
6+
import clap.server.application.port.inbound.management.FindAllMembersUsecase;
7+
import clap.server.application.port.inbound.management.FindMembersWithFilterUsecase;
8+
import clap.server.domain.model.member.Member;
9+
import io.swagger.v3.oas.annotations.Operation;
10+
import io.swagger.v3.oas.annotations.Parameter;
11+
import io.swagger.v3.oas.annotations.tags.Tag;
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.data.domain.Page;
14+
import org.springframework.data.domain.PageRequest;
15+
import org.springframework.data.domain.Pageable;
16+
import org.springframework.http.ResponseEntity;
17+
import org.springframework.security.access.annotation.Secured;
18+
import org.springframework.web.bind.annotation.*;
19+
20+
@RestController
21+
@RequestMapping("/api/managements")
22+
@RequiredArgsConstructor
23+
public class FindMemberController {
24+
private final FindAllMembersUsecase findAllMembersUsecase;
25+
private final FindMembersWithFilterUsecase findMembersWithFilterUsecase;
26+
private final RetrieveAllMemberMapper retrieveAllMemberMapper;
27+
28+
@Tag(name = "05. Admin")
29+
@Secured({"ROLE_ADMIN"})
30+
@Operation(
31+
summary = "전체 회원 조회 API",
32+
description = "모든 회원 정보를 페이징 처리하여 반환하거나 조건에 맞는 회원 정보를 반환합니다.",
33+
parameters = {
34+
@Parameter(name = "page", description = "조회할 페이지 번호 (0부터 시작, 기본값: 0)", example = "0"),
35+
@Parameter(name = "size", description = "페이지 당 회원 수 (기본값: 20)", example = "20")
36+
}
37+
)
38+
@GetMapping("/members")
39+
public ResponseEntity<Page<RetrieveAllMemberResponse>> getAllMembers(
40+
@RequestParam(defaultValue = "0") int page,
41+
@RequestParam(defaultValue = "20") int size,
42+
@ModelAttribute FindMemberRequest filterRequest) {
43+
44+
Pageable pageable = PageRequest.of(page, size);
45+
Page<Member> members;
46+
47+
if (filterRequest.getName() != null || filterRequest.getEmail() != null || filterRequest.getNickname() != null ||
48+
filterRequest.getDepartmentId() != null || filterRequest.getRole() != null) {
49+
members = findMembersWithFilterUsecase.findMembersWithFilter(pageable, filterRequest);
50+
} else {
51+
members = findAllMembersUsecase.findAllMembers(pageable);
52+
}
53+
54+
55+
Page<RetrieveAllMemberResponse> response = members.map(retrieveAllMemberMapper::toResponse);
56+
return ResponseEntity.ok(response);
57+
}
58+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package clap.server.adapter.inbound.web.dto.admin;
2+
3+
import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.Data;
6+
7+
@Data
8+
public class FindMemberRequest {
9+
@Schema(description = "회원 이름", example = "양시훈")
10+
private String name;
11+
12+
@Schema(description = "회원 이메일", example = "sihun123@gmail.com")
13+
private String email;
14+
15+
@Schema(description = "회원 닉네임", example = "leo.sh")
16+
private String nickname;
17+
18+
@Schema(description = "부서 ID", example = "1")
19+
private Long departmentId;
20+
21+
@Schema(description = "회원 역할", example = "ROLE_USER")
22+
private MemberRole role;
23+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package clap.server.adapter.inbound.web.dto.admin;
2+
3+
import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
6+
public record RetrieveAllMemberResponse(
7+
@Schema(description = "회원 이름", example = "양시훈")
8+
String name,
9+
10+
@Schema(description = "회원 이메일", example = "sihun123@gmail.com")
11+
String email,
12+
13+
@Schema(description = "회원 닉네임, 로그인할 때 쓰입니다.", example = "leo.sh")
14+
String nickname,
15+
16+
@Schema(description = "승인 권한 여부", example = "true")
17+
Boolean isReviewer,
18+
19+
@Schema(description = "부서 ID", example = "1")
20+
Long departmentId,
21+
22+
@Schema(description = "회원 역할", example = "ROLE_USER")
23+
MemberRole role,
24+
25+
@Schema(description = "회원 직책", example = "개발자")
26+
String departmentRole
27+
) {}

src/main/java/clap/server/adapter/outbound/persistense/MemberPersistenceAdapter.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,44 @@
11
package clap.server.adapter.outbound.persistense;
22

3+
import clap.server.adapter.inbound.web.dto.admin.FindMemberRequest;
34
import clap.server.adapter.outbound.persistense.entity.member.MemberEntity;
45
import clap.server.adapter.outbound.persistense.entity.member.constant.MemberStatus;
56
import clap.server.adapter.outbound.persistense.mapper.MemberPersistenceMapper;
67
import clap.server.adapter.outbound.persistense.repository.member.MemberRepository;
78
import clap.server.application.port.outbound.member.CommandMemberPort;
89
import clap.server.application.port.outbound.member.LoadMemberPort;
910
import clap.server.common.annotation.architecture.PersistenceAdapter;
11+
import clap.server.domain.model.task.Task;
12+
import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus ;
13+
import clap.server.adapter.outbound.persistense.entity.task.TaskEntity;
14+
import clap.server.adapter.outbound.persistense.repository.task.TaskRepository;
15+
import clap.server.adapter.outbound.persistense.mapper.TaskPersistenceMapper;
16+
17+
import java.util.stream.Collectors;
18+
import java.util.List;
19+
1020
import clap.server.domain.model.member.Member;
21+
import com.querydsl.core.BooleanBuilder;
22+
import com.querydsl.jpa.impl.JPAQueryFactory;
1123
import lombok.RequiredArgsConstructor;
24+
import org.springframework.data.domain.Page;
25+
import org.springframework.data.domain.PageImpl;
26+
import org.springframework.data.domain.Pageable;
1227

1328
import java.util.List;
1429
import java.util.Optional;
1530
import java.util.stream.Collectors;
1631

32+
import static clap.server.adapter.outbound.persistense.entity.member.QMemberEntity.memberEntity;
33+
1734
@PersistenceAdapter
1835
@RequiredArgsConstructor
1936
public class MemberPersistenceAdapter implements LoadMemberPort, CommandMemberPort {
2037
private final MemberRepository memberRepository;
2138
private final MemberPersistenceMapper memberPersistenceMapper;
39+
private final TaskRepository taskRepository;
40+
private final TaskPersistenceMapper taskPersistenceMapper;
41+
private final JPAQueryFactory jpaQueryFactory;
2242

2343

2444
@Override
@@ -60,5 +80,64 @@ public void save(final Member member) {
6080
memberRepository.save(memberEntity);
6181
}
6282

83+
@Override
84+
public Page<Member> findAllMembers(Pageable pageable) {
85+
return executeQueryWithPageable(pageable, new BooleanBuilder().and(memberEntity.status.ne(MemberStatus.DELETED)));
86+
}
87+
88+
@Override
89+
public Page<Member> findMembersWithFilter(Pageable pageable, FindMemberRequest filterRequest) {
90+
BooleanBuilder whereClause = createMemberFilter(filterRequest);
91+
return executeQueryWithPageable(pageable, whereClause);
92+
}
93+
94+
// 공통 쿼리 처리
95+
private Page<Member> executeQueryWithPageable(Pageable pageable, BooleanBuilder whereClause) {
96+
List<MemberEntity> entities = jpaQueryFactory
97+
.selectFrom(memberEntity)
98+
.where(whereClause)
99+
.offset(pageable.getOffset())
100+
.limit(pageable.getPageSize())
101+
.fetch();
102+
103+
long total = jpaQueryFactory
104+
.select(memberEntity.count())
105+
.from(memberEntity)
106+
.where(whereClause)
107+
.fetchOne();
108+
109+
return new PageImpl<>(
110+
entities.stream()
111+
.map(memberPersistenceMapper::toDomain)
112+
.toList(),
113+
pageable,
114+
total
115+
);
116+
}
117+
118+
// 필터 조건 생성
119+
private BooleanBuilder createMemberFilter(FindMemberRequest filterRequest) {
120+
BooleanBuilder whereClause = new BooleanBuilder();
121+
whereClause.and(memberEntity.status.ne(MemberStatus.DELETED));
122+
123+
if (filterRequest.getName() != null) {
124+
whereClause.and(memberEntity.name.containsIgnoreCase(filterRequest.getName()));
125+
}
126+
if (filterRequest.getEmail() != null) {
127+
whereClause.and(memberEntity.email.containsIgnoreCase(filterRequest.getEmail()));
128+
}
129+
if (filterRequest.getNickname() != null) {
130+
whereClause.and(memberEntity.nickname.containsIgnoreCase(filterRequest.getNickname()));
131+
}
132+
if (filterRequest.getDepartmentId() != null) {
133+
whereClause.and(memberEntity.department.departmentId.eq(filterRequest.getDepartmentId()));
134+
}
135+
if (filterRequest.getRole() != null) {
136+
whereClause.and(memberEntity.role.eq(filterRequest.getRole()));
137+
}
138+
139+
return whereClause;
140+
}
63141
}
64142

143+

src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole;
55
import clap.server.adapter.outbound.persistense.entity.member.constant.MemberStatus;
66
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
78
import org.springframework.stereotype.Repository;
89

910
import java.util.List;
1011
import java.util.Optional;
1112

1213
@Repository
13-
public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
14+
public interface MemberRepository extends JpaRepository<MemberEntity, Long>, JpaSpecificationExecutor<MemberEntity> {
1415

1516

1617
List<MemberEntity> findByRoleAndStatus(MemberRole role, MemberStatus status);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package clap.server.application.mapper;
2+
3+
import clap.server.adapter.inbound.web.dto.admin.RetrieveAllMemberResponse;
4+
import clap.server.domain.model.member.Member;
5+
import org.springframework.stereotype.Component;
6+
7+
import java.util.List;
8+
9+
@Component
10+
public class RetrieveAllMemberMapper {
11+
public List<RetrieveAllMemberResponse> toResponseList(List<Member> members) {
12+
return members.stream()
13+
.map(this::toResponse)
14+
.toList();
15+
}
16+
17+
public RetrieveAllMemberResponse toResponse(Member member) {
18+
return new RetrieveAllMemberResponse(
19+
member.getMemberInfo().getName(),
20+
member.getMemberInfo().getEmail(),
21+
member.getMemberInfo().getNickname(),
22+
member.getMemberInfo().isReviewer(),
23+
member.getMemberInfo().getDepartment().getDepartmentId(),
24+
member.getMemberInfo().getRole(),
25+
member.getMemberInfo().getDepartmentRole()
26+
);
27+
}
28+
}

src/main/java/clap/server/application/port/inbound/domain/MemberService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import clap.server.application.port.outbound.member.CommandMemberPort;
44
import clap.server.application.port.outbound.member.LoadMemberPort;
55
import clap.server.domain.model.member.Member;
6+
import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus;
67
import clap.server.exception.ApplicationException;
78
import clap.server.exception.code.MemberErrorCode;
89
import lombok.RequiredArgsConstructor;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package clap.server.application.port.inbound.management;
2+
3+
import clap.server.domain.model.member.Member;
4+
import org.springframework.data.domain.Page;
5+
import org.springframework.data.domain.Pageable;
6+
7+
8+
public interface FindAllMembersUsecase {
9+
Page<Member> findAllMembers(Pageable pageable);
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package clap.server.application.port.inbound.management;
2+
3+
import clap.server.adapter.inbound.web.dto.admin.FindMemberRequest;
4+
import clap.server.domain.model.member.Member;
5+
import org.springframework.data.domain.Page;
6+
import org.springframework.data.domain.Pageable;
7+
8+
public interface FindMembersWithFilterUsecase {
9+
Page<Member> findMembersWithFilter(Pageable pageable, FindMemberRequest filterRequest);
10+
}

src/main/java/clap/server/application/port/outbound/member/LoadMemberPort.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package clap.server.application.port.outbound.member;
22

3+
import clap.server.adapter.inbound.web.dto.admin.FindMemberRequest;
34
import clap.server.domain.model.member.Member;
5+
import clap.server.domain.model.task.Task;
6+
import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus;
7+
import org.springframework.data.domain.Page;
8+
import org.springframework.data.domain.Pageable;
49

510
import java.util.List;
611
import java.util.Optional;
@@ -14,6 +19,10 @@ public interface LoadMemberPort {
1419

1520
List<Member> findReviewers();
1621

22+
Page<Member> findAllMembers(Pageable pageable);
23+
24+
Page<Member> findMembersWithFilter(Pageable pageable, FindMemberRequest filterRequest);
25+
1726
Optional<Member> findReviewerById(Long id);
1827

1928
}

0 commit comments

Comments
 (0)