Skip to content

Commit

Permalink
Merge pull request #102 from reading-log/develop
Browse files Browse the repository at this point in the history
04.11 MAIN MERGE
  • Loading branch information
don9m1n authored Apr 11, 2024
2 parents 3df2415 + a63809f commit 9484632
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -36,6 +43,15 @@ public class BookController {

private final BookService bookService;

@GetMapping("/me")
public Response<Page<BookResponse>> myBookList(@AuthenticationPrincipal CustomUserDetail user,
@RequestParam(required = false) String keyword,
@PageableDefault(sort = "createdAt", direction = DESC) Pageable pageable) {

Page<BookResponse> response = bookService.myBookList(user.getId(), keyword, pageable);
return Response.success(HttpStatus.OK, "내가 등록한 책 목록 조회 성공", response);
}

@Operation(summary = "등록한 책 조회", description = "사용자가 등록한 책 정보를 조회합니다.")
@GetMapping("/{bookId}")
public Response<BookDetailResponse> getBookInfo(@AuthenticationPrincipal CustomUserDetail user, @PathVariable Long bookId) {
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/api/readinglog/domain/book/dto/BookResponse.java
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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> item = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<BookResponse> myBookList(Long memberId, String keyword, Pageable pageable);

Long getBookTotalCountInMonth(Long memberId, int month);

List<BookCategoryResponse> getBookCountGroupByCategoryInMonth(Long memberId, int month);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,56 @@

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{

private final JPAQueryFactory queryFactory;

@Override
public Long getBookTotalCountInMonth(long memberId, int month) {
public Page<BookResponse> myBookList(Long memberId, String keyword, Pageable pageable) {

List<BookResponse> 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)
Expand Down Expand Up @@ -43,4 +81,18 @@ public List<BookCalendarResponse> getBookCalendarInMonth(Long memberId, int mont
.orderBy(book.createdAt.asc())
.fetch();
}

private OrderSpecifier<?>[] getAllOrderSpecifiers(Pageable pageable) {
List<OrderSpecifier<?>> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
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;
import com.api.readinglog.domain.member.entity.Member;
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;
Expand All @@ -32,6 +35,12 @@ public class BookService {
private final MemberService memberService;
private final AmazonS3Service amazonS3Service;

@Transactional(readOnly = true)
public Page<BookResponse> 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);
Expand All @@ -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)
Expand All @@ -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;
}

// 책 자동 등록
Expand Down Expand Up @@ -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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,33 @@
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;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
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;
import org.springframework.http.HttpStatus;
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 {

Expand All @@ -40,19 +44,56 @@ public class BookLogController {
@GetMapping("/{bookId}/me")
public Response<BookLogResponse> 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<Page<SummaryResponse>> bookLogs(@PageableDefault(sort = "createdAt", direction = Direction.DESC)
Pageable pageable) {
// TODO: querydsl 동적 쿼리 처리
return Response.success(HttpStatus.OK, "북로그 조회 성공", bookLogService.bookLogs(pageable));
public Response<SummaryPageResponse> 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<BookLogResponse> 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<SummaryPageResponse> 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<SummaryPageResponse> findBookLogsByCategory(@RequestBody BookLogsSearchByCategoryRequest request,
@PageableDefault(sort = "createdAt", direction = Direction.DESC) Pageable pageable) {
return Response.success(HttpStatus.OK, "북로그 책 카테고리명 검색 성공",
bookLogService.findBookLogsByCategory(request, pageable));
}
}
Original file line number Diff line number Diff line change
@@ -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;

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

0 comments on commit 9484632

Please sign in to comment.