diff --git a/src/main/java/com/api/readinglog/domain/book/controller/BookController.java b/src/main/java/com/api/readinglog/domain/book/controller/BookController.java index d3c2737..1bf1548 100644 --- a/src/main/java/com/api/readinglog/domain/book/controller/BookController.java +++ b/src/main/java/com/api/readinglog/domain/book/controller/BookController.java @@ -1,10 +1,13 @@ package com.api.readinglog.domain.book.controller; +import static org.springframework.data.domain.Sort.Direction.DESC; + import com.api.readinglog.common.response.Response; import com.api.readinglog.common.security.CustomUserDetail; import com.api.readinglog.domain.book.dto.BookDetailResponse; import com.api.readinglog.domain.book.dto.BookDirectRequest; import com.api.readinglog.domain.book.dto.BookRegisterRequest; +import com.api.readinglog.domain.book.dto.BookResponse; import com.api.readinglog.domain.book.dto.BookSearchApiResponse; import com.api.readinglog.domain.book.dto.BookModifyRequest; import com.api.readinglog.domain.book.service.BookService; @@ -13,6 +16,10 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -36,6 +43,15 @@ public class BookController { private final BookService bookService; + @GetMapping("/me") + public Response> myBookList(@AuthenticationPrincipal CustomUserDetail user, + @RequestParam(required = false) String keyword, + @PageableDefault(sort = "createdAt", direction = DESC) Pageable pageable) { + + Page response = bookService.myBookList(user.getId(), keyword, pageable); + return Response.success(HttpStatus.OK, "내가 등록한 책 목록 조회 성공", response); + } + @Operation(summary = "등록한 책 조회", description = "사용자가 등록한 책 정보를 조회합니다.") @GetMapping("/{bookId}") public Response getBookInfo(@AuthenticationPrincipal CustomUserDetail user, @PathVariable Long bookId) { diff --git a/src/main/java/com/api/readinglog/domain/book/dto/BookResponse.java b/src/main/java/com/api/readinglog/domain/book/dto/BookResponse.java new file mode 100644 index 0000000..a768953 --- /dev/null +++ b/src/main/java/com/api/readinglog/domain/book/dto/BookResponse.java @@ -0,0 +1,18 @@ +package com.api.readinglog.domain.book.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class BookResponse { + + private Long bookId; + private Long memberId; + private String cover; + private String title; + private String author; + private LocalDateTime createdAt; + +} diff --git a/src/main/java/com/api/readinglog/domain/book/dto/BookSearchApiResponse.java b/src/main/java/com/api/readinglog/domain/book/dto/BookSearchApiResponse.java index 3a39de5..a622713 100644 --- a/src/main/java/com/api/readinglog/domain/book/dto/BookSearchApiResponse.java +++ b/src/main/java/com/api/readinglog/domain/book/dto/BookSearchApiResponse.java @@ -10,8 +10,10 @@ public class BookSearchApiResponse { private int totalResults; // 전체 데이터 개수 - private int startIndex; // 시작 페이지 private int itemsPerPage; // 페이지당 아이템 개수 + private int totalPage; // 전체 페이지수 + private int startIndex; // 시작 페이지 + private boolean hasNext; // 다음 페이지 존재 여부 private String query; // 검색어 private List item = new ArrayList<>(); diff --git a/src/main/java/com/api/readinglog/domain/book/repository/CustomBookRepository.java b/src/main/java/com/api/readinglog/domain/book/repository/CustomBookRepository.java index 084f35e..3ec0bd5 100644 --- a/src/main/java/com/api/readinglog/domain/book/repository/CustomBookRepository.java +++ b/src/main/java/com/api/readinglog/domain/book/repository/CustomBookRepository.java @@ -2,11 +2,16 @@ import com.api.readinglog.domain.book.dto.BookCalendarResponse; import com.api.readinglog.domain.book.dto.BookCategoryResponse; +import com.api.readinglog.domain.book.dto.BookResponse; import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; public interface CustomBookRepository { - Long getBookTotalCountInMonth(long memberId, int month); + Page myBookList(Long memberId, String keyword, Pageable pageable); + + Long getBookTotalCountInMonth(Long memberId, int month); List getBookCountGroupByCategoryInMonth(Long memberId, int month); diff --git a/src/main/java/com/api/readinglog/domain/book/repository/CustomBookRepositoryImpl.java b/src/main/java/com/api/readinglog/domain/book/repository/CustomBookRepositoryImpl.java index 2845f8f..4303116 100644 --- a/src/main/java/com/api/readinglog/domain/book/repository/CustomBookRepositoryImpl.java +++ b/src/main/java/com/api/readinglog/domain/book/repository/CustomBookRepositoryImpl.java @@ -4,10 +4,20 @@ import com.api.readinglog.domain.book.dto.BookCalendarResponse; import com.api.readinglog.domain.book.dto.BookCategoryResponse; +import com.api.readinglog.domain.book.dto.BookResponse; +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; @RequiredArgsConstructor public class CustomBookRepositoryImpl implements CustomBookRepository{ @@ -15,7 +25,35 @@ public class CustomBookRepositoryImpl implements CustomBookRepository{ private final JPAQueryFactory queryFactory; @Override - public Long getBookTotalCountInMonth(long memberId, int month) { + public Page myBookList(Long memberId, String keyword, Pageable pageable) { + + List response = queryFactory.select(Projections.constructor( + BookResponse.class, book.id, book.member.id, book.cover, book.title, book.author, book.createdAt) + ) + .from(book) + .where(book.member.id.eq(memberId), containKeyword(keyword)) + .orderBy(getAllOrderSpecifiers(pageable)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + Long count = queryFactory.select(book.count()) + .from(book) + .where(book.member.id.eq(memberId), containKeyword(keyword)) + .fetchFirst(); + + return new PageImpl<>(response, pageable, count); + } + + private BooleanExpression containKeyword(String keyword) { + if(keyword == null || keyword.isEmpty()) { + return null; + } + return book.title.containsIgnoreCase(keyword); + } + + @Override + public Long getBookTotalCountInMonth(Long memberId, int month) { return queryFactory .select(book.count()) .from(book) @@ -43,4 +81,18 @@ public List getBookCalendarInMonth(Long memberId, int mont .orderBy(book.createdAt.asc()) .fetch(); } + + private OrderSpecifier[] getAllOrderSpecifiers(Pageable pageable) { + List> orderSpecifierList = new ArrayList<>(); + + for (Sort.Order order : pageable.getSort()) { + Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC; + + switch (order.getProperty()) { + case "createdAt" -> orderSpecifierList.add(new OrderSpecifier<>(direction, book.createdAt)); + } + } + + return orderSpecifierList.toArray(OrderSpecifier[]::new); + } } diff --git a/src/main/java/com/api/readinglog/domain/book/service/BookService.java b/src/main/java/com/api/readinglog/domain/book/service/BookService.java index a298772..9b2881d 100644 --- a/src/main/java/com/api/readinglog/domain/book/service/BookService.java +++ b/src/main/java/com/api/readinglog/domain/book/service/BookService.java @@ -10,6 +10,7 @@ import com.api.readinglog.domain.book.dto.BookDirectRequest; import com.api.readinglog.domain.book.dto.BookModifyRequest; import com.api.readinglog.domain.book.dto.BookRegisterRequest; +import com.api.readinglog.domain.book.dto.BookResponse; import com.api.readinglog.domain.book.dto.BookSearchApiResponse; import com.api.readinglog.domain.book.entity.Book; import com.api.readinglog.domain.book.repository.BookRepository; @@ -17,6 +18,8 @@ import com.api.readinglog.domain.member.service.MemberService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.reactive.function.client.WebClient; @@ -32,6 +35,12 @@ public class BookService { private final MemberService memberService; private final AmazonS3Service amazonS3Service; + @Transactional(readOnly = true) + public Page myBookList(Long memberId, String keyword, Pageable pageable) { + Member member = memberService.getMemberById(memberId); + return bookRepository.myBookList(memberId, keyword, pageable); + } + @Transactional(readOnly = true) public BookDetailResponse getBookInfo(Long memberId, Long bookId) { Member member = memberService.getMemberById(memberId); @@ -52,7 +61,7 @@ public BookSearchApiResponse searchBooks(String query, int start) { throw new BookException(ErrorCode.EMPTY_SEARCH_KEYWORD); } - return webClient.get() + BookSearchApiResponse response = webClient.get() .uri(uriBuilder -> uriBuilder .path("/ItemSearch.aspx") .queryParam("Query", query) @@ -66,6 +75,20 @@ public BookSearchApiResponse searchBooks(String query, int start) { .retrieve() .bodyToMono(BookSearchApiResponse.class) .block(); + + // 다음 페이지 존재 여부 + int totalResults = response.getTotalResults(); + int itemsPerPage = response.getItemsPerPage(); + int totalPage = totalResults / itemsPerPage == 0 ? totalResults / itemsPerPage : (totalResults / itemsPerPage) + 1; + int startIndex = response.getStartIndex(); // 현재 페이지 + + // 현재 페이지가 전체 페이지보다 작으면 다음 페이지 존재 + if (startIndex < totalPage) { + response.setHasNext(true); + } + response.setTotalPage(totalPage); + + return response; } // 책 자동 등록 @@ -111,4 +134,5 @@ public void deleteBook(Long memberId, Long bookId) { public Book getBookById(Long bookId) { return bookRepository.findById(bookId).orElseThrow(() -> new BookException(ErrorCode.NOT_FOUND_BOOK)); } + } diff --git a/src/main/java/com/api/readinglog/domain/booklog/controller/BookLogController.java b/src/main/java/com/api/readinglog/domain/booklog/controller/BookLogController.java index 9f0549f..7e72070 100644 --- a/src/main/java/com/api/readinglog/domain/booklog/controller/BookLogController.java +++ b/src/main/java/com/api/readinglog/domain/booklog/controller/BookLogController.java @@ -3,8 +3,10 @@ import com.api.readinglog.common.response.Response; import com.api.readinglog.common.security.CustomUserDetail; import com.api.readinglog.domain.booklog.controller.dto.BookLogResponse; +import com.api.readinglog.domain.booklog.controller.dto.request.BookLogsSearchByBookTitleRequest; +import com.api.readinglog.domain.booklog.controller.dto.request.BookLogsSearchByCategoryRequest; import com.api.readinglog.domain.booklog.service.BookLogService; -import com.api.readinglog.domain.summary.controller.dto.response.SummaryResponse; +import com.api.readinglog.domain.summary.controller.dto.response.SummaryPageResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -12,7 +14,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.web.PageableDefault; @@ -20,12 +22,14 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Tag(name = "BookLogs", description = "북로그 API 목록입니다.") @RestController @RequiredArgsConstructor +@Slf4j @RequestMapping("/api/book-logs") public class BookLogController { @@ -40,19 +44,56 @@ public class BookLogController { @GetMapping("/{bookId}/me") public Response myLogs(@AuthenticationPrincipal CustomUserDetail user, @PathVariable Long bookId) { - return Response.success(HttpStatus.OK, "나의 로그 조회 성공", bookLogService.myLogs(user.getId(), bookId)); + return Response.success(HttpStatus.OK, "나의 로그 조회 성공", bookLogService.bookLogDetails(user.getId(), bookId)); } - @Operation(summary = "북로그 조회", description = "리딩 로그 서비스의 모든 북로그를 조회합니다. 비회원도 조회가 가능합니다.") + @Operation(summary = "북로그 목록 조회", description = "리딩 로그 서비스의 전체 북로그 목록을 조회합니다. 기본값은 최신순 정렬입니다. 정렬 조건을 추가하면 인기순 정렬이 가능합니다. 비회원도 조회가 가능합니다.") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "북로그 조회 성공", + @ApiResponse(responseCode = "200", description = "북로그 목록 조회 성공", content = {@Content(schema = @Schema(implementation = Response.class))}), @ApiResponse(responseCode = "404", description = "북로그 목록이 존재하지 않습니다!") }) @GetMapping - public Response> bookLogs(@PageableDefault(sort = "createdAt", direction = Direction.DESC) - Pageable pageable) { - // TODO: querydsl 동적 쿼리 처리 - return Response.success(HttpStatus.OK, "북로그 조회 성공", bookLogService.bookLogs(pageable)); + public Response bookLogs( + @PageableDefault(sort = "createdAt", direction = Direction.DESC) Pageable pageable) { + return Response.success(HttpStatus.OK, "북로그 목록 조회 성공", bookLogService.bookLogs(pageable)); + } + + @Operation(summary = "북로그 상세 조회", description = "북로그 상세 조회입니다. 회원과 책 ID값을 통해 조회할 수 있습니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "북로그 상세 조회 성공", + content = {@Content(schema = @Schema(implementation = Response.class))}), + @ApiResponse(responseCode = "400", description = "북로그 상세 조회 실패") + }) + @GetMapping("/{bookId}/{memberId}") + public Response bookLogsDetails(@PathVariable Long bookId, + @PathVariable Long memberId) { + return Response.success(HttpStatus.OK, "북로그 상세 조회 성공", bookLogService.bookLogDetails(memberId, bookId)); + } + + @Operation(summary = "북로그 책 제목으로 검색", description = "책 제목을 통해 북로그 목록을 조회합니다. 기본값은 최신순 정렬입니다. 정렬 조건을 추가하면 인기순 정렬이 가능합니다. 비회원도 조회가 가능합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "북로그 책 제목 검색 성공", + content = {@Content(schema = @Schema(implementation = Response.class))}), + @ApiResponse(responseCode = "404", description = "북로그 목록이 존재하지 않습니다!") + }) + @GetMapping("/search/title") + public Response findBookLogsByBookTitle(@RequestBody BookLogsSearchByBookTitleRequest request, + @PageableDefault(sort = "createdAt", direction = Direction.DESC) Pageable pageable) { + return Response.success(HttpStatus.OK, "북로그 책 제목 검색 성공", + bookLogService.findBookLogsByBookTitle(request, pageable)); + } + + @Operation(summary = "북로그 책 카테고리명으로 검색", description = "책 카테고리명을 통해 북로그 목록을 조회합니다. 기본값은 최신순 정렬입니다. 정렬 조건을 추가하면 인기순 정렬이 가능합니다. 비회원도 조회가 가능합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "북로그 책 카테고리명 검색 성공", + content = {@Content(schema = @Schema(implementation = Response.class))}), + @ApiResponse(responseCode = "404", description = "북로그 목록이 존재하지 않습니다!") + }) + @GetMapping("/search/category") + public Response findBookLogsByCategory(@RequestBody BookLogsSearchByCategoryRequest request, + @PageableDefault(sort = "createdAt", direction = Direction.DESC) Pageable pageable) { + return Response.success(HttpStatus.OK, "북로그 책 카테고리명 검색 성공", + bookLogService.findBookLogsByCategory(request, pageable)); } } \ No newline at end of file diff --git a/src/main/java/com/api/readinglog/domain/booklog/controller/dto/request/BookLogsSearchByBookTitleRequest.java b/src/main/java/com/api/readinglog/domain/booklog/controller/dto/request/BookLogsSearchByBookTitleRequest.java new file mode 100644 index 0000000..0b36783 --- /dev/null +++ b/src/main/java/com/api/readinglog/domain/booklog/controller/dto/request/BookLogsSearchByBookTitleRequest.java @@ -0,0 +1,12 @@ +package com.api.readinglog.domain.booklog.controller.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class BookLogsSearchByBookTitleRequest { + + @Schema(description = "책 제목 검색어") + private String bookTitle; + +} diff --git a/src/main/java/com/api/readinglog/domain/booklog/controller/dto/request/BookLogsSearchByCategoryRequest.java b/src/main/java/com/api/readinglog/domain/booklog/controller/dto/request/BookLogsSearchByCategoryRequest.java new file mode 100644 index 0000000..388dd32 --- /dev/null +++ b/src/main/java/com/api/readinglog/domain/booklog/controller/dto/request/BookLogsSearchByCategoryRequest.java @@ -0,0 +1,11 @@ +package com.api.readinglog.domain.booklog.controller.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class BookLogsSearchByCategoryRequest { + + @Schema(description = "카테고리명 검색어") + private String categoryName; +} diff --git a/src/main/java/com/api/readinglog/domain/booklog/service/BookLogService.java b/src/main/java/com/api/readinglog/domain/booklog/service/BookLogService.java index 58dc700..59c76f0 100644 --- a/src/main/java/com/api/readinglog/domain/booklog/service/BookLogService.java +++ b/src/main/java/com/api/readinglog/domain/booklog/service/BookLogService.java @@ -6,6 +6,8 @@ import com.api.readinglog.domain.book.entity.Book; import com.api.readinglog.domain.book.service.BookService; import com.api.readinglog.domain.booklog.controller.dto.BookLogResponse; +import com.api.readinglog.domain.booklog.controller.dto.request.BookLogsSearchByBookTitleRequest; +import com.api.readinglog.domain.booklog.controller.dto.request.BookLogsSearchByCategoryRequest; import com.api.readinglog.domain.highlight.controller.dto.response.HighlightResponse; import com.api.readinglog.domain.highlight.repository.HighlightRepository; import com.api.readinglog.domain.likesummary.service.LikeSummaryService; @@ -14,18 +16,23 @@ import com.api.readinglog.domain.review.controller.dto.response.ReviewResponse; import com.api.readinglog.domain.review.repository.ReviewRepository; import com.api.readinglog.domain.summary.controller.dto.response.MySummaryResponse; +import com.api.readinglog.domain.summary.controller.dto.response.SummaryPageResponse; import com.api.readinglog.domain.summary.controller.dto.response.SummaryResponse; +import com.api.readinglog.domain.summary.entity.Summary; import com.api.readinglog.domain.summary.repository.SummaryRepository; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor +@Transactional +@Slf4j public class BookLogService { private final MemberService memberService; @@ -35,8 +42,9 @@ public class BookLogService { private final HighlightRepository highlightRepository; private final LikeSummaryService likeSummaryService; + // 북로그 상세 조회 @Transactional(readOnly = true) - public BookLogResponse myLogs(Long memberId, Long bookId) { + public BookLogResponse bookLogDetails(Long memberId, Long bookId) { Member member = getMember(memberId); Book book = getBook(bookId); BookDetailResponse bookDetailResponse = bookService.getBookInfo(memberId, bookId); @@ -51,17 +59,59 @@ public BookLogResponse myLogs(Long memberId, Long bookId) { return BookLogResponse.of(bookDetailResponse, summary, reviews, highlights); } + // 북로그 전체 목록 조회 @Transactional(readOnly = true) - public Page bookLogs(Pageable pageable) { - Page bookLogs = summaryRepository.findAllBy(pageable) - .map(summary -> SummaryResponse.fromEntity(summary, likeSummaryService.getSummaryLikeCount(summary.getId()))); + public SummaryPageResponse bookLogs(Pageable pageable) { + Slice summaries = summaryRepository.findAllBy(pageable); // 북로그가 존재하지 않는 경우 예외 처리 - if (bookLogs.getContent().isEmpty()) { + if (summaries.getContent().isEmpty()) { throw new SummaryException(ErrorCode.NOT_FOUND_BOOK_LOGS); } - return bookLogs; + List bookLogs = summaries.getContent().stream() + .map(summary -> SummaryResponse.fromEntity(summary, + likeSummaryService.getSummaryLikeCount(summary.getId()))) + .collect(Collectors.toList()); + + return SummaryPageResponse.fromSlice(bookLogs, summaries.hasNext()); + } + + // 책 제목으로 검색 + public SummaryPageResponse findBookLogsByBookTitle(BookLogsSearchByBookTitleRequest request, + Pageable pageable) { + List summaries = summaryRepository.findByBookTitle(request.getBookTitle(), pageable); + return createSummaryPageResponse(summaries, pageable); + } + + // 카테고리명으로 검색 + public SummaryPageResponse findBookLogsByCategory(BookLogsSearchByCategoryRequest request, + Pageable pageable) { + List summaries = summaryRepository.findByCategoryName(request.getCategoryName(), pageable); + return createSummaryPageResponse(summaries, pageable); + } + + private SummaryPageResponse createSummaryPageResponse(List summaries, Pageable pageable) { + // 북로그가 존재하지 않는 경우 예외 처리 + if (summaries.isEmpty()) { + throw new SummaryException(ErrorCode.NOT_FOUND_BOOK_LOGS); + } + + List content = buildSummaryResponse(summaries); + + // 마지막 페이지 여부 판별 + boolean hasNext = content.size() > pageable.getPageSize(); + if (hasNext) { + content = content.subList(0, pageable.getPageSize()); + } + return SummaryPageResponse.fromSlice(content, hasNext); + } + + private List buildSummaryResponse(List summaries) { + return summaries.stream() + .map(summary -> SummaryResponse.fromEntity(summary, + likeSummaryService.getSummaryLikeCount(summary.getId()))) + .collect(Collectors.toList()); } private Member getMember(Long memberId) { diff --git a/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/MySummaryResponse.java b/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/MySummaryResponse.java index 6ab782f..f59174e 100644 --- a/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/MySummaryResponse.java +++ b/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/MySummaryResponse.java @@ -9,12 +9,14 @@ @Builder public class MySummaryResponse { + private String nickname; private String content; private LocalDateTime createdAt; private LocalDateTime modifiedAt; public static MySummaryResponse fromEntity(Summary summary) { return MySummaryResponse.builder() + .nickname(summary.getMember().getNickname()) .content(summary.getContent()) .createdAt(summary.getCreatedAt()) .modifiedAt(summary.getModifiedAt()) diff --git a/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/SummaryPageResponse.java b/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/SummaryPageResponse.java new file mode 100644 index 0000000..3df7d8c --- /dev/null +++ b/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/SummaryPageResponse.java @@ -0,0 +1,24 @@ +package com.api.readinglog.domain.summary.controller.dto.response; + +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class SummaryPageResponse { + private List content; + private boolean hasNext; + + @Builder + private SummaryPageResponse(List content, boolean hasNext) { + this.content = content; + this.hasNext = hasNext; + } + + public static SummaryPageResponse fromSlice(List content, boolean hasNext) { + return SummaryPageResponse.builder() + .content(content) + .hasNext(hasNext) + .build(); + } +} diff --git a/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/SummaryResponse.java b/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/SummaryResponse.java index 1a8a709..cd6c278 100644 --- a/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/SummaryResponse.java +++ b/src/main/java/com/api/readinglog/domain/summary/controller/dto/response/SummaryResponse.java @@ -9,7 +9,9 @@ @Builder public class SummaryResponse { + private Long memberId; private String nickname; + private Long bookId; private String bookTitle; private String bookAuthor; private String bookCover; @@ -19,7 +21,9 @@ public class SummaryResponse { public static SummaryResponse fromEntity(Summary summary, int likeCount) { return SummaryResponse.builder() + .memberId(summary.getMember().getId()) .nickname(summary.getMember().getNickname()) + .bookId(summary.getBook().getId()) .bookTitle(summary.getBook().getTitle()) .bookAuthor(summary.getBook().getAuthor()) .bookCover(summary.getBook().getCover()) diff --git a/src/main/java/com/api/readinglog/domain/summary/repository/CustomSummaryRepository.java b/src/main/java/com/api/readinglog/domain/summary/repository/CustomSummaryRepository.java new file mode 100644 index 0000000..42e3416 --- /dev/null +++ b/src/main/java/com/api/readinglog/domain/summary/repository/CustomSummaryRepository.java @@ -0,0 +1,12 @@ +package com.api.readinglog.domain.summary.repository; + +import com.api.readinglog.domain.summary.entity.Summary; +import java.util.List; +import org.springframework.data.domain.Pageable; + +public interface CustomSummaryRepository { + + List findByBookTitle(String bookTitle, Pageable pageable); + + List findByCategoryName(String categoryTitle, Pageable pageable); +} diff --git a/src/main/java/com/api/readinglog/domain/summary/repository/CustomSummaryRepositoryImpl.java b/src/main/java/com/api/readinglog/domain/summary/repository/CustomSummaryRepositoryImpl.java new file mode 100644 index 0000000..1178098 --- /dev/null +++ b/src/main/java/com/api/readinglog/domain/summary/repository/CustomSummaryRepositoryImpl.java @@ -0,0 +1,40 @@ +package com.api.readinglog.domain.summary.repository; + +import static com.api.readinglog.domain.book.entity.QBook.book; +import static com.api.readinglog.domain.summary.entity.QSummary.summary; + +import com.api.readinglog.domain.summary.entity.Summary; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; + +@RequiredArgsConstructor +public class CustomSummaryRepositoryImpl implements CustomSummaryRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public List findByBookTitle(String bookTitle, Pageable pageable) { + return queryFactory + .select(summary) + .from(summary) + .join(summary.book, book) + .where(book.title.contains(bookTitle)) // 검색어를 포함 + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) // 다음 페이지 존재 여부 판별 + .fetch(); + } + + @Override + public List findByCategoryName(String categoryTitle, Pageable pageable) { + return queryFactory + .select(summary) + .from(summary) + .join(summary.book, book) + .where(book.category.eq(categoryTitle)) // 검색어와 일치 + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) // 다음 페이지 존재 여부 판별 + .fetch(); + } +} diff --git a/src/main/java/com/api/readinglog/domain/summary/repository/SummaryRepository.java b/src/main/java/com/api/readinglog/domain/summary/repository/SummaryRepository.java index 6db3553..671602a 100644 --- a/src/main/java/com/api/readinglog/domain/summary/repository/SummaryRepository.java +++ b/src/main/java/com/api/readinglog/domain/summary/repository/SummaryRepository.java @@ -8,7 +8,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -public interface SummaryRepository extends JpaRepository { +public interface SummaryRepository extends JpaRepository, CustomSummaryRepository { Optional findByMemberAndBook(Member member, Book book);