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 @@ -158,7 +158,11 @@ interface BookControllerApi {
@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)
@Parameter(
description = "페이징 정보 (기본값: 10개, 최신 수정일 순)",
example = "?page=0&size=10&sort=updatedAt,desc"
)
@PageableDefault(size = 10, sort = ["updatedAt"], direction = Sort.Direction.DESC)
pageable: Pageable
): ResponseEntity<UserBookPageResponse>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ interface ReadingRecordControllerApi {
@AuthenticationPrincipal @Parameter(description = "인증된 사용자 ID") userId: UUID,
@PathVariable @Parameter(description = "독서 기록을 조회할 사용자 책 ID") userBookId: UUID,
@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
@PageableDefault(size = 10, sort = ["updatedAt"], direction = Sort.Direction.DESC)
@Parameter(description = "페이지네이션 정보 (기본값: 10개, 최신 수정일 순)") pageable: Pageable
): ResponseEntity<ReadingRecordPageResponse>
Comment on lines 105 to 110
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

sort 파라미터 충돌 위험 — enum 정렬 vs Pageable 정렬 분리 필요

여기도 @RequestParam sort: ReadingRecordSortType?와 Pageable의 sort가 충돌합니다. ?sort=updatedAt,desc 사용 시 enum 바인딩 실패 가능.

권장 수정:

-        @RequestParam(required = false) @Parameter(description = "정렬 방식 (PAGE_NUMBER_ASC, PAGE_NUMBER_DESC, CREATED_DATE_ASC, CREATED_DATE_DESC)") sort: ReadingRecordSortType?,
+        @RequestParam(name = "sortType", required = false)
+        @Parameter(description = "정렬 방식 (PAGE_NUMBER_ASC|PAGE_NUMBER_DESC|CREATED_DATE_ASC|CREATED_DATE_DESC|UPDATED_DATE_ASC|UPDATED_DATE_DESC)", example = "UPDATED_DATE_DESC")
+        sortType: ReadingRecordSortType?,
-        @PageableDefault(size = 10, sort = ["updatedAt"], direction = Sort.Direction.DESC)
-        @Parameter(description = "페이지네이션 정보 (기본값: 10개, 최신 수정일 순)") pageable: Pageable
+        @PageableDefault(size = 10, sort = ["updatedAt"], direction = Sort.Direction.DESC)
+        @Parameter(description = "페이지네이션 정보 (기본값: 10개, 최신 수정일 순)") pageable: Pageable

서비스/레포에서 sortType 반영되도록 함께 조정이 필요합니다.

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

🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/controller/ReadingRecordControllerApi.kt
around lines 105 to 110, the request parameter name `sort` collides with Spring
Pageable's `sort` query parameter causing enum binding failures; rename the enum
param (e.g., `sortType`), update the annotation to @RequestParam(name =
"sortType", required = false) and the method signature accordingly, then ensure
the controller converts that sortType into a Spring Sort (or passes it to the
service/repository) and explicitly ignore or reconcile Pageable.sort so the
service applies the enum-based ordering consistently.

Comment on lines 107 to 110
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

OpenAPI 예시/문서에 UPDATED_DATE_ 추가 고려*

도메인 정렬 타입에 UPDATED_DATE가 추가되었으므로 문서에도 반영하세요.

🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/controller/ReadingRecordControllerApi.kt
around lines 107 to 110, the @Parameter description for the sort RequestParam
omits the newly added UPDATED_DATE sort values; update the description to
include UPDATED_DATE_ASC and UPDATED_DATE_DESC (e.g. list all supported enums
including PAGE_NUMBER_ASC, PAGE_NUMBER_DESC, CREATED_DATE_ASC,
CREATED_DATE_DESC, UPDATED_DATE_ASC, UPDATED_DATE_DESC) so the OpenAPI docs
reflect the domain change.


