-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 홈 화면 도서 선별 로직 구현 #66
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 9 commits
1c89bdc
2da0ee9
3c132b8
f6ce3a5
8dbf3fe
c05c675
2ca765c
ce90a98
ec5cc12
cf98060
6b28d09
3ebfba1
b05ead7
e3ae002
dc37da2
6246d3a
f3763a6
ecb9f1a
e4dcdc4
f9cc16b
33b763b
38841f5
7a01290
0e2d446
d07f92b
274688d
1bcff18
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 | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,25 @@ | ||||||||
| package org.yapp.apis.home.controller | ||||||||
|
|
||||||||
| 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.RequestMapping | ||||||||
| import org.springframework.web.bind.annotation.RestController | ||||||||
| import org.yapp.apis.home.dto.response.UserHomeResponse | ||||||||
| import org.yapp.apis.home.usecase.HomeUseCase | ||||||||
| import java.util.* | ||||||||
|
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) 구체적인 클래스 임포트를 권장합니다. -import java.util.*
+import java.util.UUID📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||
|
|
||||||||
| @RestController | ||||||||
| @RequestMapping("/api/home") | ||||||||
| class HomeController( | ||||||||
| private val homeUseCase: HomeUseCase | ||||||||
| ) : HomeControllerApi { | ||||||||
|
|
||||||||
| @GetMapping | ||||||||
| override fun getUserHomeData( | ||||||||
| @AuthenticationPrincipal userId: UUID | ||||||||
| ): ResponseEntity<UserHomeResponse> { | ||||||||
| val homeData = homeUseCase.getUserHomeData(userId) | ||||||||
| return ResponseEntity.ok(homeData) | ||||||||
| } | ||||||||
| } | ||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package org.yapp.apis.home.controller | ||
|
|
||
| import io.swagger.v3.oas.annotations.Operation | ||
| import io.swagger.v3.oas.annotations.media.Content | ||
| import io.swagger.v3.oas.annotations.media.Schema | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses | ||
| import io.swagger.v3.oas.annotations.tags.Tag | ||
| 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.RequestMapping | ||
| import org.yapp.apis.home.dto.response.UserHomeResponse | ||
| import org.yapp.globalutils.exception.ErrorResponse | ||
| import java.util.* | ||
|
|
||
| @Tag(name = "Home", description = "홈 화면 관련 API") | ||
| @RequestMapping("/api/home") | ||
| interface HomeControllerApi { | ||
|
|
||
| @Operation( | ||
| summary = "홈 화면 데이터 조회", | ||
| description = "사용자의 홈 화면에 필요한 데이터를 조회합니다. 요즘 읽는 책 목록을 우선순위에 따라 최대 3권까지 반환합니다." | ||
| ) | ||
| @ApiResponses( | ||
| value = [ | ||
| ApiResponse( | ||
| responseCode = "200", | ||
| description = "홈 화면 데이터 조회 성공", | ||
| content = [Content(schema = Schema(implementation = UserHomeResponse::class))] | ||
| ), | ||
| ApiResponse( | ||
| responseCode = "400", | ||
| description = "잘못된 요청", | ||
| content = [Content(schema = Schema(implementation = ErrorResponse::class))] | ||
| ), | ||
| ApiResponse( | ||
| responseCode = "404", | ||
| description = "사용자를 찾을 수 없음", | ||
| content = [Content(schema = Schema(implementation = ErrorResponse::class))] | ||
| ) | ||
| ] | ||
| ) | ||
| @GetMapping | ||
| fun getUserHomeData( | ||
| @AuthenticationPrincipal userId: UUID | ||
| ): ResponseEntity<UserHomeResponse> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package org.yapp.apis.home.dto.response | ||
|
|
||
| import org.yapp.domain.userbook.vo.UserBookWithLastRecordVO | ||
| import java.time.LocalDateTime | ||
| import java.util.* | ||
|
|
||
| data class UserHomeResponse private constructor( | ||
| val recentBooks: List<RecentBookResponse> | ||
| ) { | ||
| data class RecentBookResponse( | ||
| val userBookId: UUID, | ||
| val title: String, | ||
| val author: String, | ||
| val publisher: String, | ||
| val coverImageUrl: String, | ||
| val lastRecordedAt: LocalDateTime? | ||
move-hoon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) { | ||
| companion object { | ||
| fun from(userBookInfo: UserBookWithLastRecordVO): RecentBookResponse { | ||
| return RecentBookResponse( | ||
| userBookId = userBookInfo.id.value, | ||
| title = userBookInfo.title, | ||
| author = userBookInfo.author, | ||
| publisher = userBookInfo.publisher, | ||
| coverImageUrl = userBookInfo.coverImageUrl, | ||
| lastRecordedAt = userBookInfo.lastRecordedAt | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| fun from(recentBooks: List<UserBookWithLastRecordVO>): UserHomeResponse { | ||
| return UserHomeResponse( | ||
| recentBooks = recentBooks.map { RecentBookResponse.from(it) } | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package org.yapp.apis.home.usecase | ||
|
|
||
| import org.springframework.transaction.annotation.Transactional | ||
| import org.yapp.apis.book.service.UserBookService | ||
| import org.yapp.apis.home.dto.response.* | ||
| import org.yapp.globalutils.annotation.UseCase | ||
| import java.util.* | ||
|
|
||
| @UseCase | ||
| @Transactional(readOnly = true) | ||
| class HomeUseCase( | ||
| private val userBookService: UserBookService | ||
| ) { | ||
| fun getUserHomeData(userId: UUID): UserHomeResponse { | ||
| return userBookService.findRecentReadingBooksForHome(userId, 3) | ||
| } | ||
move-hoon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package org.yapp.domain.userbook.vo | ||
|
|
||
| import org.yapp.domain.userbook.BookStatus | ||
| import org.yapp.domain.userbook.UserBook | ||
| import java.time.LocalDateTime | ||
|
|
||
| data class UserBookWithLastRecordVO private constructor( | ||
| val id: UserBook.Id, | ||
| val userId: UserBook.UserId, | ||
| val bookId: UserBook.BookId, | ||
| val bookIsbn: UserBook.BookIsbn, | ||
| val coverImageUrl: String, | ||
| val publisher: String, | ||
| val title: String, | ||
| val author: String, | ||
| val status: BookStatus, | ||
| val createdAt: LocalDateTime, | ||
| val updatedAt: LocalDateTime, | ||
| val lastRecordedAt: LocalDateTime | ||
| ) { | ||
| init { | ||
| require(coverImageUrl.isNotBlank()) { "표지 이미지 URL은 비어 있을 수 없습니다." } | ||
| require(publisher.isNotBlank()) { "출판사는 비어 있을 수 없습니다." } | ||
| require(title.isNotBlank()) { "도서 제목은 비어 있을 수 없습니다." } | ||
| require(author.isNotBlank()) { "저자는 비어 있을 수 없습니다." } | ||
| require(!createdAt.isAfter(updatedAt)) { | ||
| "생성일(createdAt)은 수정일(updatedAt)보다 이후일 수 없습니다." | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| fun newInstance( | ||
| userBook: UserBook, | ||
| lastRecordedAt: LocalDateTime | ||
| ): UserBookWithLastRecordVO { | ||
| return UserBookWithLastRecordVO( | ||
| id = userBook.id, | ||
| userId = userBook.userId, | ||
| bookId = userBook.bookId, | ||
| bookIsbn = userBook.bookIsbn, | ||
| coverImageUrl = userBook.coverImageUrl, | ||
| publisher = userBook.publisher, | ||
| title = userBook.title, | ||
| author = userBook.author, | ||
| status = userBook.status, | ||
| createdAt = userBook.createdAt ?: throw IllegalStateException("createdAt은 null일 수 없습니다."), | ||
| updatedAt = userBook.updatedAt ?: throw IllegalStateException("updatedAt은 null일 수 없습니다."), | ||
| lastRecordedAt = lastRecordedAt | ||
| ) | ||
| } | ||
| } | ||
| } |
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)
와일드카드 import 대신 구체적 import 권장
java.util.*와일드카드 사용은 필요 이상의 타입 노출로 컴파일 성능과 가독성 모두에 불리합니다. UUID 하나만 사용하므로 구체적 import로 교체해주세요.🤖 Prompt for AI Agents