-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 도서 검색 결과에 사용자 읽음 상태 표시 기능 #39
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 2 commits
b4504ee
6f2a545
0c1b5dd
a3da273
ed9bbea
2494c0a
d557cdc
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 |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ package org.yapp.apis.book.dto.response | |
|
|
||
| import org.yapp.domain.userbook.BookStatus | ||
| import org.yapp.domain.userbook.UserBook | ||
| import org.yapp.domain.userbook.vo.UserBookVO | ||
| import java.time.format.DateTimeFormatter | ||
| import java.util.* | ||
|
|
||
|
|
@@ -18,10 +19,9 @@ data class UserBookResponse private constructor( | |
| val updatedAt: String, | ||
| ) { | ||
|
|
||
|
|
||
| companion object { | ||
| fun from( | ||
| userBook: UserBook, | ||
| ): UserBookResponse { | ||
| fun from(userBook: UserBook): UserBookResponse { | ||
| return UserBookResponse( | ||
| userBookId = userBook.id, | ||
| userId = userBook.userId, | ||
|
|
@@ -35,5 +35,20 @@ data class UserBookResponse private constructor( | |
| updatedAt = userBook.updatedAt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), | ||
| ) | ||
| } | ||
|
|
||
| fun from(vo: UserBookVO): UserBookResponse { | ||
| return UserBookResponse( | ||
| userBookId = vo.id, | ||
| userId = vo.userId, | ||
| bookIsbn = vo.bookIsbn, | ||
| bookTitle = vo.title, | ||
| bookAuthor = vo.author, | ||
| status = vo.status, | ||
| coverImageUrl = vo.coverImageUrl, | ||
| publisher = vo.publisher, | ||
| createdAt = vo.createdAt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), | ||
| updatedAt = vo.updatedAt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), | ||
| ) | ||
| } | ||
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,8 +26,19 @@ class BookUseCase( | |
| private val bookQueryService: BookQueryService, | ||
| private val bookManagementService: BookManagementService | ||
| ) { | ||
| fun searchBooks(request: BookSearchRequest): BookSearchResponse { | ||
| return bookQueryService.searchBooks(request) | ||
| fun searchBooks(request: BookSearchRequest, userId: UUID): BookSearchResponse { | ||
| userAuthService.validateUserExists(userId) | ||
|
|
||
| val searchResponse = bookQueryService.searchBooks(request) | ||
| val isbns = searchResponse.books.map { it.isbn } | ||
|
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. (사소) 요거 requestDTO로 포장해서 findAllByUserIdAndBookIsbnIn 메서드의 인자로 넘기면 좋을 것 같긴 합니다! (하지만 중요한 부분은 아닙니다 ㅎㅎ) |
||
| val userBooks = userBookService.findAllByUserIdAndBookIsbnIn(userId, isbns) | ||
|
||
| val statusMap = userBooks.associateBy({ it.bookIsbn }, { it.status }) | ||
| searchResponse.books.forEach { bookSummary -> | ||
| statusMap[bookSummary.isbn]?.let { status -> | ||
| bookSummary.userBookStatus = status | ||
| } | ||
| } | ||
|
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) 매핑 로직 최적화 및 성능 개선 제안 현재 구현은 올바르게 작동하지만 다음과 같은 개선사항을 고려해볼 수 있습니다:
다음과 같이 리팩토링을 고려해보세요: - val searchResponse = bookQueryService.searchBooks(request)
- val isbns = searchResponse.books.map { it.isbn }
- val userBooks = userBookService.findAllByUserIdAndBookIsbnIn(userId, isbns)
- val statusMap = userBooks.associateBy({ it.bookIsbn }, { it.status })
- searchResponse.books.forEach { bookSummary ->
- statusMap[bookSummary.isbn]?.let { status ->
- bookSummary.userBookStatus = status
- }
- }
- return searchResponse
+ val searchResponse = bookQueryService.searchBooks(request)
+
+ if (searchResponse.books.isNotEmpty()) {
+ val statusMap = userBookService.findAllByUserIdAndBookIsbnIn(
+ userId,
+ searchResponse.books.map { it.isbn }
+ ).associateBy(UserBookResponse::bookIsbn, UserBookResponse::status)
+
+ searchResponse.books.forEach { bookSummary ->
+ bookSummary.userBookStatus = statusMap[bookSummary.isbn]
+ }
+ }
+
+ return searchResponse🤖 Prompt for AI Agents |
||
| return searchResponse | ||
| } | ||
|
|
||
| fun getBookDetail(bookDetailRequest: BookDetailRequest): BookDetailResponse { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package org.yapp.domain.userbook.vo | ||
|
|
||
| import org.yapp.domain.userbook.BookStatus | ||
| import org.yapp.domain.userbook.UserBook | ||
| import java.time.LocalDateTime | ||
| import java.util.UUID | ||
|
|
||
| data class UserBookVO private constructor( | ||
| val id: UUID, | ||
| val userId: UUID, | ||
| val bookIsbn: String, | ||
| val coverImageUrl: String, | ||
| val publisher: String, | ||
| val title: String, | ||
| val author: String, | ||
| val status: BookStatus, | ||
| val createdAt: LocalDateTime, | ||
| val updatedAt: LocalDateTime | ||
| ) { | ||
|
|
||
| companion object { | ||
| fun newInstance( | ||
| userBook: UserBook, | ||
| ): UserBookVO { | ||
| return UserBookVO( | ||
| id = userBook.id, | ||
| userId = userBook.userId, | ||
| bookIsbn = userBook.bookIsbn, | ||
| coverImageUrl = userBook.coverImageUrl, | ||
| publisher = userBook.publisher, | ||
| title = userBook.title, | ||
| author = userBook.author, | ||
| status = userBook.status, | ||
| createdAt = userBook.createdAt, | ||
| updatedAt = userBook.updatedAt | ||
| ) | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe | |||||||||||||||||||||||||||||||||
| import org.springframework.security.config.http.SessionCreationPolicy | ||||||||||||||||||||||||||||||||||
| import org.springframework.security.web.SecurityFilterChain | ||||||||||||||||||||||||||||||||||
| import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter | ||||||||||||||||||||||||||||||||||
| import org.springframework.web.cors.CorsConfiguration | ||||||||||||||||||||||||||||||||||
| import org.springframework.web.cors.CorsConfigurationSource | ||||||||||||||||||||||||||||||||||
| import org.springframework.web.cors.UrlBasedCorsConfigurationSource | ||||||||||||||||||||||||||||||||||
| import org.yapp.gateway.filter.JwtAuthenticationFilter | ||||||||||||||||||||||||||||||||||
| import org.yapp.gateway.jwt.JwtTokenProvider | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
@@ -28,6 +31,7 @@ class SecurityConfig( | |||||||||||||||||||||||||||||||||
| fun filterChain(http: HttpSecurity): SecurityFilterChain { | ||||||||||||||||||||||||||||||||||
| http | ||||||||||||||||||||||||||||||||||
| .csrf { it.disable() } | ||||||||||||||||||||||||||||||||||
| .cors { it.configurationSource(corsConfigurationSource()) } | ||||||||||||||||||||||||||||||||||
| .formLogin { it.disable() } | ||||||||||||||||||||||||||||||||||
| .httpBasic { it.disable() } | ||||||||||||||||||||||||||||||||||
| .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } | ||||||||||||||||||||||||||||||||||
|
|
@@ -44,4 +48,26 @@ class SecurityConfig( | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return http.build() | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @Bean | ||||||||||||||||||||||||||||||||||
| fun corsConfigurationSource(): CorsConfigurationSource { | ||||||||||||||||||||||||||||||||||
| val config = CorsConfiguration() | ||||||||||||||||||||||||||||||||||
| config.allowedOrigins = listOf( | ||||||||||||||||||||||||||||||||||
| "http://localhost:8080", | ||||||||||||||||||||||||||||||||||
| "http://localhost:3000", | ||||||||||||||||||||||||||||||||||
| "http://api.qweasdzxc.shop", | ||||||||||||||||||||||||||||||||||
| "https://api.qweasdzxc.shop", | ||||||||||||||||||||||||||||||||||
| "https://www.qweasdzxc.shop", | ||||||||||||||||||||||||||||||||||
| "https://qweasdzxc.shop" | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
| config.allowedOrigins = listOf( | |
| "http://localhost:8080", | |
| "http://localhost:3000", | |
| "http://api.qweasdzxc.shop", | |
| "https://api.qweasdzxc.shop", | |
| "https://www.qweasdzxc.shop", | |
| "https://qweasdzxc.shop" | |
| ) | |
| config.allowedOrigins = listOf( | |
| "http://localhost:8080", | |
| "http://localhost:3000", | |
| "http://api.qwasdzxc.shop", | |
| "https://api.qwasdzxc.shop", | |
| "https://www.qwasdzxc.shop", | |
| "https://qwasdzxc.shop" | |
| ) |
🤖 Prompt for AI Agents
In gateway/src/main/kotlin/org/yapp/gateway/config/SecurityConfig.kt between
lines 55 and 62, the allowedOrigins list contains domain names with a typo:
"qweasdzxc.shop" should be corrected to "qwasdzxc.shop" to match the actual
production domain. Update all occurrences of "qweasdzxc.shop" in the
allowedOrigins list to "qwasdzxc.shop" to fix the domain spelling.
Outdated
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)
보안 고려사항: 헤더 허용 범위를 제한하는 것을 검토해주세요.
allowedHeaders = listOf("*")는 모든 헤더를 허용하므로 보안상 위험할 수 있습니다. 필요한 헤더만 명시적으로 허용하는 것이 더 안전합니다.
다음과 같이 필요한 헤더만 명시적으로 허용하는 것을 고려해보세요:
- config.allowedHeaders = listOf("*")
+ config.allowedHeaders = listOf(
+ "Authorization",
+ "Content-Type",
+ "Accept",
+ "X-Requested-With",
+ "Cache-Control"
+ )📝 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.
| config.allowedHeaders = listOf("*") | |
| config.allowedHeaders = listOf( | |
| "Authorization", | |
| "Content-Type", | |
| "Accept", | |
| "X-Requested-With", | |
| "Cache-Control" | |
| ) |
🤖 Prompt for AI Agents
In gateway/src/main/kotlin/org/yapp/gateway/config/SecurityConfig.kt at line 64,
the allowedHeaders is set to allow all headers with "*", which poses a security
risk. Replace the wildcard with a list of only the specific headers your
application requires, such as "Authorization", "Content-Type", and any other
necessary headers, to restrict access and improve security.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,4 +7,6 @@ import java.util.* | |
| interface JpaUserBookRepository : JpaRepository<UserBookEntity, UUID> { | ||
| fun findByUserIdAndBookIsbn(userId: UUID, bookIsbn: String): UserBookEntity? | ||
| fun findAllByUserId(userId: UUID): List<UserBookEntity> | ||
| fun findAllByUserIdAndBookIsbnIn(userId: UUID, bookIsbnList: List<String>): List<UserBookEntity> | ||
|
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) JPA 메서드 명명 규칙을 올바르게 따르고 있습니다. Spring Data JPA의 🤖 Prompt for AI Agents |
||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.