diff --git a/build.gradle b/build.gradle index 5629fad..64dc761 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,8 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'com.h2database:h2' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' } tasks.named('test') { diff --git a/src/main/java/org/one/global/pagination/RequestPagingDto.java b/src/main/java/org/one/global/pagination/RequestPagingDto.java new file mode 100644 index 0000000..f61ee28 --- /dev/null +++ b/src/main/java/org/one/global/pagination/RequestPagingDto.java @@ -0,0 +1,28 @@ +package org.one.global.pagination; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class RequestPagingDto { + private Integer page = 0; + private Integer size = 10; + private String sort = "id"; + private String direction = "DESC"; + + public Pageable toPageable() { + Sort.Direction dir = Sort.Direction.fromString(direction); + return PageRequest.of(page, size, Sort.by(dir, sort)); + } +} \ No newline at end of file diff --git a/src/main/java/org/one/global/pagination/ResponsePagingDto.java b/src/main/java/org/one/global/pagination/ResponsePagingDto.java new file mode 100644 index 0000000..4550e0e --- /dev/null +++ b/src/main/java/org/one/global/pagination/ResponsePagingDto.java @@ -0,0 +1,30 @@ +package org.one.global.pagination; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.domain.Page; + +import java.util.List; + +@Getter +@Builder +public class ResponsePagingDto { + + private List content; + private Integer page; + private Integer size; + private Long totalElements; + private Integer totalPages; + private Boolean last; + + public static ResponsePagingDto from(Page page) { + return ResponsePagingDto.builder() + .content(page.getContent()) + .page(page.getNumber()) + .size(page.getSize()) + .totalElements(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .last(page.isLast()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/org/one/member/controller/MemberController.java b/src/main/java/org/one/member/controller/MemberController.java index caaa123..730750c 100644 --- a/src/main/java/org/one/member/controller/MemberController.java +++ b/src/main/java/org/one/member/controller/MemberController.java @@ -1,4 +1,48 @@ -package org.one.member.controller; +package org.one.domain.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.one.global.annotation.ApiErrorExceptions; +import org.one.global.dto.ApiResponse; +import org.one.global.enums.ErrorCode; +import org.one.member.dto.MemberListRequestDto; +import org.one.member.dto.MemberListResponseDto; +import org.one.member.service.MemberService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Tag(name = "Member", description = "부원 명부 관리 (관리자 전용)") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/members") public class MemberController { + private final MemberService memberService; + + /** + * 명부 전체 조회 API + * 요청 시, 선택적으로 page관련 설정(정렬 등) + * + * 예시 : GET /api/v1/members?page=1&sort=... + * + * 응답 데이터 : 전체 member의 명부리스트 + */ + @ApiErrorExceptions({ErrorCode.INVALID_INPUT}) + @Operation(summary = "명부 전체 조회", description = "관리자 권한(ADMIN)이 있는 계정만 전체 부원 명부를 조회할 수 있습니다.") + @PreAuthorize("hasRole('ADMIN')") + @GetMapping + public ResponseEntity>> getMemberList( + @ModelAttribute @Valid MemberListRequestDto requestDto){ + + List response = memberService.getMemberListByAdmin(requestDto); + + return ResponseEntity.ok(ApiResponse.success(response)); + } } diff --git a/src/main/java/org/one/member/dto/MemberListRequestDto.java b/src/main/java/org/one/member/dto/MemberListRequestDto.java new file mode 100644 index 0000000..73f590d --- /dev/null +++ b/src/main/java/org/one/member/dto/MemberListRequestDto.java @@ -0,0 +1,51 @@ +package org.one.member.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import org.one.global.pagination.RequestPagingDto; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +@Schema(description = "명부 리스트 조회 요청(페이지 설정)") +public class MemberListRequestDto extends RequestPagingDto { + public MemberListRequestDto(){ + //기본값 세팅 + this.setPage(0); + this.setSize(15); + this.setSort("createdAt"); + this.setDirection("ASC"); + } + + @Override + @Schema(description = "페이지 번호 (0부터 시작)", example = "0") + @Min(value = 0, message = "페이지 번호는 0 이상이어야 합니다.") + public Integer getPage() { + return super.getPage(); + } + + @Override + @Schema(description = "한 페이지에 보여줄 부원 수 [15 고정]", example = "15") + @Min(value = 15, message = "명부 조회의 페이지 크기는 15로 고정되어 있습니다.") + @Max(value = 15, message = "명부 조회의 페이지 크기는 15로 고정되어 있습니다.") + public Integer getSize() { + return super.getSize(); + } + + @Override + @Schema(description = "정렬 기준 필드", example = "createdAt") + @Pattern(regexp = "^(createdAt|grade)$", message = "정렬 기준은 createdAt 또는 grade만 가능합니다.") + public String getSort() { + return super.getSort(); + } + + @Override + @Schema(description = "정렬 방향", example = "ASC") + @Pattern(regexp = "^(ASC|DESC)$", message = "정렬 방향은 ASC 또는 DESC만 가능합니다.") + public String getDirection() { + return super.getDirection(); + } +} diff --git a/src/main/java/org/one/member/dto/MemberListResponseDto.java b/src/main/java/org/one/member/dto/MemberListResponseDto.java new file mode 100644 index 0000000..39f6984 --- /dev/null +++ b/src/main/java/org/one/member/dto/MemberListResponseDto.java @@ -0,0 +1,42 @@ +package org.one.member.dto; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import org.one.member.domain.Member; +import org.one.member.enums.MemberStatus; + + +@JsonPropertyOrder({"memberId", "name", "age", "studentId", "grade", "phoneNumber", "status"}) +@Schema(description = "명부 리스트 조회 응답") +@Getter +public class MemberListResponseDto { + @Schema(description = "부원 id", example = "2") + private Long memberId; + @Schema(description = "부원 이름", example = "홍길동") + private String name; + @Schema(description = "부원 학번", example = "20991234") + private String studentId; + @Schema(description = "부원 학년", example = "4") + private Integer grade; + @Schema(description = "부원 나이", example = "22") + private Integer age; + @Schema(description = "부원 전화번호", example = "010-1234-5678") + private String phoneNumber; + @Schema(description = "부원 활동 상태", example = "ACTIVE") + private MemberStatus status; + + private MemberListResponseDto(Member member){ + this.memberId = member.getMemberId(); + this.name = member.getName(); + this.studentId = member.getStudentId(); + this.grade = member.getGrade(); + this.age = member.getAge(); + this.phoneNumber = member.getPhoneNumber(); + this.status = member.getStatus(); + } + + public static MemberListResponseDto from(Member member){ + return new MemberListResponseDto(member); + } +} diff --git a/src/main/java/org/one/member/repository/MemberRepository.java b/src/main/java/org/one/member/repository/MemberRepository.java index 2b8da64..7d5153f 100644 --- a/src/main/java/org/one/member/repository/MemberRepository.java +++ b/src/main/java/org/one/member/repository/MemberRepository.java @@ -1,10 +1,13 @@ package org.one.member.repository; import org.one.member.domain.Member; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import java.util.List; + /** * 정규 부원 데이터 조회와 일괄 갱신을 담당하는 JPA Repository입니다. */ @@ -24,4 +27,8 @@ public interface MemberRepository extends JpaRepository { @Modifying @Query("UPDATE Member m SET m.grade = m.grade + 1, m.age = m.age + 1") void incrementGradeAndAge(); + + //동아리에 소속중인 부원들의 모든 정보를 가져와 리스트로 만듦. + @Query("SELECT m FROM Member m") + List findAllByAdmin(Pageable pageable); } diff --git a/src/main/java/org/one/member/service/MemberService.java b/src/main/java/org/one/member/service/MemberService.java new file mode 100644 index 0000000..320301c --- /dev/null +++ b/src/main/java/org/one/member/service/MemberService.java @@ -0,0 +1,37 @@ +package org.one.member.service; + + +import lombok.RequiredArgsConstructor; +import org.one.auth.repository.AdminRepository; +import org.one.global.enums.ErrorCode; +import org.one.global.exception.BusinessException; +import org.one.member.domain.Member; +import org.one.member.dto.MemberListRequestDto; +import org.one.member.dto.MemberListResponseDto; +import org.one.member.repository.MemberRepository; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MemberService { + private final MemberRepository memberRepository; + private final AdminRepository adminRepository; + + public List getMemberListByAdmin(MemberListRequestDto requestDto) { + //requestDto로 설정한 sort, size 등을 바탕으로 Pageable객체를 만듦. + Pageable pageable = requestDto.toPageable(); + + //memberRepository를 이용해 모든 부원 리스트를 가져옴. + List members = memberRepository.findAllByAdmin(pageable); + + //모든 부원 리스트를 Member(entity) -> MemberListResponseDto로 필요한 데이터만 빼서 리스트를 만듦. + return members.stream() + .map(MemberListResponseDto::from) + .toList(); + } +}