개발 기록/도서관 관리 시스템

[Library Management System] 25.10.02 (58일) | (리팩토링) 관리자 도서 등록, 도서 전체 목록 조회, 도서 상세 조회, 도서 수정, 도서 삭제, 도서 검색

dev.jelee 2025. 10. 2. 16:10

 

[ 작업한 내용 ]

< 관리자: 도서 등록 >

# AdminBookCreateReqDTO

- 도서 등록 요청 DTO

- 제목, ISBN, 저자, 출판사, 출판일, 위치, 설명 (title, isbn, author, publisher, publishedDate, location, description)

** 변경사항 없음

@Getter
public class AdminBookCreateReqDTO {
  private String title;
  private String isbn;
  private String author;
  private String publisher;
  private LocalDate publishedDate;
  private String location;
  private String description;
}

# AdminBookCreateResDTO

- 도서 등록 응답 DTO

- 도서 고유번호, ISBN, 제목 (id, isbn, title)

** 번경사항 없음

@Getter
public class AdminBookCreateResDTO {
  private Long id;
  private String isbn;
  private String title;

  public AdminBookCreateResDTO(Book book) {
    this.id = book.getId();
    this.isbn = book.getIsbn();
    this.title = book.getTitle();
  }
}

# AdminBookController

- POST /api/v1/admin/books 로 요청.

- 파라미터로 AdminBookCreateReqDTO(title, isbn, author, publisher, publishedDate, location, description), @AuthenticationPrincipal User user 전달받음.

- Service 계층으로 requestDTO, user.getId()를 전달함.

- 결과는 AdminBookCreateResDTO 형태로 저장.

- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 응답.

@PostMapping()
public ResponseEntity<?> createBook(
  @RequestBody AdminBookCreateReqDTO requestDTO, 
  @AuthenticationPrincipal User user) {

    // 서비스로직
    AdminBookCreateResDTO responseDTO = adminBookService.createBook(requestDTO, user.getId());

    // 성공메시지
    String message = messageProvider.getMessage(BookSuccessCode.BOOK_CREATED.getMessage());

    // 응답
    return ResponseEntity
            .status(BookSuccessCode.BOOK_CREATED.getHttpStatus())
            .body(ApiResponse.success(
              BookSuccessCode.BOOK_CREATED, 
              message, 
              responseDTO));
}

# AdminBookService

- userRepository.findById(userId)로 관리자 조회가 되면 User user으로 저장, 조회가 안된다면 UserErrorCode.USER_NOT_FOUND 예외를 발생시킴.

- 조회된 관리자의 권한 체크를 하고 관리자 권한이 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 발생 시킴.

- 필수 필드 Null 체크하여 Null이면 관련 예외를 발생 시킴. (title, isbn, author, publisher, publishedDate, location)

- bookRepository.existsByLocation(requestDTO.getLocation())를 사용하여 특정 위치에 다른 도서가 중복되는지 체크함.

- Book 엔티티를 생성한 후 bookRepository.save(book)을 사용하여 저장을 한다.

- Controller로 저장한 Book을 기반으로 AdminBookCreateResDTO 객체를 생성하여 반환한다.

** 권한 체크와 주석 수정

@Transactional
public AdminBookCreateResDTO createBook(AdminBookCreateReqDTO requestDTO, 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);
  }

  // 필수 필드 Null 체크
  // title, isbn, author, publisher, publishedDate, location
  if (requestDTO.getTitle() == null || requestDTO.getTitle().trim().isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_TITLE_REQUIRED);
  }
  if (requestDTO.getIsbn() == null || requestDTO.getIsbn().trim().isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_ISBN_REQUIRED);
  }
  if (requestDTO.getAuthor() == null || requestDTO.getAuthor().trim().isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_AUTHOR_REQUIRED);
  }
  if (requestDTO.getPublisher() == null || requestDTO.getPublisher().trim().isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_PUBLISHER_REQUIRED);
  }
  if (requestDTO.getPublishedDate() == null) {
    throw new BaseException(BookErrorCode.BOOK_PUBLISHERDATE_REQUIRED);
  }
  if (requestDTO.getLocation() == null || requestDTO.getLocation().trim().isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_LOCATION_REQUIRED);
  }

  // location 중복 체크
  if (bookRepository.existsByLocation(requestDTO.getLocation())) {
    Book sameLocationBook = bookRepository.findByLocation(requestDTO.getLocation());

    throw new DataBaseException(BookErrorCode.BOOK_LOCATION_DUPLICATED, sameLocationBook.getId());
  }

