Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b06d8f5
[BOOK-163] feat: infra - userbook에서 id와 userId로 도서 등록 여부 검증 메서드 구현 (#52)
minwoo1999 Jul 24, 2025
bf03a67
[BOOK-163] feat: infra - Jparepository 구현 (#52)
minwoo1999 Jul 24, 2025
48ab1d1
[BOOK-163] feat: infra - querydslrepository 구현 (#52)
minwoo1999 Jul 24, 2025
0a33bd5
[BOOK-163] feat: infra - 독서기록 Entity 구현 (#52)
minwoo1999 Jul 24, 2025
cc64171
[BOOK-163] feat: domain - 독서기록 Entity 구현 (#52)
minwoo1999 Jul 24, 2025
3881e6d
[BOOK-163] feat: domain - 독서기록 entity mapper 구현 (#52)
minwoo1999 Jul 24, 2025
5f665f8
[BOOK-163] feat: domain - 독서기록 도메인서비스 구현 (#52)
minwoo1999 Jul 24, 2025
6050cd2
[BOOK-163] feat: domain - 독서기록 domain VO 구현 (#52)
minwoo1999 Jul 24, 2025
6584426
[BOOK-163] feat: domain - 통합 interface 구현 (#52)
minwoo1999 Jul 24, 2025
c50f65c
[BOOK-163] feat: apis - 독서기록 usecase구현 (#52)
minwoo1999 Jul 24, 2025
c2cd5fe
[BOOK-163] feat: apis - 독서기록 service 구현 (#52)
minwoo1999 Jul 24, 2025
7d2172d
[BOOK-163] feat: apis - 독서기록 dto 구현 (#52)
minwoo1999 Jul 24, 2025
e947c77
[BOOK-163] feat: apis - 독서기록 controller 구현 (#52)
minwoo1999 Jul 24, 2025
dbb12b3
[BOOK-163] feat: apis - merge conflict 해결 (#52)
minwoo1999 Jul 24, 2025
c12c74a
[BOOK-163] feat: apis - userbook exception 처리 (#52)
minwoo1999 Jul 24, 2025
78e9e6c
[BOOK-163] refactor: apis - 코드컨벤션에 맞게 리팩토링 (#52)
minwoo1999 Jul 24, 2025
c4db4f4
Merge branch 'develop' into BOOK-163-feature/#52
minwoo1999 Jul 25, 2025
c6853af
[BOOK-163] refactor: apis - 1차 코드리뷰 반영(#52)
minwoo1999 Jul 26, 2025
6786eac
[BOOK-163] refactor: infra 1차 코드리뷰 반영(#52)
minwoo1999 Jul 26, 2025
49104b6
[BOOK-163] refactor: domain 1차 코드리뷰 반영(#52)
minwoo1999 Jul 26, 2025
d606fdc
[BOOK-163] refactor: domain bookId isbn -> UUID로 변경 (#52)
minwoo1999 Jul 26, 2025
93c3875
[BOOK-163] feat: apis 내서재 총 책개수 컬럼추가 (#52)
minwoo1999 Jul 26, 2025
fb6fd8d
[BOOK-163] feat: domain tag 중간테이블 기능개발 (#52)
minwoo1999 Jul 26, 2025
221d774
[BOOK-163] feat: infra tag 중간테이블 기능개발 (#52)
minwoo1999 Jul 26, 2025
413f117
[BOOK-163] feat: infra 외부API imagePage가져오도록 설정 (#52)
minwoo1999 Jul 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.yapp.apis.auth.dto.response.RefreshTokenResponse
name = "DeleteTokenRequest",
description = "Request DTO for deleting a refresh token"
)
data class DeleteTokenRequest(
data class DeleteTokenRequest private constructor(
@field:NotBlank(message = "Refresh token must not be blank.")
@Schema(description = "Refresh token to be deleted", example = "eyJhbGciOiJIUz...")
val refreshToken: String? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import java.util.*
name = "FindUserIdentityRequest",
description = "Request DTO to retrieve user identity information using userId"
)
data class FindUserIdentityRequest(
data class FindUserIdentityRequest private constructor(
@Schema(
description = "User ID (UUID format)",
example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import java.util.*
name = "TokenGenerateRequest",
description = "DTO containing information required to save the generated refresh token"
)
data class TokenGenerateRequest(
data class TokenGenerateRequest private constructor(
@field:NotNull(message = "userId must not be null")
@Schema(description = "User ID", example = "f6b7d490-1b1a-4b9f-8e8e-27f8e3a5dafa")
val userId: UUID? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import java.util.UUID
name = "UserBooksByIsbnsRequest",
description = "Request DTO for finding user books by user ID and a list of ISBNs"
)
data class UserBooksByIsbnsRequest(
data class UserBooksByIsbnsRequest private constructor(
@Schema(
description = "사용자 ID",
example = "1"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.yapp.apis.book.controller

import jakarta.validation.Valid
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.web.PageableDefault
Expand All @@ -23,6 +22,7 @@ import org.yapp.apis.book.dto.response.UserBookPageResponse
import org.yapp.apis.book.dto.response.UserBookResponse
import org.yapp.apis.book.usecase.BookUseCase
import org.yapp.domain.userbook.BookStatus
import org.yapp.domain.userbook.UserBookSortType
import java.util.UUID

@RestController
Expand Down Expand Up @@ -62,7 +62,7 @@ class BookController(
override fun getUserLibraryBooks(
@AuthenticationPrincipal userId: UUID,
@RequestParam(required = false) status: BookStatus?,
@RequestParam(required = false) sort: String?,
@RequestParam(required = false) sort: UserBookSortType?,
@PageableDefault(size = 10, sort = ["createdAt"], direction = Sort.Direction.DESC)
pageable: Pageable
): ResponseEntity<UserBookPageResponse> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.yapp.apis.book.dto.response.BookSearchResponse
import org.yapp.apis.book.dto.response.UserBookPageResponse
import org.yapp.apis.book.dto.response.UserBookResponse
import org.yapp.domain.userbook.BookStatus
import org.yapp.domain.userbook.UserBookSortType
import org.yapp.globalutils.exception.ErrorResponse
import java.util.UUID

Expand Down Expand Up @@ -140,7 +141,7 @@ interface BookControllerApi {
fun getUserLibraryBooks(
@AuthenticationPrincipal userId: UUID,
@RequestParam(required = false) status: BookStatus?,
@RequestParam(required = false) sort: String?,
@RequestParam(required = false) sort: UserBookSortType?,
@PageableDefault(size = 10, sort = ["createdAt"], direction = Sort.Direction.DESC)
pageable: Pageable
): ResponseEntity<UserBookPageResponse>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ data class BookCreateRequest private constructor(
@field:Size(max = 2048, message = "표지 URL은 2048자 이내여야 합니다.")
val coverImageUrl: String,

val description: String? = null,
val description: String? = null
) {
Comment on lines 23 to 26
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

마지막 파라미터에도 트레일링 콤마를 남겨 스타일을 통일하세요.

description 뒤의 트레일링 콤마를 제거하면서, 같은 파일 내 from() 호출부(45~46행)에는 콤마가 남아 있어 일관성이 깨집니다. Kotlin 1.4+에서는 멀티라인 인자 목록에 트레일링 콤마를 두는 것이 흔한 패턴이므로 선언부와 호출부 모두 동일한 규칙을 적용하는 편이 유지보수에 유리합니다.

-    val description: String? = null
+    val description: String? = null,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val coverImageUrl: String,
val description: String? = null,
val description: String? = null
) {
val coverImageUrl: String,
val description: String? = null,
) {
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt
around lines 23 to 26, add a trailing comma after the last parameter
'description' in the constructor to maintain consistent style with the rest of
the file, especially matching the trailing comma usage in the 'from()' call
around lines 45 to 46. This ensures uniformity in multiline parameter lists and
improves maintainability.

fun validIsbn(): String = isbn!!
fun validTitle(): String = title!!
Expand All @@ -42,7 +42,7 @@ data class BookCreateRequest private constructor(
publisher = bookDetail.publisher,
publicationYear = parsePublicationYear(bookDetail.pubDate),
coverImageUrl = bookDetail.cover,
description = bookDetail.description
description = bookDetail.description,
)
Comment on lines +45 to 46
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

선언부와 호출부의 트레일링 콤마 스타일이 불일치합니다.

위 25행에서 콤마를 제거했다면, 여기서도 제거하여 일관성을 맞추거나 반대로 둘 다 유지하는 방향으로 통일해 주세요.
현재처럼 혼재되면 차후 자동 포매터(ktlint 등) 적용 시 불필요한 변경이 반복적으로 발생할 수 있습니다.

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt
around lines 45 to 46, the trailing comma style between the declaration and call
sites is inconsistent. To fix this, either remove the trailing comma here to
match line 25 or add trailing commas in both places to maintain consistency and
prevent repeated formatting changes by tools like ktlint.

}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package org.yapp.apis.book.dto.request

import org.yapp.apis.book.dto.response.BookCreateResponse
import org.yapp.apis.book.dto.response.UserBookResponse
import org.yapp.domain.userbook.BookStatus
import java.util.UUID


data class UpsertUserBookRequest private constructor(
val userId: UUID? = null,
val bookId: UUID? = null,
val bookIsbn: String? = null,
val bookTitle: String? = null,
val bookAuthor: String? = null,
Expand All @@ -16,6 +16,7 @@ data class UpsertUserBookRequest private constructor(
val status: BookStatus? = null
) {
fun validUserId(): UUID = userId!!
fun validBookId(): UUID = bookId!!
fun validBookIsbn(): String = bookIsbn!!
fun validBookTitle(): String = bookTitle!!
fun validBookAuthor(): String = bookAuthor!!
Expand All @@ -27,10 +28,11 @@ data class UpsertUserBookRequest private constructor(
fun of(
userId: UUID,
bookCreateResponse: BookCreateResponse,
status: BookStatus
status: BookStatus,
): UpsertUserBookRequest {
return UpsertUserBookRequest(
userId = userId,
bookId = bookCreateResponse.bookId,
bookIsbn = bookCreateResponse.isbn,
bookTitle = bookCreateResponse.title,
bookAuthor = bookCreateResponse.author,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@ package org.yapp.apis.book.dto.response

import org.yapp.domain.book.vo.BookInfoVO

import java.util.UUID

data class BookCreateResponse private constructor(
val bookId: UUID,
val isbn: String,
val title: String,
val author: String,
val publisher: String,
val coverImageUrl: String
val coverImageUrl: String,
) {
companion object {
fun from(bookVO: BookInfoVO): BookCreateResponse {
return BookCreateResponse(
bookId = bookVO.id.value,
isbn = bookVO.isbn.value,
title = bookVO.title,
author = bookVO.author,
publisher = bookVO.publisher,
coverImageUrl = bookVO.coverImageUrl
coverImageUrl = bookVO.coverImageUrl,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ data class BookDetailResponse private constructor(
val cover: String,
val categoryId: Int?,
val categoryName: String?,
val publisher: String
val publisher: String,
) {
companion object {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ data class UserBookPageResponse private constructor(
val readingCount: Long,

@Schema(description = "완독한 책 개수")
val completedCount: Long
val completedCount: Long,

@Schema(description = "총 책 개수")
val totalCount: Long
) {
companion object {
fun of(
Expand All @@ -25,11 +28,13 @@ data class UserBookPageResponse private constructor(
readingCount: Long,
completedCount: Long
): UserBookPageResponse {
val totalCount = beforeReadingCount + readingCount + completedCount
return UserBookPageResponse(
books = books,
beforeReadingCount = beforeReadingCount,
readingCount = readingCount,
completedCount = completedCount
completedCount = completedCount,
totalCount = totalCount
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.yapp.apis.book.exception

import org.springframework.http.HttpStatus
import org.yapp.globalutils.exception.BaseErrorCode

enum class UserBookErrorCode(
private val status: HttpStatus,
private val code: String,
private val message: String
) : BaseErrorCode {
USER_BOOK_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_BOOK_001", "사용자의 책을 찾을 수 없습니다.");

override fun getHttpStatus(): HttpStatus = status
override fun getCode(): String = code
override fun getMessage(): String = message
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.yapp.apis.book.exception

import org.yapp.globalutils.exception.CommonException

class UserBookNotFoundException(
errorCode: UserBookErrorCode,
message: String? = null
) : CommonException(errorCode, message)
23 changes: 14 additions & 9 deletions apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import org.yapp.apis.auth.dto.request.UserBooksByIsbnsRequest
import org.yapp.apis.book.dto.response.UserBookPageResponse
import org.yapp.apis.book.dto.response.UserBookResponse
import org.yapp.apis.book.dto.request.UpsertUserBookRequest
import org.yapp.apis.book.exception.UserBookErrorCode
import org.yapp.apis.book.exception.UserBookNotFoundException
import org.yapp.domain.userbook.BookStatus
import org.yapp.domain.userbook.UserBook
import org.yapp.domain.userbook.UserBookDomainService
import org.yapp.domain.userbook.vo.UserBookInfoVO
import org.yapp.domain.userbook.vo.UserBookStatusCountsVO
import org.yapp.domain.userbook.UserBookSortType
import java.util.UUID


Expand All @@ -21,6 +23,7 @@ class UserBookService(
fun upsertUserBook(upsertUserBookRequest: UpsertUserBookRequest): UserBookResponse {
val userBookInfoVO = userBookDomainService.upsertUserBook(
upsertUserBookRequest.validUserId(),
upsertUserBookRequest.validBookId(),
upsertUserBookRequest.validBookIsbn(),
upsertUserBookRequest.validBookTitle(),
upsertUserBookRequest.validBookAuthor(),
Expand All @@ -31,13 +34,15 @@ class UserBookService(
return UserBookResponse.from(userBookInfoVO)
}

fun findAllUserBooks(userId: UUID): List<UserBookResponse> {
val userBooks: List<UserBookInfoVO> = userBookDomainService.findAllUserBooks(userId)
return userBooks.map { userBook: UserBookInfoVO ->
UserBookResponse.from(userBook)
}
fun validateUserBookExists(userId: UUID, userBookId: UUID): UserBook {
return userBookDomainService.findByIdAndUserId(userBookId, userId)
?: throw UserBookNotFoundException(
UserBookErrorCode.USER_BOOK_NOT_FOUND,
"User book not found with id: $userBookId and userId: $userId"
)
}
Comment on lines +37 to 43
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

예외 메시지에서 민감한 정보 노출을 고려해주세요.

메서드 구현은 적절하지만 다음 사항들을 검토해보시기 바랍니다:

  1. 보안 고려사항: 예외 메시지에 userBookIduserId가 직접 노출되고 있습니다. 로그나 클라이언트 응답에서 이러한 내부 ID가 노출될 수 있습니다.
  2. 파라미터 순서: 도메인 서비스의 findByIdAndUserId(userBookId, userId)와 달리 현재 메서드는 (userId, userBookId) 순서를 사용합니다. 일관성을 위해 순서를 맞추는 것을 고려해보세요.

다음과 같이 수정을 제안합니다:

-    fun validateUserBookExists(userId: UUID, userBookId: UUID): UserBook {
-        return userBookDomainService.findByIdAndUserId(userBookId, userId)
+    fun validateUserBookExists(userBookId: UUID, userId: UUID): UserBook {
+        return userBookDomainService.findByIdAndUserId(userBookId, userId)
             ?: throw UserBookNotFoundException(
                 UserBookErrorCode.USER_BOOK_NOT_FOUND,
-                "User book not found with id: $userBookId and userId: $userId"
+                "User book not found"
             )
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun validateUserBookExists(userId: UUID, userBookId: UUID): UserBook {
return userBookDomainService.findByIdAndUserId(userBookId, userId)
?: throw UserBookNotFoundException(
UserBookErrorCode.USER_BOOK_NOT_FOUND,
"User book not found with id: $userBookId and userId: $userId"
)
}
fun validateUserBookExists(userBookId: UUID, userId: UUID): UserBook {
return userBookDomainService.findByIdAndUserId(userBookId, userId)
?: throw UserBookNotFoundException(
UserBookErrorCode.USER_BOOK_NOT_FOUND,
"User book not found"
)
}
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt around
lines 35 to 41, the exception message exposes sensitive internal IDs (userBookId
and userId), which should be avoided for security reasons. Modify the exception
message to exclude these IDs or replace them with a generic message. Also,
adjust the parameter order in the validateUserBookExists method to match the
domain service's findByIdAndUserId method by passing userBookId first and then
userId for consistency.



fun findAllByUserIdAndBookIsbnIn(userBooksByIsbnsRequest: UserBooksByIsbnsRequest): List<UserBookResponse> {
val userBooks = userBookDomainService.findAllByUserIdAndBookIsbnIn(
userBooksByIsbnsRequest.validUserId(),
Expand All @@ -49,7 +54,7 @@ class UserBookService(
private fun findUserBooksByDynamicCondition(
userId: UUID,
status: BookStatus?,
sort: String?,
sort: UserBookSortType?,
pageable: Pageable
): Page<UserBookResponse> {
val page = userBookDomainService.findUserBooksByDynamicCondition(userId, status, sort, pageable)
Expand All @@ -59,7 +64,7 @@ class UserBookService(
fun findUserBooksByDynamicConditionWithStatusCounts(
userId: UUID,
status: BookStatus?,
sort: String?,
sort: UserBookSortType?,
pageable: Pageable
): UserBookPageResponse {
val userBookResponsePage = findUserBooksByDynamicCondition(userId, status, sort, pageable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.yapp.apis.book.service.BookManagementService
import org.yapp.apis.book.service.BookQueryService
import org.yapp.apis.book.service.UserBookService
import org.yapp.domain.userbook.BookStatus
import org.yapp.domain.userbook.UserBookSortType
import org.yapp.globalutils.annotation.UseCase
import java.util.UUID

Expand Down Expand Up @@ -63,7 +64,7 @@ class BookUseCase(
val upsertUserBookRequest = UpsertUserBookRequest.of(
userId = userId,
bookCreateResponse,
status = request.bookStatus
status = request.bookStatus,
)
val userBookResponse = userBookService.upsertUserBook(upsertUserBookRequest)

Expand All @@ -73,7 +74,7 @@ class BookUseCase(
fun getUserLibraryBooks(
userId: UUID,
status: BookStatus?,
sort: String?,
sort: UserBookSortType?,
pageable: Pageable
): UserBookPageResponse {
userAuthService.validateUserExists(userId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.yapp.apis.readingrecord.controller

import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.web.PageableDefault
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.yapp.apis.readingrecord.dto.request.CreateReadingRecordRequest
import org.yapp.apis.readingrecord.dto.response.ReadingRecordResponse
import org.yapp.apis.readingrecord.usecase.ReadingRecordUseCase
import org.yapp.domain.readingrecord.ReadingRecordSortType
import java.util.UUID
import jakarta.validation.Valid

@RestController
Copy link
Member

Choose a reason for hiding this comment

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

여기에 RequestMapping 추가해주시면 감사하겠습니다! (가독성을 위해서)

@RequestMapping("/api/v1/reading-records")
class ReadingRecordController(
private val readingRecordUseCase: ReadingRecordUseCase
) : ReadingRecordControllerApi {

@PostMapping("/{userBookId}")
override fun createReadingRecord(
@AuthenticationPrincipal userId: UUID,
@PathVariable userBookId: UUID,
@Valid @RequestBody request: CreateReadingRecordRequest
): ResponseEntity<ReadingRecordResponse> {
val response = readingRecordUseCase.createReadingRecord(
userId = userId,
userBookId = userBookId,
request = request
)
return ResponseEntity.status(HttpStatus.CREATED).body(response)
}

@GetMapping("/{userBookId}")
override fun getReadingRecords(
@AuthenticationPrincipal userId: UUID,
@PathVariable userBookId: UUID,
@RequestParam(required = false) sort: ReadingRecordSortType?,
@PageableDefault(size = 10, sort = ["createdAt"], direction = Sort.Direction.DESC)
pageable: Pageable
): ResponseEntity<Page<ReadingRecordResponse>> {
val response = readingRecordUseCase.getReadingRecordsByUserBookId(
userId = userId,
userBookId = userBookId,
sort = sort,
pageable = pageable
)
return ResponseEntity.ok(response)
}
}
Loading