-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 독서기록 API 기능 구현 #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b06d8f5
bf03a67
48ab1d1
0a33bd5
cc64171
3881e6d
5f665f8
6050cd2
6584426
c50f65c
c2cd5fe
7d2172d
e947c77
dbb12b3
c12c74a
78e9e6c
c4db4f4
c6853af
6786eac
49104b6
d606fdc
93c3875
fb6fd8d
221d774
413f117
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
| ) { | ||
| fun validIsbn(): String = isbn!! | ||
| fun validTitle(): String = title!! | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 선언부와 호출부의 트레일링 콤마 스타일이 불일치합니다. 위 25행에서 콤마를 제거했다면, 여기서도 제거하여 일관성을 맞추거나 반대로 둘 다 유지하는 방향으로 통일해 주세요. 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
|
|
||
| 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) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -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(), | ||||||||||||||||||||||||||||||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 예외 메시지에서 민감한 정보 노출을 고려해주세요. 메서드 구현은 적절하지만 다음 사항들을 검토해보시기 바랍니다:
다음과 같이 수정을 제안합니다: - 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| fun findAllByUserIdAndBookIsbnIn(userBooksByIsbnsRequest: UserBooksByIsbnsRequest): List<UserBookResponse> { | ||||||||||||||||||||||||||||||
| val userBooks = userBookDomainService.findAllByUserIdAndBookIsbnIn( | ||||||||||||||||||||||||||||||
| userBooksByIsbnsRequest.validUserId(), | ||||||||||||||||||||||||||||||
|
|
@@ -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) | ||||||||||||||||||||||||||||||
|
|
@@ -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) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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+에서는 멀티라인 인자 목록에 트레일링 콤마를 두는 것이 흔한 패턴이므로 선언부와 호출부 모두 동일한 규칙을 적용하는 편이 유지보수에 유리합니다.📝 Committable suggestion
🤖 Prompt for AI Agents