# BookRepository

- location 중복 체크와 location 위치 찾는 메서드 정의

** 변경사항 없음

boolean existsByLocation(String location);
Book findByLocation(String location);

< 관리자: 도서 전체 목록 조회 >

# AdminBookListResDTO

- 도서 전체 목록 조회 응답 DTO

- 도서 고유번호, 제목, ISBN, 저자, 출판사, 출판일, 상태, 위치, 생성일, 수정일 (id, title, isbn, author, publisher, publishedDate, location, createdAt, updatedAt)

- Book 엔티티를 파라미터로 받는 생성자 함수 정의

** 불필요한 어노테이션 제거

@Getter
public class AdminBookListResDTO {
  private Long id;
  private String title;
  private String isbn;
  private String author;
  private String publisher;
  private LocalDate publishedDate;
  private BookStatus status;
  private String location;
  private LocalDateTime createdAt;
  private LocalDateTime updatedAt;

  public AdminBookListResDTO(Book book) {
    this.id = book.getId();
    this.title = book.getTitle();
    this.isbn = book.getIsbn();
    this.author = book.getAuthor();
    this.publisher = book.getPublisher();
    this.publishedDate = book.getPublishedDate();
    this.status = book.getStatus();
    this.location = book.getLocation();
    this.createdAt = book.getCreatedAt();
    this.updatedAt = book.getUpdatedAt();
  }
}

# AdminBookController

- GET /api/v1/admin/books?page=...&size=... 로 요청.

- 파라미터로 page, size, @AuthenticationPrincipal User user 전달받음.

- Service 계층으로 page, size, user.getId()를 전달함.

- 결과는 PageResponse<AdminBookListResDTO> 형태로 저장.

- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 응답.

** 사용자 인증 객체 추가와 주석 수정

@GetMapping()
public ResponseEntity<?> allListBooks(
  @RequestParam(value = "page", defaultValue = "0") int page, 
  @RequestParam(value = "size", defaultValue = "10") int size,
  @AuthenticationPrincipal User user) {

    // 서비스로직
    PageResponse<AdminBookListResDTO> responseDTO = adminBookService.allListBooks(page, size, user.getId());

    // 성공메시지
    String message = messageProvider.getMessage(BookSuccessCode.BOOK_LIST_FETCHED.getMessage());
    
    // 응답
    return ResponseEntity
              .status(BookSuccessCode.BOOK_LIST_FETCHED.getHttpStatus())
              .body(ApiResponse.success(
                BookSuccessCode.BOOK_LIST_FETCHED, 
                message, 
                responseDTO));
}

# AdminBookService

- allListBooks 메서드의 타입을 Page에서 PageResponse<> 로 변경.

- userRepository.findById(userId)를 사용하여 사용자 정보를 User에 저장하고, 만약에 유효한 사용자가 아니라면 UserErrorCode.USER_NOT_FOUND 예외를 발생시킴.

- user.getRole()로 사용자 권한 체크를 한 후 관리자 권한이 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 발생 시킴.

- PageRequest.of(page, size)를 사용하여 페이징 정의를 한 뒤 Pageable로 저장.

- bookRepository.findAll(pageable)로 조회한 데이터를 Page<Book> 형태로 저장.

- Page<Book>에서 Page<AdminBookListResDTO> 형태로 .map()을 사용하여 변환.

- 변환한 데이터를 Controller로 PageResponse<>() 객체로 생성하여 반환.

** 관리자 권한 체크 추가, Page로 반환한 것을 PageResponse로 변경.

public PageResponse<AdminBookListResDTO> allListBooks(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);

  // Book 형태로 페이지 조회 후 AdminBOokListResDTO로 변환
  Page<Book> result = bookRepository.findAll(pageable);
  Page<AdminBookListResDTO> pageDTO = result.map(AdminBookListResDTO::new);
  
  // 반환
  return new PageResponse<>(pageDTO);
}

