diff --git a/apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt b/apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt index e88075d9..b5d06d45 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt @@ -101,7 +101,7 @@ interface BookControllerApi { ), ApiResponse( responseCode = "404", - description = "존재하지 않는 책 (ISBN 오류)", + description = "존재하지 않는 책 (ISBN13 오류)", content = [Content(schema = Schema(implementation = ErrorResponse::class))] ) ] diff --git a/apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt b/apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt index bb89d0f0..3265d21f 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt @@ -1,26 +1,28 @@ package org.yapp.apis.book.dto.request import io.swagger.v3.oas.annotations.media.Schema -import jakarta.validation.constraints.Max -import jakarta.validation.constraints.Min -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.Size +import jakarta.validation.constraints.* import org.yapp.apis.book.dto.response.BookDetailResponse +import org.yapp.globalutils.util.RegexUtils @Schema( title = "책 생성 요청", description = "시스템에 새로운 책 정보를 생성하는 요청 (주로 내부 API에서 사용)" ) data class BookCreateRequest private constructor( - @field:NotBlank(message = "ISBN은 필수입니다.") + @field:NotBlank(message = "ISBN13은 필수입니다.") + @field:Pattern( + regexp = RegexUtils.ISBN13_PATTERN, + message = "유효한 13자리 ISBN13 형식이 아닙니다." + ) @Schema( - description = "책의 13자리 ISBN 코드", + description = "책의 13자리 ISBN13 코드", example = "9788932473901", required = true, minLength = 13, maxLength = 13 ) - val isbn: String? = null, + val isbn13: String? = null, @field:NotBlank(message = "제목은 필수입니다.") @field:Size(max = 500, message = "제목은 500자 이내여야 합니다.") @@ -81,26 +83,25 @@ data class BookCreateRequest private constructor( ) val description: String? = null ) { - fun validIsbn(): String = isbn!! + fun validIsbn13(): String = isbn13!! fun validTitle(): String = title!! fun validAuthor(): String = author!! fun validPublisher(): String = publisher!! fun validCoverImageUrl(): String = coverImageUrl!! companion object { - - fun from(bookDetail: BookDetailResponse): BookCreateRequest { - val finalIsbn = bookDetail.isbn13 - ?: throw IllegalArgumentException("ISBN이 존재하지 않습니다.") + fun from(bookDetailResponse: BookDetailResponse): BookCreateRequest { + val finalIsbn13 = bookDetailResponse.isbn13 + ?: throw IllegalArgumentException("ISBN13이 존재하지 않습니다.") return BookCreateRequest( - isbn = finalIsbn, - title = bookDetail.title, - author = bookDetail.author, - publisher = bookDetail.publisher, - publicationYear = parsePublicationYear(bookDetail.pubDate), - coverImageUrl = bookDetail.coverImageUrl, - description = bookDetail.description, + isbn13 = finalIsbn13, + title = bookDetailResponse.title, + author = bookDetailResponse.author, + publisher = bookDetailResponse.publisher, + publicationYear = parsePublicationYear(bookDetailResponse.pubDate), + coverImageUrl = bookDetailResponse.coverImageUrl, + description = bookDetailResponse.description, ) } diff --git a/apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt b/apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt index d8bb18fa..c4f3dafb 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt @@ -7,32 +7,31 @@ import org.yapp.globalutils.util.RegexUtils @Schema( title = "책 상세 정보 요청", - description = "특정 ISBN을 통한 책 상세 정보 조회 요청" + description = "특정 ISBN13을 통한 책 상세 정보 조회 요청" ) data class BookDetailRequest private constructor( - @field:NotBlank(message = "ISBN은 비어 있을 수 없습니다.") + @field:NotBlank(message = "ISBN13은 비어 있을 수 없습니다.") @field:Pattern( regexp = RegexUtils.ISBN13_PATTERN, - message = "유효한 13자리 ISBN 형식이 아닙니다." + message = "유효한 13자리 ISBN13 형식이 아닙니다." ) @Schema( description = "조회할 책의 13자리 ISBN 코드", example = "9788932473901", required = true, - pattern = "\\d{13}", minLength = 13, maxLength = 13 ) - val isbn: String? = null, + val isbn13: String? = null, ) { - fun validIsbn(): String = isbn!! + fun validIsbn13(): String = isbn13!! companion object { fun from( - isbn: String?, + isbn13: String, ): BookDetailRequest { return BookDetailRequest( - isbn = isbn, + isbn13 = isbn13, ) } } diff --git a/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UpsertUserBookRequest.kt b/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UpsertUserBookRequest.kt index fd466fa5..a6259774 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UpsertUserBookRequest.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UpsertUserBookRequest.kt @@ -3,9 +3,11 @@ package org.yapp.apis.book.dto.request import io.swagger.v3.oas.annotations.media.Schema import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern import jakarta.validation.constraints.Size import org.yapp.apis.book.dto.response.BookCreateResponse import org.yapp.domain.userbook.BookStatus +import org.yapp.globalutils.util.RegexUtils import java.util.UUID @Schema( @@ -31,15 +33,19 @@ data class UpsertUserBookRequest private constructor( ) val bookId: UUID? = null, - @field:NotBlank(message = "책 ISBN은 필수입니다.") + @field:NotBlank(message = "책 ISBN13은 필수입니다.") + @field:Pattern( + regexp = RegexUtils.ISBN13_PATTERN, + message = "유효한 13자리 ISBN13 형식이 아닙니다." + ) @Schema( - description = "책의 13자리 ISBN 코드", + description = "책의 13자리 ISBN13 코드", example = "9788932473901", required = true, minLength = 13, maxLength = 13 ) - val bookIsbn: String? = null, + val isbn13: String? = null, @field:NotBlank(message = "책 제목은 필수입니다.") @field:Size(max = 500, message = "책 제목은 500자 이내여야 합니다.") @@ -94,7 +100,7 @@ data class UpsertUserBookRequest private constructor( ) { fun validUserId(): UUID = userId!! fun validBookId(): UUID = bookId!! - fun validBookIsbn(): String = bookIsbn!! + fun validBookIsbn13(): String = isbn13!! fun validBookTitle(): String = bookTitle!! fun validBookAuthor(): String = bookAuthor!! fun validBookPublisher(): String = bookPublisher!! @@ -110,7 +116,7 @@ data class UpsertUserBookRequest private constructor( return UpsertUserBookRequest( userId = userId, bookId = bookCreateResponse.bookId, - bookIsbn = bookCreateResponse.isbn, + isbn13 = bookCreateResponse.isbn13, bookTitle = bookCreateResponse.title, bookAuthor = bookCreateResponse.author, bookPublisher = bookCreateResponse.publisher, diff --git a/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt b/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt index ef140ed1..fede450d 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt @@ -3,22 +3,28 @@ package org.yapp.apis.book.dto.request import io.swagger.v3.oas.annotations.media.Schema import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern import org.yapp.domain.userbook.BookStatus +import org.yapp.globalutils.util.RegexUtils @Schema( title = "사용자 도서 등록 요청", description = "사용자의 서재에 도서를 등록하거나 상태를 변경하는 요청" ) data class UserBookRegisterRequest private constructor( - @field:NotBlank(message = "ISBN은 필수입니다.") + @field:NotBlank(message = "ISBN13은 비어 있을 수 없습니다.") + @field:Pattern( + regexp = RegexUtils.ISBN13_PATTERN, + message = "유효한 13자리 ISBN13 형식이 아닙니다." + ) @Schema( - description = "등록할 책의 13자리 ISBN 코드", + description = "등록할 책의 13자리 ISBN13 코드", example = "9788932473901", required = true, minLength = 13, maxLength = 13 ) - val bookIsbn: String? = null, + val isbn13: String? = null, @field:NotNull(message = "도서 상태는 필수입니다.") @Schema( @@ -30,6 +36,6 @@ data class UserBookRegisterRequest private constructor( ) val bookStatus: BookStatus? = null ) { - fun validBookIsbn(): String = bookIsbn!! + fun validIsbn13(): String = isbn13!! fun validBookStatus(): BookStatus = bookStatus!! } diff --git a/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBooksByIsbnsRequest.kt b/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBooksByIsbn13sRequest.kt similarity index 59% rename from apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBooksByIsbnsRequest.kt rename to apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBooksByIsbn13sRequest.kt index fbc0d8c9..74b79f6c 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBooksByIsbnsRequest.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBooksByIsbn13sRequest.kt @@ -6,10 +6,10 @@ import jakarta.validation.constraints.NotNull import java.util.UUID @Schema( - name = "UserBooksByIsbnsRequest", - description = "Request DTO for finding user books by user ID and a list of ISBNs" + name = "UserBooksByIsbn13sRequest", + description = "Request DTO for finding user books by user ID and a list of ISBN13s" ) -data class UserBooksByIsbnsRequest private constructor( +data class UserBooksByIsbn13sRequest private constructor( @Schema( description = "사용자 ID", example = "1" @@ -21,16 +21,16 @@ data class UserBooksByIsbnsRequest private constructor( description = "도서 ISBN 목록", example = "[\"9788966262429\", \"9791190412351\"]" ) - @field:NotEmpty(message = "isbns는 비어있을 수 없습니다.") - val isbns: List? = null + @field:NotEmpty(message = "isbn13 리스트는 비어있을 수 없습니다.") + val isbn13s: List? = null ) { fun validUserId(): UUID = userId!! - fun validIsbns(): List = isbns!! + fun validIsbn13s(): List = isbn13s!! companion object { - fun of(userId: UUID, isbns: List): UserBooksByIsbnsRequest { - return UserBooksByIsbnsRequest(userId = userId, isbns = isbns) + fun of(userId: UUID, isbn13s: List): UserBooksByIsbn13sRequest { + return UserBooksByIsbn13sRequest(userId = userId, isbn13s = isbn13s) } } } diff --git a/apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookCreateResponse.kt b/apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookCreateResponse.kt index 449c0bf5..7235afdb 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookCreateResponse.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookCreateResponse.kt @@ -6,7 +6,7 @@ import java.util.UUID data class BookCreateResponse private constructor( val bookId: UUID, - val isbn: String, + val isbn13: String, val title: String, val author: String, val publisher: String, @@ -16,7 +16,7 @@ data class BookCreateResponse private constructor( fun from(bookVO: BookInfoVO): BookCreateResponse { return BookCreateResponse( bookId = bookVO.id.value, - isbn = bookVO.isbn.value, + isbn13 = bookVO.isbn13.value, title = bookVO.title, author = bookVO.author, publisher = bookVO.publisher, 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 133edfdf..2879a9b5 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 @@ -9,7 +9,7 @@ import java.util.UUID data class UserBookResponse private constructor( val userBookId: UUID, val userId: UUID, - val bookIsbn: String, + val isbn13: String, val bookTitle: String, val bookAuthor: String, val status: BookStatus, @@ -26,7 +26,7 @@ data class UserBookResponse private constructor( return UserBookResponse( userBookId = userBook.id.value, userId = userBook.userId.value, - bookIsbn = userBook.bookIsbn.value, + isbn13 = userBook.bookIsbn13.value, bookTitle = userBook.title, bookAuthor = BookDataValidator.removeParenthesesFromAuthor(userBook.author), status = userBook.status, diff --git a/apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt b/apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt index c6a94a54..99898421 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt @@ -16,6 +16,7 @@ import org.yapp.infra.external.aladin.AladinApi import org.yapp.infra.external.aladin.request.AladinBookLookupRequest import org.yapp.infra.external.aladin.request.AladinBookSearchRequest import org.yapp.infra.external.aladin.response.AladinBookDetailResponse +import org.yapp.infra.external.aladin.response.AladinSearchItem import org.yapp.infra.external.aladin.response.AladinSearchResponse @Service @@ -46,12 +47,7 @@ class AladinBookQueryService( } val filteredItems = response.item.filter { item -> - val isbn13 = item.isbn13?.takeIf { it.isNotBlank() } - ?: item.isbn?.let { IsbnConverter.toIsbn13(it) } - - isbn13?.let { - IsbnValidator.isValidIsbn(it) && !it.startsWith("K", ignoreCase = true) - } ?: false + getValidAndFilteredIsbn13(item) != null } val filteredResponse = AladinSearchResponse( @@ -73,10 +69,10 @@ class AladinBookQueryService( override fun getBookDetail(@Valid request: BookDetailRequest): BookDetailResponse { log.info("Service - Converting BookDetailRequest to AladinBookLookupRequest and calling Aladin API for book detail lookup.") - val aladinLookupRequest = AladinBookLookupRequest.from(request.validIsbn()) + val aladinLookupRequest = AladinBookLookupRequest.from(request.validIsbn13()) val response: AladinBookDetailResponse = aladinApi.lookupBook(aladinLookupRequest) .onSuccess { response -> - log.info("Aladin lookup successful for itemId: '${aladinLookupRequest.itemId}', title: ${response.item?.firstOrNull()?.title}") + log.info("Aladin lookup successful for itemId: '${aladinLookupRequest.itemId}', title: ${response.item.firstOrNull()?.title}") } .getOrElse { exception -> log.error("Failed to call Aladin lookup API for request: '$request'", exception) @@ -84,4 +80,17 @@ class AladinBookQueryService( } return BookDetailResponse.from(response) } + + + private fun getValidAndFilteredIsbn13(item: AladinSearchItem): String? { + val primaryIsbn13 = item.isbn13 + ?.takeIf { it.isNotBlank() && IsbnValidator.isValidIsbn13(it) } + + val convertedIsbn13 = item.isbn + ?.takeIf { it.isNotBlank() && IsbnValidator.isValidIsbn10(it) } + ?.let { validIsbn10 -> IsbnConverter.toIsbn13(validIsbn10) } + ?.takeIf { IsbnValidator.isValidIsbn13(it) } + + return primaryIsbn13 ?: convertedIsbn13 + } } diff --git a/apis/src/main/kotlin/org/yapp/apis/book/service/BookManagementService.kt b/apis/src/main/kotlin/org/yapp/apis/book/service/BookManagementService.kt index 559b118b..3fbb382a 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/service/BookManagementService.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/service/BookManagementService.kt @@ -14,7 +14,7 @@ class BookManagementService( ) { fun findOrCreateBook(@Valid request: BookCreateRequest): BookCreateResponse { val bookInfoVO = bookDomainService.findOrCreate( - request.validIsbn(), + request.validIsbn13(), request.validTitle(), request.validAuthor(), request.validPublisher(), diff --git a/apis/src/main/kotlin/org/yapp/apis/book/service/BookQueryService.kt b/apis/src/main/kotlin/org/yapp/apis/book/service/BookQueryService.kt index cc1527e4..9f966f88 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/service/BookQueryService.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/service/BookQueryService.kt @@ -1,6 +1,5 @@ package org.yapp.apis.book.service -import jakarta.validation.Valid import org.yapp.apis.book.dto.request.BookDetailRequest import org.yapp.apis.book.dto.request.BookSearchRequest import org.yapp.apis.book.dto.response.BookDetailResponse @@ -8,5 +7,5 @@ import org.yapp.apis.book.dto.response.BookSearchResponse sealed interface BookQueryService { fun searchBooks(request: BookSearchRequest): BookSearchResponse - fun getBookDetail(@Valid request: BookDetailRequest): BookDetailResponse + fun getBookDetail(request: BookDetailRequest): BookDetailResponse } diff --git a/apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt b/apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt index 7b0db48c..6b0544cd 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt @@ -5,7 +5,7 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.validation.annotation.Validated -import org.yapp.apis.book.dto.request.UserBooksByIsbnsRequest +import org.yapp.apis.book.dto.request.UserBooksByIsbn13sRequest import org.yapp.apis.book.dto.request.UpsertUserBookRequest import org.yapp.apis.book.dto.response.UserBookPageResponse import org.yapp.apis.book.dto.response.UserBookResponse @@ -25,7 +25,7 @@ class UserBookService( val userBookInfoVO = userBookDomainService.upsertUserBook( upsertUserBookRequest.validUserId(), upsertUserBookRequest.validBookId(), - upsertUserBookRequest.validBookIsbn(), + upsertUserBookRequest.validBookIsbn13(), upsertUserBookRequest.validBookTitle(), upsertUserBookRequest.validBookAuthor(), upsertUserBookRequest.validBookPublisher(), @@ -44,16 +44,16 @@ class UserBookService( } } - fun findAllByUserIdAndBookIsbnIn(@Valid userBooksByIsbnsRequest: UserBooksByIsbnsRequest): List { - val userBooks = userBookDomainService.findAllByUserIdAndBookIsbnIn( - userBooksByIsbnsRequest.validUserId(), - userBooksByIsbnsRequest.validIsbns(), + fun findAllByUserIdAndBookIsbn13In(@Valid userBooksByIsbn13sRequest: UserBooksByIsbn13sRequest): List { + val userBooks = userBookDomainService.findAllByUserIdAndBookIsbn13In( + userBooksByIsbn13sRequest.validUserId(), + userBooksByIsbn13sRequest.validIsbn13s(), ) return userBooks.map { UserBookResponse.from(it) } } - fun findUserBookStatusByIsbn(userId: UUID, isbn: String): BookStatus? { - val userBook = userBookDomainService.findByUserIdAndBookIsbn(userId, isbn) + fun findUserBookStatusByIsbn13(userId: UUID, isbn13: String): BookStatus? { + val userBook = userBookDomainService.findByUserIdAndBookIsbn13(userId, isbn13) return userBook?.status } diff --git a/apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt b/apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt index 25e5e5c5..72703d19 100644 --- a/apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt +++ b/apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt @@ -4,10 +4,11 @@ package org.yapp.apis.book.usecase import org.springframework.beans.factory.annotation.Qualifier import org.springframework.data.domain.Pageable import org.springframework.transaction.annotation.Transactional -import org.yapp.apis.book.dto.request.UserBooksByIsbnsRequest +import org.yapp.apis.book.dto.request.UserBooksByIsbn13sRequest import org.yapp.apis.book.dto.request.* import org.yapp.apis.book.dto.response.BookDetailResponse import org.yapp.apis.book.dto.response.BookSearchResponse +import org.yapp.apis.book.dto.response.BookSearchResponse.* import org.yapp.apis.book.dto.response.UserBookPageResponse import org.yapp.apis.book.dto.response.UserBookResponse import org.yapp.apis.book.service.BookManagementService @@ -50,7 +51,7 @@ class BookUseCase( val isbn13 = bookDetailResponse.isbn13 ?: return bookDetailResponse.withUserBookStatus(BookStatus.BEFORE_REGISTRATION) - val userBookStatus = userBookService.findUserBookStatusByIsbn(userId, isbn13) + val userBookStatus = userBookService.findUserBookStatusByIsbn13(userId, isbn13) ?: BookStatus.BEFORE_REGISTRATION return bookDetailResponse.withUserBookStatus(userBookStatus) @@ -63,7 +64,7 @@ class BookUseCase( ): UserBookResponse { userService.validateUserExists(userId) - val bookDetailResponse = bookQueryService.getBookDetail(BookDetailRequest.from(request.validBookIsbn())) + val bookDetailResponse = bookQueryService.getBookDetail(BookDetailRequest.from(request.validIsbn13())) val bookCreateResponse = bookManagementService.findOrCreateBook(BookCreateRequest.from(bookDetailResponse)) val upsertUserBookRequest = UpsertUserBookRequest.of( userId = userId, @@ -88,36 +89,33 @@ class BookUseCase( } private fun mergeWithUserBookStatus( - searchedBooks: List, + searchedBooks: List, userId: UUID - ): List { + ): List { if (searchedBooks.isEmpty()) { return emptyList() } - val isbn13List = searchedBooks.map { it.isbn13 } - val userBookStatusMap = getUserBookStatusMap(isbn13List, userId) + val isbn13s = searchedBooks.map { it.isbn13 } + val userBookStatusMap = getUserBookStatusMap(isbn13s, userId) return searchedBooks.map { bookSummary -> - val userStatus = userBookStatusMap[bookSummary.isbn13] - if (userStatus != null) { - bookSummary.updateStatus(userStatus) - } else { - bookSummary - } + userBookStatusMap[bookSummary.isbn13] + ?.let { bookSummary.updateStatus(it) } + ?: bookSummary } } private fun getUserBookStatusMap( - isbn13List: List, + isbn13s: List, userId: UUID ): Map { - val userBooksResponse = userBookService.findAllByUserIdAndBookIsbnIn( - UserBooksByIsbnsRequest.of(userId, isbn13List) + val userBooksResponse = userBookService.findAllByUserIdAndBookIsbn13In( + UserBooksByIsbn13sRequest.of(userId, isbn13s) ) return userBooksResponse.associate { userBook -> - userBook.bookIsbn to userBook.status + userBook.isbn13 to userBook.status } } } diff --git a/domain/src/main/kotlin/org/yapp/domain/book/Book.kt b/domain/src/main/kotlin/org/yapp/domain/book/Book.kt index d9286495..48ec8870 100644 --- a/domain/src/main/kotlin/org/yapp/domain/book/Book.kt +++ b/domain/src/main/kotlin/org/yapp/domain/book/Book.kt @@ -7,7 +7,7 @@ import java.util.* data class Book private constructor( val id: Id, - val isbn: Isbn, + val isbn13: Isbn13, val title: String, val author: String, val publisher: String, @@ -20,7 +20,7 @@ data class Book private constructor( ) { companion object { fun create( - isbn: String, + isbn13: String, title: String, author: String, publisher: String, @@ -30,7 +30,7 @@ data class Book private constructor( ): Book { return Book( id = Id.newInstance(UuidGenerator.create()), - isbn = Isbn.newInstance(isbn), + isbn13 = Isbn13.newInstance(isbn13), title = title, author = author, publisher = publisher, @@ -42,7 +42,7 @@ data class Book private constructor( fun reconstruct( id: Id, - isbn: Isbn, + isbn13: Isbn13, title: String, author: String, publisher: String, @@ -55,7 +55,7 @@ data class Book private constructor( ): Book { return Book( id = id, - isbn = isbn, + isbn13 = isbn13, title = title, author = author, publisher = publisher, @@ -77,11 +77,11 @@ data class Book private constructor( } @JvmInline - value class Isbn(val value: String) { + value class Isbn13(val value: String) { companion object { - fun newInstance(value: String): Isbn { - require(IsbnValidator.isValidIsbn(value)) { "ISBN must be a 10 or 13-digit number." } - return Isbn(value) + fun newInstance(value: String): Isbn13 { + require(IsbnValidator.isValidIsbn13(value)) { "ISBN must be a 10 or 13-digit number." } + return Isbn13(value) } } } diff --git a/domain/src/main/kotlin/org/yapp/domain/book/BookDomainService.kt b/domain/src/main/kotlin/org/yapp/domain/book/BookDomainService.kt index 023145b7..c0cef53f 100644 --- a/domain/src/main/kotlin/org/yapp/domain/book/BookDomainService.kt +++ b/domain/src/main/kotlin/org/yapp/domain/book/BookDomainService.kt @@ -17,7 +17,7 @@ class BookDomainService( } fun findOrCreate( - isbn: String, + isbn13: String, title: String, author: String, publisher: String, @@ -25,9 +25,9 @@ class BookDomainService( publicationYear: Int? = null, description: String? = null ): BookInfoVO { - return findByIsbnOrNull(isbn) ?: run { + return findByIsbn13OrNull(isbn13) ?: run { val newBook = Book.create( - isbn = isbn, + isbn13 = isbn13, title = title, author = author, publisher = publisher, @@ -40,14 +40,8 @@ class BookDomainService( } } - private fun findByIsbnOrNull(isbn: String): BookInfoVO? { - val book = bookRepository.findByIsbn(isbn) - return book?.let { BookInfoVO.newInstance(it) } - } - - fun save( - isbn: String, + isbn13: String, title: String, author: String, publisher: String, @@ -55,12 +49,12 @@ class BookDomainService( publicationYear: Int? = null, description: String? = null, ): BookInfoVO { - if (bookRepository.existsByIsbn(isbn)) { + if (bookRepository.existsByIsbn13(isbn13)) { throw BookAlreadyExistsException(BookErrorCode.BOOK_ALREADY_EXISTS) } val book = Book.create( - isbn = isbn, + isbn13 = isbn13, title = title, author = author, publisher = publisher, @@ -72,4 +66,9 @@ class BookDomainService( val savedBook = bookRepository.save(book) return BookInfoVO.newInstance(savedBook) } + + private fun findByIsbn13OrNull(isbn13: String): BookInfoVO? { + val book = bookRepository.findByIsbn13(isbn13) + return book?.let { BookInfoVO.newInstance(it) } + } } diff --git a/domain/src/main/kotlin/org/yapp/domain/book/BookRepository.kt b/domain/src/main/kotlin/org/yapp/domain/book/BookRepository.kt index 818a866d..4098166c 100644 --- a/domain/src/main/kotlin/org/yapp/domain/book/BookRepository.kt +++ b/domain/src/main/kotlin/org/yapp/domain/book/BookRepository.kt @@ -5,7 +5,7 @@ import java.util.UUID interface BookRepository { fun findById(id: UUID): Book? fun existsById(id: UUID): Boolean - fun findByIsbn(isbn: String): Book? - fun existsByIsbn(isbn: String): Boolean + fun findByIsbn13(isbn13: String): Book? + fun existsByIsbn13(isbn13: String): Boolean fun save(book: Book): Book } diff --git a/domain/src/main/kotlin/org/yapp/domain/book/vo/BookInfoVO.kt b/domain/src/main/kotlin/org/yapp/domain/book/vo/BookInfoVO.kt index be497d2c..71229934 100644 --- a/domain/src/main/kotlin/org/yapp/domain/book/vo/BookInfoVO.kt +++ b/domain/src/main/kotlin/org/yapp/domain/book/vo/BookInfoVO.kt @@ -4,7 +4,7 @@ import org.yapp.domain.book.Book data class BookInfoVO private constructor( val id: Book.Id, - val isbn: Book.Isbn, + val isbn13: Book.Isbn13, val title: String, val author: String, val publisher: String, @@ -24,7 +24,7 @@ data class BookInfoVO private constructor( ): BookInfoVO { return BookInfoVO( book.id, - book.isbn, + book.isbn13, book.title, book.author, book.publisher, 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 c44f361a..722b160a 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt @@ -9,7 +9,7 @@ data class UserBook private constructor( val id: Id, val userId: UserId, val bookId: BookId, - val bookIsbn: BookIsbn, + val bookIsbn13: BookIsbn13, val coverImageUrl: String, val publisher: String, val title: String, @@ -28,12 +28,11 @@ data class UserBook private constructor( return this.copy(readingRecordCount = this.readingRecordCount + 1) } - companion object { fun create( userId: UUID, bookId: UUID, - bookIsbn: String, + bookIsbn13: String, coverImageUrl: String, publisher: String, title: String, @@ -44,7 +43,7 @@ data class UserBook private constructor( id = Id.newInstance(UuidGenerator.create()), userId = UserId.newInstance(userId), bookId = BookId.newInstance(bookId), - bookIsbn = BookIsbn.newInstance(bookIsbn), + bookIsbn13 = BookIsbn13.newInstance(bookIsbn13), coverImageUrl = coverImageUrl, publisher = publisher, title = title, @@ -57,7 +56,7 @@ data class UserBook private constructor( id: Id, userId: UserId, bookId: BookId, - bookIsbn: BookIsbn, + bookIsbn13: BookIsbn13, coverImageUrl: String, publisher: String, title: String, @@ -72,7 +71,7 @@ data class UserBook private constructor( id = id, userId = userId, bookId = bookId, - bookIsbn = bookIsbn, + bookIsbn13 = bookIsbn13, coverImageUrl = coverImageUrl, publisher = publisher, title = title, @@ -108,11 +107,11 @@ data class UserBook private constructor( } @JvmInline - value class BookIsbn(val value: String) { + value class BookIsbn13(val value: String) { companion object { - fun newInstance(value: String): BookIsbn { - require(IsbnValidator.isValidIsbn(value)) { "ISBN must be a 10 or 13-digit number." } - return BookIsbn(value) + fun newInstance(value: String): BookIsbn13 { + require(IsbnValidator.isValidIsbn13(value)) { "ISBN13 must be a 13-digit number." } + return BookIsbn13(value) } } } 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 3af4aeb7..cb41b205 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt @@ -15,18 +15,18 @@ class UserBookDomainService( fun upsertUserBook( userId: UUID, bookId: UUID, - bookIsbn: String, + bookIsbn13: String, bookTitle: String, bookAuthor: String, bookPublisher: String, bookCoverImageUrl: String, status: BookStatus ): UserBookInfoVO { - val userBook = userBookRepository.findByUserIdAndBookIsbn(userId, bookIsbn)?.updateStatus(status) + val userBook = userBookRepository.findByUserIdAndBookIsbn13(userId, bookIsbn13)?.updateStatus(status) ?: UserBook.create( userId = userId, bookId = bookId, - bookIsbn = bookIsbn, + bookIsbn13 = bookIsbn13, title = bookTitle, author = bookAuthor, publisher = bookPublisher, @@ -49,16 +49,16 @@ class UserBookDomainService( return page.map { UserBookInfoVO.newInstance(it, it.readingRecordCount) } } - fun findAllByUserIdAndBookIsbnIn(userId: UUID, isbns: List): List { - if (isbns.isEmpty()) { + fun findAllByUserIdAndBookIsbn13In(userId: UUID, isbn13s: List): List { + if (isbn13s.isEmpty()) { return emptyList() } - val userBooks = userBookRepository.findAllByUserIdAndBookIsbnIn(userId, isbns) + val userBooks = userBookRepository.findAllByUserIdAndBookIsbn13In(userId, isbn13s) return userBooks.map { UserBookInfoVO.newInstance(it, it.readingRecordCount) } } - fun findByUserIdAndBookIsbn(userId: UUID, isbn: String): UserBookInfoVO? { - val userBook = userBookRepository.findByUserIdAndBookIsbn(userId, isbn) + fun findByUserIdAndBookIsbn13(userId: UUID, isbn13: String): UserBookInfoVO? { + val userBook = userBookRepository.findByUserIdAndBookIsbn13(userId, isbn13) return userBook?.let { UserBookInfoVO.newInstance(it, it.readingRecordCount) } } @@ -108,4 +108,4 @@ class UserBookDomainService( ) } } -} \ No newline at end of file +} 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 d580b8d7..c60635ee 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt @@ -7,18 +7,13 @@ import java.util.* interface UserBookRepository { - - fun findByUserIdAndBookIsbn(userId: UUID, isbn: String): UserBook? + fun findByUserIdAndBookIsbn13(userId: UUID, isbn13: String): UserBook? fun findByBookIdAndUserId(bookId: UUID, userId: UUID): UserBook? fun existsByIdAndUserId(id: UUID, userId: UUID): Boolean fun findById(id: UUID): UserBook? - fun save(userBook: UserBook): UserBook - fun findAllByUserId(userId: UUID): List - - fun findAllByUserIdAndBookIsbnIn(userId: UUID, bookIsbns: List): List - + fun findAllByUserIdAndBookIsbn13In(userId: UUID, bookIsbn13s: List): List fun findUserBooksByDynamicCondition( userId: UUID, status: BookStatus?, @@ -36,6 +31,4 @@ interface UserBookRepository { limit: Int, excludeIds: Set ): List - - } diff --git a/domain/src/main/kotlin/org/yapp/domain/userbook/vo/HomeBookVO.kt b/domain/src/main/kotlin/org/yapp/domain/userbook/vo/HomeBookVO.kt index 8785a452..84ba184a 100644 --- a/domain/src/main/kotlin/org/yapp/domain/userbook/vo/HomeBookVO.kt +++ b/domain/src/main/kotlin/org/yapp/domain/userbook/vo/HomeBookVO.kt @@ -8,7 +8,7 @@ data class HomeBookVO private constructor( val id: UserBook.Id, val userId: UserBook.UserId, val bookId: UserBook.BookId, - val bookIsbn: UserBook.BookIsbn, + val bookIsbn13: UserBook.BookIsbn13, val coverImageUrl: String, val publisher: String, val title: String, @@ -40,7 +40,7 @@ data class HomeBookVO private constructor( id = userBook.id, userId = userBook.userId, bookId = userBook.bookId, - bookIsbn = userBook.bookIsbn, + bookIsbn13 = userBook.bookIsbn13, coverImageUrl = userBook.coverImageUrl, publisher = userBook.publisher, title = userBook.title, 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 019bb07a..c5e7647b 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 @@ -8,7 +8,7 @@ data class UserBookInfoVO private constructor( val id: UserBook.Id, val userId: UserBook.UserId, val bookId: UserBook.BookId, - val bookIsbn: UserBook.BookIsbn, + val bookIsbn13: UserBook.BookIsbn13, val coverImageUrl: String, val publisher: String, val title: String, @@ -38,7 +38,7 @@ data class UserBookInfoVO private constructor( id = userBook.id, userId = userBook.userId, bookId = userBook.bookId, - bookIsbn = userBook.bookIsbn, + bookIsbn13 = userBook.bookIsbn13, coverImageUrl = userBook.coverImageUrl, publisher = userBook.publisher, title = userBook.title, diff --git a/global-utils/src/main/kotlin/org/yapp/globalutils/validator/IsbnValidator.kt b/global-utils/src/main/kotlin/org/yapp/globalutils/validator/IsbnValidator.kt index ab55042a..3843f218 100644 --- a/global-utils/src/main/kotlin/org/yapp/globalutils/validator/IsbnValidator.kt +++ b/global-utils/src/main/kotlin/org/yapp/globalutils/validator/IsbnValidator.kt @@ -6,7 +6,11 @@ object IsbnValidator { private val ISBN10_REGEX = Regex(RegexUtils.ISBN10_PATTERN) private val ISBN13_REGEX = Regex(RegexUtils.ISBN13_PATTERN) - fun isValidIsbn(isbn: String): Boolean { - return isbn.matches(ISBN10_REGEX) || isbn.matches(ISBN13_REGEX) + fun isValidIsbn10(isbn: String): Boolean { + return isbn.matches(ISBN10_REGEX) + } + + fun isValidIsbn13(isbn: String): Boolean { + return isbn.matches(ISBN13_REGEX) } } diff --git a/infra/src/main/kotlin/org/yapp/infra/book/entity/BookEntity.kt b/infra/src/main/kotlin/org/yapp/infra/book/entity/BookEntity.kt index 382dc14a..a0864f31 100644 --- a/infra/src/main/kotlin/org/yapp/infra/book/entity/BookEntity.kt +++ b/infra/src/main/kotlin/org/yapp/infra/book/entity/BookEntity.kt @@ -10,7 +10,7 @@ import org.hibernate.annotations.SQLRestriction import org.yapp.domain.book.Book import org.yapp.infra.common.BaseTimeEntity import java.sql.Types -import java.util.UUID +import java.util.* @Entity @Table(name = "books") @@ -23,7 +23,7 @@ class BookEntity private constructor( val id: UUID, @Column(length = 13, updatable = false, nullable = false, unique = true) - val isbn: String, + val isbn13: String, title: String, author: String, @@ -57,10 +57,9 @@ class BookEntity private constructor( var description: String? = description protected set - fun toDomain(): Book = Book.reconstruct( id = Book.Id.newInstance(this.id), - isbn = Book.Isbn.newInstance(this.isbn), + isbn13 = Book.Isbn13.newInstance(this.isbn13), title = title, author = author, publisher = publisher, @@ -75,7 +74,7 @@ class BookEntity private constructor( companion object { fun fromDomain(book: Book): BookEntity = BookEntity( id = book.id.value, - isbn = book.isbn.value, + isbn13 = book.isbn13.value, title = book.title, author = book.author, publisher = book.publisher, diff --git a/infra/src/main/kotlin/org/yapp/infra/book/repository/JpaBookRepository.kt b/infra/src/main/kotlin/org/yapp/infra/book/repository/JpaBookRepository.kt index 25006647..b76e4dff 100644 --- a/infra/src/main/kotlin/org/yapp/infra/book/repository/JpaBookRepository.kt +++ b/infra/src/main/kotlin/org/yapp/infra/book/repository/JpaBookRepository.kt @@ -9,6 +9,6 @@ import org.yapp.infra.book.entity.BookEntity import java.util.UUID interface JpaBookRepository : JpaRepository { - fun findByIsbn(isbn: String): BookEntity? - fun existsByIsbn(isbn: String): Boolean + fun findByIsbn13(isbn13: String): BookEntity? + fun existsByIsbn13(isbn13: String): Boolean } diff --git a/infra/src/main/kotlin/org/yapp/infra/book/repository/impl/BookRepositoryImpl.kt b/infra/src/main/kotlin/org/yapp/infra/book/repository/impl/BookRepositoryImpl.kt index a4c69541..8b7ce0da 100644 --- a/infra/src/main/kotlin/org/yapp/infra/book/repository/impl/BookRepositoryImpl.kt +++ b/infra/src/main/kotlin/org/yapp/infra/book/repository/impl/BookRepositoryImpl.kt @@ -20,12 +20,12 @@ class BookRepositoryImpl( return jpaBookRepository.existsById(id) } - override fun findByIsbn(isbn: String): Book? { - return jpaBookRepository.findByIsbn(isbn)?.toDomain() + override fun findByIsbn13(isbn13: String): Book? { + return jpaBookRepository.findByIsbn13(isbn13)?.toDomain() } - override fun existsByIsbn(isbn: String): Boolean { - return jpaBookRepository.existsByIsbn(isbn) + override fun existsByIsbn13(isbn13: String): Boolean { + return jpaBookRepository.existsByIsbn13(isbn13) } override fun save(book: Book): Book { diff --git a/infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookLookupRequest.kt b/infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookLookupRequest.kt index a0575fc2..08196c3b 100644 --- a/infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookLookupRequest.kt +++ b/infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookLookupRequest.kt @@ -17,10 +17,10 @@ data class AladinBookLookupRequest private constructor( companion object { fun from( - itemId: String, + isbn13: String, ): AladinBookLookupRequest { return AladinBookLookupRequest( - itemId = itemId, + itemId = isbn13, itemIdType = "ISBN13", cover = BookCoverSize.BIG.apiValue ) 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 81ab204c..f80e9d58 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 @@ -4,10 +4,9 @@ import jakarta.persistence.* import org.hibernate.annotations.JdbcTypeCode import org.hibernate.annotations.SQLDelete import org.hibernate.annotations.SQLRestriction -import jakarta.persistence.Index -import org.yapp.infra.common.BaseTimeEntity import org.yapp.domain.userbook.BookStatus import org.yapp.domain.userbook.UserBook +import org.yapp.infra.common.BaseTimeEntity import java.sql.Types import java.util.* @@ -35,8 +34,8 @@ class UserBookEntity( @JdbcTypeCode(Types.VARCHAR) val bookId: UUID, - @Column(name = "book_isbn", nullable = false) - val bookIsbn: String, + @Column(name = "book_isbn13", nullable = false, unique = true) + val bookIsbn13: String, coverImageUrl: String, publisher: String, @@ -74,7 +73,7 @@ class UserBookEntity( id = UserBook.Id.newInstance(this.id), userId = UserBook.UserId.newInstance(this.userId), bookId = UserBook.BookId.newInstance(this.bookId), - bookIsbn = UserBook.BookIsbn.newInstance(this.bookIsbn), + bookIsbn13 = UserBook.BookIsbn13.newInstance(this.bookIsbn13), coverImageUrl = this.coverImageUrl, publisher = this.publisher, title = this.title, @@ -92,7 +91,7 @@ class UserBookEntity( id = userBook.id.value, userId = userBook.userId.value, bookId = userBook.bookId.value, - bookIsbn = userBook.bookIsbn.value, + bookIsbn13 = userBook.bookIsbn13.value, coverImageUrl = userBook.coverImageUrl, publisher = userBook.publisher, title = userBook.title, diff --git a/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt b/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt index c63fee6b..6db7d51b 100644 --- a/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt +++ b/infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt @@ -5,9 +5,9 @@ import org.yapp.infra.userbook.entity.UserBookEntity import java.util.* interface JpaUserBookRepository : JpaRepository, JpaUserBookQuerydslRepository { - fun findByUserIdAndBookIsbn(userId: UUID, bookIsbn: String): UserBookEntity? + fun findByUserIdAndBookIsbn13(userId: UUID, bookIsbn13: String): UserBookEntity? fun findByBookIdAndUserId(bookId: UUID, userId: UUID): UserBookEntity? fun existsByIdAndUserId(id: UUID, userId: UUID): Boolean fun findAllByUserId(userId: UUID): List - fun findAllByUserIdAndBookIsbnIn(userId: UUID, bookIsbnList: List): List + fun findAllByUserIdAndBookIsbn13In(userId: UUID, bookIsbn13List: List): List } 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 41302f8e..08bbffb4 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 @@ -17,8 +17,8 @@ class UserBookRepositoryImpl( private val jpaUserBookRepository: JpaUserBookRepository ) : UserBookRepository { - override fun findByUserIdAndBookIsbn(userId: UUID, isbn: String): UserBook? { - return jpaUserBookRepository.findByUserIdAndBookIsbn(userId, isbn)?.toDomain() + override fun findByUserIdAndBookIsbn13(userId: UUID, isbn13: String): UserBook? { + return jpaUserBookRepository.findByUserIdAndBookIsbn13(userId, isbn13)?.toDomain() } override fun findByBookIdAndUserId(bookId: UUID, userId: UUID): UserBook? { @@ -42,11 +42,11 @@ class UserBookRepositoryImpl( return jpaUserBookRepository.findAllByUserId(userId).map { it.toDomain() } } - override fun findAllByUserIdAndBookIsbnIn( + override fun findAllByUserIdAndBookIsbn13In( userId: UUID, bookIsbns: List ): List { - return jpaUserBookRepository.findAllByUserIdAndBookIsbnIn(userId, bookIsbns) + return jpaUserBookRepository.findAllByUserIdAndBookIsbn13In(userId, bookIsbns) .map { it.toDomain() } }