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 220d950e..a036e959 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 @@ -63,10 +63,11 @@ class BookController( @AuthenticationPrincipal userId: UUID, @RequestParam(required = false) status: BookStatus?, @RequestParam(required = false) sort: UserBookSortType?, + @RequestParam(required = false) title: String?, @PageableDefault(size = 10, sort = ["createdAt"], direction = Sort.Direction.DESC) pageable: Pageable ): ResponseEntity { - val response = bookUseCase.getUserLibraryBooks(userId, status, sort, pageable) + val response = bookUseCase.getUserLibraryBooks(userId, status, sort, title, pageable) return ResponseEntity.ok(response) } 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 348bac2d..67ead64d 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 @@ -117,7 +117,7 @@ interface BookControllerApi { @Valid @RequestBody request: UserBookRegisterRequest ): ResponseEntity - @Operation(summary = "사용자 서재 조회", description = "현재 사용자의 서재에 등록된 모든 책을 조회합니다.") + @Operation(summary = "사용자 서재 조회", description = "현재 사용자의 서재에 등록된 모든 책을 조회합니다. 제목(title)으로 검색할 수 있습니다.") @ApiResponses( value = [ ApiResponse( @@ -140,8 +140,9 @@ interface BookControllerApi { @GetMapping("/my-library") fun getUserLibraryBooks( @AuthenticationPrincipal userId: UUID, - @RequestParam(required = false) status: BookStatus?, - @RequestParam(required = false) sort: UserBookSortType?, + @RequestParam(required = false) @Parameter(description = "책 상태 필터") status: BookStatus?, + @RequestParam(required = false) @Parameter(description = "정렬 방식") sort: UserBookSortType?, + @RequestParam(required = false) @Parameter(description = "책 제목 검색") title: String?, @PageableDefault(size = 10, sort = ["createdAt"], direction = Sort.Direction.DESC) pageable: Pageable ): ResponseEntity 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 290efda6..41814c34 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 @@ -55,9 +55,10 @@ class UserBookService( userId: UUID, status: BookStatus?, sort: UserBookSortType?, + title: String?, pageable: Pageable ): Page { - val page = userBookDomainService.findUserBooksByDynamicCondition(userId, status, sort, pageable) + val page = userBookDomainService.findUserBooksByDynamicCondition(userId, status, sort, title, pageable) return page.map { UserBookResponse.from(it) } } @@ -65,9 +66,10 @@ class UserBookService( userId: UUID, status: BookStatus?, sort: UserBookSortType?, + title: String?, pageable: Pageable ): UserBookPageResponse { - val userBookResponsePage = findUserBooksByDynamicCondition(userId, status, sort, pageable) + val userBookResponsePage = findUserBooksByDynamicCondition(userId, status, sort, title, pageable) val userBookStatusCountsVO = userBookDomainService.getUserBookStatusCounts(userId) return UserBookPageResponse.of( 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 6be48d7f..2d6bf7b1 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 @@ -75,10 +75,11 @@ class BookUseCase( userId: UUID, status: BookStatus?, sort: UserBookSortType?, + title: String?, pageable: Pageable ): UserBookPageResponse { userAuthService.validateUserExists(userId) - return userBookService.findUserBooksByDynamicConditionWithStatusCounts(userId, status, sort, pageable) + return userBookService.findUserBooksByDynamicConditionWithStatusCounts(userId, status, sort, title, pageable) } } diff --git a/apis/src/main/kotlin/org/yapp/apis/readingrecord/controller/ReadingRecordControllerApi.kt b/apis/src/main/kotlin/org/yapp/apis/readingrecord/controller/ReadingRecordControllerApi.kt index 54973e63..4f1644bd 100644 --- a/apis/src/main/kotlin/org/yapp/apis/readingrecord/controller/ReadingRecordControllerApi.kt +++ b/apis/src/main/kotlin/org/yapp/apis/readingrecord/controller/ReadingRecordControllerApi.kt @@ -69,7 +69,7 @@ interface ReadingRecordControllerApi { ApiResponse( responseCode = "200", description = "독서 기록 목록 조회 성공", - content = [Content(schema = Schema(implementation = ReadingRecordResponse::class))] + content = [Content(schema = Schema(implementation = Page::class))] ), ApiResponse( responseCode = "404", 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 5d54a4c6..72b89f2f 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt @@ -41,9 +41,10 @@ class UserBookDomainService( userId: UUID, status: BookStatus?, sort: UserBookSortType?, + title: String?, pageable: Pageable ): Page { - val page = userBookRepository.findUserBooksByDynamicCondition(userId, status, sort, pageable) + val page = userBookRepository.findUserBooksByDynamicCondition(userId, status, sort, title, pageable) return page.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 b89b51d2..ee1dd3f1 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt @@ -21,6 +21,7 @@ interface UserBookRepository { userId: UUID, status: BookStatus?, sort: UserBookSortType?, + title: String?, pageable: Pageable ): Page diff --git a/infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt b/infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt index 1802ae03..a163d6fd 100644 --- a/infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt +++ b/infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt @@ -4,6 +4,7 @@ import jakarta.persistence.* import org.hibernate.annotations.JdbcTypeCode import org.hibernate.annotations.SQLDelete import org.hibernate.annotations.SQLRestriction +import jakarta.persistence.Index import org.yapp.infra.common.BaseTimeEntity import org.yapp.domain.userbook.BookStatus import org.yapp.domain.userbook.UserBook @@ -11,7 +12,13 @@ import java.sql.Types import java.util.* @Entity -@Table(name = "user_books") +@Table( + name = "user_books", + indexes = [ + Index(name = "idx_user_books_title", columnList = "title"), + Index(name = "idx_user_books_user_id_title", columnList = "user_id, title") + ] +) @SQLDelete(sql = "UPDATE user_books SET deleted_at = NOW() WHERE id = ?") @SQLRestriction("deleted_at IS NULL") class UserBookEntity( diff --git a/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookQuerydslRepository.kt b/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookQuerydslRepository.kt index 3f178a38..943c7917 100644 --- a/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookQuerydslRepository.kt +++ b/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookQuerydslRepository.kt @@ -12,6 +12,7 @@ interface JpaUserBookQuerydslRepository { userId: UUID, status: BookStatus?, sort: UserBookSortType?, + title: String?, pageable: Pageable ): Page diff --git a/infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/JpaUserBookQuerydslRepositoryImpl.kt b/infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/JpaUserBookQuerydslRepositoryImpl.kt index 6c4a5f82..c035b0ad 100644 --- a/infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/JpaUserBookQuerydslRepositoryImpl.kt +++ b/infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/JpaUserBookQuerydslRepositoryImpl.kt @@ -25,13 +25,15 @@ class JpaUserBookQuerydslRepositoryImpl( userId: UUID, status: BookStatus?, sort: UserBookSortType?, + title: String?, pageable: Pageable ): Page { val baseQuery = queryFactory .selectFrom(userBook) .where( userBook.userId.eq(userId), - statusEq(status) + statusEq(status), + titleContains(title) ) val results = baseQuery @@ -45,7 +47,8 @@ class JpaUserBookQuerydslRepositoryImpl( .from(userBook) .where( userBook.userId.eq(userId), - statusEq(status) + statusEq(status), + titleContains(title) ) .fetchOne() ?: 0L @@ -70,6 +73,12 @@ class JpaUserBookQuerydslRepositoryImpl( return status?.let { userBook.status.eq(it) } } + private fun titleContains(title: String?): BooleanExpression? { + return title?.takeIf { it.isNotBlank() }?.let { + userBook.title.like("%" + it + "%") + } + } + private fun createOrderSpecifier(sort: UserBookSortType?): OrderSpecifier<*> { return when (sort) { UserBookSortType.TITLE_ASC -> userBook.title.asc() 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 16cfaae0..dc933d22 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 @@ -49,9 +49,10 @@ class UserBookRepositoryImpl( userId: UUID, status: BookStatus?, sort: UserBookSortType?, + title: String?, pageable: Pageable ): Page { - return jpaUserBookRepository.findUserBooksByDynamicCondition(userId, status, sort, pageable) + return jpaUserBookRepository.findUserBooksByDynamicCondition(userId, status, sort, title, pageable) .map { it.toDomain() } }