< 관리자: 도서 상세 조회 >

# AdminBookDetailResDTO

- 도서 상세 조회 응답 DTO

- 도서 고유번호, 제목, ISBN, 저자, 출판사, 출판일, 상태, 위치, 설명, 생성일, 수정일 (id, title, isbn, author, publisher, publishedDate, location, description, createdAt, updatedAt)

- Book 엔티티를 파라미터로 받는 생성자 함수 정의

** 불필요한 어노테이션 제거

@Getter
public class AdminBookDetailResDTO {
  private Long id;
  private String title;
  private String isbn;
  private String author;
  private String publisher;
  private LocalDate publishedDate;
  private BookStatus status;
  private String location;
  private String description;
  private LocalDateTime createdAt;
  private LocalDateTime updatedAt;

  public AdminBookDetailResDTO(Book book) {
    this.id = book.getId();
    this.title = book.getTitle();
    this.isbn = book.getIsbn();
    this.author = book.getAuthor();
    this.publisher = book.getPublisher();
    this.publishedDate = book.getPublishedDate();
    this.status = book.getStatus();
    this.location = book.getLocation();
    this.description = book.getDescription();
    this.createdAt = book.getCreatedAt();
    this.updatedAt = book.getUpdatedAt();
  }
}

# AdminBookController

- GET /api/v1/admin/books/{bookId} 로 요청.

- 파라미터로 bookId, @AuthenticationPrincipal User user 전달받음.

- Service 계층으로 bookId, user.getId()를 전달함.

- 결과는 AdminBookDetailResDTO 형태로 저장.

- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 응답.

** 사용자 인증 객체 추가와 주석 수정

@GetMapping("/{bookId}")
public ResponseEntity<?> detailBook(
  @PathVariable("bookId") Long bookId, 
  @AuthenticationPrincipal User user) {

    // 서비스로직
    AdminBookDetailResDTO responseDTO = adminBookService.detailBook(bookId, user.getId());

    // 성공메시지
    String message = messageProvider.getMessage(BookSuccessCode.BOOK_FETCHED.getMessage());

    // 응답
    return ResponseEntity
              .status(BookSuccessCode.BOOK_FETCHED.getHttpStatus())
              .body(ApiResponse.success(
                BookSuccessCode.BOOK_FETCHED, 
                message, 
                responseDTO));
}

# AdminBookService

- userRepository.findById(userId) 사용하여 사용자 조회 후 User로 저장. 만약에 조회가 안된

다면 UserErrorCode.USER_NOT_FOUND 예외를 발생 시킴.

- user.getRole()으로 권한이 관리자인지 체크하고 관리자가 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 발생 시킴.

- bookRepository.findById(bookId)로 특정 도서를 조회하여 Book으로 저장. 만약에 정보가 없다면 BookErrorCode.BOOK_NOT_FOUND 예외를 발생 시킴.

- Controller로 조회한 정보를 AdminBookDetailResDTO(book) 객체로 생성하여 반환.

public AdminBookDetailResDTO detailBook(Long bookId, 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);
  }

  // bookId로 도서 조회
  Book book = bookRepository.findById(bookId)
      .orElseThrow(() -> new BaseException(BookErrorCode.BOOK_NOT_FOUND));

  // 반환
  return new AdminBookDetailResDTO(book);
}

< 관리자: 도서 수정 >

# Book

- update() 메서드에 updatedAt 현재 시간 추가.

// 업데이트 메서드
public void update(AdminBookUpdateReqDTO request) {
  this.title = request.getTitle();
  this.isbn = request.getIsbn();
  this.author = request.getAuthor();
  this.publisher = request.getPublisher();
  this.publishedDate = request.getPublishedDate();
  this.location = request.getLocation();
  this.description = request.getDescription();
  this.updatedAt = LocalDateTime.now();
}

# AdminBookUpdateReqDTO

- 도서 수정 요청 DTO

- 제목, ISBN, 저자, 출판사, 출판일, 상태, 위치, 설명 (itle, isbn, author, publisher, publishedDate, location, description)

- Book 엔티티를 파라미터로 받는 생성자 함수 정의

