[ 작업한 내용 ]
< 관리자: 도서 대출 등록 >
# AdminLoanCreateReqDTO
- 도서 대출 등록 요청 DTO
- 사용자 고유번호, 도서 고유번호 (userId, bookId)
** 변경사항 없음
@Getter
public class AdminLoanCreateReqDTO {
private Long userId;
private Long bookId;
}
# AdminLoanCreateResDTO
- 도서 대출 등록 응답 DTO
- loan, borrower, borrowedBook으로 엔티티별로 묶어서 응답
- 대출 내역 고유번호, 대출일, 반납일, 사용자 고유번호, 사용자 아이디, 도서 고유번호, 도서명, 도서 상태 (id, loanDate, dueDate, userId, username, bookId, bookTitle, bookStatus)
- Loan 엔티티를 파라미터로 받는 생성자 함수 정의
@Getter
public class AdminLoanCreateResDTO {
private LoanDTO loan;
private BorrowerDTO borrower;
private BookSimpleDTO borrowedBook;
public AdminLoanCreateResDTO(Loan loan) {
this.loan = new LoanDTO(loan);
this.borrower = new BorrowerDTO(loan.getUser());
this.borrowedBook = new BookSimpleDTO(loan.getBook());
}
}
# LoanDTO
- 도서 대출 등록 응답에 사용할 대출내역 관련 DTO 정의
- id, loanDate, dueDate (대출내역 고유번호, 대출일, 반납일)
- Loan 엔티티를 파라미터로 받는 생성자 함수 정의
@Getter
public class LoanDTO {
private Long id;
private LocalDateTime loanDate;
private LocalDateTime dueDate;
public LoanDTO(Loan loan) {
this.id = loan.getId();
this.loanDate = loan.getLoanDate();
this.dueDate = loan.getDueDate();
}
}
# AdminBookController
- POST /api/v1/admin/loans 로 요청.
- 파라미터로 AdminLoanCreateReqDTO(userId, bookId), @AuthenticationPrincipal User user 전달받음.
- Service 계층으로 requestDTO, user.getId()를 전달함.
- 결과는 AdminLoanCreateResDTO 형태로 저장.
- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 응답.
** 사용자 인증 객체 추가와 주석 수정
@PostMapping()
public ResponseEntity<?> createLoan(
@RequestBody AdminLoanCreateReqDTO requestDTO,
@AuthenticationPrincipal User user) {
// 서비스로직
AdminLoanCreateResDTO responseDTO = adminLoanService.createLoan(requestDTO, user.getId());
// 성공메시지
String message = messageProvider.getMessage(LoanSuccessCode.LOAN_CREATED.getMessage());
// 응답
return ResponseEntity
.status(LoanSuccessCode.LOAN_CREATED.getHttpStatus())
.body(ApiResponse.success(
LoanSuccessCode.LOAN_CREATED,
message,
responseDTO));
}
# AdminBookService
- userRepository.findById(adminUserId) 사용하여 사용자 조회 후 User로 저장. 만약에 조회가 안된다면 UserErrorCode.USER_NOT_FOUND 예외를 발생 시킴.
- user.getRole()으로 권한이 관리자인지 체크하고 관리자가 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 발생 시킴.
- bookRepository.findById(requestDTO.getBookId())를 사용하여 대출할 도서를 조회하여 Book book으로 저장. 만약에 조회가 안된다면 BookErrorCode.BOOK_NOT_FOUND 예외를 발생시킴.
- userRepository.findById(requestDTO.getUserId())를 사용하여 도서 대출하는 회원을 조회하여 User user로 저장. 만약에 조회가 안된다면 UserErrorCode.USER_NOT_FOUND 예외를 발생 시킴.
- 조회한 book의 상태를 체크하여 대출 가능한지 확인. 대출 불가라면 LoanErrorCode.LOAN_ALREADY_BORROWED 예외를 발생 시킴.
- 조회한 user의 상태를 확인하여 ACTIVE가 아니라면 LoanErrorCode.LOAN_USER_INELIGIBLE 예외를 발생 시킴.
- loanRepository.countByUserAndStatus(user, LoanStatus.LOANED)를 사용하여 사용자의 대출건이 3건 이상인지 체크하여 이상이라면 LoanErrorCode.LOAN_LIMIT_EXCEEDED 예외를 발생 시킴.
- Loan.builder()를 사용하여 조회한 user, book 데이터를 생성하여 loanRepository.save(loan)를 사용하여 DB에 저장함.
- Controller로 loan 기반으로 AdminLoanCreateResDTO 객체를 생성하여 반환
@Transactional
public AdminLoanCreateResDTO createLoan(AdminLoanCreateReqDTO requestDTO, Long adminUserId) {
// 관리자 권환 조회 및 예외 처리
User userAdmin = userRepository.findById(adminUserId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
if (userAdmin.getRole() != Role.ROLE_ADMIN) {
throw new BaseException(AuthErrorCode.AUTH_FORBIDDEN);
}
// 도서 유효성 검사
Book book = bookRepository.findById(requestDTO.getBookId())
.orElseThrow(() -> new BaseException(BookErrorCode.BOOK_NOT_FOUND));
// 사용자 유효성 검사 (도서 대출자)
User user = userRepository.findById(requestDTO.getUserId())
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
// 도서 대출 가능 여부 확인
if (book.getStatus() != BookStatus.AVAILABLE) {
throw new BaseException(LoanErrorCode.LOAN_ALREADY_BORROWED);
}
// 사용자의 상태가 ACTIVE만 대출 가능
if (user.getStatus() != UserStatus.ACTIVE) {
throw new BaseException(LoanErrorCode.LOAN_USER_INELIGIBLE);
}
// 사용자 대출 3건이상은 대출 불가
int loanedCount = loanRepository.countByUserAndStatus(user, LoanStatus.LOANED);
if (loanedCount >= 3) {
throw new BaseException(LoanErrorCode.LOAN_LIMIT_EXCEEDED);
}
// 대출 생성 & 저장
Loan loan = Loan.builder()
.user(user)
.book(book)
.build();
loanRepository.save(loan);
// 도서 상태 변경
book.setStatus(BookStatus.BORROWED);
// 반환
return new AdminLoanCreateResDTO(loan);
}
< 관리자: 도서 대출 전체 목록 조회 >
# AdminLoanListResDTO
- 도서 대출 전체 목록 조회 응답 DTO
- loan, borrower, borrowedBook으로 엔티티별로 묶어서 응답
- Loan 엔티티를 파라미터로 받는 생성자 함수 정의
@Getter
public class AdminLoanListResDTO {
private LoanSearchDTO loan;
private BorrowerDTO borrower;
private BookSimpleDTO borrowedBook;
public AdminLoanListResDTO(Loan loan) {
this.loan = new LoanSearchDTO(loan);
this.borrower = new BorrowerDTO(loan.getUser());
this.borrowedBook = new BookSimpleDTO(loan.getBook());
}
}
# LoanSearchDTO
- 도서 대출 전체 목록 조회 응답에 사용할 대출내역 관련 DTO 정의
- id, loanDate, dueDate, returnDate, lostDate, extended, status (대출내역 고유번호, 대출일, 반납일, 분실일, 연장 유무, 상태)
- Loan 엔티티를 파라미터로 받는 생성자 함수 정의
@Getter
public class LoanSearchDTO {
private Long id;
private LocalDateTime loanDate;
private LocalDateTime dueDate;
private LocalDateTime returnDate;
private LocalDateTime lostDate;
private boolean extended;
private LoanStatus status;
public LoanSearchDTO(Loan loan) {
this.id = loan.getId();
this.loanDate = loan.getLoanDate();
this.dueDate = loan.getDueDate();
this.returnDate = loan.getReturnDate();
this.lostDate = loan.getLostDate();
this.extended = loan.isExtended();
this.status = loan.getStatus();
}
}
# AdminBookController
- GET /api/v1/admin/loans 로 요청.
- 파라미터로 status, page, size, @AuthenticationPrincipal User user 전달받음.
- Service 계층으로 status, page, size, user.getId()를 전달함.
- 결과는 PageResponse<AdminLoanListResDTO> 형태로 저장.
- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 응답.
@GetMapping()
public ResponseEntity<?> allListLoans(
@RequestParam(value = "status", required = false) LoanStatus status,
@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "10") int size,
@AuthenticationPrincipal User user) {
// 서비스로직
PageResponse<AdminLoanListResDTO> responseDTO = adminLoanService.allListLoans(status, page, size, user.getId());
// 성공메시지
String message = messageProvider.getMessage(LoanSuccessCode.LOAN_LIST_FETCHED.getMessage());
return ResponseEntity
.status(LoanSuccessCode.LOAN_LIST_FETCHED.getHttpStatus())
.body(ApiResponse.success(
LoanSuccessCode.LOAN_LIST_FETCHED,
message,
responseDTO));
}
# AdminBookService
- userRepository.findById(userId) 사용하여 사용자 조회 후 User로 저장. 만약에 조회가 안된다면 UserErrorCode.USER_NOT_FOUND 예외를 발생 시킴.
- user.getRole()으로 권한이 관리자인지 체크하고 관리자가 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 발생 시킴.
- PageRerquest.of(page, size)를 사용하여 페이징 정의 후 Pageable로 저장.
- status에 값이 없으면 loanRepository.findAll(pageable)사용하여 전체 조회하고 값이 있으면 loanRepository.findByStatus(status, pageable) 사용하여 상태별 페이징 조회를 하여 Page<Lona> result로 저장함.
- 만약에 result가 비어 있으면 LoanErrorCode.LOAN_NOT_FOUND 예외를 발생시킴.
- Page<Loan>을 Page<AdminLoanListResDTO>로 맵핑하여 pageDTO 생성.
- Controller로 pageDTO를 기반으로 PageResponse<>() 생성하여 반환.
public PageResponse<AdminLoanListResDTO> allListLoans(LoanStatus status, int page, int size, Long userId) {
// 관리자 권환 조회 및 예외 처리
User user = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
if (user.getRole() != Role.ROLE_ADMIN) {
throw new BaseException(AuthErrorCode.AUTH_FORBIDDEN);
}
// 페이징 정의
Pageable pageable = PageRequest.of(page, size);
// status에 값이 없으면 findAll, 값이 있으면 해당 status로 조회
// 결과를 Page<Loan> 타입으로 저장
Page<Loan> result;
if (status != null) {
result = loanRepository.findByStatus(status, pageable);
} else {
result = loanRepository.findAll(pageable);
}
// result에 값이 없으면 예외처리
if (result.isEmpty()) {
throw new BaseException(LoanErrorCode.LOAN_NOT_FOUND);
}
// Page<Loan>을 AdminLoanListResDTO로 맵핑하여 생성
Page<AdminLoanListResDTO> pageDTO = result.map(AdminLoanListResDTO::new);
// 반환
return new PageResponse<>(pageDTO);
}
< 관리자: 도서 대출 상세 조회 >
# AdminLoanDetailResDTO
- 도서 대출 상세 조회 응답 DTO
- loan, borrower, borrowedBook으로 엔티티별로 묶어서 응답
- Loan 엔티티를 파라미터로 받는 생성자 함수 정의
@Getter
public class AdminLoanDetailResDTO {
private LoanSearchDTO loan;
private BorrowerDTO user;
private BookSimpleDTO book;
public AdminLoanDetailResDTO(Loan loan) {
this.loan = new LoanSearchDTO(loan);
this.user = new BorrowerDTO(loan.getUser());
this.book = new BookSimpleDTO(loan.getBook());
}
}
# AdminBookController
- GET /api/v1/admin/loans/{loanId} 로 요청.
- 파라미터로 loanId, @AuthenticationPrincipal User user 전달받음.
- Service 계층으로 loanId, user.getId()를 전달함.
- 결과는 AdminLoanDetailResDTO 형태로 저장.
- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 응답.
@GetMapping("/{loanId}")
public ResponseEntity<?> detailLoan(
@PathVariable("loanId") Long loanId,
@AuthenticationPrincipal User user) {
// 서비스로직
AdminLoanDetailResDTO responseDTO = adminLoanService.detailLoan(loanId, user.getId());
// 성공메시지
String message = messageProvider.getMessage(LoanSuccessCode.LOAN_FETCHED.getMessage());
// 응답
return ResponseEntity
.status(LoanSuccessCode.LOAN_FETCHED.getHttpStatus())
.body(ApiResponse.success(
LoanSuccessCode.LOAN_FETCHED,
message,
responseDTO));
}
# AdminBookService
- userRepository.findById(userId) 사용하여 사용자 조회 후 User로 저장. 만약에 조회가 안된다면 UserErrorCode.USER_NOT_FOUND 예외를 발생 시킴.
- user.getRole()으로 권한이 관리자인지 체크하고 관리자가 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 발생 시킴.
- loanRepository.findById(loanId)를 사용하여 도서 대출 조회 하여 Loan loan으로 저장. 만약에 조회가 안된다면 LoanErrorCode.LOAN_NOT_FOUND 예외를 발생 시킴.
- Controller로 loan을 기반으로 AdminLoanDetailResDTO를 생성하여 반환.
public AdminLoanDetailResDTO detailLoan(Long loanId, Long userId) {
// 관리자 권환 조회 및 예외 처리
User user = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
if (user.getRole() != Role.ROLE_ADMIN) {
throw new BaseException(AuthErrorCode.AUTH_FORBIDDEN);
}
// 도서 대출 조회 및 예외 처리
Loan loan = loanRepository.findById(loanId)
.orElseThrow(() -> new BaseException(LoanErrorCode.LOAN_NOT_FOUND));
// 반환
return new AdminLoanDetailResDTO(loan);
}
< 관리자: 도서 대출 타입별 검색 >
# AdminLoanSearchResDTO
- 도서 대출 타입별 검색 응답 DTO
- loan, borrower, borrowedBook으로 엔티티별로 묶어서 응답
- Loan 엔티티를 파라미터로 받는 생성자 함수 정의
@Getter
public class AdminLoanSearchResDTO {
private LoanSearchDTO loan;
private BorrowerDTO user;
private BookSimpleDTO book;
public AdminLoanSearchResDTO(Loan loan) {
this.loan = new LoanSearchDTO(loan);
this.user = new BorrowerDTO(loan.getUser());
this.book = new BookSimpleDTO(loan.getBook());
}
}
# LoanSearchType
- BOOKTITLE, USERID 에서 USERID를 USERNAME으로 변경
public enum LoanSearchType {
BOOKTITLE,
USERNAME;
}
# AdminBookController
- GET /api/v1/admin/loans/search 로 요청.
- 파라미터로 type, keyword, status, page, size, @AuthenticationPrincipal User user 전달받음.
- Service 계층으로 type, keyword, status, page, size, user.getId()를 전달함.
- 결과는 PageResponse<AdminLoanSearchResDTO> 형태로 저장.
- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 응답.
@GetMapping("/search")
public ResponseEntity<?> searchLoan(
@RequestParam(value = "type", required = false) LoanSearchType type,
@RequestParam("keyword") String keyword,
@RequestParam(value = "status", required = false) LoanStatus status,
@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "10") int size,
@AuthenticationPrincipal User user) {
// 서비스로직
PageResponse<AdminLoanSearchResDTO> responseDTO = adminLoanService.searchLoan(type, keyword, status, page, size, user.getId());
// 성공메시지
String message = messageProvider.getMessage(LoanSuccessCode.LOAN_FETCHED.getMessage());
// 응답
return ResponseEntity
.status(LoanSuccessCode.LOAN_FETCHED.getHttpStatus())
.body(ApiResponse.success(
LoanSuccessCode.LOAN_FETCHED,
message,
responseDTO));
}
# AdminBookService
- userRepository.findById(adminUserId) 사용하여 사용자 조회 후 User로 저장. 만약에 조회가 안된다면 UserErrorCode.USER_NOT_FOUND 예외를 발생 시킴.
- user.getRole()으로 권한이 관리자인지 체크하고 관리자가 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 발생 시킴.
- PageRerquest.of(page, size)를 사용하여 페이징 정의 후 Pageable로 저장.
- swtich문을 사용하여 type별 조회.
- BOOKTITLE의 경우 bookRepository.findByTitleContainingIgnoreCase(keyword)로 조회하고 조회가 안되면 BookErrorCode.BOOK_NOT_FOUND 예외를 발생 시킴. 그리고 status의 값에 따라서 도서명과 상태별로 조회하고 status가 없다면 도서명으로만 조회한다.
- USERNAME의 경우 userRepository.findByUsername(keyword)로 조회하고 조회가 안되면
serErrorCode.USER_NOT_FOUND 예외를 발생 시킴. 그리고 staus의 값에 따라서 username과 상태별로 조회하고 status가 없다면 username으로만 조회한다.
- Page<Loan>을 Page<AdminLoanSearchResDTO>형태로 맵핑하여 pageDTO로 저장.
- Controller로 pageDTO를 기반으로 PageResponse 생성하여 반환.
public PageResponse<AdminLoanSearchResDTO> searchLoan(LoanSearchType type, String keyword, LoanStatus status, int page, int size, Long adminUserId) {
// 관리자 권환 조회 및 예외 처리
User userAdmin = userRepository.findById(adminUserId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
if (userAdmin.getRole() != Role.ROLE_ADMIN) {
throw new BaseException(AuthErrorCode.AUTH_FORBIDDEN);
}
// 페이징 정의
Pageable pageable = PageRequest.of(page, size);
// type별 검색 - BOOKTITLE, USERID
Page<Loan> result;
switch (type) {
case BOOKTITLE:
// 도서 유효검사
Book book = bookRepository.findByTitleContainingIgnoreCase(keyword)
.orElseThrow(() -> new BaseException(BookErrorCode.BOOK_NOT_FOUND));
// 도서 대출 상태가 null이면 도서명으로만 검색
// 도서 대출 상태가 null이 아니면, 도서명 + 대출 상태로 검색
result = (status != null)
? loanRepository.findByBook_TitleContainingIgnoreCaseAndStatus(book.getTitle(), status, pageable)
: loanRepository.findByBook_TitleContainingIgnoreCase(book.getTitle(), pageable);
break;
case USERNAME:
// 사용자 유효검사
User user = userRepository.findByUsername(keyword)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
// 도서 대출 상태가 null이면 사용자명으로만 검색
// 도서 대출 상태가 null이 아니면 사옹자명 + 대출 상태로 검색
result = (status != null)
? loanRepository.findByUser_UsernameContainingIgnoreCaseAndStatus(user.getUsername(), status, pageable)
: loanRepository.findByUser_UsernameContainingIgnoreCase(user.getUsername(), pageable);
break;
default:
throw new BaseException(LoanErrorCode.LOAN_SEARCH_TYPE_INVALID);
}
// Page<Loan> -> Page<AdminLoanSearchResDTO>로 맵핑
Page<AdminLoanSearchResDTO> pageDTO = result.map(AdminLoanSearchResDTO::new);
// 반환
return new PageResponse<>(pageDTO);
}