@Operation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,19 @@ data class CreateReadingRecordRequest private constructor(
@field:Schema(description = "기억에 남는 문장", example = "이것은 기억에 남는 문장입니다.", required = true)
val quote: String? = null,

@field:NotBlank(message = "감상평은 필수입니다.")
@field:Size(max = 1000, message = "감상평은 1000자를 초과할 수 없습니다.")
@field:Schema(description = "감상평", example = "이 책은 매우 인상적이었습니다.", required = true)
@field:Schema(description = "감상평", example = "이 책은 매우 인상적이었습니다.", required = false)
val review: String? = null,
Comment on lines 33 to 35
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

LGTM! 감상평 필드가 선택사항으로 올바르게 변경되었습니다.

@NotBlank 제약 조건이 제거되고 required = false로 설정되어 감상평을 선택적으로 입력할 수 있게 되었습니다. 기존의 @Size 검증은 유지되어 값이 제공될 경우 최대 1000자로 제한됩니다.

다음 스크립트를 실행하여 백엔드 로직이 nullable review를 올바르게 처리하는지 확인하세요:


🏁 Script executed:

#!/bin/bash
# Description: CreateReadingRecordRequest를 사용하는 서비스 계층이나 컨트롤러에서 
# review 필드를 null로 처리하는지 확인

# Test: CreateReadingRecordRequest를 사용하는 모든 위치 검색
rg -n --type=kt -C 5 "CreateReadingRecordRequest" 

Length of output: 26



🏁 Script executed:

#!/bin/bash
# Search for CreateReadingRecordRequest usages without specifying file type
rg -n "CreateReadingRecordRequest" -A 5 -B 5

# Search for any calls to validReview method
echo "=== Searching for validReview() calls ==="
rg -n "validReview\s*\(" -A 3 -B 3

# Check the current state of the CreateReadingRecordRequest file
echo "=== Current CreateReadingRecordRequest.kt file ==="
fd "CreateReadingRecordRequest.kt" -x cat {}

Length of output: 18142


긴급: validReview() 메서드가 제거되었으나 서비스 계층에서 여전히 호출되고 있습니다.

CreateReadingRecordRequest.kt에서 validReview() 메서드가 제거되었지만, ReadingRecordService.kt의 다음 위치에서 여전히 호출되고 있어 컴파일 실패가 발생합니다:

  • 라인 28: review = request.validReview(),
  • 라인 67: review = request.validReview(),

수정 방법:

  1. CreateReadingRecordRequest.ktvalidReview() 메서드를 추가하여 nullable review를 반환하거나,
  2. ReadingRecordService.kt에서 request.validReview() 호출을 request.review로 변경해야 합니다.

review 필드가 이제 선택사항이므로 force unwrap(!!)을 사용할 수 없습니다.

🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt
around lines 33-35 and in
apis/src/main/kotlin/org/yapp/apis/readingrecord/ReadingRecordService.kt at
lines 28 and 67, the request.validReview() method was removed causing
compilation failures; fix by either (A) reintroducing a simple validReview():
String? method on CreateReadingRecordRequest that returns the nullable review
as-is, or (B) update ReadingRecordService.kt at the two call sites to use
request.review (nullable) instead of request.validReview(); do not use !!,
preserve nullability.


@field:Size(max = 1, message = "감정 태그는 최대 1개까지 가능합니다. (단일 감정만 받지만, 확장성을 위해 리스트 형태로 관리됩니다.)")
@field:Schema(description = "감정 태그 목록 (현재는 최대 1개, 확장 가능)", example = "[\"감동적\"]")
val emotionTags: List<@Size(max = 10, message = "감정 태그는 10자를 초과할 수 없습니다.") String> = emptyList()
) {
fun validPageNumber(): Int = pageNumber!!
fun validQuote(): String = quote!!
fun validReview(): String = review!!
fun validPageNumber(): Int =
requireNotNull(pageNumber) { "pageNumber는 null일 수 없습니다." }

fun validQuote(): String =
requireNotNull(quote) { "quote는 null일 수 없습니다." }

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

LGTM! validReview() 헬퍼 메서드 제거가 적절합니다.

감상평이 선택사항이 되면서 validReview() 메서드(이전의 review!!)가 제거되었습니다. 필수 필드인 pageNumberquote는 여전히 검증 헬퍼를 유지하고 있어 일관성 있는 구조를 유지하고 있습니다.

제거된 validReview() 메서드를 호출하는 코드가 있는지 확인하세요:


🏁 Script executed:

#!/bin/bash
# Description: validReview() 메서드 사용처 검색. 
# 예상 결과: 사용처가 없어야 함 (있다면 컴파일 에러 발생)

# Test: validReview() 호출 검색
rg -n --type=kt "\.validReview\(\)"

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Description: validReview() 메서드 사용처 검색 (수정된 버전)
# 방법: glob 패턴으로 .kt 파일만 검색

rg -n --glob="*.kt" "\.validReview\(\)"

Length of output: 255


🏁 Script executed:

#!/bin/bash
# Description: ReadingRecordService.kt의 validReview() 호출 부분 확인

# 라인 28 주변 코드 보기 (context: 10줄)
echo "=== Line 28 context ==="
rg -n --glob="*.kt" -B5 -A5 "validReview\(\)" apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordService.kt | head -20

echo ""
echo "=== Full view of problematic method ==="
# ReadingRecordService.kt 전체 구조 파악
cat apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordService.kt

Length of output: 3998


🏁 Script executed:

#!/bin/bash
# Description: ReadingRecordDomainService의 review 파라미터 타입 확인

# ReadingRecordDomainService 찾기
find . -name "ReadingRecordDomainService.kt" -type f 2>/dev/null | head -5

# createReadingRecord, modifyReadingRecord 메서드 시그니처 확인
rg -n --glob="*.kt" -A10 "fun createReadingRecord|fun modifyReadingRecord" | grep -A10 "ReadingRecordDomainService" | head -30

Length of output: 3575


🏁 Script executed:

#!/bin/bash
# Description: 요청 DTO 파일들에서 review 필드 확인

echo "=== CreateReadingRecordRequest.kt ==="
cat apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt

echo ""
echo "=== UpdateReadingRecordRequest.kt ==="
cat apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/UpdateReadingRecordRequest.kt

Length of output: 3252


apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordService.kt에서 컴파일 에러 수정 필요

validReview() 메서드가 제거되었지만, 서비스 레이어에서 여전히 호출되고 있습니다.

  • 라인 28: review = request.validReview()review = request.review 로 변경
  • 라인 67: review = request.validReview()review = request.review 로 변경

추가로, CreateReadingRecordRequestreviewString? (nullable)이지만 ReadingRecordDomainService.createReadingRecord()review: String (non-nullable)을 기대합니다. 도메인 서비스의 파라미터 타입도 함께 검토 필요합니다.

🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordService.kt
around lines 28 and 67, replace the removed request.validReview() calls with
request.review and handle the nullable mismatch with the domain API: either (A)
update ReadingRecordDomainService.createReadingRecord(...) signature to accept
review: String? so it accepts the DTO's nullable review, or (B) add an explicit
null-check in the service before calling the domain (e.g., provide a default,
skip passing review, or throw a controlled validation exception) so you no
longer pass a nullable String into a parameter typed as non-nullable; also
ensure CreateReadingRecordRequest
(apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt
line ~43) remains String? if you choose option A, or adjust DTO/validation if
you choose to keep domain non-nullable.

fun validEmotionTags(): List<String> = emotionTags
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,4 @@ data class UpdateReadingRecordRequest private constructor(
@field:Size(max = 3, message = "감정 태그는 최대 3개까지 가능합니다.")
@field:Schema(description = "수정할 감정 태그 목록", example = """["따뜻함","즐거움","슬픔","깨달음"]""")
val emotionTags: List<@Size(max = 10, message = "감정 태그는 10자를 초과할 수 없습니다.") String>?
) {
fun validPageNumber(): Int = pageNumber!!
fun validQuote(): String = quote!!
fun validReview(): String = review!!
fun validEmotionTags(): List<String> = emotionTags!!
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ data class ReadingRecordResponse private constructor(
val quote: String,

@field:Schema(description = "감상평", example = "이 책은 매우 인상적이었습니다.")
val review: String,
val review: String?,

@field:Schema(description = "감정 태그 목록", example = "[\"감동적\", \"슬픔\", \"희망\"]")
val emotionTags: List<String>,
Expand Down Expand Up @@ -56,7 +56,7 @@ data class ReadingRecordResponse private constructor(
userBookId = readingRecordInfoVO.userBookId.value,
pageNumber = readingRecordInfoVO.pageNumber.value,
quote = readingRecordInfoVO.quote.value,
review = readingRecordInfoVO.review.value,
review = readingRecordInfoVO.review?.value,
emotionTags = readingRecordInfoVO.emotionTags,
createdAt = readingRecordInfoVO.createdAt.format(dateTimeFormatter),
updatedAt = readingRecordInfoVO.updatedAt.format(dateTimeFormatter),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ReadingRecordService(
userBookId = userBookId,
pageNumber = request.validPageNumber(),
quote = request.validQuote(),
review = request.validReview(),
review = request.review,
emotionTags = request.validEmotionTags()
)

Expand Down Expand Up @@ -62,10 +62,10 @@ class ReadingRecordService(
): ReadingRecordResponse {
val readingRecordInfoVO = readingRecordDomainService.modifyReadingRecord(
readingRecordId = readingRecordId,
pageNumber = request.validPageNumber(),
quote = request.validQuote(),
review = request.validReview(),
emotionTags = request.validEmotionTags()
pageNumber = request.pageNumber,
quote = request.quote,
review = request.review,
emotionTags = request.emotionTags
)

// Update user's lastActivity when a reading record is updated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ data class ReadingRecord private constructor(
val userBookId: UserBookId,
val pageNumber: PageNumber,
val quote: Quote,
val review: Review,
val review: Review?,
val emotionTags: List<EmotionTag> = emptyList(),
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null,
Expand All @@ -20,15 +20,15 @@ data class ReadingRecord private constructor(
userBookId: UUID,
pageNumber: Int,
quote: String,
review: String,
review: String?,
emotionTags: List<String> = emptyList()
): ReadingRecord {
return ReadingRecord(
id = Id.newInstance(UuidGenerator.create()),
userBookId = UserBookId.newInstance(userBookId),
pageNumber = PageNumber.newInstance(pageNumber),
quote = Quote.newInstance(quote),
review = Review.newInstance(review),
review = review?.let { Review.newInstance(it) },
emotionTags = emotionTags.map { EmotionTag.newInstance(it) }
)
}
Expand All @@ -38,7 +38,7 @@ data class ReadingRecord private constructor(
userBookId: UserBookId,
pageNumber: PageNumber,
quote: Quote,
review: Review,
review: Review?,
emotionTags: List<EmotionTag> = emptyList(),
createdAt: LocalDateTime? = null,
updatedAt: LocalDateTime? = null,
Expand Down Expand Up @@ -67,7 +67,7 @@ data class ReadingRecord private constructor(
return this.copy(
pageNumber = pageNumber?.let { PageNumber.newInstance(it) } ?: this.pageNumber,
quote = quote?.let { Quote.newInstance(it) } ?: this.quote,
review = review?.let { Review.newInstance(it) } ?: this.review,
review = review?.let { Review.newInstance(it) },
emotionTags = emotionTags?.map { EmotionTag.newInstance(it) } ?: this.emotionTags,
updatedAt = LocalDateTime.now()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ReadingRecordDomainService(
userBookId: UUID,
pageNumber: Int,
quote: String,
review: String,
review: String?,
emotionTags: List<String>
): ReadingRecordInfoVO {
val userBook = userBookRepository.findById(userBookId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ data class ReadingRecordInfoVO private constructor(
val userBookId: ReadingRecord.UserBookId,
val pageNumber: ReadingRecord.PageNumber,
val quote: ReadingRecord.Quote,
val review: ReadingRecord.Review,
val review: ReadingRecord.Review?,
val emotionTags: List<String>,
val createdAt: LocalDateTime,
val updatedAt: LocalDateTime,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ enum class UserBookSortType {
TITLE_ASC,
TITLE_DESC,
CREATED_DATE_ASC,
CREATED_DATE_DESC;
CREATED_DATE_DESC,
UPDATED_DATE_ASC,
UPDATED_DATE_DESC;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ReadingRecordEntity(

pageNumber: Int,
quote: String,
review: String,
review: String?,


) : BaseTimeEntity() {
Expand All @@ -38,8 +38,8 @@ class ReadingRecordEntity(
var quote: String = quote
protected set

@Column(name = "review", nullable = false, length = 1000)
var review: String = review
@Column(name = "review", nullable = true, length = 1000)
var review: String? = review
protected set

fun toDomain(): ReadingRecord {
Expand All @@ -48,7 +48,7 @@ class ReadingRecordEntity(
userBookId = ReadingRecord.UserBookId.newInstance(this.userBookId),
pageNumber = ReadingRecord.PageNumber.newInstance(this.pageNumber),
quote = ReadingRecord.Quote.newInstance(this.quote),
review = ReadingRecord.Review.newInstance(this.review),
review = this.review?.let { ReadingRecord.Review.newInstance(it) },
emotionTags = emptyList(),
createdAt = this.createdAt,
updatedAt = this.updatedAt,
Expand All @@ -63,7 +63,7 @@ class ReadingRecordEntity(
userBookId = readingRecord.userBookId.value,
pageNumber = readingRecord.pageNumber.value,
quote = readingRecord.quote.value,
review = readingRecord.review.value
review = readingRecord.review?.value
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,9 @@ class JpaUserBookQuerydslRepositoryImpl(
UserBookSortType.TITLE_DESC -> userBook.title.desc()
UserBookSortType.CREATED_DATE_ASC -> userBook.createdAt.asc()
UserBookSortType.CREATED_DATE_DESC -> userBook.createdAt.desc()
null -> userBook.createdAt.desc()
UserBookSortType.UPDATED_DATE_ASC -> userBook.updatedAt.asc()
UserBookSortType.UPDATED_DATE_DESC -> userBook.updatedAt.desc()
null -> userBook.updatedAt.desc()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE reading_records MODIFY COLUMN review VARCHAR(1000) NULL;