** 변경사항 없음

@Getter
public class AdminBookUpdateReqDTO {

  @NotBlank
  private String title;

  @NotBlank
  private String isbn;

  @NotBlank
  private String author;

  @NotBlank
  private String publisher;

  @NotNull
  private LocalDate publishedDate;

  @NotBlank
  private String location;

  @NotBlank
  private String description;
}

# AdminBookUpdateResDTO

- 도서 수정 응답 DTO 정의

- 도서 고유번호, 제목, 수정일 (id, title, updatedAt)

- Book 엔티티를 파라미터로 받는 생성자 함수 정의

@Getter
public class AdminBookUpdateResDTO {
  private Long id;
  private String title;
  private LocalDateTime updatedAt;

  public AdminBookUpdateResDTO(Book book) {
    this.id = book.getId();
    this.title = book.getTitle();
    this.updatedAt = book.getUpdatedAt();
  }
}

# AdminBookController

- PATCH /api/v1/admin/books/{bookId} 로 요청.

- 파라미터로 bookId, AdminBookUpdateReqDTO, @AuthenticationPrincipal User user 전달받음.

- Service 계층으로 bookId, requestDTO, user.getId()를 전달함.

- 결과는 AdminBookUpdateResDTO 형태로 저장.

- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 응답.

** 사용자 인증 객체 추가와 주석 수정, 응답 DTO 변경

@PatchMapping("/{bookId}")
public ResponseEntity<?> updateBook(
  @PathVariable("bookId") Long bookId, 
  @RequestBody AdminBookUpdateReqDTO requestDTO,
  @AuthenticationPrincipal User user) {

    // 서비스로직
    AdminBookUpdateResDTO responseDTO = adminBookService.updateBook(bookId, requestDTO, user.getId());
    
    // 성공메시지
    String message = messageProvider.getMessage(BookSuccessCode.BOOK_UPDATED.getMessage());

    // 응답
    return ResponseEntity
            .status(BookSuccessCode.BOOK_UPDATED.getHttpStatus())
            .body(ApiResponse.success(
              BookSuccessCode.BOOK_UPDATED, 
              message, 
              responseDTO));
}

# AdminBookService

- userRepository.findById(userId) 사용하여 사용자 조회 후 User로 저장. 만약에 조회가 안된다면 UserErrorCode.USER_NOT_FOUND 예외를 발생 시킴.

- user.getRole()으로 권한이 관리자인지 체크하고 관리자가 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 발생 시킴.

- bookRepository.findById(bookId)로 특정 도서를 조회하여 Book으로 저장. 만약에 정보가 없다면 BookErrorCode.BOOK_NOT_FOUND 예외를 발생 시킴.

- 필드 Null 체크 후 각 관련 예외 발생 시키고, location 중복 체크 후 예외 발생 시킴.

- requestDTO를 Book엔티티의 .update() 메서드 사용하여 내용을 객체에 업데이트.

- Controller로 업데이트한 정보를 AdminBookUpdateResDTO 객체로 생성하여 반환.

@Transactional
public AdminBookUpdateResDTO updateBook(Long bookId, AdminBookUpdateReqDTO requestDTO, 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);
  }

  // 도서 조회 및 예외 처리
  Book book = bookRepository.findById(bookId)
      .orElseThrow(() -> new BaseException(BookErrorCode.BOOK_NOT_FOUND));

  // 필수 필드 Null 체크
  // (title, isbn, author, publisher, publishedDate, location)
  if (requestDTO.getTitle() == null || requestDTO.getTitle().trim().isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_TITLE_REQUIRED);
  }
  if (requestDTO.getIsbn() == null || requestDTO.getIsbn().trim().isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_ISBN_REQUIRED);
  }
  if (requestDTO.getAuthor() == null || requestDTO.getAuthor().trim().isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_AUTHOR_REQUIRED);
  }
  if (requestDTO.getPublisher() == null || requestDTO.getPublisher().trim().isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_PUBLISHER_REQUIRED);
  }
  if (requestDTO.getPublishedDate() == null) {
    throw new BaseException(BookErrorCode.BOOK_PUBLISHERDATE_REQUIRED);
  }
  if (requestDTO.getLocation() == null || requestDTO.getLocation().trim().isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_LOCATION_REQUIRED);
  }

  // location 중복 체크
  if (bookRepository.existsByLocationAndIdNot(requestDTO.getLocation(), bookId)) {
    Book sameLocationBook = bookRepository.findByLocation(requestDTO.getLocation());

    throw new DataBaseException(BookErrorCode.BOOK_LOCATION_DUPLICATED, sameLocationBook);
  }
  
  // 도서 DB에 업데이트
  book.update(requestDTO);

  // 반환
  return new AdminBookUpdateResDTO(book);
}

