Skip to content
Original file line number Diff line number Diff line change
@@ -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<String>? = null

) {


fun validUserId(): UUID = userId!!
fun validIsbns(): List<String> = isbns!!

companion object {
fun of(userId: UUID, isbns: List<String>): UserBooksByIsbnsRequest {
return UserBooksByIsbnsRequest(userId = userId, isbns = isbns)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ class BookController(

@GetMapping("/search")
override fun searchBooks(
@AuthenticationPrincipal userId: UUID,
@Valid @ModelAttribute request: BookSearchRequest
): ResponseEntity<BookSearchResponse> {
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<BookDetailResponse> {
val response = bookUseCase.getBookDetail(request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -44,7 +47,10 @@ interface BookControllerApi {
]
)
@GetMapping("/search")
fun searchBooks(@Valid @Parameter(description = "도서 검색 요청 객체") request: BookSearchRequest): ResponseEntity<BookSearchResponse>
fun searchBooks(
@AuthenticationPrincipal userId: UUID,
@Valid @Parameter(description = "도서 검색 요청 객체") request: BookSearchRequest
): ResponseEntity<BookSearchResponse>

@Operation(summary = "도서 상세 조회", description = "특정 도서의 상세 정보를 조회합니다.")
@ApiResponses(
Expand All @@ -67,7 +73,10 @@ interface BookControllerApi {
]
)
@GetMapping("/detail")
fun getBookDetail(@Valid @Parameter(description = "도서 상세 조회 요청 객체") request: BookDetailRequest): ResponseEntity<BookDetailResponse>
fun getBookDetail(
@AuthenticationPrincipal userId: UUID,
@Valid @Parameter(description = "도서 상세 조회 요청 객체") request: BookDetailRequest
): ResponseEntity<BookDetailResponse>
Comment on lines +76 to +79
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

getBookDetail 메서드의 userId 파라미터 필요성 검토

인터페이스에서 userId 파라미터를 추가했지만, 구현체에서는 실제로 사용하지 않습니다. 도서 상세 조회에서 사용자별 상태 정보가 필요한지 검토하고, 필요하지 않다면 이 파라미터를 제거하는 것을 고려해보세요.

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt
around lines 76 to 79, the getBookDetail method includes a userId parameter that
is not used in the implementation. Review whether user-specific information is
needed for the book detail retrieval; if not, remove the userId parameter from
both the interface and its implementations to simplify the method signature.

💡 Verification agent

🧩 Analysis chain

getBookDetail 메소드의 userId 파라미터 필요성을 확인해주세요.

인터페이스에서 userId 파라미터가 추가되었지만, 실제 구현체(BookController.kt)에서는 이 파라미터를 사용하지 않고 있습니다. 만약 도서 상세 조회에서 사용자별 상태 정보가 필요하지 않다면, 인터페이스에서도 이 파라미터를 제거하는 것이 좋겠습니다.

일관성을 위해 다음 중 하나를 선택해야 합니다:

  1. 도서 상세 조회에서도 사용자 상태가 필요하다면 구현체에서 userId를 사용하도록 수정
  2. 필요하지 않다면 인터페이스에서 userId 파라미터 제거

다음 스크립트로 BookUseCase의 getBookDetail 메소드 구현을 확인해보세요:


🏁 Script executed:

#!/bin/bash
# BookUseCase의 getBookDetail 메소드 구현 확인
ast-grep --pattern $'fun getBookDetail($$$) {
  $$$
}'

Length of output: 55


BookUseCase 인터페이스 및 구현체에서 getBookDetail 메서드 정의를 전체적으로 찾아보려면 아래 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
# BookUseCase 인터페이스와 구현체에서 getBookDetail 메소드 검색
rg "getBookDetail" -n .

Length of output: 1131


getBookDetail에서 사용되지 않는 userId 파라미터 제거
현재 API 인터페이스와 컨트롤러 구현에만 존재하는 @AuthenticationPrincipal userId: UUID 파라미터는 실제 로직(BookUseCase → BookQueryService)에서 전혀 사용되지 않으므로, 일관성을 위해 제거해 주세요.

• apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt (76–79행)
• apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt (37–42행)

예시 diff:

--- a/apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt
+++ b/apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt
@@ -76,4 +76,4 @@
-    fun getBookDetail(
-        @AuthenticationPrincipal userId: UUID,
+    fun getBookDetail(
+        @Valid @Parameter(description = "도서 상세 조회 요청 객체") request: BookDetailRequest
     ): ResponseEntity<BookDetailResponse>
--- a/apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt
+++ b/apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt
@@ -37,5 +37,4 @@
-    override fun getBookDetail(
-        @AuthenticationPrincipal userId: UUID,
-        request: BookDetailRequest
-    ): ResponseEntity<BookDetailResponse> {
+    override fun getBookDetail(request: BookDetailRequest): ResponseEntity<BookDetailResponse> {
         val response = bookUseCase.getBookDetail(request)
         return ResponseEntity.ok(response)
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt lines
76 to 79 and
apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt lines 37 to
42, remove the unused parameter @AuthenticationPrincipal userId: UUID from the
getBookDetail function signature in both the API interface and the controller
implementation to maintain consistency since it is not used in the business
logic.


@Operation(summary = "서재에 책 등록 또는 상태 업데이트 (Upsert)", description = "사용자의 서재에 책을 등록하거나, 이미 등록된 책의 상태를 업데이트합니다.")
@ApiResponses(
Expand Down
Original file line number Diff line number Diff line change
@@ -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?,
Expand Down Expand Up @@ -41,18 +44,25 @@ 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(
isbn = isbn,
title = item.title ?: unknownTitle,
author = item.author,
publisher = item.publisher,
coverImageUrl = item.cover
coverImageUrl = item.cover,
userBookStatus = BookStatus.BEFORE_READING
)
}
}
Expand Down
17 changes: 15 additions & 2 deletions apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -30,4 +32,15 @@ class UserBookService(
UserBookResponse.from(userBook)
}
}

fun findAllByUserIdAndBookIsbnIn(userBooksByIsbnsRequest: UserBooksByIsbnsRequest): List<UserBookResponse> {
return userBookDomainService
.findAllByUserIdAndBookIsbnIn(
userBooksByIsbnsRequest.validUserId(),
userBooksByIsbnsRequest.validIsbns(),
)
.map { UserBookResponse.from(it) }
}

}

17 changes: 15 additions & 2 deletions apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(사소) 요거 requestDTO로 포장해서 findAllByUserIdAndBookIsbnIn 메서드의 인자로 넘기면 좋을 것 같긴 합니다! (하지만 중요한 부분은 아닙니다 ㅎㅎ)


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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,12 @@ class UserBookDomainService(
return userBookRepository.findAllByUserId(userId)
.map(UserBookInfoVO::newInstance)
}

fun findAllByUserIdAndBookIsbnIn(userId: UUID, isbns: List<String>): List<UserBookInfoVO> {
if (isbns.isEmpty()) {
return emptyList()
}
return userBookRepository.findAllByUserIdAndBookIsbnIn(userId, isbns)
.map { UserBookInfoVO.newInstance(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ interface UserBookRepository {
fun save(userBook: UserBook): UserBook

fun findAllByUserId(userId: UUID): List<UserBook>

fun findAllByUserIdAndBookIsbnIn(userId: UUID, bookIsbns: List<String>): List<UserBook>

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ import java.util.*
interface JpaUserBookRepository : JpaRepository<UserBookEntity, UUID> {
fun findByUserIdAndBookIsbn(userId: UUID, bookIsbn: String): UserBookEntity?
fun findAllByUserId(userId: UUID): List<UserBookEntity>
fun findAllByUserIdAndBookIsbnIn(userId: UUID, bookIsbnList: List<String>): List<UserBookEntity>

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,12 @@ class UserBookRepositoryImpl(
override fun findAllByUserId(userId: UUID): List<UserBook> {
return jpaUserBookRepository.findAllByUserId(userId).map { it.toDomain() }
}

override fun findAllByUserIdAndBookIsbnIn(
userId: UUID,
bookIsbns: List<String>
): List<UserBook> {
return jpaUserBookRepository.findAllByUserIdAndBookIsbnIn(userId, bookIsbns)
.map { it.toDomain() }
}
}