diff --git a/src/main/java/com/jisungin/api/book/BookController.java b/src/main/java/com/jisungin/api/book/BookController.java index 5ff5ebe..60fc2f5 100644 --- a/src/main/java/com/jisungin/api/book/BookController.java +++ b/src/main/java/com/jisungin/api/book/BookController.java @@ -3,10 +3,12 @@ import com.jisungin.api.ApiResponse; import com.jisungin.api.book.request.BookCreateRequest; import com.jisungin.api.book.request.BookPageRequest; +import com.jisungin.api.oauth.Auth; import com.jisungin.application.PageResponse; import com.jisungin.application.book.BestSellerService; import com.jisungin.application.book.BookService; import com.jisungin.application.book.response.BestSellerResponse; +import com.jisungin.application.book.response.BookRelatedTalkRoomPageResponse; import com.jisungin.application.book.response.BookResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -31,9 +33,18 @@ public ApiResponse getBook(@PathVariable("isbn") String isbn) { return ApiResponse.ok(bookService.getBook(isbn)); } + @GetMapping("/books/{isbn}/talk-rooms") + public ApiResponse getTalkRoomsByRelatedBook( + @PathVariable("isbn") String isbn, + @ModelAttribute BookPageRequest request, + @Auth Long userId + ) { + return ApiResponse.ok(bookService.getBookRelatedTalkRooms(isbn, request.toService(), userId)); + } + @GetMapping("/books/best-seller") public ApiResponse> getBestSellers(@ModelAttribute BookPageRequest page) { - return ApiResponse.ok(bestSellerService.getBestSellers(page.toServiceRequest())); + return ApiResponse.ok(bestSellerService.getBestSellers(page.toService())); } @PostMapping("/books") diff --git a/src/main/java/com/jisungin/api/book/request/BookPageRequest.java b/src/main/java/com/jisungin/api/book/request/BookPageRequest.java index 9a9cade..c5c858f 100644 --- a/src/main/java/com/jisungin/api/book/request/BookPageRequest.java +++ b/src/main/java/com/jisungin/api/book/request/BookPageRequest.java @@ -11,8 +11,8 @@ @NoArgsConstructor public class BookPageRequest { - Integer page; - Integer size; + private Integer page; + private Integer size; @Builder private BookPageRequest(Integer page, Integer size) { @@ -20,7 +20,7 @@ private BookPageRequest(Integer page, Integer size) { this.size = size != null ? size : 5; } - public BookServicePageRequest toServiceRequest() { + public BookServicePageRequest toService() { return BookServicePageRequest.builder() .page(page) .size(size) diff --git a/src/main/java/com/jisungin/api/oauth/AuthArgumentResolver.java b/src/main/java/com/jisungin/api/oauth/AuthArgumentResolver.java index 717f46c..9df296a 100644 --- a/src/main/java/com/jisungin/api/oauth/AuthArgumentResolver.java +++ b/src/main/java/com/jisungin/api/oauth/AuthArgumentResolver.java @@ -34,10 +34,10 @@ public Object resolveArgument( NativeWebRequest webRequest, WebDataBinderFactory binderFactory ) throws Exception { - if (authContext.getUserId() == null) { +// if (authContext.getUserId() == null) { // TODO. 추후에 인증과 관련된 예외처리를 적용할 예정 - throw new BusinessException(UNAUTHORIZED_REQUEST); - } +// throw new BusinessException(UNAUTHORIZED_REQUEST); +// } return authContext.getUserId(); } diff --git a/src/main/java/com/jisungin/application/PageResponse.java b/src/main/java/com/jisungin/application/PageResponse.java index 308bb54..8c472f0 100644 --- a/src/main/java/com/jisungin/application/PageResponse.java +++ b/src/main/java/com/jisungin/application/PageResponse.java @@ -29,4 +29,12 @@ public void addContents(List contents) { this.likeContents = contents; } + public static PageResponse of(int size, long totalCount, List queryResponse) { + return PageResponse.builder() + .size(size) + .totalCount(totalCount) + .queryResponse(queryResponse) + .build(); + } + } diff --git a/src/main/java/com/jisungin/application/book/BookService.java b/src/main/java/com/jisungin/application/book/BookService.java index 9702766..79e8ace 100644 --- a/src/main/java/com/jisungin/application/book/BookService.java +++ b/src/main/java/com/jisungin/application/book/BookService.java @@ -1,14 +1,25 @@ package com.jisungin.application.book; +import com.jisungin.application.PageResponse; import com.jisungin.application.book.request.BookCreateServiceRequest; +import com.jisungin.application.book.request.BookServicePageRequest; +import com.jisungin.application.book.response.BookRelatedTalkRoomPageResponse; +import com.jisungin.application.book.response.BookRelatedTalkRoomResponse; import com.jisungin.application.book.response.BookResponse; +import com.jisungin.application.talkroom.response.TalkRoomQueryResponse; +import com.jisungin.domain.ReadingStatus; import com.jisungin.domain.book.Book; import com.jisungin.domain.book.repository.BookRepository; import com.jisungin.domain.review.repository.ReviewRepository; +import com.jisungin.domain.talkroom.repository.TalkRoomRepository; +import com.jisungin.domain.talkroom.repository.TalkRoomRoleRepository; +import com.jisungin.domain.talkroomlike.repository.TalkRoomLikeRepository; import com.jisungin.exception.BusinessException; import com.jisungin.exception.ErrorCode; import com.jisungin.infra.crawler.Crawler; +import java.util.Collections; import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,6 +31,9 @@ public class BookService { private final Crawler crawler; private final BookRepository bookRepository; + private final TalkRoomRepository talkRoomRepository; + private final TalkRoomRoleRepository talkRoomRoleRepository; + private final TalkRoomLikeRepository talkRoomLikeRepository; private final ReviewRepository reviewRepository; public BookResponse getBook(String isbn) { @@ -31,6 +45,31 @@ public BookResponse getBook(String isbn) { return BookResponse.of(book, averageRating); } + public BookRelatedTalkRoomPageResponse getBookRelatedTalkRooms(String isbn, BookServicePageRequest request, + Long userId + ) { + Book book = bookRepository.findById(isbn) + .orElseThrow(() -> new BusinessException(ErrorCode.BOOK_NOT_FOUND)); + + List talkRooms = talkRoomRepository.findTalkRoomsRelatedBook(book.getIsbn(), + request.getOffset(), request.getSize()); + + List talkRoomIds = extractTalkRoomIds(talkRooms); + + Map> readingStatuses = talkRoomRoleRepository.findTalkRoomRoleByIds(talkRoomIds); + + List responses = BookRelatedTalkRoomResponse.create(talkRooms, readingStatuses); + + long totalCount = talkRoomRepository.countTalkRoomsRelatedBook(isbn); + + List likeTalkRoomIds = (userId != null) + ? talkRoomLikeRepository.findLikeTalkRoomIdsByUserId(userId, talkRoomIds) + : Collections.emptyList(); + + return BookRelatedTalkRoomPageResponse.of(PageResponse.of(request.getSize(), totalCount, responses), + likeTalkRoomIds); + } + @Transactional public BookResponse createBook(BookCreateServiceRequest request) { if (bookRepository.existsBookByIsbn(request.getIsbn())) { @@ -50,4 +89,10 @@ public void addNewBooks(List requests) { .forEach(bookRepository::save); } + private List extractTalkRoomIds(List talkRooms) { + return talkRooms.stream() + .map(TalkRoomQueryResponse::getId) + .toList(); + } + } diff --git a/src/main/java/com/jisungin/application/book/request/BookServicePageRequest.java b/src/main/java/com/jisungin/application/book/request/BookServicePageRequest.java index 64a33f0..4ac3b19 100644 --- a/src/main/java/com/jisungin/application/book/request/BookServicePageRequest.java +++ b/src/main/java/com/jisungin/application/book/request/BookServicePageRequest.java @@ -23,4 +23,8 @@ public Integer extractEndIndex() { return page * size; } + public long getOffset() { + return (long) (Math.max(1, this.page) - 1) * Math.min(this.size, 2000); + } + } diff --git a/src/main/java/com/jisungin/application/book/response/BookRelatedTalkRoomPageResponse.java b/src/main/java/com/jisungin/application/book/response/BookRelatedTalkRoomPageResponse.java new file mode 100644 index 0000000..32e0e01 --- /dev/null +++ b/src/main/java/com/jisungin/application/book/response/BookRelatedTalkRoomPageResponse.java @@ -0,0 +1,30 @@ +package com.jisungin.application.book.response; + +import com.jisungin.application.PageResponse; +import java.util.ArrayList; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class BookRelatedTalkRoomPageResponse { + + PageResponse response; + List userLikeTalkRoomIds = new ArrayList<>(); + + @Builder + private BookRelatedTalkRoomPageResponse(PageResponse response, + List userLikeTalkRoomIds) { + this.response = response; + this.userLikeTalkRoomIds = userLikeTalkRoomIds; + } + + public static BookRelatedTalkRoomPageResponse of(PageResponse response, + List userLikeTalkRoomIds) { + return BookRelatedTalkRoomPageResponse.builder() + .response(response) + .userLikeTalkRoomIds(userLikeTalkRoomIds) + .build(); + } + +} diff --git a/src/main/java/com/jisungin/application/book/response/BookRelatedTalkRoomResponse.java b/src/main/java/com/jisungin/application/book/response/BookRelatedTalkRoomResponse.java new file mode 100644 index 0000000..a8b5f22 --- /dev/null +++ b/src/main/java/com/jisungin/application/book/response/BookRelatedTalkRoomResponse.java @@ -0,0 +1,63 @@ +package com.jisungin.application.book.response; + +import com.jisungin.application.talkroom.response.TalkRoomQueryResponse; +import com.jisungin.domain.ReadingStatus; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class BookRelatedTalkRoomResponse { + + private Long id; + private String profileImage; + private String username; + private String title; + private String bookName; + private String bookThumbnail; + private Long likeCount; + private List readingStatuses = new ArrayList<>(); + + @Builder + public BookRelatedTalkRoomResponse(Long id, String profileImage, String username, String title, String bookName, + String bookThumbnail, Long likeCount, List readingStatuses) { + this.id = id; + this.profileImage = profileImage; + this.username = username; + this.title = title; + this.bookName = bookName; + this.bookThumbnail = bookThumbnail; + this.likeCount = likeCount; + this.readingStatuses = readingStatuses; + } + + public static List create(List talkRooms, + Map> readingStatuses) { + return talkRooms.stream() + .map(talkRoom -> { + List talkRoomReadingStatus = extractReadingStatuses(readingStatuses, talkRoom); + + return BookRelatedTalkRoomResponse.builder() + .id(talkRoom.getId()) + .profileImage(talkRoom.getProfileImage()) + .username(talkRoom.getUsername()) + .title(talkRoom.getTitle()) + .bookName(talkRoom.getBookName()) + .bookThumbnail(talkRoom.getBookThumbnail()) + .likeCount(talkRoom.getLikeCount()) + .readingStatuses(talkRoomReadingStatus) + .build(); + }) + .toList(); + } + + private static List extractReadingStatuses(Map> readingStatuses, + TalkRoomQueryResponse talkRoom) { + return readingStatuses.get(talkRoom.getId()).stream() + .map(ReadingStatus::getText) + .toList(); + } + +} diff --git a/src/main/java/com/jisungin/application/talkroom/response/TalkRoomQueryResponse.java b/src/main/java/com/jisungin/application/talkroom/response/TalkRoomQueryResponse.java new file mode 100644 index 0000000..c533b6b --- /dev/null +++ b/src/main/java/com/jisungin/application/talkroom/response/TalkRoomQueryResponse.java @@ -0,0 +1,38 @@ +package com.jisungin.application.talkroom.response; + +import com.querydsl.core.annotations.QueryProjection; +import java.time.LocalDateTime; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class TalkRoomQueryResponse { + + private Long id; + private String profileImage; + private String username; + private String title; + private String content; + private String bookName; + private String bookThumbnail; + private Long likeCount; + private LocalDateTime createTime; + + @Builder + @QueryProjection + public TalkRoomQueryResponse(Long id, String profileImage, String username, String title, String content, + String bookName, String bookThumbnail, Long likeCount, LocalDateTime createTime) { + this.id = id; + this.profileImage = profileImage; + this.username = username; + this.title = title; + this.content = content; + this.bookName = bookName; + this.bookThumbnail = bookThumbnail; + this.likeCount = likeCount; + this.createTime = createTime; + } + +} diff --git a/src/main/java/com/jisungin/config/QueryDslConfig.java b/src/main/java/com/jisungin/config/QueryDslConfig.java index 45ad2f2..12893a3 100644 --- a/src/main/java/com/jisungin/config/QueryDslConfig.java +++ b/src/main/java/com/jisungin/config/QueryDslConfig.java @@ -1,5 +1,6 @@ package com.jisungin.config; +import com.querydsl.jpa.JPQLTemplates; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -14,7 +15,7 @@ public class QueryDslConfig { @Bean public JPAQueryFactory jpaQueryFactory() { - return new JPAQueryFactory(em); + return new JPAQueryFactory(JPQLTemplates.DEFAULT, em); } } diff --git a/src/main/java/com/jisungin/domain/BaseEntity.java b/src/main/java/com/jisungin/domain/BaseEntity.java index 0f90a8a..557ef47 100644 --- a/src/main/java/com/jisungin/domain/BaseEntity.java +++ b/src/main/java/com/jisungin/domain/BaseEntity.java @@ -1,23 +1,25 @@ package com.jisungin.domain; +import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; import lombok.Getter; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.LocalDateTime; - @Getter @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public abstract class BaseEntity { - @CreatedDate + @CreationTimestamp + @Column(updatable = false) private LocalDateTime createDateTime; - @LastModifiedDate + @UpdateTimestamp + @Column(insertable = false) private LocalDateTime modifiedDateTime; } diff --git a/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryCustom.java b/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryCustom.java index 4b622a8..5f23271 100644 --- a/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryCustom.java +++ b/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryCustom.java @@ -3,11 +3,17 @@ import com.jisungin.application.PageResponse; import com.jisungin.application.talkroom.response.TalkRoomFindAllResponse; import com.jisungin.application.talkroom.response.TalkRoomFindOneResponse; +import com.jisungin.application.talkroom.response.TalkRoomQueryResponse; +import java.util.List; public interface TalkRoomRepositoryCustom { PageResponse findAllTalkRoom(long offset, int size, String order, String query); + List findTalkRoomsRelatedBook(String isbn, long offset, Integer size); + + Long countTalkRoomsRelatedBook(String isbn); + TalkRoomFindOneResponse findOneTalkRoom(Long talkRoomId); } diff --git a/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryImpl.java b/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryImpl.java index e0e3a5c..8c68888 100644 --- a/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryImpl.java +++ b/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryImpl.java @@ -12,9 +12,11 @@ import com.jisungin.application.talkroom.response.QTalkRoomFindAllResponse; import com.jisungin.application.talkroom.response.QTalkRoomFindOneResponse; import com.jisungin.application.talkroom.response.QTalkRoomQueryReadingStatusResponse; +import com.jisungin.application.talkroom.response.QTalkRoomQueryResponse; import com.jisungin.application.talkroom.response.TalkRoomFindAllResponse; import com.jisungin.application.talkroom.response.TalkRoomFindOneResponse; import com.jisungin.application.talkroom.response.TalkRoomQueryReadingStatusResponse; +import com.jisungin.application.talkroom.response.TalkRoomQueryResponse; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -52,6 +54,40 @@ public PageResponse findAllTalkRoom(long offset, int si .build(); } + // 책과 연관된 TalkRoomResponse 조회 + @Override + public List findTalkRoomsRelatedBook(String isbn, long offset, Integer size) { + return queryFactory.select(new QTalkRoomQueryResponse( + talkRoom.id, + user.profileImage, + user.name.as("username"), + talkRoom.title, + talkRoom.content, + book.title.as("bookName"), + book.thumbnail.as("bookThumbnail"), + talkRoomLike.count().as("likeCount"), + talkRoom.createDateTime.as("createTime") + )) + .from(talkRoom) + .join(talkRoom.user, user) + .join(talkRoom.book, book) + .leftJoin(talkRoomLike).on(talkRoom.eq(talkRoomLike.talkRoom)) + .where(book.isbn.eq(isbn)) + .groupBy(talkRoom.id) + .offset(offset) + .limit(size) + .orderBy(talkRoomLike.count().desc()) + .fetch(); + } + + public Long countTalkRoomsRelatedBook(String isbn) { + return queryFactory.select(talkRoom.count()) + .from(talkRoom) + .join(talkRoom.book, book) + .where(book.isbn.eq(isbn)) + .fetchOne(); + } + // 토크룸 단건 조회 @Override public TalkRoomFindOneResponse findOneTalkRoom(Long talkRoomId) { diff --git a/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRoleRepository.java b/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRoleRepository.java index 3f39ae6..efaaa50 100644 --- a/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRoleRepository.java +++ b/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRoleRepository.java @@ -4,7 +4,7 @@ import com.jisungin.domain.talkroom.TalkRoomRole; import org.springframework.data.jpa.repository.JpaRepository; -public interface TalkRoomRoleRepository extends JpaRepository { +public interface TalkRoomRoleRepository extends JpaRepository, TalkRoomRoleRepositoryCustom { void deleteAllByTalkRoom(TalkRoom talkRoom); diff --git a/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRoleRepositoryCustom.java b/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRoleRepositoryCustom.java new file mode 100644 index 0000000..ea4f594 --- /dev/null +++ b/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRoleRepositoryCustom.java @@ -0,0 +1,11 @@ +package com.jisungin.domain.talkroom.repository; + +import com.jisungin.domain.ReadingStatus; +import java.util.List; +import java.util.Map; + +public interface TalkRoomRoleRepositoryCustom { + + Map> findTalkRoomRoleByIds(List talkRoomIds); + +} diff --git a/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRoleRepositoryImpl.java b/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRoleRepositoryImpl.java new file mode 100644 index 0000000..28cc12a --- /dev/null +++ b/src/main/java/com/jisungin/domain/talkroom/repository/TalkRoomRoleRepositoryImpl.java @@ -0,0 +1,26 @@ +package com.jisungin.domain.talkroom.repository; + +import static com.jisungin.domain.talkroom.QTalkRoomRole.talkRoomRole; +import static com.querydsl.core.group.GroupBy.groupBy; +import static com.querydsl.core.group.GroupBy.list; + +import com.jisungin.domain.ReadingStatus; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class TalkRoomRoleRepositoryImpl implements TalkRoomRoleRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public Map> findTalkRoomRoleByIds(List talkRoomIds) { + return queryFactory.select(talkRoomRole.talkRoom.id, talkRoomRole.readingStatus) + .from(talkRoomRole) + .where(talkRoomRole.talkRoom.id.in(talkRoomIds)) + .transform(groupBy(talkRoomRole.talkRoom.id).as(list(talkRoomRole.readingStatus))); + } + +} diff --git a/src/main/java/com/jisungin/domain/talkroomlike/repository/TalkRoomLikeRepository.java b/src/main/java/com/jisungin/domain/talkroomlike/repository/TalkRoomLikeRepository.java index 748072a..0f08456 100644 --- a/src/main/java/com/jisungin/domain/talkroomlike/repository/TalkRoomLikeRepository.java +++ b/src/main/java/com/jisungin/domain/talkroomlike/repository/TalkRoomLikeRepository.java @@ -24,4 +24,11 @@ public interface TalkRoomLikeRepository extends JpaRepository findLikeTalkRoomIdsByUserId(@Param("userId") Long userId, @Param("talkRoomIds") List talkRoomIds); + } diff --git a/src/test/java/com/jisungin/api/book/BookControllerTest.java b/src/test/java/com/jisungin/api/book/BookControllerTest.java index d0b9828..13dee87 100644 --- a/src/test/java/com/jisungin/api/book/BookControllerTest.java +++ b/src/test/java/com/jisungin/api/book/BookControllerTest.java @@ -1,6 +1,8 @@ package com.jisungin.api.book; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -11,11 +13,16 @@ import com.jisungin.ControllerTestSupport; import com.jisungin.api.book.request.BookCreateRequest; +import com.jisungin.application.PageResponse; +import com.jisungin.application.book.request.BookServicePageRequest; import com.jisungin.application.book.response.BestSellerResponse; +import com.jisungin.application.book.response.BookRelatedTalkRoomPageResponse; +import com.jisungin.application.book.response.BookRelatedTalkRoomResponse; import com.jisungin.application.book.response.BookResponse; import java.time.LocalDateTime; import java.util.List; import java.util.stream.IntStream; +import java.util.stream.LongStream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -50,6 +57,19 @@ public void getBestSellers() throws Exception { .andExpect(jsonPath("$.message").value("OK")); } + @Test + @DisplayName("책과 연관된 토크룸을 조회한다.") + public void getTalkRoomsRelatedBook() throws Exception { + // when // then + mockMvc.perform(get("/v1/books/00001/talk-rooms?page=1&size=10") + .accept(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.status").value("OK")) + .andExpect(jsonPath("$.message").value("OK")) + .andDo(print()); + } + @Test @DisplayName("신규 도서를 등록한다.") public void createBook() throws Exception { diff --git a/src/test/java/com/jisungin/application/service/book/BookServiceTest.java b/src/test/java/com/jisungin/application/service/book/BookServiceTest.java index f30d8f2..c195ec8 100644 --- a/src/test/java/com/jisungin/application/service/book/BookServiceTest.java +++ b/src/test/java/com/jisungin/application/service/book/BookServiceTest.java @@ -7,14 +7,30 @@ import com.jisungin.ServiceTestSupport; import com.jisungin.application.book.BookService; import com.jisungin.application.book.request.BookCreateServiceRequest; +import com.jisungin.application.book.request.BookServicePageRequest; +import com.jisungin.application.book.response.BookRelatedTalkRoomPageResponse; import com.jisungin.application.book.response.BookResponse; +import com.jisungin.domain.ReadingStatus; import com.jisungin.domain.book.Book; -import com.jisungin.domain.book.repository.BestSellerRepository; import com.jisungin.domain.book.repository.BookRepository; +import com.jisungin.domain.oauth.OauthId; +import com.jisungin.domain.oauth.OauthType; +import com.jisungin.domain.talkroom.TalkRoom; +import com.jisungin.domain.talkroom.TalkRoomRole; +import com.jisungin.domain.talkroom.repository.TalkRoomRepository; +import com.jisungin.domain.talkroom.repository.TalkRoomRoleRepository; +import com.jisungin.domain.talkroomlike.TalkRoomLike; +import com.jisungin.domain.talkroomlike.repository.TalkRoomLikeRepository; +import com.jisungin.domain.user.User; +import com.jisungin.domain.user.repository.UserRepository; import com.jisungin.exception.BusinessException; import com.jisungin.infra.crawler.Crawler; import com.jisungin.infra.crawler.CrawlingBook; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -29,14 +45,27 @@ public class BookServiceTest extends ServiceTestSupport { @Autowired private BookRepository bookRepository; - @MockBean - private BestSellerRepository bestSellerRepository; + @Autowired + private TalkRoomRepository talkRoomRepository; + + @Autowired + private TalkRoomRoleRepository talkRoomRoleRepository; + + @Autowired + private TalkRoomLikeRepository talkRoomLikeRepository; + + @Autowired + private UserRepository userRepository; @MockBean private Crawler crawler; @AfterEach void tearDown() { + talkRoomLikeRepository.deleteAllInBatch(); + talkRoomRoleRepository.deleteAllInBatch(); + talkRoomRepository.deleteAllInBatch(); + userRepository.deleteAllInBatch(); bookRepository.deleteAllInBatch(); } @@ -70,6 +99,82 @@ public void getBookWithInvalidIsbn() { .hasMessage("책을 찾을 수 없습니다."); } + @Test + @DisplayName("책과 관련된 토크룸 정보를 가져온다.") + public void getTalkRoomRelatedBook() { + // given + List users = userRepository.saveAll(createUsers()); + + Book book = bookRepository.save(createBookWithIsbn("00001")); + Book anotherBook = bookRepository.save(createBookWithIsbn("00002")); + + List talkRoomsWithBook = talkRoomRepository.saveAll(createTalkRooms(users.get(0), book)); + List talkRoomsWithAnotherBook = talkRoomRepository.saveAll( + createTalkRooms(users.get(0), anotherBook)); + + talkRoomsWithBook.forEach(this::createTalkRoomRole); + talkRoomsWithAnotherBook.forEach(this::createTalkRoomRole); + + List likes1 = talkRoomLikeRepository.saveAll( + createTalkRoomLikes(users, talkRoomsWithBook.get(0), 10)); + List likes2 = talkRoomLikeRepository.saveAll( + createTalkRoomLikes(users, talkRoomsWithAnotherBook.get(0), 9)); + + BookServicePageRequest request = BookServicePageRequest.builder() + .page(1) + .size(5) + .build(); + + // when + BookRelatedTalkRoomPageResponse responses = bookService.getBookRelatedTalkRooms(book.getIsbn(), + request, users.get(0).getId()); + + // then + assertThat(responses.getResponse().getSize()).isEqualTo(5); + assertThat(responses.getResponse().getTotalCount()).isEqualTo(10); + assertThat(responses.getResponse().getQueryResponse().size()).isEqualTo(5); + assertThat(responses.getResponse().getQueryResponse().get(0).getLikeCount()).isEqualTo(10); + assertThat(responses.getResponse().getQueryResponse().get(1).getLikeCount()).isEqualTo(0); + assertThat(responses.getUserLikeTalkRoomIds().size()).isEqualTo(1); + assertThat(responses.getUserLikeTalkRoomIds().get(0)).isEqualTo(1); + } + + @NotNull + private static List createTalkRoomLikes(List users, TalkRoom talkRoom, Integer endIndex) { + return IntStream.range(0, endIndex).mapToObj(i -> TalkRoomLike.builder() + .user(users.get(i)) + .talkRoom(talkRoom) + .build()) + .toList(); + } + + @NotNull + private static List createTalkRooms(User user, Book book) { + return IntStream.range(0, 10) + .mapToObj(i -> TalkRoom.builder() + .user(user) + .book(book) + .title("토론방" + i) + .content("내용" + i) + .build()) + .toList(); + } + + @NotNull + private static List createUsers() { + return IntStream.range(0, 10) + .mapToObj(i -> User.builder() + .name("user@gmail.com " + i) + .profileImage("image") + .oauthId( + OauthId.builder() + .oauthId("oauthId " + i) + .oauthType(OauthType.KAKAO) + .build() + ) + .build()).toList(); + } + @Test @DisplayName("도서 정보에 대한 책을 생성한다.") public void createBook() { @@ -140,4 +245,29 @@ private static Book create() { .build(); } + private static Book createBookWithIsbn(String isbn) { + return Book.builder() + .title("제목" + isbn) + .content("내용" + isbn) + .authors("작가") + .isbn(isbn) + .publisher("publisher") + .dateTime(LocalDateTime.now()) + .imageUrl("www.image.com/" + isbn) + .thumbnail("www.thumbnail.com/" + isbn) + .build(); + } + + private void createTalkRoomRole(TalkRoom talkRoom) { + List request = new ArrayList<>(); + request.add("읽는 중"); + request.add("읽음"); + + List readingStatuses = List.of(ReadingStatus.READING, ReadingStatus.READ); + + readingStatuses.stream() + .map(status -> TalkRoomRole.roleCreate(talkRoom, status)) + .forEach(talkRoomRoleRepository::save); + } + } diff --git a/src/test/java/com/jisungin/domain/talkRoomLike/repository/TalkRoomLikeRepositoryTest.java b/src/test/java/com/jisungin/domain/talkRoomLike/repository/TalkRoomLikeRepositoryTest.java new file mode 100644 index 0000000..4bcbc0f --- /dev/null +++ b/src/test/java/com/jisungin/domain/talkRoomLike/repository/TalkRoomLikeRepositoryTest.java @@ -0,0 +1,121 @@ +package com.jisungin.domain.talkRoomLike.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.jisungin.RepositoryTestSupport; +import com.jisungin.domain.book.Book; +import com.jisungin.domain.book.repository.BookRepository; +import com.jisungin.domain.oauth.OauthId; +import com.jisungin.domain.oauth.OauthType; +import com.jisungin.domain.talkroom.TalkRoom; +import com.jisungin.domain.talkroom.repository.TalkRoomRepository; +import com.jisungin.domain.talkroomlike.TalkRoomLike; +import com.jisungin.domain.talkroomlike.repository.TalkRoomLikeRepository; +import com.jisungin.domain.user.User; +import com.jisungin.domain.user.repository.UserRepository; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class TalkRoomLikeRepositoryTest extends RepositoryTestSupport { + + @Autowired + private UserRepository userRepository; + + @Autowired + private BookRepository bookRepository; + + @Autowired + private TalkRoomRepository talkRoomRepository; + + @Autowired + private TalkRoomLikeRepository talkRoomLikeRepository; + + @Test + @DisplayName("사용자가 좋아요한 토크방 아이디 조회") + public void findLikeTalkRoomIdsByUserId() { + // given + User user = userRepository.save(createUser()); + Book book = bookRepository.save(createBook()); + + List talkRooms = talkRoomRepository.saveAll(createTalkRooms(user, book)); + + List talkRoomIds = extractTalkRoomIds(talkRooms); + + List talkRoomLikes = talkRoomLikeRepository.saveAll(createTalkRoomLikes(user, talkRooms)); + + // when + List response = talkRoomLikeRepository.findLikeTalkRoomIdsByUserId(user.getId(), + talkRoomIds); + + // then + assertThat(response).contains( + talkRooms.get(0).getId(), + talkRooms.get(1).getId(), + talkRooms.get(2).getId(), + talkRooms.get(3).getId(), + talkRooms.get(4).getId() + ); + } + + @NotNull + private static List createTalkRoomLikes(User user, List talkRooms) { + return IntStream.range(0, 5) + .mapToObj(i -> TalkRoomLike.builder() + .user(user) + .talkRoom(talkRooms.get(i)) + .build()) + .toList(); + } + + @NotNull + private static List extractTalkRoomIds(List talkRooms) { + return talkRooms.stream() + .map(TalkRoom::getId) + .toList(); + } + + @NotNull + private static List createTalkRooms(User user, Book book) { + return LongStream.range(0, 5) + .mapToObj(i -> TalkRoom.builder() + .user(user) + .book(book) + .title("title" + i) + .content("content" + i) + .build()) + .toList(); + } + + private static User createUser() { + return User.builder() + .name("user@gmail.com") + .profileImage("image") + .oauthId( + OauthId.builder() + .oauthId("oauthId") + .oauthType(OauthType.KAKAO) + .build() + ) + .build(); + } + + private static Book createBook() { + return Book.builder() + .title("제목") + .content("내용") + .authors("작가") + .isbn("11111") + .publisher("publisher") + .dateTime(LocalDateTime.now()) + .imageUrl("www") + .thumbnail("www.thumbnail.com") + .build(); + } + +} diff --git a/src/test/java/com/jisungin/domain/talkRoomRole/repository/TalkRoomRoleRepositoryTest.java b/src/test/java/com/jisungin/domain/talkRoomRole/repository/TalkRoomRoleRepositoryTest.java new file mode 100644 index 0000000..547c778 --- /dev/null +++ b/src/test/java/com/jisungin/domain/talkRoomRole/repository/TalkRoomRoleRepositoryTest.java @@ -0,0 +1,119 @@ +package com.jisungin.domain.talkRoomRole.repository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import com.jisungin.RepositoryTestSupport; +import com.jisungin.domain.ReadingStatus; +import com.jisungin.domain.book.Book; +import com.jisungin.domain.book.repository.BookRepository; +import com.jisungin.domain.oauth.OauthId; +import com.jisungin.domain.oauth.OauthType; +import com.jisungin.domain.talkroom.TalkRoom; +import com.jisungin.domain.talkroom.TalkRoomRole; +import com.jisungin.domain.talkroom.repository.TalkRoomRepository; +import com.jisungin.domain.talkroom.repository.TalkRoomRoleRepository; +import com.jisungin.domain.user.User; +import com.jisungin.domain.user.repository.UserRepository; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.stream.LongStream; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class TalkRoomRoleRepositoryTest extends RepositoryTestSupport { + + @Autowired + private UserRepository userRepository; + + @Autowired + private BookRepository bookRepository; + + @Autowired + private TalkRoomRepository talkRoomRepository; + + @Autowired + private TalkRoomRoleRepository talkRoomRoleRepository; + + @Test + @DisplayName("토크방 아이디와 관련된 토크방 조건을 가져온다.") + public void findTalkRoomRoleByTalkRoomIds() { + // given + User user = userRepository.save(createUser()); + Book book = bookRepository.save(createBook()); + + List talkRooms = talkRoomRepository.saveAll(createTalkRooms(user, book)); + + List talkRoomIds = extractTalkRoomIds(talkRooms); + + List talkRoomRoles = talkRoomRoleRepository.saveAll(createTalkRoomRole(talkRooms)); + + // when + Map> response = talkRoomRoleRepository.findTalkRoomRoleByIds(talkRoomIds); + + // then + assertThat(response).hasSize(5) + .contains( + entry(talkRooms.get(0).getId(), List.of(ReadingStatus.READ)), + entry(talkRooms.get(1).getId(), List.of(ReadingStatus.READ)), + entry(talkRooms.get(2).getId(), List.of(ReadingStatus.READ)), + entry(talkRooms.get(3).getId(), List.of(ReadingStatus.READ)), + entry(talkRooms.get(4).getId(), List.of(ReadingStatus.READ)) + ); + } + + @NotNull + private static List extractTalkRoomIds(List talkRooms) { + return talkRooms.stream() + .map(TalkRoom::getId) + .toList(); + } + + @NotNull + private static List createTalkRooms(User user, Book book) { + return LongStream.range(0, 5) + .mapToObj(i -> TalkRoom.builder() + .user(user) + .book(book) + .title("title" + i) + .content("content" + i) + .build()) + .toList(); + } + + private static User createUser() { + return User.builder() + .name("user@gmail.com") + .profileImage("image") + .oauthId( + OauthId.builder() + .oauthId("oauthId") + .oauthType(OauthType.KAKAO) + .build() + ) + .build(); + } + + private static Book createBook() { + return Book.builder() + .title("제목") + .content("내용") + .authors("작가") + .isbn("11111") + .publisher("publisher") + .dateTime(LocalDateTime.now()) + .imageUrl("www") + .thumbnail("www.thumbnail.com") + .build(); + } + + private List createTalkRoomRole(List talkRooms) { + return talkRooms.stream() + .map(talkRoom -> TalkRoomRole.roleCreate(talkRoom, ReadingStatus.READ)) + .toList(); + } + +} diff --git a/src/test/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryTest.java b/src/test/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryTest.java index 2d71515..0f29cc3 100644 --- a/src/test/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryTest.java +++ b/src/test/java/com/jisungin/domain/talkroom/repository/TalkRoomRepositoryTest.java @@ -1,15 +1,18 @@ package com.jisungin.domain.talkroom.repository; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; import com.jisungin.RepositoryTestSupport; import com.jisungin.application.PageResponse; import com.jisungin.application.SearchServiceRequest; import com.jisungin.application.talkroom.response.TalkRoomFindAllResponse; import com.jisungin.application.talkroom.response.TalkRoomFindOneResponse; +import com.jisungin.application.talkroom.response.TalkRoomQueryResponse; import com.jisungin.domain.ReadingStatus; import com.jisungin.domain.book.Book; import com.jisungin.domain.book.repository.BookRepository; +import com.jisungin.domain.comment.Comment; import com.jisungin.domain.comment.repository.CommentRepository; import com.jisungin.domain.commentlike.repository.CommentLikeRepository; import com.jisungin.domain.oauth.OauthId; @@ -397,6 +400,140 @@ void findAllTalkRoomWithSearch() { assertThat(talkRoom3.getTitle()).isEqualTo(response.getQueryResponse().get(2).getTitle()); } + @Test + @DisplayName("querydsl 책과 연관된 토크방 조회") + void getTalkRoomRelatedBook() { + // given + List users = IntStream.range(0, 10) + .mapToObj(i -> User.builder() + .name("user@gmail.com " + i) + .profileImage("image") + .oauthId( + OauthId.builder() + .oauthId("oauthId " + i) + .oauthType(OauthType.KAKAO) + .build() + ) + .build()).toList(); + + userRepository.saveAll(users); + + Book book = bookRepository.save(createBookWithIsbn("00001")); + Book anotherBook = bookRepository.save(createBookWithIsbn("00002")); + + List talkRoomsWithBook = IntStream.range(0, 10) + .mapToObj(i -> TalkRoom.builder() + .user(users.get(0)) + .book(book) + .title("토론방" + i) + .content("내용" + i) + .build()) + .toList(); + + List talkRoomsWithAnotherBook = IntStream.range(10, 20) + .mapToObj(i -> TalkRoom.builder() + .user(users.get(0)) + .book(anotherBook) + .title("토론방" + i) + .content("내용" + i) + .build()) + .toList(); + + talkRoomRepository.saveAll(talkRoomsWithBook); + talkRoomRepository.saveAll(talkRoomsWithAnotherBook); + + talkRoomsWithBook.forEach(this::createTalkRoomRole); + talkRoomsWithAnotherBook.forEach(this::createTalkRoomRole); + + List likes1 = IntStream.range(0, 10).mapToObj(i -> TalkRoomLike.builder() + .user(users.get(i)) + .talkRoom(talkRoomsWithBook.get(0)) + .build()) + .toList(); + + List likes2 = IntStream.range(0, 9).mapToObj(i -> TalkRoomLike.builder() + .user(users.get(i)) + .talkRoom(talkRoomsWithAnotherBook.get(1)) + .build()) + .toList(); + + talkRoomLikeRepository.saveAll(likes1); + talkRoomLikeRepository.saveAll(likes2); + + // when + List talkRoomsRelatedBook = talkRoomRepository.findTalkRoomsRelatedBook(book.getIsbn(), + 0, 20); + + // then + assertThat(talkRoomsRelatedBook.size()).isEqualTo(10); + assertThat(talkRoomsRelatedBook.get(0).getLikeCount()).isEqualTo(10); + assertThat(talkRoomsRelatedBook.get(1).getLikeCount()).isEqualTo(0); + assertThat(talkRoomsRelatedBook).extracting("bookName", "bookThumbnail") + .containsOnly(tuple("제목00001", "www.thumbnail.com/00001")); + } + + @Test + @DisplayName("querydsl 책과 관련된 총 페이지 조회") + public void getTalkRoomsRelatedBookTotalSize() { + // given + List users = IntStream.range(0, 10) + .mapToObj(i -> User.builder() + .name("user@gmail.com " + i) + .profileImage("image") + .oauthId( + OauthId.builder() + .oauthId("oauthId " + i) + .oauthType(OauthType.KAKAO) + .build() + ) + .build()).toList(); + + userRepository.saveAll(users); + + Book book = bookRepository.save(createBookWithIsbn("00001")); + Book anotherBook = bookRepository.save(createBookWithIsbn("00002")); + + List talkRoomsWithBook = IntStream.range(0, 10) + .mapToObj(i -> TalkRoom.builder() + .user(users.get(0)) + .book(book) + .title("토론방" + i) + .content("내용" + i) + .build()) + .toList(); + + List talkRoomsWithAnotherBook = IntStream.range(10, 20) + .mapToObj(i -> TalkRoom.builder() + .user(users.get(0)) + .book(anotherBook) + .title("토론방" + i) + .content("내용" + i) + .build()) + .toList(); + + talkRoomRepository.saveAll(talkRoomsWithBook); + talkRoomRepository.saveAll(talkRoomsWithAnotherBook); + + talkRoomsWithBook.forEach(this::createTalkRoomRole); + talkRoomsWithAnotherBook.forEach(this::createTalkRoomRole); + + // when + Long totalCount = talkRoomRepository.countTalkRoomsRelatedBook(book.getIsbn()); + Long anotherTotalCount = talkRoomRepository.countTalkRoomsRelatedBook(anotherBook.getIsbn()); + + // then + assertThat(totalCount).isEqualTo(10L); + assertThat(anotherTotalCount).isEqualTo(10L); + } + + private static Comment createComment(TalkRoom talkRoom, User user) { + return Comment.builder() + .talkRoom(talkRoom) + .user(user) + .content("의견 남기기") + .build(); + } + private void createTalkRoomRole(TalkRoom talkRoom) { List request = new ArrayList<>(); request.add("읽는 중"); @@ -439,6 +576,20 @@ private static Book createBook() { .publisher("publisher") .dateTime(LocalDateTime.now()) .imageUrl("www") + .thumbnail("www.thumbnail.com") + .build(); + } + + private static Book createBookWithIsbn(String isbn) { + return Book.builder() + .title("제목" + isbn) + .content("내용" + isbn) + .authors("작가") + .isbn(isbn) + .publisher("publisher") + .dateTime(LocalDateTime.now()) + .imageUrl("www.image.com/" + isbn) + .thumbnail("www.thumbnail.com/" + isbn) .build(); }