[ 작업한 내용 ]
< 관리자: 책 리뷰 전체 목록 >
# AdminReviewListResDTO
- 책 리뷰 전체 목록 응답 DTO 정의
- 리뷰 고유번호, 사용자 고유번호, 사용자 아이디, 책 고유번호, 책 제목, 책 ISBN, 리뷰 내용, 작성일, 수정일 (id, userId, username, bookId, bookTItle, bookIsbn, content, createdDate, updatedDate)
- Review 엔티티를 파라미터로 받는 AdminReviewListResDTO 생성사 정의
@Getter
public class AdminReviewListResDTO {
private Long id;
private Long userId;
private String username;
private Long bookId;
private String bookTitle;
private String bookIsbn;
private String content;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
public AdminReviewListResDTO(Review review) {
this.id = review.getId();
this.userId = review.getUser().getId();
this.username = review.getUser().getUsername();
this.bookId = review.getBook().getId();
this.bookTitle = review.getBook().getTitle();
this.bookIsbn = review.getBook().getIsbn();
this.content = review.getContent();
this.createdDate = review.getCreatedDate();
this.updatedDate = review.getUpdatedDate();
}
}
# AdminReviewController
- GET /api/v1/admin/reviews 로 요청받습니다.
- RequestParam 형태로 page, size 파라미터를 받습니다.
- page와 size는 각 기본값이 0과 10입니다.
- @AuthenticationPrincipal로 사용자 인증 객체를 User 엔티티 형태로 받습니다.
- Service 계층으로 page, size, user.getId()를 전달합니다.
- 결과는 Page<AdminReviewListResDTO> 형태로 받습니다.
- 클라이언트 측으로 성공 메시지와 결과 데이터를 ApiResponse.success()로 감싸서 반환합니다.
@GetMapping("/reviews")
public ResponseEntity<?> allListReview(
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "10") int size,
@AuthenticationPrincipal User user) {
// 서비스로직
Page<AdminReviewListResDTO> responseDTO = adminReviewService.allListReview(page, size, user.getId());
// 성공메시지
String message = messageProvider.getMessage(ReviewSuccessCode.REVIEW_LIST_FETCHED.getMessage());
// 반환
return ResponseEntity
.status(ReviewSuccessCode.REVIEW_LIST_FETCHED.getHttpStatus())
.body(ApiResponse.success(
ReviewSuccessCode.REVIEW_LIST_FETCHED,
message,
responseDTO));
}
# AdminReviewService
- usreRepository.findById(userId)로 사용자 조회를 하여 User 엔티티 형태로 저장합니다. 사용자가 조회가 안된다면 UserErrorCode.USER_NOT_FOUND) 예외를 던집니다.
- 가져온 사용자의 권한이 ROLE_ADMIN인지 체크하고 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 던집니다.
- PageRequest.of(page, size)를 사용하여 페이징 정보를 하여 Pageable로 정의합니다.
- reviewRepository.findAll(pageable)로 전체 리뷰를 조회하여 Page<Review> 형태로 저장합니다.
- Page 형태로 가져온 결과를 List 형태로 변환하기 위해 .steram().map()을 사용하여 List<AdminReviewListResDTO> 형태로 저장합니다.
- Controller로 반환할 때에는 PageImpl<>()을 사용하여 List형태의 DTO를 Pageable 형태로 감싸서 반환합니다.
public Page<AdminReviewListResDTO> allListReview(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 형태로 가져오기
Page<Review> result = reviewRepository.findAll(pageable);
// Page 결과를 List 형태로 변환
List<AdminReviewListResDTO> listDTO = result.getContent()
.stream()
.map(AdminReviewListResDTO::new)
.toList();
// PageImpl을 사용하여 Page 형태로 감싸서 반환
return new PageImpl<>(listDTO, result.getPageable(), result.getTotalElements());
}
# SecurityConfig
- 원활한 개발을 위해 /api/v1/admin/reviews/** 엔드포인트를 .permitAll()로 추가해줍니다.
- 추후에 권한을 변경하도록 합니다.
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/admin/reviews/**").permitAll()
.anyRequest().authenticated())
< 관리자: 책 리뷰 타입별 검색 >
# AdminReviewSearchResDTO
- 관리자 도서 기준 리뷰 전체 목록 조회 응답 DTO 정의
- 리뷰 고유번호, 책 고유번호, 책 제목, 책 isbn, 사용자 고유번호, 사용자 아이디, 리뷰 내용, 작성일, 수정일(id, bookId, bookTitle, bookIsbn, userId, username, content, createdDate, updatedDate)
@Getter
public class AdminReviewSearchResDTO {
private Long id;
private Long userId;
private String username;
private Long bookId;
private String bookTitle;
private String bookIsbn;
private String content;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
public AdminReviewSearchResDTO(Review review) {
this.id = review.getId();
this.userId = review.getUser().getId();
this.username = review.getUser().getUsername();
this.bookId = review.getBook().getId();
this.bookTitle = review.getBook().getTitle();
this.bookIsbn = review.getBook().getIsbn();
this.content = review.getContent();
this.createdDate = review.getCreatedDate();
this.updatedDate = review.getUpdatedDate();
}
}
# ReviewSearchType enum
- 타입별 조회를 위해 enum 형태로 정의
- ALL, BOOKTITLE, USERNAME
public enum ReviewSearchType {
ALL,
BOOKTITLE,
USERNAME
}
# AdminReviewController
- GET /api/v1/admin/reviews/search?type=...&keyword=...&page=...&size=... 로 요청합니다.
- 파라미터는 type, keyword, page, size이며, RequestParam형태로 받습니다.
- type의 기본 값은 ALL이며, page와 size의 기본 값은 각 0과 10입니다.
- @AuthenticationPrincipal을 사용하여 사용자 정보를 가져오고 User 엔티티로 저장합니다.
- Service 계층으로 type, keyword, page, size, user.getId()를 전달하여 처리합니다.
- 결과는 Page<AdminReviewSearchResDTO> 타입으로 받습니다.
- 클라이언트 측으로 성공메시지와 결과를 ApiResponse.success()로 감싸서 반환합니다.
@GetMapping("/reviews/search")
public ResponseEntity<?> typeSearchReview(
@RequestParam(name = "type", defaultValue = "ALL") ReviewSearchType type,
@RequestParam(name = "keyword") String keyword,
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "10") int size,
@AuthenticationPrincipal User user) {
// 서비스로직
Page<AdminReviewSearchResDTO> responseDTO = adminReviewService.typeSearchReview(type, keyword, page, size, user.getId());
// 성공메시지
String message = messageProvider.getMessage(ReviewSuccessCode.REVIEW_LIST_FETCHED.getMessage());
// 응답
return ResponseEntity
.status(ReviewSuccessCode.REVIEW_LIST_FETCHED.getHttpStatus())
.body(ApiResponse.success(
ReviewSuccessCode.REVIEW_LIST_FETCHED,
message,
responseDTO));
}
# AdminReviewService
- userRepository.findById(userId)를 사용하여 사용자를 조회하여 User 엔티티 형태로 저장하고 없는 사용자면 UserErrorCode.USER_NOT_FOUND 예외를 던집니다.
- 로그인한 사용자의 권한을 체크하기 위해 user.getRole()이 ROLE_ADMIN인지 확인합니다. 권한이 ROLE_ADMIN이 아니라면 AuthErrorCode.AUTH_FORBIDDEN 예외를 던집니다.
- PageRequest.of(page, size)를 사용하여 페이징을 정의하고 Pageable 형태로 저장합니다.
- 타입별 검색을 switch문을 사용하여 타입별 검색 로직을 처리합니다.
- 만약에 타입별 검색 조회를 했는데 값이 없는 경우 ReviewErrorCode.REVIEW_NOT_FOUND 예외를 던집니다.
- Page타입의 DTO를 List타입으로 변환하여 저장합니다.
- Controller로 반환 시 PageImpl<>()을 사용하여 List형태의 DTO를 Pageable 형태로 감싸서 반환합니다.
public Page<AdminReviewSearchResDTO> typeSearchReview(ReviewSearchType 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<Review> result;
// 검색 타입
String searchType = type.name();
// 타입별 검색
switch(searchType) {
case "ALL":
// 전체
result = reviewRepository.findByBook_TitleContainingIgnoreCaseOrUser_UsernameContainingIgnoreCase(keyword, keyword, pageable);
break;
case "BOOKTITLE":
// 책 제목
result = reviewRepository.findByBook_TitleContainingIgnoreCase(keyword, pageable);
break;
case "USERNAME":
// 사용자 이름
result = reviewRepository.findByUser_UsernameContainingIgnoreCase(keyword, pageable);
break;
default:
throw new IllegalArgumentException("Invalid search type: " + type);
}
// 값 없는 경우
if (result.isEmpty()) {
throw new BaseException(ReviewErrorCode.REVIEW_NOT_FOUND);
}
// Page 형태를 List로 변환
List<AdminReviewSearchResDTO> listDTO = result.getContent()
.stream()
.map(AdminReviewSearchResDTO::new)
.toList();
// PageImpl을 사용하여 ListDTO를 Pageable로 감싸서 반환
return new PageImpl<>(listDTO, result.getPageable(), result.getTotalElements());
}
# ReviewReopsitory
- JPA 네이밍 규칙으로 책 제목+사용자 이름, 책 제목만, 사용자 이름만 조회하는 메서드를 정의합니다.
Page<Review> findByBook_TitleContainingIgnoreCaseOrUser_UsernameContainingIgnoreCase(String bookTitle, String username, Pageable pageable);
Page<Review> findByBook_TitleContainingIgnoreCase(String bookTitle, Pageable pageable);
Page<Review> findByUser_UsernameContainingIgnoreCase(String username, Pageable pageable);