From 3df647b60d919f6aa60de28f12b5f94482db0d75 Mon Sep 17 00:00:00 2001 From: rhkr8521 Date: Thu, 18 Dec 2025 13:50:37 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[FEAT]=20=EC=B1=85=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/book/controller/BookController.java | 17 +++++++++++++++++ .../api/book/dto/BookSearchRequestDTO.java | 1 + .../backend/api/book/service/BookService.java | 11 +++++++++++ .../backend/common/response/SuccessStatus.java | 1 + 4 files changed, 30 insertions(+) diff --git a/src/main/java/com/moongeul/backend/api/book/controller/BookController.java b/src/main/java/com/moongeul/backend/api/book/controller/BookController.java index 95ab8e8..8054891 100644 --- a/src/main/java/com/moongeul/backend/api/book/controller/BookController.java +++ b/src/main/java/com/moongeul/backend/api/book/controller/BookController.java @@ -1,5 +1,6 @@ package com.moongeul.backend.api.book.controller; +import com.moongeul.backend.api.book.dto.BookDTO; import com.moongeul.backend.api.book.dto.BookSearchRequestDTO; import com.moongeul.backend.api.book.dto.BookSearchResponseDTO; import com.moongeul.backend.api.book.service.BookService; @@ -48,5 +49,21 @@ public ResponseEntity> searchBooks( BookSearchResponseDTO bookSearchResponseDTO = bookService.searchBooks(bookSearchRequestDTO); return ApiResponse.success(SuccessStatus.SEARCH_BOOK_SUCCESS, bookSearchResponseDTO); } + + @Operation( + summary = "도서 상세 조회 API", + description = "ISBN을 받아서 DB에 등록된 도서의 상세 정보를 조회합니다." + ) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "도서 상세 조회 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "해당 도서를 찾을 수 없습니다.") + }) + @GetMapping("/{isbn}") + public ResponseEntity> getBookDetail( + @PathVariable @NotBlank(message = "ISBN은 필수입니다.") String isbn) { + + BookDTO bookDTO = bookService.getBookDetail(isbn); + return ApiResponse.success(SuccessStatus.GET_BOOK_DETAIL_SUCCESS, bookDTO); + } } diff --git a/src/main/java/com/moongeul/backend/api/book/dto/BookSearchRequestDTO.java b/src/main/java/com/moongeul/backend/api/book/dto/BookSearchRequestDTO.java index 5ff5cd2..d6e1a6d 100644 --- a/src/main/java/com/moongeul/backend/api/book/dto/BookSearchRequestDTO.java +++ b/src/main/java/com/moongeul/backend/api/book/dto/BookSearchRequestDTO.java @@ -22,3 +22,4 @@ public class BookSearchRequestDTO { private Integer size = 10; // 한 페이지당 개수 (기본값 10, 최대 100) } + diff --git a/src/main/java/com/moongeul/backend/api/book/service/BookService.java b/src/main/java/com/moongeul/backend/api/book/service/BookService.java index ff7d0c1..2a188e8 100644 --- a/src/main/java/com/moongeul/backend/api/book/service/BookService.java +++ b/src/main/java/com/moongeul/backend/api/book/service/BookService.java @@ -4,6 +4,7 @@ import com.moongeul.backend.api.book.entity.Book; import com.moongeul.backend.api.book.repository.BookRepository; import com.moongeul.backend.common.exception.InternalServerException; +import com.moongeul.backend.common.exception.NotFoundException; import com.moongeul.backend.common.response.ErrorStatus; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -37,6 +38,7 @@ public class BookService { @Value("${naver.book.client-secret}") private String clientSecret; + // 도서 검색 메서드 (with 네이버 도서) @Transactional public BookSearchResponseDTO searchBooks(BookSearchRequestDTO bookSearchRequestDTO) { // 네이버 API 호출 @@ -255,5 +257,14 @@ private String truncateString(String text, int maxLength) { } return text.substring(0, maxLength); } + + // 책 상세 정보 조회 + @Transactional(readOnly = true) + public BookDTO getBookDetail(String isbn) { + Book book = bookRepository.findByIsbn(isbn) + .orElseThrow(() -> new NotFoundException(ErrorStatus.BOOK_NOTFOUND_EXCEPTION.getMessage())); + + return convertToDTO(book); + } } 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 c62bd5d..94aeb33 100644 --- a/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java +++ b/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java @@ -21,6 +21,7 @@ public enum SuccessStatus { /* BOOK */ SEARCH_BOOK_SUCCESS(HttpStatus.OK, "도서 검색 성공"), + GET_BOOK_DETAIL_SUCCESS(HttpStatus.OK, "도서 상세 조회 성공"), /* BOOKSHELF */ ADD_WISH_READ_BOOK_SUCCESS(HttpStatus.OK, "읽고 싶은 책 등록 성공"), From bbd55a5615c340e310e6fde99fd82d7c1dca99bf Mon Sep 17 00:00:00 2001 From: rhkr8521 Date: Thu, 18 Dec 2025 14:02:40 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[FEAT]=20=EC=9D=BD=EC=9D=80=20=EC=B1=85=20?= =?UTF-8?q?=EC=B1=85=EC=9E=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookshelf/entity/DoneReadBookshelf.java | 39 ++++++++++++ .../bookshelf/entity/WishReadBookshelf.java | 3 +- .../DoneReadBookshelfRepository.java | 8 +++ .../bookshelf/util/BookshelfCalculator.java | 61 +++++++++++++++++++ .../backend/api/post/service/PostService.java | 18 ++++++ 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/moongeul/backend/api/bookshelf/entity/DoneReadBookshelf.java create mode 100644 src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java create mode 100644 src/main/java/com/moongeul/backend/api/bookshelf/util/BookshelfCalculator.java diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/entity/DoneReadBookshelf.java b/src/main/java/com/moongeul/backend/api/bookshelf/entity/DoneReadBookshelf.java new file mode 100644 index 0000000..9e44f3a --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/bookshelf/entity/DoneReadBookshelf.java @@ -0,0 +1,39 @@ +package com.moongeul.backend.api.bookshelf.entity; + +import com.moongeul.backend.api.member.entity.Member; +import com.moongeul.backend.api.post.entity.Post; +import com.moongeul.backend.common.entity.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "done_read_bookshelf") +public class DoneReadBookshelf extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "done_bookshelf_id") + private Long doneBookshelfId; + + @Column(name = "weight") + private Float weight; // 도서 길이 기반 두께 + + @Column(name = "height") + private Float height; // 별점 기반 높낮이 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "article_id", nullable = false) + private Post article; // 게시글 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; // 회원 +} + diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/entity/WishReadBookshelf.java b/src/main/java/com/moongeul/backend/api/bookshelf/entity/WishReadBookshelf.java index 90dd91d..d213c97 100644 --- a/src/main/java/com/moongeul/backend/api/bookshelf/entity/WishReadBookshelf.java +++ b/src/main/java/com/moongeul/backend/api/bookshelf/entity/WishReadBookshelf.java @@ -21,7 +21,8 @@ public class WishReadBookshelf extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Column(name = "wish_bookshelf_id") + private Long wishBookshelfId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", nullable = false) diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java b/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java new file mode 100644 index 0000000..488ef91 --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java @@ -0,0 +1,8 @@ +package com.moongeul.backend.api.bookshelf.repository; + +import com.moongeul.backend.api.bookshelf.entity.DoneReadBookshelf; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DoneReadBookshelfRepository extends JpaRepository { +} + diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/util/BookshelfCalculator.java b/src/main/java/com/moongeul/backend/api/bookshelf/util/BookshelfCalculator.java new file mode 100644 index 0000000..5aa6b99 --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/bookshelf/util/BookshelfCalculator.java @@ -0,0 +1,61 @@ +package com.moongeul.backend.api.bookshelf.util; + +import org.springframework.stereotype.Component; + +@Component +public class BookshelfCalculator { + + // 페이지 수에 따른 weight(두께) 계산 + public Float calculateWeight(Integer page) { + if (page == null || page <= 0) { + return 24.0f; // 기본값 + } + + if (page <= 100) { + return 24.0f; + } else if (page <= 150) { + return 28.0f; + } else if (page <= 200) { + return 32.0f; + } else if (page <= 250) { + return 36.0f; + } else if (page <= 300) { + return 40.0f; + } else if (page <= 350) { + return 44.0f; + } else if (page <= 400) { + return 48.0f; + } else if (page <= 450) { + return 52.0f; + } else if (page <= 500) { + return 56.0f; + } else if (page <= 600) { + return 60.0f; + } else if (page <= 800) { + return 64.0f; + } else if (page <= 1000) { + return 68.0f; + } else { + return 72.0f; // 1001쪽 이상 (최대 고정) + } + } + + // 별점에 따른 height(높이) 계산 + public Float calculateHeight(Double rating) { + if (rating == null || rating < 0.0) { + return 88.0f; // 기본값 (0.0) + } + + // 0.5 단위로 반올림 + double roundedRating = Math.round(rating * 2) / 2.0; + + // 5.0을 초과하면 5.0으로 제한 + if (roundedRating > 5.0) { + roundedRating = 5.0; + } + + // 별점에 따른 높이 계산 (0.5점당 4px 증가) + return 88.0f + (float)(roundedRating * 8); + } +} + 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 6addb45..924ed26 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 @@ -2,6 +2,9 @@ import com.moongeul.backend.api.book.entity.Book; import com.moongeul.backend.api.book.repository.BookRepository; +import com.moongeul.backend.api.bookshelf.entity.DoneReadBookshelf; +import com.moongeul.backend.api.bookshelf.repository.DoneReadBookshelfRepository; +import com.moongeul.backend.api.bookshelf.util.BookshelfCalculator; import com.moongeul.backend.api.member.entity.Member; import com.moongeul.backend.api.member.repository.MemberRepository; import com.moongeul.backend.api.post.dto.PostCreateRequestDTO; @@ -26,6 +29,8 @@ public class PostService { private final BookRepository bookRepository; private final PostRepository postRepository; private final CategoryRepository categoryRepository; + private final DoneReadBookshelfRepository doneReadBookshelfRepository; + private final BookshelfCalculator bookshelfCalculator; /* 글쓰기 */ @Transactional @@ -43,6 +48,19 @@ public PostCreateResponseDTO createPost(PostCreateRequestDTO postCreateRequestDT Post newPost = postCreateRequestDTO.toEntity(category, member, book); Post savedPost = postRepository.save(newPost); + // 읽은 책 책장에 등록 + Float weight = bookshelfCalculator.calculateWeight(savedPost.getPage()); + Float height = bookshelfCalculator.calculateHeight(savedPost.getRating()); + + DoneReadBookshelf doneReadBookshelf = DoneReadBookshelf.builder() + .weight(weight) + .height(height) + .article(savedPost) + .member(member) + .build(); + + doneReadBookshelfRepository.save(doneReadBookshelf); + return PostCreateResponseDTO.builder() .postId(savedPost.getId()) .build(); From 019e00df1e31d24f2b035b876cba99085fe97b42 Mon Sep 17 00:00:00 2001 From: rhkr8521 Date: Thu, 18 Dec 2025 14:13:28 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[FEAT]=20=EC=B1=85=EC=9E=A5=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DoneReadBookshelfController.java | 46 +++++++++++ .../WishReadBookshelfController.java | 23 ++++++ .../dto/DoneReadBookshelfItemDTO.java | 26 ++++++ .../dto/DoneReadBookshelfResponseDTO.java | 22 +++++ .../dto/WishReadBookshelfResponseDTO.java | 23 ++++++ .../DoneReadBookshelfRepository.java | 7 ++ .../WishReadBookshelfRepository.java | 7 ++ .../service/DoneReadBookshelfService.java | 80 +++++++++++++++++++ .../service/WishReadBookshelfService.java | 54 +++++++++++++ .../common/response/SuccessStatus.java | 2 + 10 files changed, 290 insertions(+) create mode 100644 src/main/java/com/moongeul/backend/api/bookshelf/controller/DoneReadBookshelfController.java create mode 100644 src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfItemDTO.java create mode 100644 src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfResponseDTO.java create mode 100644 src/main/java/com/moongeul/backend/api/bookshelf/dto/WishReadBookshelfResponseDTO.java create mode 100644 src/main/java/com/moongeul/backend/api/bookshelf/service/DoneReadBookshelfService.java diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/controller/DoneReadBookshelfController.java b/src/main/java/com/moongeul/backend/api/bookshelf/controller/DoneReadBookshelfController.java new file mode 100644 index 0000000..4a55786 --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/bookshelf/controller/DoneReadBookshelfController.java @@ -0,0 +1,46 @@ +package com.moongeul.backend.api.bookshelf.controller; + +import com.moongeul.backend.api.bookshelf.dto.DoneReadBookshelfResponseDTO; +import com.moongeul.backend.api.bookshelf.service.DoneReadBookshelfService; +import com.moongeul.backend.common.response.ApiResponse; +import com.moongeul.backend.common.response.SuccessStatus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Min; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "DoneReadBookshelf", description = "읽은 책장 관련 API 입니다.") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v2/bookshelf/done-read") +@Validated +public class DoneReadBookshelfController { + + private final DoneReadBookshelfService doneReadBookshelfService; + + @Operation( + summary = "읽은 책장 전체 조회 API", + description = "인증된 사용자의 읽은 책장 목록을 최신순으로 조회합니다. (페이지와 사이즈는 1부터 시작합니다)" + ) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "읽은 책장 조회 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청 파라미터"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없습니다.") + }) + @GetMapping + public ResponseEntity> getDoneReadBooks( + @AuthenticationPrincipal UserDetails userDetails, + @RequestParam(required = false, defaultValue = "1") @Min(value = 1, message = "페이지는 1 이상이어야 합니다.") Integer page, + @RequestParam(required = false, defaultValue = "10") @Min(value = 1, message = "한 페이지당 개수는 1 이상이어야 합니다.") Integer size) { + + DoneReadBookshelfResponseDTO doneReadBookshelfResponseDTO = doneReadBookshelfService.getDoneReadBooks(userDetails.getUsername(), page, size); + return ApiResponse.success(SuccessStatus.GET_DONE_READ_BOOKS_SUCCESS, doneReadBookshelfResponseDTO); + } +} + diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/controller/WishReadBookshelfController.java b/src/main/java/com/moongeul/backend/api/bookshelf/controller/WishReadBookshelfController.java index 05e0606..9944bfd 100644 --- a/src/main/java/com/moongeul/backend/api/bookshelf/controller/WishReadBookshelfController.java +++ b/src/main/java/com/moongeul/backend/api/bookshelf/controller/WishReadBookshelfController.java @@ -1,6 +1,7 @@ package com.moongeul.backend.api.bookshelf.controller; import com.moongeul.backend.api.bookshelf.dto.WishReadBookshelfRequestDTO; +import com.moongeul.backend.api.bookshelf.dto.WishReadBookshelfResponseDTO; import com.moongeul.backend.api.bookshelf.service.WishReadBookshelfService; import com.moongeul.backend.common.response.ApiResponse; import com.moongeul.backend.common.response.SuccessStatus; @@ -8,16 +9,19 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @Tag(name = "WishReadBookshelf", description = "읽고 싶은 책장 관련 API 입니다.") @RestController @RequiredArgsConstructor @RequestMapping("/api/v2/bookshelf/wish-read") +@Validated public class WishReadBookshelfController { private final WishReadBookshelfService wishReadBookshelfService; @@ -56,4 +60,23 @@ public ResponseEntity> removeWishReadBook( wishReadBookshelfService.removeWishReadBook(userDetails.getUsername(), wishReadBookshelfRequestDTO.getIsbn()); return ApiResponse.success_only(SuccessStatus.REMOVE_WISH_READ_BOOK_SUCCESS); } + + @Operation( + summary = "읽고 싶은 책장 전체 조회 API", + description = "사용자의 읽고 싶은 책장 목록을 최신순으로 조회합니다. (페이지와 사이즈는 1부터 시작합니다)" + ) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "읽고 싶은 책장 조회 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청 파라미터"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없습니다.") + }) + @GetMapping + public ResponseEntity> getWishReadBooks( + @AuthenticationPrincipal UserDetails userDetails, + @RequestParam(required = false, defaultValue = "1") @Min(value = 1, message = "페이지는 1 이상이어야 합니다.") Integer page, + @RequestParam(required = false, defaultValue = "10") @Min(value = 1, message = "한 페이지당 개수는 1 이상이어야 합니다.") Integer size) { + + WishReadBookshelfResponseDTO wishReadBookshelfResponseDTO = wishReadBookshelfService.getWishReadBooks(userDetails.getUsername(), page, size); + return ApiResponse.success(SuccessStatus.GET_WISH_READ_BOOKS_SUCCESS, wishReadBookshelfResponseDTO); + } } diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfItemDTO.java b/src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfItemDTO.java new file mode 100644 index 0000000..8385b4a --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfItemDTO.java @@ -0,0 +1,26 @@ +package com.moongeul.backend.api.bookshelf.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DoneReadBookshelfItemDTO { + private Long articleId; // 게시글 ID + private String isbn; // ISBN + private String title; // 책 제목 + private String author; // 저자 + private String bookImage; // 표지 이미지 + private String publisher; // 출판사 + private String description; // 책 소개 + private String pubdate; // 출판연도 + private Double ratingAverage; // 별점 평균 + private Integer ratingCount; // 별점 개수 + private Float weight; // 두께 + private Float height; // 높이 +} + diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfResponseDTO.java b/src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfResponseDTO.java new file mode 100644 index 0000000..1298b64 --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfResponseDTO.java @@ -0,0 +1,22 @@ +package com.moongeul.backend.api.bookshelf.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DoneReadBookshelfResponseDTO { + private Integer total; // 전체 개수 + private Integer page; // 현재 페이지 + private Integer size; // 페이지당 개수 + private Integer totalPages; // 전체 페이지 수 + private Boolean isLast; // 마지막 페이지 여부 + private List books; // 읽은 책 목록 +} + diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/dto/WishReadBookshelfResponseDTO.java b/src/main/java/com/moongeul/backend/api/bookshelf/dto/WishReadBookshelfResponseDTO.java new file mode 100644 index 0000000..07b5699 --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/bookshelf/dto/WishReadBookshelfResponseDTO.java @@ -0,0 +1,23 @@ +package com.moongeul.backend.api.bookshelf.dto; + +import com.moongeul.backend.api.book.dto.BookDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WishReadBookshelfResponseDTO { + private Integer total; // 전체 개수 + private Integer page; // 현재 페이지 + private Integer size; // 페이지당 개수 + private Integer totalPages; // 전체 페이지 수 + private Boolean isLast; // 마지막 페이지 여부 + private List books; // 읽고 싶은 책 목록 +} + diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java b/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java index 488ef91..8ee2c7b 100644 --- a/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java +++ b/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java @@ -1,8 +1,15 @@ package com.moongeul.backend.api.bookshelf.repository; import com.moongeul.backend.api.bookshelf.entity.DoneReadBookshelf; +import com.moongeul.backend.api.member.entity.Member; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface DoneReadBookshelfRepository extends JpaRepository { + @Query("SELECT d FROM DoneReadBookshelf d WHERE d.member = :member ORDER BY d.createdAt DESC") + Page findByMemberOrderByCreatedAtDesc(@Param("member") Member member, Pageable pageable); } diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/repository/WishReadBookshelfRepository.java b/src/main/java/com/moongeul/backend/api/bookshelf/repository/WishReadBookshelfRepository.java index 078a728..a894c59 100644 --- a/src/main/java/com/moongeul/backend/api/bookshelf/repository/WishReadBookshelfRepository.java +++ b/src/main/java/com/moongeul/backend/api/bookshelf/repository/WishReadBookshelfRepository.java @@ -3,7 +3,11 @@ import com.moongeul.backend.api.bookshelf.entity.WishReadBookshelf; import com.moongeul.backend.api.book.entity.Book; import com.moongeul.backend.api.member.entity.Member; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.Optional; @@ -12,5 +16,8 @@ public interface WishReadBookshelfRepository extends JpaRepository findByMemberOrderByCreatedAtDesc(@Param("member") Member member, Pageable pageable); } diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/service/DoneReadBookshelfService.java b/src/main/java/com/moongeul/backend/api/bookshelf/service/DoneReadBookshelfService.java new file mode 100644 index 0000000..3f47614 --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/bookshelf/service/DoneReadBookshelfService.java @@ -0,0 +1,80 @@ +package com.moongeul.backend.api.bookshelf.service; + +import com.moongeul.backend.api.book.entity.Book; +import com.moongeul.backend.api.bookshelf.dto.DoneReadBookshelfItemDTO; +import com.moongeul.backend.api.bookshelf.dto.DoneReadBookshelfResponseDTO; +import com.moongeul.backend.api.bookshelf.entity.DoneReadBookshelf; +import com.moongeul.backend.api.bookshelf.repository.DoneReadBookshelfRepository; +import com.moongeul.backend.api.member.entity.Member; +import com.moongeul.backend.api.member.repository.MemberRepository; +import com.moongeul.backend.common.exception.NotFoundException; +import com.moongeul.backend.common.response.ErrorStatus; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DoneReadBookshelfService { + + private final DoneReadBookshelfRepository doneReadBookshelfRepository; + private final MemberRepository memberRepository; + + @Transactional(readOnly = true) + public DoneReadBookshelfResponseDTO getDoneReadBooks(String email, Integer page, Integer size) { + + Member member = memberRepository.findByEmail(email) + .orElseThrow(() -> new NotFoundException(ErrorStatus.USER_NOTFOUND_EXCEPTION.getMessage())); + + // 페이지네이션 설정 + Pageable pageable = PageRequest.of(page - 1, size); + + // 읽은 책 목록 조회 + Page doneReadBookshelfPage = doneReadBookshelfRepository.findByMemberOrderByCreatedAtDesc(member, pageable); + + List books = doneReadBookshelfPage.getContent().stream() + .map(this::convertToItemDTO) + .toList(); + + // 페이지네이션 정보 계산 + int total = (int) doneReadBookshelfPage.getTotalElements(); + int totalPages = doneReadBookshelfPage.getTotalPages(); + boolean isLast = doneReadBookshelfPage.isLast(); + + return DoneReadBookshelfResponseDTO.builder() + .total(total) + .page(page) + .size(size) + .totalPages(totalPages) + .isLast(isLast) + .books(books) + .build(); + } + + private DoneReadBookshelfItemDTO convertToItemDTO(DoneReadBookshelf doneReadBookshelf) { + Book book = doneReadBookshelf.getArticle().getBook(); + + return DoneReadBookshelfItemDTO.builder() + .articleId(doneReadBookshelf.getArticle().getId()) + .isbn(book.getIsbn()) + .title(book.getTitle()) + .author(book.getAuthor()) + .bookImage(book.getBookImage()) + .publisher(book.getPublisher()) + .description(book.getDescription()) + .pubdate(book.getPubdate()) + .ratingAverage(book.getRatingAverage()) + .ratingCount(book.getRatingCount()) + .weight(doneReadBookshelf.getWeight()) + .height(doneReadBookshelf.getHeight()) + .build(); + } +} + diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/service/WishReadBookshelfService.java b/src/main/java/com/moongeul/backend/api/bookshelf/service/WishReadBookshelfService.java index dca9172..cb25ceb 100644 --- a/src/main/java/com/moongeul/backend/api/bookshelf/service/WishReadBookshelfService.java +++ b/src/main/java/com/moongeul/backend/api/bookshelf/service/WishReadBookshelfService.java @@ -1,7 +1,9 @@ package com.moongeul.backend.api.bookshelf.service; +import com.moongeul.backend.api.book.dto.BookDTO; import com.moongeul.backend.api.book.entity.Book; import com.moongeul.backend.api.book.repository.BookRepository; +import com.moongeul.backend.api.bookshelf.dto.WishReadBookshelfResponseDTO; import com.moongeul.backend.api.bookshelf.entity.WishReadBookshelf; import com.moongeul.backend.api.bookshelf.repository.WishReadBookshelfRepository; import com.moongeul.backend.api.member.entity.Member; @@ -11,9 +13,14 @@ import com.moongeul.backend.common.response.ErrorStatus; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Slf4j @Service @RequiredArgsConstructor @@ -23,6 +30,7 @@ public class WishReadBookshelfService { private final MemberRepository memberRepository; private final BookRepository bookRepository; + // 읽고 싶은 책장 등록 @Transactional public void addWishReadBook(String email, String isbn) { @@ -62,5 +70,51 @@ public void removeWishReadBook(String email, String isbn) { // 읽고 싶은 책 삭제 wishReadBookshelfRepository.deleteByMemberAndBook(member, book); } + + // 읽고 싶은 책장 전체 조회 + @Transactional(readOnly = true) + public WishReadBookshelfResponseDTO getWishReadBooks(String email, Integer page, Integer size) { + + Member member = memberRepository.findByEmail(email) + .orElseThrow(() -> new NotFoundException(ErrorStatus.USER_NOTFOUND_EXCEPTION.getMessage())); + + // 페이지네이션 설정 + Pageable pageable = PageRequest.of(page - 1, size); + + // 읽고 싶은 책 목록 조회 + Page wishReadBookshelfPage = wishReadBookshelfRepository.findByMemberOrderByCreatedAtDesc(member, pageable); + + List books = wishReadBookshelfPage.getContent().stream() + .map(wishReadBookshelf -> convertToBookDTO(wishReadBookshelf.getBook())) + .toList(); + + // 페이지네이션 정보 계산 + int total = (int) wishReadBookshelfPage.getTotalElements(); + int totalPages = wishReadBookshelfPage.getTotalPages(); + boolean isLast = wishReadBookshelfPage.isLast(); + + return WishReadBookshelfResponseDTO.builder() + .total(total) + .page(page) + .size(size) + .totalPages(totalPages) + .isLast(isLast) + .books(books) + .build(); + } + + private BookDTO convertToBookDTO(Book book) { + return BookDTO.builder() + .isbn(book.getIsbn()) + .title(book.getTitle()) + .author(book.getAuthor()) + .bookImage(book.getBookImage()) + .publisher(book.getPublisher()) + .description(book.getDescription()) + .pubdate(book.getPubdate()) + .ratingAverage(book.getRatingAverage()) + .ratingCount(book.getRatingCount()) + .build(); + } } 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 94aeb33..64325d2 100644 --- a/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java +++ b/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java @@ -26,6 +26,8 @@ public enum SuccessStatus { /* BOOKSHELF */ ADD_WISH_READ_BOOK_SUCCESS(HttpStatus.OK, "읽고 싶은 책 등록 성공"), REMOVE_WISH_READ_BOOK_SUCCESS(HttpStatus.OK, "읽고 싶은 책 삭제 성공"), + GET_WISH_READ_BOOKS_SUCCESS(HttpStatus.OK, "읽고 싶은 책장 조회 성공"), + GET_DONE_READ_BOOKS_SUCCESS(HttpStatus.OK, "읽은 책장 조회 성공"), /* POST */ CREATE_POST_SUCCESS(HttpStatus.OK, "글쓰기 성공"), From b556c5ad60483833f1bc658cee424aa0bb1ba945 Mon Sep 17 00:00:00 2001 From: rhkr8521 Date: Sat, 27 Dec 2025 10:30:43 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[REFAC]=20=EC=9D=91=EB=8B=B5=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=97=AC=EB=9F=AC?= =?UTF-8?q?=20=EA=B8=B0=EB=A1=9D=20=EA=B4=80=EB=A6=AC=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/DoneReadBookshelfItemDTO.java | 6 +---- .../bookshelf/entity/DoneReadBookshelf.java | 14 +++++++++- .../DoneReadBookshelfRepository.java | 6 +++++ .../service/DoneReadBookshelfService.java | 6 +---- .../service/WishReadBookshelfService.java | 5 ---- .../backend/api/post/service/PostService.java | 26 +++++++++++++------ 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfItemDTO.java b/src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfItemDTO.java index 8385b4a..fb1d9bd 100644 --- a/src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfItemDTO.java +++ b/src/main/java/com/moongeul/backend/api/bookshelf/dto/DoneReadBookshelfItemDTO.java @@ -13,14 +13,10 @@ public class DoneReadBookshelfItemDTO { private Long articleId; // 게시글 ID private String isbn; // ISBN private String title; // 책 제목 - private String author; // 저자 - private String bookImage; // 표지 이미지 - private String publisher; // 출판사 - private String description; // 책 소개 - private String pubdate; // 출판연도 private Double ratingAverage; // 별점 평균 private Integer ratingCount; // 별점 개수 private Float weight; // 두께 private Float height; // 높이 + private Integer postCount; // 해당 책에 대한 게시글 개수 } diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/entity/DoneReadBookshelf.java b/src/main/java/com/moongeul/backend/api/bookshelf/entity/DoneReadBookshelf.java index 9e44f3a..b7f49cd 100644 --- a/src/main/java/com/moongeul/backend/api/bookshelf/entity/DoneReadBookshelf.java +++ b/src/main/java/com/moongeul/backend/api/bookshelf/entity/DoneReadBookshelf.java @@ -28,12 +28,24 @@ public class DoneReadBookshelf extends BaseTimeEntity { @Column(name = "height") private Float height; // 별점 기반 높낮이 + @Column(name = "post_count", nullable = false) + @Builder.Default + private Integer postCount = 1; // 해당 책에 대한 게시글 개수 + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "article_id", nullable = false) - private Post article; // 게시글 + private Post article; // 가장 최근 게시글 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", nullable = false) private Member member; // 회원 + + // 읽은책장 업데이트 (가장 최근 게시글로 변경, 게시글 개수 증가) + public void updateWithNewPost(Post newPost, Float weight, Float height) { + this.article = newPost; + this.weight = weight; + this.height = height; + this.postCount = this.postCount + 1; + } } diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java b/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java index 8ee2c7b..cd897df 100644 --- a/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java +++ b/src/main/java/com/moongeul/backend/api/bookshelf/repository/DoneReadBookshelfRepository.java @@ -1,5 +1,6 @@ package com.moongeul.backend.api.bookshelf.repository; +import com.moongeul.backend.api.book.entity.Book; import com.moongeul.backend.api.bookshelf.entity.DoneReadBookshelf; import com.moongeul.backend.api.member.entity.Member; import org.springframework.data.domain.Page; @@ -8,8 +9,13 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.Optional; + public interface DoneReadBookshelfRepository extends JpaRepository { @Query("SELECT d FROM DoneReadBookshelf d WHERE d.member = :member ORDER BY d.createdAt DESC") Page findByMemberOrderByCreatedAtDesc(@Param("member") Member member, Pageable pageable); + + @Query("SELECT d FROM DoneReadBookshelf d WHERE d.member = :member AND d.article.book = :book") + Optional findByMemberAndBook(@Param("member") Member member, @Param("book") Book book); } diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/service/DoneReadBookshelfService.java b/src/main/java/com/moongeul/backend/api/bookshelf/service/DoneReadBookshelfService.java index 3f47614..9eb25f6 100644 --- a/src/main/java/com/moongeul/backend/api/bookshelf/service/DoneReadBookshelfService.java +++ b/src/main/java/com/moongeul/backend/api/bookshelf/service/DoneReadBookshelfService.java @@ -65,15 +65,11 @@ private DoneReadBookshelfItemDTO convertToItemDTO(DoneReadBookshelf doneReadBook .articleId(doneReadBookshelf.getArticle().getId()) .isbn(book.getIsbn()) .title(book.getTitle()) - .author(book.getAuthor()) - .bookImage(book.getBookImage()) - .publisher(book.getPublisher()) - .description(book.getDescription()) - .pubdate(book.getPubdate()) .ratingAverage(book.getRatingAverage()) .ratingCount(book.getRatingCount()) .weight(doneReadBookshelf.getWeight()) .height(doneReadBookshelf.getHeight()) + .postCount(doneReadBookshelf.getPostCount()) .build(); } } diff --git a/src/main/java/com/moongeul/backend/api/bookshelf/service/WishReadBookshelfService.java b/src/main/java/com/moongeul/backend/api/bookshelf/service/WishReadBookshelfService.java index cb25ceb..24073e2 100644 --- a/src/main/java/com/moongeul/backend/api/bookshelf/service/WishReadBookshelfService.java +++ b/src/main/java/com/moongeul/backend/api/bookshelf/service/WishReadBookshelfService.java @@ -107,11 +107,6 @@ private BookDTO convertToBookDTO(Book book) { return BookDTO.builder() .isbn(book.getIsbn()) .title(book.getTitle()) - .author(book.getAuthor()) - .bookImage(book.getBookImage()) - .publisher(book.getPublisher()) - .description(book.getDescription()) - .pubdate(book.getPubdate()) .ratingAverage(book.getRatingAverage()) .ratingCount(book.getRatingCount()) .build(); 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 924ed26..1ea12af 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 @@ -48,18 +48,28 @@ public PostCreateResponseDTO createPost(PostCreateRequestDTO postCreateRequestDT Post newPost = postCreateRequestDTO.toEntity(category, member, book); Post savedPost = postRepository.save(newPost); - // 읽은 책 책장에 등록 + // 읽은 책 책장에 등록 또는 업데이트 Float weight = bookshelfCalculator.calculateWeight(savedPost.getPage()); Float height = bookshelfCalculator.calculateHeight(savedPost.getRating()); - DoneReadBookshelf doneReadBookshelf = DoneReadBookshelf.builder() - .weight(weight) - .height(height) - .article(savedPost) - .member(member) - .build(); + // 기존 읽은 책이 있는지 확인 + DoneReadBookshelf doneReadBookshelf = doneReadBookshelfRepository.findByMemberAndBook(member, book) + .orElse(null); - doneReadBookshelfRepository.save(doneReadBookshelf); + if (doneReadBookshelf != null) { + // 기존 책장이 있으면 업데이트 (가장 최근 게시글로 변경, 게시글 개수 증가) + doneReadBookshelf.updateWithNewPost(savedPost, weight, height); + } else { + // 기존 책장이 없으면 새로 생성 + doneReadBookshelf = DoneReadBookshelf.builder() + .weight(weight) + .height(height) + .postCount(1) + .article(savedPost) + .member(member) + .build(); + doneReadBookshelfRepository.save(doneReadBookshelf); + } return PostCreateResponseDTO.builder() .postId(savedPost.getId())