Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -48,5 +49,21 @@ public ResponseEntity<ApiResponse<BookSearchResponseDTO>> 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<ApiResponse<BookDTO>> getBookDetail(
@PathVariable @NotBlank(message = "ISBN은 필수입니다.") String isbn) {

BookDTO bookDTO = bookService.getBookDetail(isbn);
return ApiResponse.success(SuccessStatus.GET_BOOK_DETAIL_SUCCESS, bookDTO);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ public class BookSearchRequestDTO {
private Integer size = 10; // 한 페이지당 개수 (기본값 10, 최대 100)
}


Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -37,6 +38,7 @@ public class BookService {
@Value("${naver.book.client-secret}")
private String clientSecret;

// 도서 검색 메서드 (with 네이버 도서)
@Transactional
public BookSearchResponseDTO searchBooks(BookSearchRequestDTO bookSearchRequestDTO) {
// 네이버 API 호출
Expand Down Expand Up @@ -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);
}
}

Original file line number Diff line number Diff line change
@@ -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<ApiResponse<DoneReadBookshelfResponseDTO>> 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);
}
}

Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
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;
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.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;
Expand Down Expand Up @@ -56,4 +60,23 @@ public ResponseEntity<ApiResponse<Void>> 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<ApiResponse<WishReadBookshelfResponseDTO>> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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 Double ratingAverage; // 별점 평균
private Integer ratingCount; // 별점 개수
private Float weight; // 두께
private Float height; // 높이
private Integer postCount; // 해당 책에 대한 게시글 개수
}

Original file line number Diff line number Diff line change
@@ -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<DoneReadBookshelfItemDTO> books; // 읽은 책 목록
}

Original file line number Diff line number Diff line change
@@ -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<BookDTO> books; // 읽고 싶은 책 목록
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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; // 별점 기반 높낮이

@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; // 가장 최근 게시글

@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;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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;
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;

public interface DoneReadBookshelfRepository extends JpaRepository<DoneReadBookshelf, Long> {
@Query("SELECT d FROM DoneReadBookshelf d WHERE d.member = :member ORDER BY d.createdAt DESC")
Page<DoneReadBookshelf> findByMemberOrderByCreatedAtDesc(@Param("member") Member member, Pageable pageable);

@Query("SELECT d FROM DoneReadBookshelf d WHERE d.member = :member AND d.article.book = :book")
Optional<DoneReadBookshelf> findByMemberAndBook(@Param("member") Member member, @Param("book") Book book);
}

Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -12,5 +16,8 @@ public interface WishReadBookshelfRepository extends JpaRepository<WishReadBooks

boolean existsByMemberAndBook(Member member, Book book);
void deleteByMemberAndBook(Member member, Book book);

@Query("SELECT w FROM WishReadBookshelf w WHERE w.member = :member ORDER BY w.createdAt DESC")
Page<WishReadBookshelf> findByMemberOrderByCreatedAtDesc(@Param("member") Member member, Pageable pageable);
}

Loading