Skip to content

Commit 89989ab

Browse files
authored
feat: BookDetailResponse에 사용자 도서 상태 및 페이지 정보 추가 (#72)
* [BOOK-207] refactor: domain, apis - BookDetailRequest 및 AladinApiHelper 코드 정리 및 개선 * [BOOK-207] refactor: apis - AladinBookQueryService에서 Companion 객체 사용 제거 및 import 정리 * [BOOK-207] fix: apis, infra - AladinBookLookupRequest 패키지 변경 및 import 정리 * [BOOK-207] chore: apis - BookDetailResponse에서 불필요한 주석 제거 및 코드 정리 * [BOOK-207] chore: apis - Swagger UI 경로를 /swagger-ui로 변경 * [BOOK-207] feat: apis - Aladin API Manager 클래스 추가 * [BOOK-207] feat: apis - AladinBookQueryService 클래스 추가 및 BookQueryService 인터페이스를 sealed로 변경, 불필요한 상수 제거 * [BOOK-207] feat: apis, infra - AladinBookLookupRequest 및 AladinBookSearchRequest의 메서드 이름 변경 및 파라미터 정리 * [BOOK-207] refactor: apis - getBookDetail 메서드에 userId 파라미터 추가 및 관련 검증 로직 구현 * [BOOK-207] feat: apis - BookUseCase에 AladinBookQueryService 주입을 위한 Qualifier 추가 * [BOOK-207] refactor: apis - BookDetailRequest 및 BookSearchRequest의 필드 이름 변경 및 유효성 검증 메시지 수정 * [BOOK-207] refactor: apis - Aladin API 관련 예외 처리 추가 및 AladinApiManager 클래스 제거 * [BOOK-207] refactor: domain, infra - UserBookRepository 및 관련 클래스에서 findByIdAndUserId 메서드를 existsByIdAndUserId로 변경하여 존재 여부 확인 로직 개선 * [BOOK-207] refactor: global-utils - RegexUtils.kt에서 이메일 및 프로필 이미지 URL 패턴 제거하고 ISBN13 패턴 추가 * [BOOK-207] feat: infra - Aladin API 응답을 위한 AladinDetailResponse 및 AladinSearchResponse 데이터 클래스 추가 * [BOOK-207] chore: apis - AladinBookQueryService의 getBookDetail 메서드에서 변수 이름을 변경 * [BOOK-207] fix: infra - 중복된 필드 제거 * [BOOK-207] feat: global-utils - ISBN10을 ISBN13으로 변환하는 IsbnConverter 객체 추가 * [BOOK-207] refactor: infra - AladinRestClient에서 addCommonQueryParams 메서드 위치 변경 및 대소문자 수정 * [BOOK-207] refactor: apis - BookDetailResponse 및 BookSearchResponse 데이터 클래스의 필드 정리 및 ISBN 처리 로직 개선 * [BOOK-207] refactor: apis - BookCreateRequest에서 ISBN 처리 로직을 개선하여 ISBN13만 사용하도록 변경 * [BOOK-207] refactor: infra - AladinDetailResponse 및 AladinSearchResponse의 isbn13 필드를 nullable로 변경 * [BOOK-207] delete: infra - AladinResponseBase.kt 파일 삭제 * [BOOK-207] refactor: apis - BookUseCase에서 사용자 책 상태를 병합하는 로직 추가 및 코드 정리 * [BOOK-207] fix: infra - 중복된 필드 제거 * [BOOK-207] feat: apis - AuthorExtractor 객체 추가 및 BookDetailResponse에서 저자 처리 로직 개선 * [BOOK-207] feat: apis - NicknameGenerator 객체 추가하여 랜덤한 닉네임 생성 기능 구현 * [BOOK-207] refactor: apis, global-utils - IsbnConverter 클래스를 apis 패키지로 이동하고 관련된 코드 정리 * [BOOK-207] feat: apis - 책 관련 요청 데이터 클래스에 Swagger 스키마 및 유효성 검사 추가 * [BOOK-207] feat: apis - BookDetailResponse에 사용자 책 상태 추가 및 UserBookService에 상태 조회 기능 구현 * [BOOK-207] feat: domain - 사용자 책 정보를 조회하는 기능 추가 및 예외 처리 구현 * [BOOK-207] fix: apis, domain - 사용자 책 상태 조회 기능을 nullable로 변경하고 관련 로직 수정 * [BOOK-207] chore: domain - 불필요한 import문 제거 * [BOOK-207] feat: global-utils, infra - 책 표지 크기 열거형 추가 및 AladinBookLookupRequest, AladinBookSearchRequest에서 사용 * [BOOK-207] refactor: apis - BookCreateRequest 및 BookDetailResponse에서 cover 필드 이름을 coverImageUrl로 변경 * [BOOK-207] refactor: apis - UserBookRegisterRequest에서 bookStatus를 nullable로 변경하고 관련 메서드 수정 * [BOOK-207] refactor: apis - BookSearchResponse에서 저자 정보를 AuthorExtractor를 통해 추출하도록 수정 * [BOOK-207] refactor: domain, apis, infra - ReadingRecordService에서 UserBookService 의존성 제거 및 예외 처리 로직 개선 * [BOOK-207] chore: apis - CreateReadingRecordRequest에 Swagger 예제 추가 및 설명 수정 * [BOOK-207] fix: apis - ReadingRecordUseCase에서 userBookService의 매개변수 순서 수정 * [BOOK-207] refactor: infra - 생성자 호출 named parameter 형식으로 변경 * [BOOK-207] refactor: apis - converImageUrl validation 추가 * [BOOK-207] fix: apis - emotionTags 필드 스웨거 설명 수정 * [BOOK-207] refactor: apis - BookDetailResponse에서 기본 페이지 수 상수 추가 및 totalPage 기본값 수정 * [BOOK-207] refactor: apis - 서비스 클래스에 @validated@Valid 어노테이션 추가하여 유효성 검사 적용 * [BOOK-207] fix: apis - 컨트롤러의 dto를 바로 받는 메서드의 경우 @Valid 어노테이션 제거
1 parent 2c0ef87 commit 89989ab

File tree

47 files changed

+751
-412
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+751
-412
lines changed

apis/src/main/kotlin/org/yapp/apis/auth/service/TokenService.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.yapp.apis.auth.service
22

3+
import jakarta.validation.Valid
34
import org.springframework.stereotype.Service
5+
import org.springframework.validation.annotation.Validated
46
import org.yapp.apis.auth.dto.request.TokenGenerateRequest
57
import org.yapp.apis.auth.dto.request.TokenRefreshRequest
68
import org.yapp.apis.auth.dto.response.RefreshTokenResponse
@@ -9,14 +11,15 @@ import org.yapp.domain.token.TokenDomainRedisService
911
import java.util.*
1012

1113
@Service
14+
@Validated
1215
class TokenService(
1316
private val tokenDomainRedisService: TokenDomainRedisService,
1417
) {
1518
fun deleteRefreshTokenByToken(token: String) {
1619
tokenDomainRedisService.deleteRefreshTokenByToken(token)
1720
}
1821

19-
fun saveRefreshToken(tokenGenerateRequest: TokenGenerateRequest): RefreshTokenResponse {
22+
fun saveRefreshToken(@Valid tokenGenerateRequest: TokenGenerateRequest): RefreshTokenResponse {
2023
val token = tokenDomainRedisService.saveRefreshToken(
2124
tokenGenerateRequest.validUserId(),
2225
tokenGenerateRequest.validRefreshToken(),

apis/src/main/kotlin/org/yapp/apis/auth/service/UserAuthService.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.yapp.apis.auth.service
22

3+
import jakarta.validation.Valid
34
import org.springframework.stereotype.Service
5+
import org.springframework.validation.annotation.Validated
46
import org.yapp.apis.auth.dto.request.FindOrCreateUserRequest
57
import org.yapp.apis.auth.dto.request.FindUserIdentityRequest
68
import org.yapp.apis.auth.dto.response.CreateUserResponse
@@ -13,6 +15,7 @@ import org.yapp.domain.user.vo.UserIdentityVO
1315
import java.util.*
1416

1517
@Service
18+
@Validated
1619
class UserAuthService(
1720
private val userDomainService: UserDomainService
1821
) {
@@ -33,12 +36,12 @@ class UserAuthService(
3336
}
3437
}
3538

36-
fun findUserIdentityByUserId(findUserIdentityRequest: FindUserIdentityRequest): UserAuthInfoResponse {
39+
fun findUserIdentityByUserId(@Valid findUserIdentityRequest: FindUserIdentityRequest): UserAuthInfoResponse {
3740
val userIdentity = userDomainService.findUserIdentityById(findUserIdentityRequest.validUserId())
3841
return UserAuthInfoResponse.from(userIdentity)
3942
}
4043

41-
fun findOrCreateUser(findOrCreateUserRequest: FindOrCreateUserRequest): CreateUserResponse {
44+
fun findOrCreateUser(@Valid findOrCreateUserRequest: FindOrCreateUserRequest): CreateUserResponse {
4245
userDomainService.findUserByProviderTypeAndProviderId(
4346
findOrCreateUserRequest.validProviderType(),
4447
findOrCreateUserRequest.validProviderId()

apis/src/main/kotlin/org/yapp/apis/book/constant/BookQueryServiceQualifier.kt

Lines changed: 0 additions & 5 deletions
This file was deleted.

apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class BookController(
4545
@AuthenticationPrincipal userId: UUID,
4646
@Valid @ModelAttribute request: BookDetailRequest
4747
): ResponseEntity<BookDetailResponse> {
48-
val response = bookUseCase.getBookDetail(request)
48+
val response = bookUseCase.getBookDetail(request, userId)
4949
return ResponseEntity.ok(response)
5050
}
5151

apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,96 @@
11
package org.yapp.apis.book.dto.request
22

3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.Max
5+
import jakarta.validation.constraints.Min
36
import jakarta.validation.constraints.NotBlank
47
import jakarta.validation.constraints.Size
58
import org.yapp.apis.book.dto.response.BookDetailResponse
69

10+
@Schema(
11+
title = "책 생성 요청",
12+
description = "시스템에 새로운 책 정보를 생성하는 요청 (주로 내부 API에서 사용)"
13+
)
714
data class BookCreateRequest private constructor(
815
@field:NotBlank(message = "ISBN은 필수입니다.")
16+
@Schema(
17+
description = "책의 13자리 ISBN 코드",
18+
example = "9788932473901",
19+
required = true,
20+
minLength = 13,
21+
maxLength = 13
22+
)
923
val isbn: String? = null,
1024

1125
@field:NotBlank(message = "제목은 필수입니다.")
26+
@field:Size(max = 500, message = "제목은 500자 이내여야 합니다.")
27+
@Schema(
28+
description = "책 제목",
29+
example = "해리 포터와 마법사의 돌",
30+
required = true,
31+
maxLength = 500
32+
)
1233
val title: String? = null,
1334

1435
@field:NotBlank(message = "저자는 필수입니다.")
36+
@field:Size(max = 200, message = "저자는 200자 이내여야 합니다.")
37+
@Schema(
38+
description = "저자명 (여러 저자인 경우 쉼표로 구분)",
39+
example = "J.K. 롤링",
40+
required = true,
41+
maxLength = 200
42+
)
1543
val author: String? = null,
1644

1745
@field:NotBlank(message = "출판사는 필수입니다.")
46+
@field:Size(max = 200, message = "출판사는 200자 이내여야 합니다.")
47+
@Schema(
48+
description = "출판사명",
49+
example = "문학수첩",
50+
required = true,
51+
maxLength = 200
52+
)
1853
val publisher: String? = null,
1954

55+
@field:Min(value = 1000, message = "출간연도는 1000년 이후여야 합니다.")
56+
@field:Max(value = 2100, message = "출간연도는 2100년 이전이어야 합니다.")
57+
@Schema(
58+
description = "출간연도 (4자리 년도)",
59+
example = "2000",
60+
minimum = "1000",
61+
maximum = "2100"
62+
)
2063
val publicationYear: Int? = null,
2164

2265
@field:Size(max = 2048, message = "표지 URL은 2048자 이내여야 합니다.")
23-
val coverImageUrl: String,
66+
@field:NotBlank(message = "표지 이미지 URL은 필수입니다.")
67+
@Schema(
68+
description = "책 표지 이미지 URL",
69+
example = "https://image.aladin.co.kr/product/123/45/cover/1234567890123.jpg",
70+
required = true,
71+
maxLength = 2048,
72+
format = "uri"
73+
)
74+
val coverImageUrl: String? = null,
2475

76+
@field:Size(max = 2000, message = "책 설명은 2000자 이내여야 합니다.")
77+
@Schema(
78+
description = "책 소개 및 줄거리",
79+
example = "11살 해리 포터는 이모네 집에서 갖은 구박을 당하며 지낸다...",
80+
maxLength = 2000
81+
)
2582
val description: String? = null
2683
) {
2784
fun validIsbn(): String = isbn!!
2885
fun validTitle(): String = title!!
2986
fun validAuthor(): String = author!!
3087
fun validPublisher(): String = publisher!!
88+
fun validCoverImageUrl(): String = coverImageUrl!!
3189

3290
companion object {
3391

3492
fun from(bookDetail: BookDetailResponse): BookCreateRequest {
35-
val finalIsbn = bookDetail.isbn ?: bookDetail.isbn13
93+
val finalIsbn = bookDetail.isbn13
3694
?: throw IllegalArgumentException("ISBN이 존재하지 않습니다.")
3795

3896
return BookCreateRequest(
@@ -41,7 +99,7 @@ data class BookCreateRequest private constructor(
4199
author = bookDetail.author,
42100
publisher = bookDetail.publisher,
43101
publicationYear = parsePublicationYear(bookDetail.pubDate),
44-
coverImageUrl = bookDetail.cover,
102+
coverImageUrl = bookDetail.coverImageUrl,
45103
description = bookDetail.description,
46104
)
47105
}

apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
11
package org.yapp.apis.book.dto.request
22

3+
import io.swagger.v3.oas.annotations.media.Schema
34
import jakarta.validation.constraints.NotBlank
4-
import jakarta.validation.constraints.Pattern // Pattern 어노테이션 추가
5+
import jakarta.validation.constraints.Pattern
56
import org.yapp.globalutils.util.RegexUtils
6-
import org.yapp.infra.external.aladin.dto.AladinBookLookupRequest
77

8+
@Schema(
9+
title = "책 상세 정보 요청",
10+
description = "특정 ISBN을 통한 책 상세 정보 조회 요청"
11+
)
812
data class BookDetailRequest private constructor(
9-
@field:NotBlank(message = "아이템 ID는 필수입니다.")
13+
@field:NotBlank(message = "ISBN은 비어 있을 수 없습니다.")
1014
@field:Pattern(
11-
regexp = RegexUtils.NOT_BLANK_AND_NOT_NULL_STRING_PATTERN,
12-
message = "아이템 ID유효한 ISBN 형식이 아닙니다."
15+
regexp = RegexUtils.ISBN13_PATTERN,
16+
message = "유효한 13자리 ISBN 형식이 아닙니다."
1317
)
14-
val itemId: String? = null,
15-
val itemIdType: String? = "ISBN",
16-
val optResult: List<String>? = null
18+
@Schema(
19+
description = "조회할 책의 13자리 ISBN 코드",
20+
example = "9788932473901",
21+
required = true,
22+
pattern = "\\d{13}",
23+
minLength = 13,
24+
maxLength = 13
25+
)
26+
val isbn: String? = null,
1727
) {
18-
19-
fun validIsbn(): String = itemId!!
20-
21-
22-
fun toAladinRequest(): AladinBookLookupRequest {
23-
return AladinBookLookupRequest.create(
24-
itemId = this.itemId!!,
25-
itemIdType = this.itemIdType ?: "ISBN",
26-
optResult = this.optResult
27-
)
28-
}
28+
fun validIsbn(): String = isbn!!
2929

3030
companion object {
31-
fun of(isbn: String?, optResult: List<String>? = null): BookDetailRequest {
31+
fun from(
32+
isbn: String?,
33+
): BookDetailRequest {
3234
return BookDetailRequest(
33-
itemId = isbn,
34-
itemIdType = "ISBN",
35-
optResult = optResult
35+
isbn = isbn,
3636
)
3737
}
3838
}
Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,73 @@
11
package org.yapp.apis.book.dto.request
22

3-
import org.yapp.infra.external.aladin.dto.AladinBookSearchRequest
4-
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.Max
5+
import jakarta.validation.constraints.Min
6+
import jakarta.validation.constraints.NotBlank
57

8+
@Schema(
9+
title = "책 검색 요청",
10+
description = "알라딘 API를 통한 책 검색 요청 정보"
11+
)
612
data class BookSearchRequest private constructor(
13+
@field:NotBlank(message = "검색어는 필수입니다.")
14+
@Schema(
15+
description = "검색할 키워드 (제목, 저자, 출판사 등)",
16+
example = "해리포터",
17+
required = true
18+
)
719
val query: String? = null,
20+
21+
@Schema(
22+
description = "검색 유형",
23+
example = "Title",
24+
allowableValues = ["Title", "Author", "Publisher", "Keyword"],
25+
defaultValue = "Keyword"
26+
)
827
val queryType: String? = null,
28+
29+
@Schema(
30+
description = "검색 대상",
31+
example = "Book",
32+
allowableValues = ["Book", "Foreign", "Music", "DVD"],
33+
defaultValue = "Book"
34+
)
935
val searchTarget: String? = null,
36+
37+
@field:Min(value = 1, message = "최대 결과 수는 1 이상이어야 합니다.")
38+
@field:Max(value = 100, message = "최대 결과 수는 100 이하여야 합니다.")
39+
@Schema(
40+
description = "한 번에 가져올 최대 결과 수 (1-100)",
41+
example = "10",
42+
minimum = "1",
43+
maximum = "100",
44+
defaultValue = "10"
45+
)
1046
val maxResults: Int? = null,
47+
48+
@field:Min(value = 1, message = "시작 인덱스는 1 이상이어야 합니다.")
49+
@Schema(
50+
description = "검색 시작 인덱스 (페이징)",
51+
example = "1",
52+
minimum = "1",
53+
defaultValue = "1"
54+
)
1155
val start: Int? = null,
56+
57+
@Schema(
58+
description = "정렬 방식",
59+
example = "Accuracy",
60+
allowableValues = ["Accuracy", "PublishTime", "Title", "SalesPoint"],
61+
defaultValue = "Accuracy"
62+
)
1263
val sort: String? = null,
13-
val cover: String? = null,
64+
65+
@Schema(
66+
description = "카테고리 ID (0: 전체)",
67+
example = "0",
68+
defaultValue = "0"
69+
)
1470
val categoryId: Int? = null
1571
) {
16-
1772
fun validQuery(): String = query!!
18-
fun toAladinRequest(): AladinBookSearchRequest {
19-
20-
return AladinBookSearchRequest.create(
21-
query = this.validQuery(),
22-
queryType = this.queryType,
23-
searchTarget = this.searchTarget,
24-
maxResults = this.maxResults,
25-
start = this.start,
26-
sort = this.sort,
27-
cover = this.cover,
28-
categoryId = this.categoryId
29-
)
30-
}
3173
}

0 commit comments

Comments
 (0)