diff --git a/apis/src/main/kotlin/org/yapp/apis/auth/dto/request/UserBooksByIsbnsRequest.kt b/apis/src/main/kotlin/org/yapp/apis/auth/dto/request/UserBooksByIsbnsRequest.kt new file mode 100644 index 00000000..97a3b739 --- /dev/null +++ b/apis/src/main/kotlin/org/yapp/apis/auth/dto/request/UserBooksByIsbnsRequest.kt @@ -0,0 +1,38 @@ +package org.yapp.apis.auth.dto.request + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotEmpty +import java.util.UUID + +@Schema( + name = "UserBooksByIsbnsRequest", + description = "Request DTO for finding user books by user ID and a list of ISBNs" +) +data class UserBooksByIsbnsRequest( + @Schema( + description = "사용자 ID", + example = "1" + ) + @field:NotBlank(message = "userId는 필수입니다.") + val userId: UUID? = null, + + @Schema( + description = "도서 ISBN 목록", + example = "[\"9788966262429\", \"9791190412351\"]" + ) + @field:NotEmpty(message = "isbns는 비어있을 수 없습니다.") + val isbns: List? = null + +) { + + + fun validUserId(): UUID = userId!! + fun validIsbns(): List = isbns!! + + companion object { + fun of(userId: UUID, isbns: List): UserBooksByIsbnsRequest { + return UserBooksByIsbnsRequest(userId = userId, isbns = isbns) + } + } +} diff --git a/apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt b/apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt index 827d3912..f29c7f1b 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt @@ -26,14 +26,16 @@ class BookController( @GetMapping("/search") override fun searchBooks( + @AuthenticationPrincipal userId: UUID, @Valid @ModelAttribute request: BookSearchRequest ): ResponseEntity { - val response = bookUseCase.searchBooks(request) + val response = bookUseCase.searchBooks(request, userId) return ResponseEntity.ok(response) } @GetMapping("/detail") override fun getBookDetail( + @AuthenticationPrincipal userId: UUID, @Valid @ModelAttribute request: BookDetailRequest ): ResponseEntity { val response = bookUseCase.getBookDetail(request) diff --git a/apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt b/apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt index 35b27c7b..84ed0c5c 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt @@ -28,7 +28,10 @@ import java.util.UUID @RequestMapping("/api/v1/books") interface BookControllerApi { - @Operation(summary = "도서 검색", description = "키워드를 사용하여 알라딘 도서 정보를 검색합니다.") + @Operation( + summary = "도서 검색", description = "알라딘 API를 통해 키워드로 도서를 검색합니다. \n" + + " 유저의 도서 상태(읽음, 읽는 중 등)가 함께 표시됩니다. " + ) @ApiResponses( value = [ ApiResponse( @@ -44,7 +47,10 @@ interface BookControllerApi { ] ) @GetMapping("/search") - fun searchBooks(@Valid @Parameter(description = "도서 검색 요청 객체") request: BookSearchRequest): ResponseEntity + fun searchBooks( + @AuthenticationPrincipal userId: UUID, + @Valid @Parameter(description = "도서 검색 요청 객체") request: BookSearchRequest + ): ResponseEntity @Operation(summary = "도서 상세 조회", description = "특정 도서의 상세 정보를 조회합니다.") @ApiResponses( @@ -67,7 +73,10 @@ interface BookControllerApi { ] ) @GetMapping("/detail") - fun getBookDetail(@Valid @Parameter(description = "도서 상세 조회 요청 객체") request: BookDetailRequest): ResponseEntity + fun getBookDetail( + @AuthenticationPrincipal userId: UUID, + @Valid @Parameter(description = "도서 상세 조회 요청 객체") request: BookDetailRequest + ): ResponseEntity @Operation(summary = "서재에 책 등록 또는 상태 업데이트 (Upsert)", description = "사용자의 서재에 책을 등록하거나, 이미 등록된 책의 상태를 업데이트합니다.") @ApiResponses( diff --git a/apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt b/apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt index ada62b8b..a3fb953b 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt @@ -1,7 +1,10 @@ package org.yapp.apis.book.dto.response +import org.yapp.domain.userbook.BookStatus +import org.yapp.domain.userbook.UserBook import org.yapp.infra.external.aladin.response.AladinSearchResponse import org.yapp.infra.external.aladin.response.BookItem +import java.time.LocalDateTime data class BookSearchResponse private constructor( val version: String?, @@ -41,10 +44,16 @@ data class BookSearchResponse private constructor( val author: String?, val publisher: String?, val coverImageUrl: String?, + val userBookStatus: BookStatus ) { + fun updateStatus(newStatus: BookStatus): BookSummary { + return this.copy(userBookStatus = newStatus) + } + companion object { private val unknownTitle = "제목없음" + fun fromAladinItem(item: BookItem): BookSummary? { val isbn = item.isbn ?: item.isbn13 ?: return null return BookSummary( @@ -52,7 +61,8 @@ data class BookSearchResponse private constructor( title = item.title ?: unknownTitle, author = item.author, publisher = item.publisher, - coverImageUrl = item.cover + coverImageUrl = item.cover, + userBookStatus = BookStatus.BEFORE_READING ) } } diff --git a/apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt b/apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt index 0655bc6d..9d51096c 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt @@ -1,11 +1,13 @@ package org.yapp.apis.book.service import org.springframework.stereotype.Service -import org.yapp.apis.book.dto.request.UpsertUserBookRequest +import org.yapp.apis.auth.dto.request.UserBooksByIsbnsRequest import org.yapp.apis.book.dto.response.UserBookResponse +import org.yapp.apis.book.dto.request.UpsertUserBookRequest import org.yapp.domain.userbook.UserBookDomainService import org.yapp.domain.userbook.vo.UserBookInfoVO -import java.util.* +import java.util.UUID + @Service class UserBookService( @@ -30,4 +32,15 @@ class UserBookService( UserBookResponse.from(userBook) } } + + fun findAllByUserIdAndBookIsbnIn(userBooksByIsbnsRequest: UserBooksByIsbnsRequest): List { + return userBookDomainService + .findAllByUserIdAndBookIsbnIn( + userBooksByIsbnsRequest.validUserId(), + userBooksByIsbnsRequest.validIsbns(), + ) + .map { UserBookResponse.from(it) } + } + } + diff --git a/apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt b/apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt index e7ff72f9..9dd53009 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt @@ -2,6 +2,7 @@ package org.yapp.apis.book.usecase import org.springframework.beans.factory.annotation.Qualifier import org.springframework.transaction.annotation.Transactional +import org.yapp.apis.auth.dto.request.UserBooksByIsbnsRequest import org.yapp.apis.auth.service.UserAuthService import org.yapp.apis.book.dto.request.BookCreateRequest import org.yapp.apis.book.dto.request.BookDetailRequest @@ -29,8 +30,20 @@ class BookUseCase( private val userBookService: UserBookService, private val bookManagementService: BookManagementService ) { - fun searchBooks(request: BookSearchRequest): BookSearchResponse { - return bookQueryService.searchBooks(request) + fun searchBooks(request: BookSearchRequest, userId: UUID): BookSearchResponse { + userAuthService.validateUserExists(userId) + + val searchResponse = bookQueryService.searchBooks(request) + val isbns = searchResponse.books.map { it.isbn } + + val userBooksReponse = userBookService.findAllByUserIdAndBookIsbnIn(UserBooksByIsbnsRequest.of(userId, isbns)) + val statusMap = userBooksReponse.associateBy({ it.bookIsbn }, { it.status }) + searchResponse.books.forEach { bookSummary -> + statusMap[bookSummary.isbn]?.let { status -> + bookSummary.updateStatus(status) + } + } + return searchResponse } fun getBookDetail(bookDetailRequest: BookDetailRequest): BookDetailResponse { diff --git a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt index d7313614..8063ca88 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt @@ -36,4 +36,12 @@ class UserBookDomainService( return userBookRepository.findAllByUserId(userId) .map(UserBookInfoVO::newInstance) } + + fun findAllByUserIdAndBookIsbnIn(userId: UUID, isbns: List): List { + if (isbns.isEmpty()) { + return emptyList() + } + return userBookRepository.findAllByUserIdAndBookIsbnIn(userId, isbns) + .map { UserBookInfoVO.newInstance(it) } + } } diff --git a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt index c7f224b7..54933ec3 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt @@ -10,4 +10,7 @@ interface UserBookRepository { fun save(userBook: UserBook): UserBook fun findAllByUserId(userId: UUID): List + + fun findAllByUserIdAndBookIsbnIn(userId: UUID, bookIsbns: List): List + } diff --git a/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt b/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt index f90fc89e..53efb57b 100644 --- a/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt +++ b/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt @@ -7,4 +7,6 @@ import java.util.* interface JpaUserBookRepository : JpaRepository { fun findByUserIdAndBookIsbn(userId: UUID, bookIsbn: String): UserBookEntity? fun findAllByUserId(userId: UUID): List + fun findAllByUserIdAndBookIsbnIn(userId: UUID, bookIsbnList: List): List + } diff --git a/infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/UserBookRepositoryImpl.kt b/infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/UserBookRepositoryImpl.kt index 1fd01a2d..79bc98ac 100644 --- a/infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/UserBookRepositoryImpl.kt +++ b/infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/UserBookRepositoryImpl.kt @@ -24,4 +24,12 @@ class UserBookRepositoryImpl( override fun findAllByUserId(userId: UUID): List { return jpaUserBookRepository.findAllByUserId(userId).map { it.toDomain() } } + + override fun findAllByUserIdAndBookIsbnIn( + userId: UUID, + bookIsbns: List + ): List { + return jpaUserBookRepository.findAllByUserIdAndBookIsbnIn(userId, bookIsbns) + .map { it.toDomain() } + } }