diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/api/AdminMemberController.java b/src/main/java/com/gdschongik/gdsc/domain/member/api/AdminMemberController.java new file mode 100644 index 000000000..f1189e103 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/member/api/AdminMemberController.java @@ -0,0 +1,26 @@ +package com.gdschongik.gdsc.domain.member.api; + +import com.gdschongik.gdsc.domain.member.application.MemberService; +import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; +import com.gdschongik.gdsc.domain.member.dto.response.MemberFindAllResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/admin/members") +@RequiredArgsConstructor +public class AdminMemberController { + + private final MemberService memberService; + + @GetMapping + public ResponseEntity> getMembers(MemberQueryRequest queryRequest, Pageable pageable) { + Page response = memberService.findAll(queryRequest, pageable); + return ResponseEntity.ok().body(response); + } +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/application/MemberService.java b/src/main/java/com/gdschongik/gdsc/domain/member/application/MemberService.java new file mode 100644 index 000000000..208f4c6e6 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/member/application/MemberService.java @@ -0,0 +1,24 @@ +package com.gdschongik.gdsc.domain.member.application; + +import com.gdschongik.gdsc.domain.member.dao.MemberRepository; +import com.gdschongik.gdsc.domain.member.domain.Member; +import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; +import com.gdschongik.gdsc.domain.member.dto.response.MemberFindAllResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MemberService { + + private final MemberRepository memberRepository; + + public Page findAll(MemberQueryRequest queryRequest, Pageable pageable) { + Page members = memberRepository.findAll(queryRequest, pageable); + return members.map(MemberFindAllResponse::of); + } +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepository.java b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepository.java new file mode 100644 index 000000000..b7458d7d6 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepository.java @@ -0,0 +1,10 @@ +package com.gdschongik.gdsc.domain.member.dao; + +import com.gdschongik.gdsc.domain.member.domain.Member; +import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface MemberCustomRepository { + Page findAll(MemberQueryRequest queryRequest, Pageable pageable); +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java new file mode 100644 index 000000000..e8ca112d0 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberCustomRepositoryImpl.java @@ -0,0 +1,77 @@ +package com.gdschongik.gdsc.domain.member.dao; + +import static com.gdschongik.gdsc.domain.member.domain.QMember.*; + +import com.gdschongik.gdsc.domain.member.domain.Member; +import com.gdschongik.gdsc.domain.member.dto.request.MemberQueryRequest; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; + +@RequiredArgsConstructor +public class MemberCustomRepositoryImpl implements MemberCustomRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public Page findAll(MemberQueryRequest queryRequest, Pageable pageable) { + List fetch = queryFactory + .selectFrom(member) + .where(queryOption(queryRequest)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery countQuery = + queryFactory.select(member.count()).from(member).where(queryOption(queryRequest)); + + return PageableExecutionUtils.getPage(fetch, pageable, countQuery::fetchOne); + } + + private BooleanBuilder queryOption(MemberQueryRequest queryRequest) { + BooleanBuilder booleanBuilder = new BooleanBuilder(); + + return booleanBuilder + .and(eqStudentId(queryRequest.studentId())) + .and(eqName(queryRequest.name())) + .and(eqPhone(queryRequest.phone())) + .and(eqDepartment(queryRequest.department())) + .and(eqEmail(queryRequest.email())) + .and(eqDiscordUsername(queryRequest.discordUsername())) + .and(eqDiscordNickname(queryRequest.discordNickname())); + } + + private BooleanExpression eqStudentId(String studentId) { + return studentId != null ? member.studentId.containsIgnoreCase(studentId) : null; + } + + private BooleanExpression eqName(String name) { + return name != null ? member.name.containsIgnoreCase(name) : null; + } + + private BooleanExpression eqPhone(String phone) { + return phone != null ? member.phone.containsIgnoreCase(phone) : null; + } + + private BooleanExpression eqDepartment(String department) { + return department != null ? member.department.containsIgnoreCase(department) : null; + } + + private BooleanExpression eqEmail(String email) { + return email != null ? member.email.containsIgnoreCase(email) : null; + } + + private BooleanExpression eqDiscordUsername(String discordUsername) { + return discordUsername != null ? member.discordUsername.containsIgnoreCase(discordUsername) : null; + } + + private BooleanExpression eqDiscordNickname(String discordNickname) { + return discordNickname != null ? member.nickname.containsIgnoreCase(discordNickname) : null; + } +} diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberRepository.java b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberRepository.java index bee57ef79..2839662a2 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberRepository.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dao/MemberRepository.java @@ -4,7 +4,7 @@ import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -public interface MemberRepository extends JpaRepository { +public interface MemberRepository extends JpaRepository, MemberCustomRepository { Optional findByOauthId(String oauthId); } diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dto/request/MemberQueryRequest.java b/src/main/java/com/gdschongik/gdsc/domain/member/dto/request/MemberQueryRequest.java new file mode 100644 index 000000000..c88c77670 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dto/request/MemberQueryRequest.java @@ -0,0 +1,10 @@ +package com.gdschongik.gdsc.domain.member.dto.request; + +public record MemberQueryRequest( + String studentId, + String name, + String phone, + String department, + String email, + String discordUsername, + String discordNickname) {} diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dto/response/MemberFindAllResponse.java b/src/main/java/com/gdschongik/gdsc/domain/member/dto/response/MemberFindAllResponse.java new file mode 100644 index 000000000..a0a5ebdfc --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dto/response/MemberFindAllResponse.java @@ -0,0 +1,26 @@ +package com.gdschongik.gdsc.domain.member.dto.response; + +import com.gdschongik.gdsc.domain.member.domain.Member; + +public record MemberFindAllResponse( + Long memberId, + String studentId, + String name, + String phone, + String department, + String email, + String discordUsername, + String nickname) { + + public static MemberFindAllResponse of(Member member) { + return new MemberFindAllResponse( + member.getId(), + member.getStudentId(), + member.getName(), + member.getPhone(), + member.getDepartment(), + member.getEmail(), + member.getDiscordUsername(), + member.getNickname()); + } +} diff --git a/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java index a9ff9e2ab..319412314 100644 --- a/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java +++ b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java @@ -11,7 +11,10 @@ public enum ErrorCode { // Jwt INVALID_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 JWT 토큰입니다."), - EXPIRED_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 JWT 토큰입니다."); + EXPIRED_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 JWT 토큰입니다."), + + // Parameter + INVALID_QUERY_PARAMETER(HttpStatus.BAD_REQUEST, "잘못된 쿼리 파라미터입니다."); private final HttpStatus status; private final String message;