diff --git a/src/main/java/com/moongeul/backend/api/post/controller/PostController.java b/src/main/java/com/moongeul/backend/api/post/controller/PostController.java index 71bc44a..05596d7 100644 --- a/src/main/java/com/moongeul/backend/api/post/controller/PostController.java +++ b/src/main/java/com/moongeul/backend/api/post/controller/PostController.java @@ -194,5 +194,19 @@ public ResponseEntity> getWeeklyRec WeeklyRecommendationResponseDTO weeklyRecommendationResponseDTO = postService.getWeeklyRecommendation(userDetails.getUsername()); return ApiResponse.success(SuccessStatus.GET_WEEKLY_RECOMMENDATION_SUCCESS, weeklyRecommendationResponseDTO); } -} + @Operation( + summary = "가장 많이 기록된 책 조회 API", + description = "전체 공개 기록 기준으로 가장 많이 기록된 책을 조회합니다. 동률이면 최근에 작성된 책을 우선합니다." + ) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "가장 많이 기록된 책 조회 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "가장 많이 기록된 책을 찾을 수 없습니다.") + }) + @GetMapping("/most-recorded-book") + public ResponseEntity> getMostRecordedBook() { + + MostRecordedBookResponseDTO mostRecordedBookResponseDTO = postService.getMostRecordedBook(); + return ApiResponse.success(SuccessStatus.GET_MOST_RECORDED_BOOK_SUCCESS, mostRecordedBookResponseDTO); + } +} diff --git a/src/main/java/com/moongeul/backend/api/post/dto/MostRecordedBookResponseDTO.java b/src/main/java/com/moongeul/backend/api/post/dto/MostRecordedBookResponseDTO.java new file mode 100644 index 0000000..060723a --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/post/dto/MostRecordedBookResponseDTO.java @@ -0,0 +1,24 @@ +package com.moongeul.backend.api.post.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MostRecordedBookResponseDTO { + + private Long postId; // 기록 ID + private String bookImage; // 책 사진 + private String bookTitle; // 책 제목 + private String isbn; // 책 ISBN + private String author; // 책 저자 + private String publisher; // 출판사 + private String pubdate; // 출판년도 + private Double bookRating; // 책 별점 + private Double rating; // 평점 + private String content; // 기록글 +} diff --git a/src/main/java/com/moongeul/backend/api/post/repository/PostRepository.java b/src/main/java/com/moongeul/backend/api/post/repository/PostRepository.java index c8a1028..eaf0d66 100644 --- a/src/main/java/com/moongeul/backend/api/post/repository/PostRepository.java +++ b/src/main/java/com/moongeul/backend/api/post/repository/PostRepository.java @@ -3,6 +3,7 @@ import com.moongeul.backend.api.book.entity.Book; import com.moongeul.backend.api.member.entity.ReadingTasteType; import com.moongeul.backend.api.post.entity.Post; +import com.moongeul.backend.api.post.entity.PostVisibility; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,6 +12,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; public interface PostRepository extends JpaRepository { @@ -37,6 +39,17 @@ public interface PostRepository extends JpaRepository { @Query("SELECT p FROM Post p WHERE p.book = :book ORDER BY p.createdAt DESC") Page findByBookOrderByCreatedAtDesc(@Param("book") Book book, Pageable pageable); + // 전체 공개 기록 기준으로 가장 많이 기록된 책 ISBN 조회 + @Query("SELECT p.book.isbn FROM Post p " + + "WHERE p.postVisibility = 'PUBLIC' " + + "GROUP BY p.book.isbn " + + "ORDER BY COUNT(p) DESC, MAX(p.createdAt) DESC") + List findMostRecordedPublicBookIsbn(Pageable pageable); + + // 특정 ISBN의 전체 공개 게시글 중 가장 최근 기록 조회 + Optional findFirstByBookIsbnAndPostVisibilityOrderByCreatedAtDesc( + String isbn, PostVisibility postVisibility); + // 같은 취향 사용자들의 기록 중 이번 주 가장 공감을 많이 받은 기록 조회 // 모든 공감 유형의 합계(relatableCount + sameTasteCount + impressiveExpressionCount + wantToReadCount + helpfulCount) 기준으로 정렬 @Query("SELECT p FROM Post p " + diff --git a/src/main/java/com/moongeul/backend/api/post/service/PostService.java b/src/main/java/com/moongeul/backend/api/post/service/PostService.java index c4250b4..2aade4d 100644 --- a/src/main/java/com/moongeul/backend/api/post/service/PostService.java +++ b/src/main/java/com/moongeul/backend/api/post/service/PostService.java @@ -384,6 +384,37 @@ private void decrementLikeCount(Post post, LikeType likeType) { /* * 추천 */ + + // 가장 많이 기록된 책 조회 + @Transactional(readOnly = true) + public MostRecordedBookResponseDTO getMostRecordedBook() { + List mostRecordedBookIsbnList = postRepository.findMostRecordedPublicBookIsbn(PageRequest.of(0, 1)); + + if (mostRecordedBookIsbnList.isEmpty()) { + throw new NotFoundException(ErrorStatus.MOST_RECORDED_BOOK_NOT_FOUND_EXCEPTION.getMessage()); + } + + String mostRecordedBookIsbn = mostRecordedBookIsbnList.get(0); + + Post post = postRepository.findFirstByBookIsbnAndPostVisibilityOrderByCreatedAtDesc( + mostRecordedBookIsbn, + PostVisibility.PUBLIC + ) + .orElseThrow(() -> new NotFoundException(ErrorStatus.MOST_RECORDED_BOOK_NOT_FOUND_EXCEPTION.getMessage())); + + return MostRecordedBookResponseDTO.builder() + .postId(post.getId()) + .bookImage(post.getBook().getBookImage()) + .bookTitle(post.getBook().getTitle()) + .isbn(post.getBook().getIsbn()) + .author(post.getBook().getAuthor()) + .publisher(post.getBook().getPublisher()) + .pubdate(post.getBook().getPubdate()) + .bookRating(post.getBook().getRatingAverage()) + .rating(post.getRating()) + .content(post.getContent()) + .build(); + } // 주간 추천 기록 조회 @Transactional(readOnly = true) diff --git a/src/main/java/com/moongeul/backend/common/response/ErrorStatus.java b/src/main/java/com/moongeul/backend/common/response/ErrorStatus.java index 316b9d3..2830a9d 100644 --- a/src/main/java/com/moongeul/backend/common/response/ErrorStatus.java +++ b/src/main/java/com/moongeul/backend/common/response/ErrorStatus.java @@ -47,6 +47,7 @@ public enum ErrorStatus { REVIEW_NOTFOUND_EXCEPTION(HttpStatus.NOT_FOUND, "해당 리뷰를 찾을 수 없습니다."), USER_READING_TASTE_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "사용자의 독서 취향 정보를 찾을 수 없습니다."), WEEKLY_RECOMMENDATION_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "주간 추천 기록을 찾을 수 없습니다."), + MOST_RECORDED_BOOK_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "가장 많이 기록된 책을 찾을 수 없습니다."), QUESTION_NOTFOUND_EXCEPTION(HttpStatus.NOT_FOUND, "해당 질문을 찾을 수 없습니다."), ANSWER_NOTFOUND_EXCEPTION(HttpStatus.NOT_FOUND, "해당 답변을 찾을 수 없습니다."), TERMS_NOTFOUND_EXCEPTION(HttpStatus.NOT_FOUND, "약관 정보를 찾을 수 없습니다."), diff --git a/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java b/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java index 88ea67e..ab506df 100644 --- a/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java +++ b/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java @@ -53,6 +53,7 @@ public enum SuccessStatus { DELETE_POST_SUCCESS(HttpStatus.OK, "기록(게시글) 삭제 성공"), POST_LIKE_SUCCESS(HttpStatus.OK, "기록(게시글) 공감 버튼 등록 성공"), GET_WEEKLY_RECOMMENDATION_SUCCESS(HttpStatus.OK, "주간 추천 기록 조회 성공"), + GET_MOST_RECORDED_BOOK_SUCCESS(HttpStatus.OK, "가장 많이 기록된 책 조회 성공"), GET_WRITING_GUIDE(HttpStatus.OK, "글쓰기 도움 받기 조회 성공"), /* CATEGORY */