Skip to content

Commit 387105a

Browse files
authored
feat: 도서 검색 결과에 사용자 읽음 상태 표시 기능 (#39)
* [BOOK-139] feat: apis,domain,infra - 도서 검색 결과에 사용자 읽음 상태 표시 기능 (#38) * [BOOK-139] feat: gateway - security cors 추가 (#38) * [BOOK-139] �chore: apis - 불필요한 import 제거 (#39) * [BOOK-139] refactor: domain - with함수로 this 간소화 (#39) * [BOOK-139] fix: apis,domain conflict resolve (#38) * [BOOK-139] refactor: apis - 리뷰 1차 반영 (#38)
1 parent bdebb66 commit 387105a

File tree

10 files changed

+115
-9
lines changed

10 files changed

+115
-9
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.yapp.apis.auth.dto.request
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.NotBlank
5+
import jakarta.validation.constraints.NotEmpty
6+
import java.util.UUID
7+
8+
@Schema(
9+
name = "UserBooksByIsbnsRequest",
10+
description = "Request DTO for finding user books by user ID and a list of ISBNs"
11+
)
12+
data class UserBooksByIsbnsRequest(
13+
@Schema(
14+
description = "사용자 ID",
15+
example = "1"
16+
)
17+
@field:NotBlank(message = "userId는 필수입니다.")
18+
val userId: UUID? = null,
19+
20+
@Schema(
21+
description = "도서 ISBN 목록",
22+
example = "[\"9788966262429\", \"9791190412351\"]"
23+
)
24+
@field:NotEmpty(message = "isbns는 비어있을 수 없습니다.")
25+
val isbns: List<String>? = null
26+
27+
) {
28+
29+
30+
fun validUserId(): UUID = userId!!
31+
fun validIsbns(): List<String> = isbns!!
32+
33+
companion object {
34+
fun of(userId: UUID, isbns: List<String>): UserBooksByIsbnsRequest {
35+
return UserBooksByIsbnsRequest(userId = userId, isbns = isbns)
36+
}
37+
}
38+
}

apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,16 @@ class BookController(
2626

2727
@GetMapping("/search")
2828
override fun searchBooks(
29+
@AuthenticationPrincipal userId: UUID,
2930
@Valid @ModelAttribute request: BookSearchRequest
3031
): ResponseEntity<BookSearchResponse> {
31-
val response = bookUseCase.searchBooks(request)
32+
val response = bookUseCase.searchBooks(request, userId)
3233
return ResponseEntity.ok(response)
3334
}
3435

3536
@GetMapping("/detail")
3637
override fun getBookDetail(
38+
@AuthenticationPrincipal userId: UUID,
3739
@Valid @ModelAttribute request: BookDetailRequest
3840
): ResponseEntity<BookDetailResponse> {
3941
val response = bookUseCase.getBookDetail(request)

apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ import java.util.UUID
2828
@RequestMapping("/api/v1/books")
2929
interface BookControllerApi {
3030

31-
@Operation(summary = "도서 검색", description = "키워드를 사용하여 알라딘 도서 정보를 검색합니다.")
31+
@Operation(
32+
summary = "도서 검색", description = "알라딘 API를 통해 키워드로 도서를 검색합니다. \n" +
33+
" 유저의 도서 상태(읽음, 읽는 중 등)가 함께 표시됩니다. "
34+
)
3235
@ApiResponses(
3336
value = [
3437
ApiResponse(
@@ -44,7 +47,10 @@ interface BookControllerApi {
4447
]
4548
)
4649
@GetMapping("/search")
47-
fun searchBooks(@Valid @Parameter(description = "도서 검색 요청 객체") request: BookSearchRequest): ResponseEntity<BookSearchResponse>
50+
fun searchBooks(
51+
@AuthenticationPrincipal userId: UUID,
52+
@Valid @Parameter(description = "도서 검색 요청 객체") request: BookSearchRequest
53+
): ResponseEntity<BookSearchResponse>
4854

4955
@Operation(summary = "도서 상세 조회", description = "특정 도서의 상세 정보를 조회합니다.")
5056
@ApiResponses(
@@ -67,7 +73,10 @@ interface BookControllerApi {
6773
]
6874
)
6975
@GetMapping("/detail")
70-
fun getBookDetail(@Valid @Parameter(description = "도서 상세 조회 요청 객체") request: BookDetailRequest): ResponseEntity<BookDetailResponse>
76+
fun getBookDetail(
77+
@AuthenticationPrincipal userId: UUID,
78+
@Valid @Parameter(description = "도서 상세 조회 요청 객체") request: BookDetailRequest
79+
): ResponseEntity<BookDetailResponse>
7180

7281
@Operation(summary = "서재에 책 등록 또는 상태 업데이트 (Upsert)", description = "사용자의 서재에 책을 등록하거나, 이미 등록된 책의 상태를 업데이트합니다.")
7382
@ApiResponses(

apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package org.yapp.apis.book.dto.response
22

3+
import org.yapp.domain.userbook.BookStatus
4+
import org.yapp.domain.userbook.UserBook
35
import org.yapp.infra.external.aladin.response.AladinSearchResponse
46
import org.yapp.infra.external.aladin.response.BookItem
7+
import java.time.LocalDateTime
58

69
data class BookSearchResponse private constructor(
710
val version: String?,
@@ -41,18 +44,25 @@ data class BookSearchResponse private constructor(
4144
val author: String?,
4245
val publisher: String?,
4346
val coverImageUrl: String?,
47+
val userBookStatus: BookStatus
4448
) {
49+
fun updateStatus(newStatus: BookStatus): BookSummary {
50+
return this.copy(userBookStatus = newStatus)
51+
}
52+
4553
companion object {
4654
private val unknownTitle = "제목없음"
4755

56+
4857
fun fromAladinItem(item: BookItem): BookSummary? {
4958
val isbn = item.isbn ?: item.isbn13 ?: return null
5059
return BookSummary(
5160
isbn = isbn,
5261
title = item.title ?: unknownTitle,
5362
author = item.author,
5463
publisher = item.publisher,
55-
coverImageUrl = item.cover
64+
coverImageUrl = item.cover,
65+
userBookStatus = BookStatus.BEFORE_READING
5666
)
5767
}
5868
}

apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package org.yapp.apis.book.service
22

33
import org.springframework.stereotype.Service
4-
import org.yapp.apis.book.dto.request.UpsertUserBookRequest
4+
import org.yapp.apis.auth.dto.request.UserBooksByIsbnsRequest
55
import org.yapp.apis.book.dto.response.UserBookResponse
6+
import org.yapp.apis.book.dto.request.UpsertUserBookRequest
67
import org.yapp.domain.userbook.UserBookDomainService
78
import org.yapp.domain.userbook.vo.UserBookInfoVO
8-
import java.util.*
9+
import java.util.UUID
10+
911

1012
@Service
1113
class UserBookService(
@@ -30,4 +32,15 @@ class UserBookService(
3032
UserBookResponse.from(userBook)
3133
}
3234
}
35+
36+
fun findAllByUserIdAndBookIsbnIn(userBooksByIsbnsRequest: UserBooksByIsbnsRequest): List<UserBookResponse> {
37+
return userBookDomainService
38+
.findAllByUserIdAndBookIsbnIn(
39+
userBooksByIsbnsRequest.validUserId(),
40+
userBooksByIsbnsRequest.validIsbns(),
41+
)
42+
.map { UserBookResponse.from(it) }
43+
}
44+
3345
}
46+

apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.yapp.apis.book.usecase
22

33
import org.springframework.beans.factory.annotation.Qualifier
44
import org.springframework.transaction.annotation.Transactional
5+
import org.yapp.apis.auth.dto.request.UserBooksByIsbnsRequest
56
import org.yapp.apis.auth.service.UserAuthService
67
import org.yapp.apis.book.dto.request.BookCreateRequest
78
import org.yapp.apis.book.dto.request.BookDetailRequest
@@ -29,8 +30,20 @@ class BookUseCase(
2930
private val userBookService: UserBookService,
3031
private val bookManagementService: BookManagementService
3132
) {
32-
fun searchBooks(request: BookSearchRequest): BookSearchResponse {
33-
return bookQueryService.searchBooks(request)
33+
fun searchBooks(request: BookSearchRequest, userId: UUID): BookSearchResponse {
34+
userAuthService.validateUserExists(userId)
35+
36+
val searchResponse = bookQueryService.searchBooks(request)
37+
val isbns = searchResponse.books.map { it.isbn }
38+
39+
val userBooksReponse = userBookService.findAllByUserIdAndBookIsbnIn(UserBooksByIsbnsRequest.of(userId, isbns))
40+
val statusMap = userBooksReponse.associateBy({ it.bookIsbn }, { it.status })
41+
searchResponse.books.forEach { bookSummary ->
42+
statusMap[bookSummary.isbn]?.let { status ->
43+
bookSummary.updateStatus(status)
44+
}
45+
}
46+
return searchResponse
3447
}
3548

3649
fun getBookDetail(bookDetailRequest: BookDetailRequest): BookDetailResponse {

domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,12 @@ class UserBookDomainService(
3636
return userBookRepository.findAllByUserId(userId)
3737
.map(UserBookInfoVO::newInstance)
3838
}
39+
40+
fun findAllByUserIdAndBookIsbnIn(userId: UUID, isbns: List<String>): List<UserBookInfoVO> {
41+
if (isbns.isEmpty()) {
42+
return emptyList()
43+
}
44+
return userBookRepository.findAllByUserIdAndBookIsbnIn(userId, isbns)
45+
.map { UserBookInfoVO.newInstance(it) }
46+
}
3947
}

domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ interface UserBookRepository {
1010
fun save(userBook: UserBook): UserBook
1111

1212
fun findAllByUserId(userId: UUID): List<UserBook>
13+
14+
fun findAllByUserIdAndBookIsbnIn(userId: UUID, bookIsbns: List<String>): List<UserBook>
15+
1316
}

infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ import java.util.*
77
interface JpaUserBookRepository : JpaRepository<UserBookEntity, UUID> {
88
fun findByUserIdAndBookIsbn(userId: UUID, bookIsbn: String): UserBookEntity?
99
fun findAllByUserId(userId: UUID): List<UserBookEntity>
10+
fun findAllByUserIdAndBookIsbnIn(userId: UUID, bookIsbnList: List<String>): List<UserBookEntity>
11+
1012
}

infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/UserBookRepositoryImpl.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,12 @@ class UserBookRepositoryImpl(
2424
override fun findAllByUserId(userId: UUID): List<UserBook> {
2525
return jpaUserBookRepository.findAllByUserId(userId).map { it.toDomain() }
2626
}
27+
28+
override fun findAllByUserIdAndBookIsbnIn(
29+
userId: UUID,
30+
bookIsbns: List<String>
31+
): List<UserBook> {
32+
return jpaUserBookRepository.findAllByUserIdAndBookIsbnIn(userId, bookIsbns)
33+
.map { it.toDomain() }
34+
}
2735
}

0 commit comments

Comments
 (0)