diff --git a/apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookResponse.kt b/apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookResponse.kt index f28a6402..133edfdf 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookResponse.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookResponse.kt @@ -17,6 +17,7 @@ data class UserBookResponse private constructor( val publisher: String, val createdAt: String, val updatedAt: String, + val recordCount: Int, ) { companion object { fun from( @@ -33,6 +34,7 @@ data class UserBookResponse private constructor( publisher = BookDataValidator.removeParenthesesFromPublisher(userBook.publisher), createdAt = userBook.createdAt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), updatedAt = userBook.updatedAt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), + recordCount = userBook.recordCount, ) } } diff --git a/apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordResponse.kt b/apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordResponse.kt index 609d9f11..11e1e1a4 100644 --- a/apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordResponse.kt +++ b/apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/response/ReadingRecordResponse.kt @@ -39,7 +39,10 @@ data class ReadingRecordResponse private constructor( val bookPublisher: String?, @Schema(description = "도서 썸네일 URL", example = "https://example.com/book-cover.jpg") - val bookCoverImageUrl: String? + val bookCoverImageUrl: String?, + + @Schema(description = "저자", example = "로버트 C. 마틴") + val author: String? ) { companion object { private val dateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME @@ -56,7 +59,8 @@ data class ReadingRecordResponse private constructor( updatedAt = readingRecordInfoVO.updatedAt.format(dateTimeFormatter), bookTitle = readingRecordInfoVO.bookTitle, bookPublisher = readingRecordInfoVO.bookPublisher, - bookCoverImageUrl = readingRecordInfoVO.bookCoverImageUrl + bookCoverImageUrl = readingRecordInfoVO.bookCoverImageUrl, + author = readingRecordInfoVO.author ) } } diff --git a/domain/src/main/kotlin/org/yapp/domain/readingrecord/ReadingRecordDomainService.kt b/domain/src/main/kotlin/org/yapp/domain/readingrecord/ReadingRecordDomainService.kt index bdf772a8..043cf77e 100644 --- a/domain/src/main/kotlin/org/yapp/domain/readingrecord/ReadingRecordDomainService.kt +++ b/domain/src/main/kotlin/org/yapp/domain/readingrecord/ReadingRecordDomainService.kt @@ -13,6 +13,9 @@ import org.yapp.domain.userbook.UserBookRepository import org.yapp.globalutils.annotation.DomainService import java.util.UUID +import org.yapp.domain.userbook.exception.UserBookNotFoundException +import org.yapp.domain.userbook.exception.UserBookErrorCode + @DomainService class ReadingRecordDomainService( private val readingRecordRepository: ReadingRecordRepository, @@ -28,6 +31,11 @@ class ReadingRecordDomainService( review: String, emotionTags: List ): ReadingRecordInfoVO { + val userBook = userBookRepository.findById(userBookId) + ?: throw UserBookNotFoundException( + UserBookErrorCode.USER_BOOK_NOT_FOUND, + "User book not found with id: $userBookId" + ) val readingRecord = ReadingRecord.create( userBookId = userBookId, @@ -50,17 +58,19 @@ class ReadingRecordDomainService( } readingRecordTagRepository.saveAll(readingRecordTags) - val userBook = userBookRepository.findById(userBookId) + userBookRepository.save(userBook.increaseReadingRecordCount()) return ReadingRecordInfoVO.newInstance( readingRecord = savedReadingRecord, emotionTags = tags.map { it.name }, - bookTitle = userBook?.title, - bookPublisher = userBook?.publisher, - bookCoverImageUrl = userBook?.coverImageUrl + bookTitle = userBook.title, + bookPublisher = userBook.publisher, + bookCoverImageUrl = userBook.coverImageUrl, + author = userBook.author ) } + fun findReadingRecordById(readingRecordId: UUID): ReadingRecordInfoVO { val readingRecord = readingRecordRepository.findById(readingRecordId) ?: throw ReadingRecordNotFoundException( @@ -82,7 +92,8 @@ class ReadingRecordDomainService( emotionTags = tags.map { it.name }, bookTitle = userBook?.title, bookPublisher = userBook?.publisher, - bookCoverImageUrl = userBook?.coverImageUrl + bookCoverImageUrl = userBook?.coverImageUrl, + author = userBook?.author ) } @@ -105,7 +116,8 @@ class ReadingRecordDomainService( emotionTags = tags.map { it.name }, bookTitle = userBook?.title, bookPublisher = userBook?.publisher, - bookCoverImageUrl = userBook?.coverImageUrl + bookCoverImageUrl = userBook?.coverImageUrl, + author = userBook?.author ) } } diff --git a/domain/src/main/kotlin/org/yapp/domain/readingrecord/vo/ReadingRecordInfoVO.kt b/domain/src/main/kotlin/org/yapp/domain/readingrecord/vo/ReadingRecordInfoVO.kt index 4b840393..34943b90 100644 --- a/domain/src/main/kotlin/org/yapp/domain/readingrecord/vo/ReadingRecordInfoVO.kt +++ b/domain/src/main/kotlin/org/yapp/domain/readingrecord/vo/ReadingRecordInfoVO.kt @@ -14,7 +14,8 @@ data class ReadingRecordInfoVO private constructor( val updatedAt: LocalDateTime, val bookTitle: String? = null, val bookPublisher: String? = null, - val bookCoverImageUrl: String? = null + val bookCoverImageUrl: String? = null, + val author: String? = null ) { init { require(emotionTags.size <= 3) { "Maximum 3 emotion tags are allowed" } @@ -29,7 +30,8 @@ data class ReadingRecordInfoVO private constructor( emotionTags: List, bookTitle: String? = null, bookPublisher: String? = null, - bookCoverImageUrl: String? = null + bookCoverImageUrl: String? = null, + author: String? = null ): ReadingRecordInfoVO { return ReadingRecordInfoVO( id = readingRecord.id, @@ -42,7 +44,8 @@ data class ReadingRecordInfoVO private constructor( updatedAt = readingRecord.updatedAt ?: throw IllegalStateException("updatedAt은 null일 수 없습니다."), bookTitle = bookTitle, bookPublisher = bookPublisher, - bookCoverImageUrl = bookCoverImageUrl + bookCoverImageUrl = bookCoverImageUrl, + author = author ) } } diff --git a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt index 4cb233ef..c44f361a 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt @@ -15,6 +15,7 @@ data class UserBook private constructor( val title: String, val author: String, val status: BookStatus, + val readingRecordCount: Int = 0, val createdAt: LocalDateTime? = null, val updatedAt: LocalDateTime? = null, val deletedAt: LocalDateTime? = null, @@ -23,6 +24,11 @@ data class UserBook private constructor( return this.copy(status = newStatus) } + fun increaseReadingRecordCount(): UserBook { + return this.copy(readingRecordCount = this.readingRecordCount + 1) + } + + companion object { fun create( userId: UUID, @@ -57,6 +63,7 @@ data class UserBook private constructor( title: String, author: String, status: BookStatus, + readingRecordCount: Int, createdAt: LocalDateTime? = null, updatedAt: LocalDateTime? = null, deletedAt: LocalDateTime? = null @@ -71,6 +78,7 @@ data class UserBook private constructor( title = title, author = author, status = status, + readingRecordCount = readingRecordCount, createdAt = createdAt, updatedAt = updatedAt, deletedAt = deletedAt 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 445ec5f4..3af4aeb7 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt @@ -35,7 +35,7 @@ class UserBookDomainService( ) val savedUserBook = userBookRepository.save(userBook) - return UserBookInfoVO.newInstance(savedUserBook) + return UserBookInfoVO.newInstance(savedUserBook, savedUserBook.readingRecordCount) } fun findUserBooksByDynamicCondition( @@ -46,7 +46,7 @@ class UserBookDomainService( pageable: Pageable ): Page { val page = userBookRepository.findUserBooksByDynamicCondition(userId, status, sort, title, pageable) - return page.map { UserBookInfoVO.newInstance(it) } + return page.map { UserBookInfoVO.newInstance(it, it.readingRecordCount) } } fun findAllByUserIdAndBookIsbnIn(userId: UUID, isbns: List): List { @@ -54,12 +54,12 @@ class UserBookDomainService( return emptyList() } val userBooks = userBookRepository.findAllByUserIdAndBookIsbnIn(userId, isbns) - return userBooks.map { UserBookInfoVO.newInstance(it) } + return userBooks.map { UserBookInfoVO.newInstance(it, it.readingRecordCount) } } fun findByUserIdAndBookIsbn(userId: UUID, isbn: String): UserBookInfoVO? { val userBook = userBookRepository.findByUserIdAndBookIsbn(userId, isbn) - return userBook?.let { UserBookInfoVO.newInstance(it) } + return userBook?.let { UserBookInfoVO.newInstance(it, it.readingRecordCount) } } fun getUserBookStatusCounts(userId: UUID): UserBookStatusCountsVO { 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 4b2c23ba..d580b8d7 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt @@ -36,4 +36,6 @@ interface UserBookRepository { limit: Int, excludeIds: Set ): List + + } diff --git a/domain/src/main/kotlin/org/yapp/domain/userbook/vo/UserBookInfoVO.kt b/domain/src/main/kotlin/org/yapp/domain/userbook/vo/UserBookInfoVO.kt index 2ec08712..019bb07a 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/vo/UserBookInfoVO.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/vo/UserBookInfoVO.kt @@ -15,13 +15,15 @@ data class UserBookInfoVO private constructor( val author: String, val status: BookStatus, val createdAt: LocalDateTime, - val updatedAt: LocalDateTime + val updatedAt: LocalDateTime, + val recordCount: Int ) { init { require(coverImageUrl.isNotBlank()) { "표지 이미지 URL은 비어 있을 수 없습니다." } require(publisher.isNotBlank()) { "출판사는 비어 있을 수 없습니다." } require(title.isNotBlank()) { "도서 제목은 비어 있을 수 없습니다." } require(author.isNotBlank()) { "저자는 비어 있을 수 없습니다." } + require(recordCount >= 0) { "독서 기록 수는 0 이상이어야 합니다." } require(!createdAt.isAfter(updatedAt)) { "생성일(createdAt)은 수정일(updatedAt)보다 이후일 수 없습니다." } @@ -30,6 +32,7 @@ data class UserBookInfoVO private constructor( companion object { fun newInstance( userBook: UserBook, + recordCount: Int ): UserBookInfoVO { return UserBookInfoVO( id = userBook.id, @@ -42,7 +45,8 @@ data class UserBookInfoVO private constructor( author = userBook.author, status = userBook.status, createdAt = userBook.createdAt ?: throw IllegalStateException("createdAt은 null일 수 없습니다."), - updatedAt = userBook.updatedAt ?: throw IllegalStateException("updatedAt은 null일 수 없습니다.") + updatedAt = userBook.updatedAt ?: throw IllegalStateException("updatedAt은 null일 수 없습니다."), + recordCount = recordCount ) } } 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 a163d6fd..81ab204c 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 @@ -66,6 +66,10 @@ class UserBookEntity( var status: BookStatus = status protected set + @Column(name = "reading_record_count", nullable = false) + var readingRecordCount: Int = 0 + protected set + fun toDomain(): UserBook = UserBook.reconstruct( id = UserBook.Id.newInstance(this.id), userId = UserBook.UserId.newInstance(this.userId), @@ -76,6 +80,7 @@ class UserBookEntity( title = this.title, author = this.author, status = this.status, + readingRecordCount = this.readingRecordCount, createdAt = this.createdAt, updatedAt = this.updatedAt, deletedAt = this.deletedAt @@ -93,7 +98,9 @@ class UserBookEntity( title = userBook.title, author = userBook.author, status = userBook.status, - ) + ).apply { + this.readingRecordCount = userBook.readingRecordCount + } } } 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 38320e2b..41302f8e 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 @@ -14,7 +14,7 @@ import java.util.* @Repository class UserBookRepositoryImpl( - private val jpaUserBookRepository: JpaUserBookRepository, + private val jpaUserBookRepository: JpaUserBookRepository ) : UserBookRepository { override fun findByUserIdAndBookIsbn(userId: UUID, isbn: String): UserBook? { @@ -81,4 +81,6 @@ class UserBookRepositoryImpl( val entities = jpaUserBookRepository.findUnrecordedBooksSortedByPriority(userId, excludeIds, limit) return entities.map { it.toDomain() } } + + }