Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package clap.server.adapter.inbound.web.admin;

import clap.server.adapter.inbound.web.dto.admin.FindMemberRequest;
import clap.server.adapter.inbound.web.dto.admin.RetrieveAllMemberResponse;
import clap.server.application.mapper.RetrieveAllMemberMapper;
import clap.server.application.port.inbound.management.FindAllMembersUsecase;
import clap.server.application.port.inbound.management.FindMembersWithFilterUsecase;
import clap.server.domain.model.member.Member;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/managements")
@RequiredArgsConstructor
public class FindMemberController {
private final FindAllMembersUsecase findAllMembersUsecase;
private final FindMembersWithFilterUsecase findMembersWithFilterUsecase;
private final RetrieveAllMemberMapper retrieveAllMemberMapper;

@Tag(name = "05. Admin")
@Secured({"ROLE_ADMIN"})
@Operation(
summary = "전체 회원 조회 API",
description = "모든 회원 정보를 페이징 처리하여 반환하거나 조건에 맞는 회원 정보를 반환합니다.",
parameters = {
@Parameter(name = "page", description = "조회할 페이지 번호 (0부터 시작, 기본값: 0)", example = "0"),
@Parameter(name = "size", description = "페이지 당 회원 수 (기본값: 20)", example = "20")
}
)
@GetMapping("/members")
public ResponseEntity<Page<RetrieveAllMemberResponse>> getAllMembers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@ModelAttribute FindMemberRequest filterRequest) {

Pageable pageable = PageRequest.of(page, size);
Page<Member> members;

if (filterRequest.getName() != null || filterRequest.getEmail() != null || filterRequest.getNickname() != null ||
filterRequest.getDepartmentId() != null || filterRequest.getRole() != null) {
members = findMembersWithFilterUsecase.findMembersWithFilter(pageable, filterRequest);
} else {
members = findAllMembersUsecase.findAllMembers(pageable);
}


Page<RetrieveAllMemberResponse> response = members.map(retrieveAllMemberMapper::toResponse);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package clap.server.adapter.inbound.web.dto.admin;

import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

@Data
public class FindMemberRequest {
@Schema(description = "회원 이름", example = "양시훈")
private String name;

@Schema(description = "회원 이메일", example = "sihun123@gmail.com")
private String email;

@Schema(description = "회원 닉네임", example = "leo.sh")
private String nickname;

@Schema(description = "부서 ID", example = "1")
private Long departmentId;

@Schema(description = "회원 역할", example = "ROLE_USER")
private MemberRole role;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package clap.server.adapter.inbound.web.dto.admin;

import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole;
import io.swagger.v3.oas.annotations.media.Schema;

public record RetrieveAllMemberResponse(
@Schema(description = "회원 이름", example = "양시훈")
String name,

@Schema(description = "회원 이메일", example = "sihun123@gmail.com")
String email,

@Schema(description = "회원 닉네임, 로그인할 때 쓰입니다.", example = "leo.sh")
String nickname,

@Schema(description = "승인 권한 여부", example = "true")
Boolean isReviewer,

@Schema(description = "부서 ID", example = "1")
Long departmentId,

@Schema(description = "회원 역할", example = "ROLE_USER")
MemberRole role,

@Schema(description = "회원 직책", example = "개발자")
String departmentRole
) {}
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
package clap.server.adapter.outbound.persistense;

import clap.server.adapter.inbound.web.dto.admin.FindMemberRequest;
import clap.server.adapter.outbound.persistense.entity.member.MemberEntity;
import clap.server.adapter.outbound.persistense.entity.member.constant.MemberStatus;
import clap.server.adapter.outbound.persistense.mapper.MemberPersistenceMapper;
import clap.server.adapter.outbound.persistense.repository.member.MemberRepository;
import clap.server.application.port.outbound.member.CommandMemberPort;
import clap.server.application.port.outbound.member.LoadMemberPort;
import clap.server.common.annotation.architecture.PersistenceAdapter;
import clap.server.domain.model.task.Task;
import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus ;
import clap.server.adapter.outbound.persistense.entity.task.TaskEntity;
import clap.server.adapter.outbound.persistense.repository.task.TaskRepository;
import clap.server.adapter.outbound.persistense.mapper.TaskPersistenceMapper;

import java.util.stream.Collectors;
import java.util.List;

import clap.server.domain.model.member.Member;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static clap.server.adapter.outbound.persistense.entity.member.QMemberEntity.memberEntity;

@PersistenceAdapter
@RequiredArgsConstructor
public class MemberPersistenceAdapter implements LoadMemberPort, CommandMemberPort {
private final MemberRepository memberRepository;
private final MemberPersistenceMapper memberPersistenceMapper;
private final TaskRepository taskRepository;
private final TaskPersistenceMapper taskPersistenceMapper;
private final JPAQueryFactory jpaQueryFactory;


@Override
Expand Down Expand Up @@ -60,5 +80,64 @@ public void save(final Member member) {
memberRepository.save(memberEntity);
}

@Override
public Page<Member> findAllMembers(Pageable pageable) {
return executeQueryWithPageable(pageable, new BooleanBuilder().and(memberEntity.status.ne(MemberStatus.DELETED)));
}

@Override
public Page<Member> findMembersWithFilter(Pageable pageable, FindMemberRequest filterRequest) {
BooleanBuilder whereClause = createMemberFilter(filterRequest);
return executeQueryWithPageable(pageable, whereClause);
}

// 공통 쿼리 처리
private Page<Member> executeQueryWithPageable(Pageable pageable, BooleanBuilder whereClause) {
List<MemberEntity> entities = jpaQueryFactory
.selectFrom(memberEntity)
.where(whereClause)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

long total = jpaQueryFactory
.select(memberEntity.count())
.from(memberEntity)
.where(whereClause)
.fetchOne();

return new PageImpl<>(
entities.stream()
.map(memberPersistenceMapper::toDomain)
.toList(),
pageable,
total
);
}

// 필터 조건 생성
private BooleanBuilder createMemberFilter(FindMemberRequest filterRequest) {
BooleanBuilder whereClause = new BooleanBuilder();
whereClause.and(memberEntity.status.ne(MemberStatus.DELETED));

if (filterRequest.getName() != null) {
whereClause.and(memberEntity.name.containsIgnoreCase(filterRequest.getName()));
}
if (filterRequest.getEmail() != null) {
whereClause.and(memberEntity.email.containsIgnoreCase(filterRequest.getEmail()));
}
if (filterRequest.getNickname() != null) {
whereClause.and(memberEntity.nickname.containsIgnoreCase(filterRequest.getNickname()));
}
if (filterRequest.getDepartmentId() != null) {
whereClause.and(memberEntity.department.departmentId.eq(filterRequest.getDepartmentId()));
}
if (filterRequest.getRole() != null) {
whereClause.and(memberEntity.role.eq(filterRequest.getRole()));
}

return whereClause;
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole;
import clap.server.adapter.outbound.persistense.entity.member.constant.MemberStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
public interface MemberRepository extends JpaRepository<MemberEntity, Long>, JpaSpecificationExecutor<MemberEntity> {


List<MemberEntity> findByRoleAndStatus(MemberRole role, MemberStatus status);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package clap.server.application.mapper;

import clap.server.adapter.inbound.web.dto.admin.RetrieveAllMemberResponse;
import clap.server.domain.model.member.Member;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class RetrieveAllMemberMapper {
public List<RetrieveAllMemberResponse> toResponseList(List<Member> members) {
return members.stream()
.map(this::toResponse)
.toList();
}

public RetrieveAllMemberResponse toResponse(Member member) {
return new RetrieveAllMemberResponse(
member.getMemberInfo().getName(),
member.getMemberInfo().getEmail(),
member.getMemberInfo().getNickname(),
member.getMemberInfo().isReviewer(),
member.getMemberInfo().getDepartment().getDepartmentId(),
member.getMemberInfo().getRole(),
member.getMemberInfo().getDepartmentRole()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import clap.server.application.port.outbound.member.CommandMemberPort;
import clap.server.application.port.outbound.member.LoadMemberPort;
import clap.server.domain.model.member.Member;
import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus;
import clap.server.exception.ApplicationException;
import clap.server.exception.code.MemberErrorCode;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package clap.server.application.port.inbound.management;

import clap.server.domain.model.member.Member;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;


public interface FindAllMembersUsecase {
Page<Member> findAllMembers(Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package clap.server.application.port.inbound.management;

import clap.server.adapter.inbound.web.dto.admin.FindMemberRequest;
import clap.server.domain.model.member.Member;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface FindMembersWithFilterUsecase {
Page<Member> findMembersWithFilter(Pageable pageable, FindMemberRequest filterRequest);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package clap.server.application.port.outbound.member;

import clap.server.adapter.inbound.web.dto.admin.FindMemberRequest;
import clap.server.domain.model.member.Member;
import clap.server.domain.model.task.Task;
import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Optional;
Expand All @@ -14,6 +19,10 @@ public interface LoadMemberPort {

List<Member> findReviewers();

Page<Member> findAllMembers(Pageable pageable);

Page<Member> findMembersWithFilter(Pageable pageable, FindMemberRequest filterRequest);

Optional<Member> findReviewerById(Long id);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package clap.server.application.service.member;

import clap.server.application.port.inbound.management.FindAllMembersUsecase;
import clap.server.application.port.outbound.member.LoadMemberPort;
import clap.server.domain.model.member.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class FindAllMembersService implements FindAllMembersUsecase {
private final LoadMemberPort loadMemberPort;

@Override
public Page<Member> findAllMembers(Pageable pageable) {
return loadMemberPort.findAllMembers(pageable);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package clap.server.application.service.member;

import clap.server.adapter.inbound.web.dto.admin.FindMemberRequest;
import clap.server.application.port.inbound.management.FindMembersWithFilterUsecase;
import clap.server.application.port.outbound.member.LoadMemberPort;
import clap.server.domain.model.member.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class FindMembersWithFilterService implements FindMembersWithFilterUsecase {
private final LoadMemberPort loadMemberPort;

@Override
public Page<Member> findMembersWithFilter(Pageable pageable, FindMemberRequest filterRequest) {
return loadMemberPort.findMembersWithFilter(pageable, filterRequest);
}
}