< 관리자: 도서 삭제 >

# AdminBookDeleteResDTO

- 도서 삭제 응답 DTO 정의

- 도서 고유번호, 제목 (id, title)

- Book 엔티티를 파라미터로 받는 생성자 함수 정의

@Getter
public class AdminBookDeleteResDTO {
  private Long id;
  private String title;

  public AdminBookDeleteResDTO(Book book) {
    this.id = book.getId();
    this.title = book.getTitle();
  }
}

# AdminBookController

- DELETE /api/v1/admin/books/{bookId} 로 요청.

- 파라미터로 bookId, @AuthenticationPrincipal User user 전달받음.

- Service 계층으로 bookId, user.getId()를 전달함.

- 결과는 AdminBookDeleteResDTO 형태로 저장.

- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 응답.

** 사용자 인증 객체 추가와 주석 수정, 응답 DTO 추가

@DeleteMapping("/{bookId}")
public ResponseEntity<?> deleteBook(
  @PathVariable("bookId") Long bookId, 
  @AuthenticationPrincipal User user) {

    // 서비스로직
    AdminBookDeleteResDTO responseDTO = adminBookService.deleteBook(bookId, user.getId());
    
    // 성공메시지
    String message = messageProvider.getMessage(BookSuccessCode.BOOK_DELETED.getMessage());

    // 응답
    return ResponseEntity
              .status(BookSuccessCode.BOOK_DELETED.getHttpStatus())
              .body(ApiResponse.success(
                BookSuccessCode.BOOK_DELETED, 
                message, 
                responseDTO));
}

# AdminBookService

- userRepository.findById(userId) 사용하여 사용자 조회 후 User로 저장. 만약에 조회가 안된다면 UserErrorCode.USER_NOT_FOUND 예외를 발생 시킴.

- user.getRole()으로 권한이 관리자인지 체크하고 관리자가 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 발생 시킴.

- bookRepository.findById(bookId)로 특정 도서를 조회하여 Book으로 저장. 만약에 정보가 없다면 BookErrorCode.BOOK_NOT_FOUND 예외를 발생 시킴.

- 응답용 Book responseBook를 생성 후 book 저장.

-  bookRepository.delete(book)사용하여 book 제거.

- Controller로 응답용 삭제 도서 정보를 AdminBookDeleteResDTO 객체로 생성하여 반환.

@Transactional
public AdminBookDeleteResDTO deleteBook(Long bookId, 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);
  }

  // 도서 조회 및 예외 처리
  Book book = bookRepository.findById(bookId)
      .orElseThrow(() -> new BaseException(BookErrorCode.BOOK_NOT_FOUND));

  // 응답용 도서 저장 후 도서 삭제
  Book responseBook = book;
  bookRepository.delete(book);

  // 반환
  return new AdminBookDeleteResDTO(responseBook);
}

< 관리자: 도서 검색 >

# AdminBookSearchResDTO

- 도서 검색 응답 DTO 정의

- 도서 고유번호, 제목, ISBN, 저자, 출판사, 출판일, 상태, 위치, 생성일, 수정일 (id, title, isbn, author, publisher, publishedDate, location, createdAt, updatedAt)

- Book 엔티티를 파라미터로 받는 생성자 함수 정의

** 사용 안하는 어노테이션 삭제

@Getter
public class AdminBookSearchResDTO {
  private Long id;
  private String title;
  private String isbn;
  private String author;
  private String publisher;
  private LocalDate publishedDate;
  private BookStatus status;
  private String location;
  private LocalDateTime createdAt;
  private LocalDateTime updatedAt;

