Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -19,12 +19,6 @@ data class BookSearchResponse private constructor(
@field:Schema(description = "검색 결과 제목", example = "데미안")
val title: String?,

@field:Schema(
description = "검색 결과 링크",
example = "http://www.aladin.co.kr/shop/wsarch.aspx?SearchTarget=Book&query=%EC%95%84%EB%B0%94%EC%9D%98+%ED%95%B4%EB%B0%A9%EC%9D%BC%EC%A7%80&partner=openAPI"
)
val link: String?,

@field:Schema(description = "출간일", example = "2025-07-30")
val pubDate: String?,

Expand All @@ -46,6 +40,9 @@ data class BookSearchResponse private constructor(
@field:Schema(description = "검색 카테고리 이름", example = "소설/시/희곡")
val searchCategoryName: String?,

@field:Schema(description = "마지막 페이지 여부", example = "false")
val lastPage: Boolean,

Comment on lines +43 to +45
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

lastPage 필드 추가 LGTM — 스키마 설명에 200개 제한 정책을 명시 제안

필터링/상한(200개) 정책 하에서 lastPage는 총합과 불일치할 수 있음을 스키마 설명에 한 줄 보강하면 소비자 혼선을 줄일 수 있습니다.

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt
around lines 43 to 45, the lastPage field's schema description should be
enhanced to mention the 200-item limit policy. Update the @field:Schema
description to include a note that due to filtering or the 200-item maximum
limit, lastPage may not always align with the total count, to reduce confusion
for API consumers.

@field:Schema(description = "검색된 책 목록")
val books: List<BookSummary>
) {
Expand All @@ -54,26 +51,27 @@ data class BookSearchResponse private constructor(
}

companion object {
fun from(response: AladinSearchResponse): BookSearchResponse {
fun of(response: AladinSearchResponse, isLastPage: Boolean): BookSearchResponse { // Added isLastPage parameter
return BookSearchResponse(
version = response.version,
title = response.title,
link = response.link,
pubDate = response.pubDate,
totalResults = response.totalResults,
startIndex = response.startIndex,
itemsPerPage = response.itemsPerPage,
query = response.query,
searchCategoryId = response.searchCategoryId,
searchCategoryName = response.searchCategoryName,
lastPage = isLastPage,
books = response.item.map {
BookSummary.of(
isbn = it.isbn,
isbn13 = it.isbn13,
title = it.title,
author = AuthorExtractor.extractAuthors(it.author),
publisher = it.publisher,
coverImageUrl = it.cover
coverImageUrl = it.cover,
link = it.link
)
}
)
Expand Down Expand Up @@ -101,6 +99,12 @@ data class BookSearchResponse private constructor(
)
val coverImageUrl: String,

@field:Schema(
description = "알라딘 도서 상세 페이지 링크",
example = "http://www.aladin.co.kr/shop/wproduct.aspx?ItemId=3680175"
)
val link: String, // Added link field

Comment on lines +102 to +107
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

BookSummary.link의 null 허용 또는 기본값 제공 검토

외부 링크가 누락될 수 있다면 nullable 또는 기본값이 안전합니다. 오픈API 문서에도 선택값임을 반영하세요.

-        val link: String, // Added link field
+        val link: String?, // 외부 응답 누락 대비
📝 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
@field:Schema(
description = "알라딘 도서 상세 페이지 링크",
example = "http://www.aladin.co.kr/shop/wproduct.aspx?ItemId=3680175"
)
val link: String, // Added link field
@field:Schema(
description = "알라딘 도서 상세 페이지 링크",
example = "http://www.aladin.co.kr/shop/wproduct.aspx?ItemId=3680175"
)
val link: String?, // 외부 응답 누락 대비
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt
around lines 102 to 107, the 'link' field should be made nullable or given a
default value to handle cases where the external link might be missing. Update
the 'link' property to be nullable (e.g., String?) or provide a default value,
and modify the @Schema annotation to indicate that this field is optional in the
OpenAPI documentation.

@field:Schema(description = "사용자의 책 상태", example = "BEFORE_REGISTRATION")
val userBookStatus: BookStatus
) {
Expand All @@ -115,7 +119,8 @@ data class BookSearchResponse private constructor(
title: String?,
author: String?,
publisher: String?,
coverImageUrl: String
coverImageUrl: String,
link: String // Added link
): BookSummary {
Comment on lines +122 to 124
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

팩토리 시그니처/구현을 null 허용에 맞추어 조정

위 제안과 함께 일관되게 처리하세요.

-                coverImageUrl: String,
-                link: String // Added link
+                coverImageUrl: String?,
+                link: String?
             ): BookSummary {
                 require(!title.isNullOrBlank()) { "Title is required" }
 
                 return BookSummary(
                     isbn13 = isbn13 ?: IsbnConverter.toIsbn13(isbn)
                     ?: throw IllegalArgumentException("Either isbn13 or isbn must be provided"),
                     title = title,
                     author = author,
                     publisher = publisher?.let { BookDataValidator.removeParenthesesFromPublisher(it) },
-                    coverImageUrl = coverImageUrl,
-                    link = link,
+                    coverImageUrl = coverImageUrl ?: "",
+                    link = link,
                     userBookStatus = BookStatus.BEFORE_REGISTRATION
                 )

추가로, 커버 기본값(예: placeholder 이미지 URL)을 환경설정으로 관리하면 좋습니다.

Also applies to: 134-135

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt
around lines 122-124 and 134-135, adjust the factory method signatures and
implementations to consistently allow null values for parameters like
coverImageUrl and link. Additionally, manage the default cover image URL (e.g.,
a placeholder image) through environment configuration rather than hardcoding
it. Update the code to fetch this default from configuration when the
coverImageUrl is null.

require(!title.isNullOrBlank()) { "Title is required" }

Expand All @@ -126,6 +131,7 @@ data class BookSearchResponse private constructor(
author = author,
publisher = publisher?.let { BookDataValidator.removeParenthesesFromPublisher(it) },
coverImageUrl = coverImageUrl,
link = link,
userBookStatus = BookStatus.BEFORE_REGISTRATION
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ enum class BookErrorCode(
private val message: String
) : BaseErrorCode {

/* 500 INTERNAL_SERVER_ERROR */
ALADIN_API_SEARCH_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "BOOK_500_01", "알라딘 도서 검색 API 호출에 실패했습니다."),
ALADIN_API_LOOKUP_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "BOOK_500_02", "알라딘 도서 상세 조회 API 호출에 실패했습니다.");
/* 403 FORBIDDEN */
ALADIN_API_SEARCH_FAILED(HttpStatus.FORBIDDEN, "BOOK_403_01", "알라딘 도서 검색 API 호출에 실패했습니다."),
ALADIN_API_LOOKUP_FAILED(HttpStatus.FORBIDDEN, "BOOK_403_02", "알라딘 도서 상세 조회 API 호출에 실패했습니다."),
ALADIN_API_RESULT_LIMIT(HttpStatus.FORBIDDEN, "BOOK_403_03", "검색 결과가 200개를 초과하여 모든 결과를 조회했습니다.");
Comment on lines +12 to +15
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

ALADIN_API_RESULT_LIMIT 메시지 수정 및 403 적용 타당성 검토 제안

  • 현재 메시지 "검색 결과가 200개를 초과하여 모든 결과를 조회했습니다."는 예외를 던지는 동작과 상충됩니다. "요청 범위가 제한을 초과"했다는 식으로 명확화가 필요합니다.
  • 200개 한도 위반은 권한 문제(403)라기보다 요청 유효성(400/422) 혹은 범위 문제(416)에 가깝습니다. 기존 클라이언트와 에러 정책에 따라 403 유지도 가능하나, 사전 합의/문서화가 필요해 보입니다.

적용 예시:

-    ALADIN_API_RESULT_LIMIT(HttpStatus.FORBIDDEN, "BOOK_403_03", "검색 결과가 200개를 초과하여 모든 결과를 조회했습니다.");
+    ALADIN_API_RESULT_LIMIT(
+        HttpStatus.FORBIDDEN,
+        "BOOK_403_03",
+        "검색 결과 상한(200개)을 초과하는 범위를 요청했습니다. 상위 200개까지만 조회 가능합니다."
+    );

추가 권고:

  • 이 파일의 403 전환이 외부/내부 클라이언트 계약(에러 처리 분기)에 영향을 주지 않는지 통합테스트/계약테스트 확인을 권장합니다. 필요 시 API 문서(에러 코드표) 업데이트 바랍니다.

ALADIN_API_RESULT_LIMIT 메시지 및 HTTP 상태 코드 검토 필요

ALADIN_API_RESULT_LIMIT 예외 메시지가 실제 동작(요청 범위 초과)과 불일치하며, 403 FORBIDDEN 적용 타당성도 재검토가 필요합니다.

• 위치

  • 파일: apis/src/main/kotlin/org/yapp/apis/book/exception/BookErrorCode.kt
  • 라인: 12–15

• 제안된 수정 예시

-    ALADIN_API_RESULT_LIMIT(HttpStatus.FORBIDDEN, "BOOK_403_03", "검색 결과가 200개를 초과하여 모든 결과를 조회했습니다.");
+    ALADIN_API_RESULT_LIMIT(
+        HttpStatus.FORBIDDEN,
+        "BOOK_403_03",
+        "검색 결과 상한(200개)을 초과하는 범위를 요청했습니다. 상위 200개까지만 조회 가능합니다."
+    );

• 검토 사항

  1. “요청 범위 제한 초과”를 명확히 반영한 메시지로 변경
  2. 403 FORBIDDEN 대신 400/422(요청 유효성) 또는 416(범위 초과)이 더 적합한지 사내 정책·클라이언트 계약에 따라 결정
  3. 통합 테스트 및 계약 테스트에서 에러 분기 영향 확인
  4. API 문서(에러 코드표) 및 클라이언트 가이드 업데이트
📝 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
/* 403 FORBIDDEN */
ALADIN_API_SEARCH_FAILED(HttpStatus.FORBIDDEN, "BOOK_403_01", "알라딘 도서 검색 API 호출에 실패했습니다."),
ALADIN_API_LOOKUP_FAILED(HttpStatus.FORBIDDEN, "BOOK_403_02", "알라딘 도서 상세 조회 API 호출에 실패했습니다."),
ALADIN_API_RESULT_LIMIT(HttpStatus.FORBIDDEN, "BOOK_403_03", "검색 결과가 200개를 초과하여 모든 결과를 조회했습니다.");
/* 403 FORBIDDEN */
ALADIN_API_SEARCH_FAILED(HttpStatus.FORBIDDEN, "BOOK_403_01", "알라딘 도서 검색 API 호출에 실패했습니다."),
ALADIN_API_LOOKUP_FAILED(HttpStatus.FORBIDDEN, "BOOK_403_02", "알라딘 도서 상세 조회 API 호출에 실패했습니다."),
ALADIN_API_RESULT_LIMIT(
HttpStatus.FORBIDDEN,
"BOOK_403_03",
"검색 결과 상한(200개)을 초과하는 범위를 요청했습니다. 상위 200개까지만 조회 가능합니다."
);
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/exception/BookErrorCode.kt around
lines 12 to 15, the ALADIN_API_RESULT_LIMIT error message and HTTP status code
do not accurately reflect the actual error condition of exceeding the request
range limit. Update the error message to clearly indicate the request range
limit exceeded scenario. Replace the HTTP status code 403 FORBIDDEN with a more
appropriate code such as 400 BAD REQUEST, 422 UNPROCESSABLE ENTITY, or 416 RANGE
NOT SATISFIABLE based on internal policy and client contract. After making these
changes, verify the impact on integration and contract tests, and update the API
documentation and client guides accordingly.


override fun getHttpStatus(): HttpStatus = httpStatus
override fun getCode(): String = code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class AladinBookQueryService(
private val aladinApi: AladinApi
) : BookQueryService {
private val log = KotlinLogging.logger {}
private val MAX_ALADIN_RESULTS = 200 // Added constant
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

상수는 companion object의 const로 승격

인스턴스별로 생성할 이유가 없고 컴파일 타임 상수로 적합합니다.

-    private val MAX_ALADIN_RESULTS = 200 // Added constant
+    companion object {
+        private const val MAX_ALADIN_RESULTS: Int = 200
+    }
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt at
line 25, the MAX_ALADIN_RESULTS constant is currently a private val instance
property. Move this constant into a companion object and declare it as const val
to make it a compile-time constant and avoid creating it per instance.


override fun searchBooks(request: BookSearchRequest): BookSearchResponse {
log.info { "Service - Converting BookSearchRequest to AladinBookSearchRequest and calling Aladin API for book search." }
Expand Down Expand Up @@ -50,23 +51,33 @@ class AladinBookQueryService(
validIsbn13?.let { item.copy(isbn13 = it) }
}

val cappedTotalResults = minOf(response.totalResults ?: 0, MAX_ALADIN_RESULTS)
val currentFetchedCount = (request.start ?: 1) * (response.itemsPerPage ?: 0)

if (currentFetchedCount > MAX_ALADIN_RESULTS) {
throw BookException(
BookErrorCode.ALADIN_API_RESULT_LIMIT
)
}

val isLastPage = currentFetchedCount >= cappedTotalResults

Comment on lines +54 to +64
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

필터링 불일치로 인한 빈 페이지 문제: 점진적 개선안

ISBN 필터링으로 유효 아이템이 줄어 lastPage 계산과 실제 페이지네이션이 엇갈릴 수 있습니다. 현재 PR 범위에서는 정확한 총량을 즉시 알 수 없으므로, 다음 중 하나를 고려해 주세요.

  • 간단안: isLastPage = transformedItems.size < (response.itemsPerPage ?: transformedItems.size) 조건을 추가해 마지막 페이지 근사
  • 근본해결: 제안하신 비동기 집계(검색 세션 searchId 발급 → 백그라운드 수집/필터링 → 클라이언트 폴링) 모델을 별도 이슈로 도입

비동기 모델을 추진할 경우, 필요 API/캐시 키 설계 초안을 함께 드릴 수 있습니다.


다음 스크립트로 transformedItems 사용 위치를 확인해 보겠습니다:


🏁 Script executed:

#!/bin/bash
rg "transformedItems" -n -C3 apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt

Length of output: 687


알라딘 API 필터링 후 빈 페이지 문제: 점진적 개선 제안

File: apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt (54–64)

ISBN 필터링으로 유효 아이템 수가 줄어들면, isLastPage 계산이 실제 페이지네이션과 어긋나 빈 페이지가 반환될 수 있습니다. PR 범위 내에서 즉시 정확한 총건수를 알기 어렵기 때문에, 아래 두 가지 중 하나를 고려해주세요.

  • 간단안: 마지막 페이지 근사치를 위해 isLastPage 계산식에 필터 후 아이템 개수 비교 추가
    // 기존
    val isLastPage = currentFetchedCount >= cappedTotalResults
    // 개선
    val isLastPage = transformedItems.size < (response.itemsPerPage ?: transformedItems.size)
        || currentFetchedCount >= cappedTotalResults
  • 근본해결: 검색 세션(searchId) 발급 → 백그라운드 수집·필터링 → 클라이언트 폴링 방식의 비동기 집계 모델을 별도 이슈로 도입
    필요 시 API 설계 및 캐시 키 흐름 초안을 함께 검토할 수 있습니다.
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt
around lines 54 to 64, the calculation of isLastPage does not account for the
reduced number of valid items after ISBN filtering, which can cause empty pages
to be returned. To fix this, update the isLastPage logic to also check if the
size of the filtered items is less than the expected items per page, indicating
the last page, by adding a condition comparing transformedItems.size with
response.itemsPerPage or its fallback. This adjustment will provide a better
approximation of the last page without requiring immediate changes to total
count handling.

val filteredResponse = AladinSearchResponse(
version = response.version,
title = response.title,
link = response.link,
pubDate = response.pubDate,
totalResults = transformedItems.size,
totalResults = response.totalResults,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

totalResults는 capped 값으로 노출(UX/정합성 일치)

UI는 totalResults를 기반으로 페이지 수를 계산하는 경우가 많습니다. UX 정책이 상위 200개까지만 제공이라면, 응답에도 이를 반영하는 편이 혼선을 줄입니다.

-            totalResults = response.totalResults,
+            totalResults = cappedTotalResults,

참고: 실제 전체 건수(200 초과)를 별도로 노출해야 한다면 originalTotalResults 같은 필드를 추가하는 것도 선택지입니다.

📝 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
totalResults = response.totalResults,
val filteredResponse = AladinSearchResponse(
version = response.version,
title = response.title,
link = response.link,
pubDate = response.pubDate,
- totalResults = response.totalResults,
+ totalResults = cappedTotalResults,
startIndex = response.startIndex,
itemsPerPage = response.itemsPerPage,
query = response.query,
searchCategoryId = response.searchCategoryId,
searchCategoryName = response.searchCategoryName,
item = transformedItems,
lastPage = isLastPage
)
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt at
line 70, the totalResults field should be capped to a maximum value (e.g., 200)
to align with the UX policy limiting results to the top 200 items. Modify the
assignment to totalResults to return the minimum between response.totalResults
and the cap value. Optionally, if the actual total count above the cap is
needed, add a separate field like originalTotalResults to expose the true total
without confusing the UI pagination.

startIndex = response.startIndex,
itemsPerPage = response.itemsPerPage,
query = response.query,
searchCategoryId = response.searchCategoryId,
searchCategoryName = response.searchCategoryName,
item = transformedItems
)

log.info { "After filtering - Full Response: $filteredResponse" }

return BookSearchResponse.from(filteredResponse)
return BookSearchResponse.of(filteredResponse, isLastPage) // Passed isLastPage
}

override fun getBookDetail(request: BookDetailRequest): BookDetailResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ 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.ReadingRecordPageResponse // Added import
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

리뷰 잔재 주석 제거

프로덕션 코드에서 // Added import 주석은 불필요합니다. 정리 부탁드립니다.

-import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse // Added import
+import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse
📝 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
import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse // Added import
import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse
🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/controller/ReadingRecordController.kt
at line 18, remove the unnecessary comment "// Added import" that was added
alongside the import statement for ReadingRecordPageResponse, as such comments
are not needed in production code.

import org.yapp.apis.readingrecord.dto.response.ReadingRecordResponse
import org.yapp.apis.readingrecord.usecase.ReadingRecordUseCase
import org.yapp.domain.readingrecord.ReadingRecordSortType
Expand Down Expand Up @@ -61,7 +62,7 @@ class ReadingRecordController(
@RequestParam(required = false) sort: ReadingRecordSortType?,
@PageableDefault(size = 10, sort = ["createdAt"], direction = Sort.Direction.DESC)
pageable: Pageable
): ResponseEntity<Page<ReadingRecordResponse>> {
): ResponseEntity<ReadingRecordPageResponse> {
val response = readingRecordUseCase.getReadingRecordsByUserBookId(
userId = userId,
userBookId = userBookId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.*
import org.yapp.apis.readingrecord.dto.request.CreateReadingRecordRequest
import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse
import org.yapp.apis.readingrecord.dto.response.ReadingRecordResponse
import org.yapp.apis.readingrecord.dto.response.SeedStatsResponse
import org.yapp.domain.readingrecord.ReadingRecordSortType
Expand Down Expand Up @@ -105,7 +106,7 @@ interface ReadingRecordControllerApi {
@RequestParam(required = false) @Parameter(description = "정렬 방식 (PAGE_NUMBER_ASC, PAGE_NUMBER_DESC, CREATED_DATE_ASC, CREATED_DATE_DESC)") sort: ReadingRecordSortType?,
@PageableDefault(size = 10, sort = ["createdAt"], direction = Sort.Direction.DESC)
@Parameter(description = "페이지네이션 정보 (페이지 번호, 페이지 크기, 정렬 방식)") pageable: Pageable
): ResponseEntity<Page<ReadingRecordResponse>>
): ResponseEntity<ReadingRecordPageResponse>

@Operation(
summary = "씨앗 통계 조회",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.yapp.apis.readingrecord.dto.response

import io.swagger.v3.oas.annotations.media.Schema
import org.springframework.data.domain.Page

@Schema(
name = "ReadingRecordPageResponse",
description = "독서 기록 페이징 조회 응답"
)
data class ReadingRecordPageResponse private constructor(
@field:Schema(description = "마지막 페이지 여부", example = "false")
val lastPage: Boolean,

@field:Schema(description = "총 결과 개수", example = "42")
val totalResults: Int,

Comment on lines +10 to +16
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

totalResults 다운캐스팅(Int)으로 인한 정보 손실 가능성 — Long 사용 권장

Page.totalElements는 Long입니다. 현재 toInt()로 다운캐스팅하고 있어 대용량 시 오버플로/정보 손실 위험이 있습니다. totalResults를 Long으로 변경해 주세요.

 data class ReadingRecordPageResponse private constructor(
   @field:Schema(description = "마지막 페이지 여부", example = "false")
   val lastPage: Boolean,

-  @field:Schema(description = "총 결과 개수", example = "42")
-  val totalResults: Int,
+  @field:Schema(description = "총 결과 개수", example = "42")
+  val totalResults: Long,
@@
             return ReadingRecordPageResponse(
                 lastPage = page.isLast,
-                totalResults = page.totalElements.toInt(),
+                totalResults = page.totalElements,

Also applies to: 27-31

🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordPageResponse.kt
around lines 10 to 16 and also lines 27 to 31, the totalResults property is
currently an Int but should be changed to Long to avoid potential overflow or
data loss when converting from Page.totalElements which is a Long. Update the
totalResults type from Int to Long and ensure any related code uses Long
consistently without downcasting to Int.

@field:Schema(description = "현재 페이지 번호 (0부터 시작)", example = "0")
val startIndex: Int,

@field:Schema(description = "한 페이지당 아이템 개수", example = "10")
val itemsPerPage: Int,

Comment on lines +17 to +22
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

startIndex 의미 명확화 필요: 필드명 vs 값 불일치 가능성

설명은 "현재 페이지 번호(0부터 시작)"인데, 필드명은 보통 "첫 항목의 시작 오프셋"을 연상시키는 startIndex입니다. 혼란 방지를 위해 아래 둘 중 하나를 권장합니다.

  • 대안 A(권장, 오프셋 의미 유지): startIndex = page.number * page.size로 변경하고 설명도 오프셋으로 수정
  • 대안 B(페이지 번호 의미 유지): 필드명을 pageIndex로 변경하고 설명은 현행 유지

대안 A(diff):

-  @field:Schema(description = "현재 페이지 번호 (0부터 시작)", example = "0")
-  val startIndex: Int,
+  @field:Schema(description = "현재 페이지의 첫 결과 오프셋(0부터 시작)", example = "0")
+  val startIndex: Int,
@@
-                startIndex = page.number,
+                startIndex = page.number * page.size,

대안 B(diff):

-  @field:Schema(description = "현재 페이지 번호 (0부터 시작)", example = "0")
-  val startIndex: Int,
+  @field:Schema(description = "현재 페이지 번호 (0부터 시작)", example = "0")
+  val pageIndex: Int,
@@
-                startIndex = page.number,
+                pageIndex = page.number,

확정하신 의도를 알려주시면 그에 맞춰 나머지 레이어(문서/클라이언트 영향 범위) 반영 패치도 도와드리겠습니다.

Also applies to: 27-35


아래 스크립트를 실행하여 ReadingRecordPageResponse.kt 파일 내에서 startIndex 필드 정의와 매핑 로직을 확인해 주세요.


🏁 Script executed:

#!/bin/bash
set -e

# 파일 위치 찾기
file=$(fd -e kt ReadingRecordPageResponse.kt)

echo "Found file: $file"
echo
echo "=== Fields (1~50 lines) ==="
sed -n '1,50p' "$file"
echo
echo "=== Mapping logic for startIndex ==="
rg -n 'startIndex' -C 3 "$file"

Length of output: 2465


startIndex 필드명과 실제 매핑 불일치: 페이지 번호 vs 오프셋 의미 검토 및 수정 필요

현재 startIndex는 필드명으로는 “첫 결과 오프셋”을 연상시키지만, 실제 매핑 로직에서는 page.number(페이지 인덱스)를 사용하고 있어 혼동을 초래합니다. 아래 둘 중 하나를 선택해 반영해 주세요.

• 대상 파일
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordPageResponse.kt

• 문제 위치

  • 필드 정의: 17행
  • 매핑 로직: 31행

• 대안 A (오프셋 의미 유지, 매핑 수정)

  • 설명 변경: "현재 페이지의 첫 결과 오프셋(0부터 시작)"
  • 매핑 변경: startIndex = page.number * page.size
@@ 17,18c17,18
-    @field:Schema(description = "현재 페이지 번호 (0부터 시작)", example = "0")
+    @field:Schema(description = "현재 페이지의 첫 결과 오프셋(0부터 시작)", example = "0")
@@ 31c31
-                startIndex = page.number,
+                startIndex = page.number * page.size,

• 대안 B (페이지 번호 의미 유지, 필드명 변경)

  • 필드명: startIndexpageIndex
  • 설명 유지 및 매핑 유지
@@ 17,18c17,18
-    @field:Schema(description = "현재 페이지 번호 (0부터 시작)", example = "0")
-    val startIndex: Int,
+    @field:Schema(description = "현재 페이지 번호 (0부터 시작)", example = "0")
+    val pageIndex: Int,
@@ 31,32c31,32
-                startIndex = page.number,
+                pageIndex = page.number,

위 중 적절한 의도를 선택해 알려주시면, 나머지 문서·클라이언트 레이어 반영도 도와드리겠습니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordPageResponse.kt
around lines 17 to 22 and 31, the field name 'startIndex' suggests an offset but
is mapped to the page number, causing confusion. To fix this, either update the
description to "현재 페이지의 첫 결과 오프셋(0부터 시작)" and change the mapping to calculate
offset as page.number * page.size, or rename the field to 'pageIndex' to reflect
the current page number and keep the existing mapping and description. Choose
one approach and apply consistent changes to the field definition and mapping
accordingly.

@field:Schema(description = "독서 기록 목록")
val readingRecords: List<ReadingRecordResponse>
) {
Comment on lines +23 to +25
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

배열 스키마 명시로 문서 명확성 향상 권장

스웨거에서 제네릭 배열을 잘 추론하긴 하지만, 명시적으로 item 타입을 지정하면 문서 가독성이 좋아집니다.

+import io.swagger.v3.oas.annotations.media.ArraySchema
@@
-  @field:Schema(description = "독서 기록 목록")
-  val readingRecords: List<ReadingRecordResponse>
+  @field:ArraySchema(arraySchema = Schema(description = "독서 기록 목록"), schema = Schema(implementation = ReadingRecordResponse::class))
+  val readingRecords: List<ReadingRecordResponse>
🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordPageResponse.kt
around lines 23 to 25, the Swagger schema annotation for the readingRecords list
should explicitly specify the item type to improve documentation clarity. Update
the @field:Schema annotation to include the 'implementation' or 'type' attribute
that clearly defines the type of items in the list, ensuring Swagger generates
more readable and precise API docs.

companion object {
fun from(page: Page<ReadingRecordResponse>): ReadingRecordPageResponse {
return ReadingRecordPageResponse(
lastPage = page.isLast,
totalResults = page.totalElements.toInt(),
startIndex = page.number,
itemsPerPage = page.size,
readingRecords = page.content
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.springframework.data.domain.Pageable
import org.springframework.transaction.annotation.Transactional
import org.yapp.apis.book.service.UserBookService
import org.yapp.apis.readingrecord.dto.request.CreateReadingRecordRequest
import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse // Added import
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

리뷰 잔재 주석 제거

import 라인의 // Added import 주석은 제거하는 것이 깔끔합니다.

-import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse // Added import
+import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse
📝 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
import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse // Added import
import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse
🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/usecase/ReadingRecordUseCase.kt
at line 8, remove the trailing comment "// Added import" from the import
statement to keep the code clean and professional.

import org.yapp.apis.readingrecord.dto.response.ReadingRecordResponse
import org.yapp.apis.readingrecord.dto.response.SeedStatsResponse
import org.yapp.apis.readingrecord.service.ReadingRecordService
Expand Down Expand Up @@ -55,15 +56,16 @@ class ReadingRecordUseCase(
userBookId: UUID,
sort: ReadingRecordSortType?,
pageable: Pageable
): Page<ReadingRecordResponse> {
): ReadingRecordPageResponse {
userService.validateUserExists(userId)
userBookService.validateUserBookExists(userBookId, userId)

return readingRecordService.getReadingRecordsByDynamicCondition(
val page = readingRecordService.getReadingRecordsByDynamicCondition( // Stored in a variable
userBookId = userBookId,
sort = sort,
pageable = pageable
)
return ReadingRecordPageResponse.from(page) // Converted to new DTO
}

fun getSeedStats(
Expand Down