[ 작업한 내용 ]
< 사용자: 책 리뷰 수정 >
1. messages.properties
- 사용자 책 리뷰 수정 관련 성공/에러 메시지 정의
success.review.updated=리뷰가 성공적으로 수정되었습니다.
error.review.update_content_blank=내용을 입력해주세요.
2. ReviewSuccessCode
- 사용자 책 리뷰 수정 성공 코드 정의
REVIEW_UPDATED(HttpStatus.OK, "REVIEW_203", "success.review.updated");
3. ReviewErrorCode
- 사용자 책 리뷰 수정 에러 코드 정의
REVIEW_CONENT_NOT_BLANK(HttpStatus.BAD_REQUEST, "REVIEW_403", "error.review.update_content_not_blank");
4. UserReviewUpdateReqDTO
- 사용자 책 리뷰 요청 DTO 정의
- 필드: 수정된 리뷰 내용(content)
@Getter
public class UserReviewUpdateReqDTO {
private String content;
}
5. UserReviewUpdateResDTO
- 사용자 책 리뷰 응답 DTO 정의
- 필드: 책 제목, 리뷰 내용, 수정날짜(bookTitle, content, updatedDate)
- Reivew 엔티티를 파라미터로 받는 UserReviewUpdateResDTO 생성자 정의
@Getter
public class UserReviewUpdateResDTO {
private String bookTitle;
private String content;
private LocalDateTime updatedDate;
public UserReviewUpdateResDTO(Review review) {
this.bookTitle = review.getBook().getTitle();
this.content = review.getContent();
this.updatedDate = review.getUpdatedDate();
}
}
6. Review 엔티티
- 책 리뷰 수정을 위한 .updateReview(String content) 메서드 정의
public void updateReview(String content) {
if (content.isBlank() || content == null) {
throw new BaseException(ReviewErrorCode.REVIEW_CONENT_NOT_BLANK);
}
this.content = content;
this.updatedDate = LocalDateTime.now();
}
7. UserReviewController
- PATCH /api/v1/user/me/reviews/{reviewId} 로 요청받습니다.
- 파라미터는 reviewId, UserReviewUpdateReqDTO, @AuthenticationPrincipal로 인증 객체를 User 엔티티로 받습니다.
- Service 계층으로 reviewId, UserReviewUpdateReqDTO, user.getId()를 전달하여 비즈니스 로직을 수행합니다.
- 결과는 UserReviewUpdateResDTO 형태로 받습니다.
- 클라이언트 측으로 반환시 ApiResponse.success()로 성공메시지와 응답DTO를 감싸서 반환합니다.
@PatchMapping("/me/reviews/{reviewId}")
public ResponseEntity<?> updateReview(
@PathVariable("reviewId") Long reviewId,
@RequestBody UserReviewUpdateReqDTO requestDTO,
@AuthenticationPrincipal User user) {
// 서비스로직
UserReviewUpdateResDTO responseDTO = userReviewService.updateReview(reviewId, requestDTO, user.getId());
// 성공메시지
String message = messageProvider.getMessage(ReviewSuccessCode.REVIEW_UPDATED.getMessage());
return ResponseEntity
.status(ReviewSuccessCode.REVIEW_UPDATED.getHttpStatus())
.body(ApiResponse.success(
ReviewSuccessCode.REVIEW_UPDATED,
message,
responseDTO));
}
8. UserReviewService
- userRepository.findById(userId)로 사용자 조회를 하고 사용자가 없는 경우 UserErrorCode.USER_NOT_FOUND 예외를 던집니다.
- reviewRepository.findById(reviewId)로 리뷰 조회를 하고 리뷰가 없는 경우 ReviewErrorCode.REVIEW_NOT_FOUND 예외를 던집니다.
- 로그인한 사용자와 책 리뷰의 작성자가 일치하지 않는 경우 ReviewErrorCode.REVIEW_USER_NOT_SAME 예외를 던집니다.
- Review 엔티티에 정의한 .updateReview() 메서드를 호출하여 request.DTO.getContent()를 파라미터로 전달하여 리뷰 내용을 업데이트합니다.
- Controller로 반환시 reivew 데이터를 토대로 UserReviewUpdateResDTO 객체로 생성하여 반환합니다.
@Transactional
public UserReviewUpdateResDTO updateReview(Long reviewId, UserReviewUpdateReqDTO requestDTO, Long userId) {
// 사용자 조회 + 예외 처리
User user = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
// 리뷰 조회 + 예외 처리
Review review = reviewRepository.findById(reviewId)
.orElseThrow(() -> new BaseException(ReviewErrorCode.REVIEW_NOT_FOUND));
// 로그인한 사용자와 리뷰 작성자 검증 (본인 리뷰만 수정 가능)
if (!user.getId().equals(review.getUser().getId())) {
throw new BaseException(ReviewErrorCode.REVIEW_USER_NOT_SAME);
}
// 리뷰 내용 업데이트
review.updateReview(requestDTO.getContent());
// 반환
return new UserReviewUpdateResDTO(review);
}
< 사용자: 책 리뷰 삭제>
1. messages.properties
- 사용자 책 리뷰 삭제 관련 성공/에러 메시지 정의
success.review.deleted=리뷰가 성공적으로 삭제되었습니다.
2. ReviewSuccessCode
- 사용자 책 리뷰 삭제 성공 코드 정의
REVIEW_DELETED(HttpStatus.OK, "REVIEW_204", "success.review.deleted");
3. UserReviewDeleteResDTO
- 사용자 책 리뷰 삭제 응답 DTO 정의
- 필드: 책 제목, 삭제 날짜(bookTitle, deletedAt)
@Getter
public class UserReviewDeleteResDTO {
private String bookTitle;
private LocalDateTime deletedAt;
public UserReviewDeleteResDTO(Review review) {
this.bookTitle = review.getBook().getTitle();
this.deletedAt = LocalDateTime.now();
}
}
4. UserReviewController
- DELETE /api/v1/user/me/reviews/{reviewId} 로 요청합니다.
- 파라미터로 reviewId와 @AuthenticationPrincipal로 인증 객체를 User 엔티티로 받습니다.
- Service 계층으로 reviewId와 user.getId()를 전달하여 비즈니스 로직을 처리합니다.
- 결과는 UserReviewDeleteResDTO형태로 저장합니다.
- 클라이언트 측으로 반환할 때 성공메시지와 응답DTO를 ApiResponse.success()로 감싸어 반환합니다.
@DeleteMapping("/me/reviews/{reviewId}")
public ResponseEntity<?> deleteReview(
@PathVariable("reviewId") Long reviewId,
@AuthenticationPrincipal User user) {
// 서비스로직
UserReviewDeleteResDTO responseDTO = userReviewService.deleteReview(reviewId, user.getId());
// 성공메시지
String message = messageProvider.getMessage(ReviewSuccessCode.REVIEW_DELETED.getMessage());
// 반환
return ResponseEntity
.status(ReviewSuccessCode.REVIEW_DELETED.getHttpStatus())
.body(ApiResponse.success(
ReviewSuccessCode.REVIEW_DELETED,
message,
responseDTO));
}
5. UserReviewService
- userRepository.findById(userId)로 사용자 조회를 하고 사용자가 없는 경우 UserErrorCode.USER_NOT_FOUND 예외를 던집니다.
- reviewRepository.findById(reviewId)로 리뷰 조회를 하고 리뷰가 없는 경우 ReviewErrorCode.REVIEW_NOT_FOUND 예외를 던집니다.
- 로그인한 사용자와 책 리뷰의 작성자가 일치하지 않는 경우 ReviewErrorCode.REVIEW_USER_NOT_SAME 예외를 던집니다.
- 특정 리뷰를 삭제하기 전에 응답 DTO에 review 데이터를 저장합니다.
- reviewRepository.delete(review) 메서드를 호출하여 review를 삭제합니다.
- controller로 삭제 전에 저장한 DTO를 반환합니다.
@Transactional
public UserReviewDeleteResDTO deleteReview(Long reviewId, Long userId) {
// 사용자 조회 + 예외 처리
User user = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
// 리뷰 조회 + 예외 처리
Review review = reviewRepository.findById(reviewId)
.orElseThrow(() -> new BaseException(ReviewErrorCode.REVIEW_NOT_FOUND));
// 로그인한 사용자와 리뷰 작성자 검증 (본인 리뷰만 삭제 가능)
if (!user.getId().equals(review.getUser().getId())) {
throw new BaseException(ReviewErrorCode.REVIEW_USER_NOT_SAME);
}
// 반환할 데이터 미리 저장
UserReviewDeleteResDTO resopnseDTO= new UserReviewDeleteResDTO(review);
// 리뷰 삭제
reviewRepository.delete(review);
return resopnseDTO;
}
< 사용자: 책 리뷰 검색 (책 제목) >
1. messages.properties
- 사용자 책 리뷰 검색 관련 성공/에러 메시지 정의
success.review.fetched=리뷰가 성공적으로 조회되었습니다.
2. ReviewSuccessCode
- 사용자 책 리뷰 검색 성공 코드 정의
REVIEW_FETCHED(HttpStatus.OK, "REVIEW_205", "success.review.fetched");
3. UserReviewSearchResDTO
- 사용자 책 리뷰 검색 응답 DTO 정의
- 필드: 책 제목, 리뷰 내용, 작성일(bookTItle, content, createdDate)
@Getter
public class UserReviewSearchResDTO {
private String bookTitle;
private String content;
private LocalDateTime createdDate;
public UserReviewSearchResDTO(Review review) {
this.bookTitle = review.getBook().getTitle();
this.content = review.getContent();
this.createdDate = review.getCreatedDate();
}
}
4. UserReviewController
- GET /api/v1/user/me/reviews/search 로 요청을 합니다.
- 파라미터로 keyword, page, size를 RequestParam 형태로 받으며, page와 size는 각 기본값이 0과 10입니다.
- @AuthenticationPrincipal을 사용하여 사용자 객체를 User 엔티티로 받습니다.
- Service 계층으로 keyword, page, size, user.getId()를 전달하여 비즈니스 로직을 처리합니다.
- 처리된 결과는 Page<UserReviewSearchResDTO> 타입으로 저장됩니다.
- 클라이언트 측으로 성공 메시지와 결과 DTO를 ApiResponse.success()로 감싸서 반환합니다.
@GetMapping("/me/reviews/search")
public ResponseEntity<?> searchReview(
@RequestParam(name = "keyword", required = false) String keyword,
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "10") int size,
@AuthenticationPrincipal User user) {
// 서비스로직
Page<UserReviewSearchResDTO> responseDTO = userReviewService.searchReview(keyword, page, size, user.getId());
// 성공메시지
String message = messageProvider.getMessage(ReviewSuccessCode.REVIEW_FETCHED.getMessage());
// 반환
return ResponseEntity
.status(ReviewSuccessCode.REVIEW_FETCHED.getHttpStatus())
.body(ApiResponse.success(
ReviewSuccessCode.REVIEW_FETCHED,
message,
responseDTO));
}
5. UserReviewService
- userReository.findById(userId)로 사용자 조회를 하고 사용자가 없으면 UserErrorCode.USER_NOT_FOUND 예외를 던집니다.
- PageRequest.of(page, size)로 페이징 처리를하여 Pageable로 저장합니다.
- keyword에 값이 있으면 reviewRepository.findByUser_IdAndBook_TitleContainingIgnoreCase(user.getId(), keyword, pageable)로 로그인한 사용자 id이고 키워드가 있는 책 제목을 찾습니다. 만약에 값이 keyword에 값이 없으면 모든 리뷰를 검색하여 결과를 보여줍니다. 결과는 Page<Review> 형태로 저장합니다.
- Page형태으 결과를 List로 맵핑하여 변환시킨 다음, Controller로 반환할 때에는 PageImpl<>을 사용하여 Page로 랩핑하여 반환합니다.
public Page<UserReviewSearchResDTO> searchReview(String keyword, int page, int size, Long userId) {
// 사용자 조회 + 예외 처리
User user = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
// 페이징 정의
Pageable pageable = PageRequest.of(page, size);
// 검색어 조회 + 페이지 처리
Page<Review> result = keyword != null ? result = reviewRepository.findByUser_IdAndBook_TitleContainingIgnoreCase(user.getId(), keyword, pageable) : reviewRepository.findAll(pageable);
// Page -> List 맵핑하여 변환
List<UserReviewSearchResDTO> listDTO = result.getContent()
.stream()
.map(UserReviewSearchResDTO::new)
.toList();
// PageImpl로 감싸 Page 형태로 변환하여 반환
return new PageImpl<>(listDTO, result.getPageable(), result.getTotalElements());
}
6. ReviewReopsitory
- JPA 네이밍 규칙으로 User엔티티의 id가 userId이고, Book엔티티의 title이 keyword인 데이터를 찾아서 page로 가져오도록 정의.
Page<Review> findByUser_IdAndBook_TitleContainingIgnoreCase(Long userId, String keyword, Pageable pageable);
'개발 기록 > 도서관 관리 시스템' 카테고리의 다른 글
[Library Management System] 25.09.18 (49일) (0) | 2025.09.18 |
---|---|
[Library Management System] 25.09.17 (48일) (0) | 2025.09.17 |
[Library Management System] 25.09.16 (47일) (0) | 2025.09.16 |
[Library Management System] 25.09.12 (45일) (0) | 2025.09.12 |
[Library Management System] 25.09.11 (44일) (0) | 2025.09.11 |