  public AdminBookSearchResDTO(Book book) {
    this.id = book.getId();
    this.title = book.getTitle();
    this.isbn = book.getIsbn();
    this.author = book.getAuthor();
    this.publisher = book.getPublisher();
    this.publishedDate = book.getPublishedDate();
    this.status = book.getStatus();
    this.location = book.getLocation();
    this.createdAt = book.getCreatedAt();
    this.updatedAt = book.getUpdatedAt();
  }
}

# AdminBookController

- GET /api/v1/admin/books/search 로 요청.

- 파라미터로 type, keyword, page, size, @AuthenticationPrincipal User user 전달받음.

- Service 계층으로 type, keyword, page, size, user.getId()를 전달함.

- 결과는 PageResponse<AdminBookSearchResDTO> 형태로 저장.

- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 응답.
** 사용자 인증 객체 추가와 주석 수정, 데이터 조회 결과시 Page형태를 PageResponse로 변경

@GetMapping("/search")
public ResponseEntity<?> searchBooks(
  @RequestParam("type") BookSearchType type,
  @RequestParam("keyword") String keyword,
  @RequestParam(value = "page", defaultValue = "0") int page,
  @RequestParam(value = "size", defaultValue = "10") int size,
  @AuthenticationPrincipal User user) {

    // 서비스로직
    PageResponse<AdminBookSearchResDTO> responseDTO = adminBookService.searchBooks(type, keyword, page, size, user.getId());

    // 성공메시지
    String message = messageProvider.getMessage(BookSuccessCode.BOOK_LIST_FETCHED.getMessage());

    // 응답
    return ResponseEntity
              .status(BookSuccessCode.BOOK_LIST_FETCHED.getHttpStatus())
              .body(ApiResponse.success(
                BookSuccessCode.BOOK_LIST_FETCHED, 
                message, 
                responseDTO));
}

# AdminBookService

- userRepository.findById(userId) 사용하여 사용자 조회 후 User로 저장. 만약에 조회가 안된다면 UserErrorCode.USER_NOT_FOUND 예외를 발생 시킴.

- user.getRole()으로 권한이 관리자인지 체크하고 관리자가 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 발생 시킴.

- PageRerquest.of(page, size)를 사용하여 페이징 정의 후 Pageable로 저장.

- 타입별 키워드 검색을 switch()문을 사용하여 로직 정의. 타입 케이스는 ALL, TITLE, AUTHOR가 있음.

- 조회 후 결과가 없으면 BookErrorCode.BOOK_NOT_FOUND 예외를 발생 시킴.

- bookRepository.findById(bookId)로 특정 도서를 조회하여 Book으로 저장. 만약에 정보가 없다면 BookErrorCode.BOOK_NOT_FOUND 예외를 발생 시킴.

- Page<Book> 으로 결과를 조회한 것을 Page<AdminBookSearchResDTO> 타입으로 매핑하여 저장.

- Controller로 맵핑한 결과를 PageResponse<>로 생성하여 반환.

public PageResponse<AdminBookSearchResDTO> searchBooks(BookSearchType type, String keyword, 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);

  // 타이별 도서 검색
  Page<Book> result;
  switch (type) {
    case ALL:
      result = bookRepository.findByTitleContainingIgnoreCaseOrAuthorContainingIgnoreCase(keyword, keyword, pageable);
      break;
    case TITLE:
      result = bookRepository.findByTitleContainingIgnoreCase(keyword, pageable);
      break;
    case AUTHOR:
      result = bookRepository.findByAuthorContainingIgnoreCase(keyword, pageable);
      break;
    default:
      throw new IllegalStateException("Unexpected search type: " + type);
  }

  if (result.isEmpty()) {
    throw new BaseException(BookErrorCode.BOOK_NOT_FOUND);
  }

  // Page<Book> -> AdminBookSearchResDTO 맵핑
  Page<AdminBookSearchResDTO> pageDTO = result.map(AdminBookSearchResDTO::new);

  // 반환
  return new PageResponse<>(pageDTO);
}

commit
도서 등록(좌), 도서 전체 목록 조회(가운데), 도서 상세 조회(우)
도서 수정(좌), 도서 삭제(가운데), 도서 검색(우)