From fb2834e1f6914016d0703c12d20793adee7ea0bb Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 5 Jul 2024 23:17:31 +0900 Subject: [PATCH] =?UTF-8?q?7/5=20=EB=B0=B0=ED=8F=AC=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../itracker/crawl/common/ProductCategory.kt | 21 +++++++----- .../crawl/common/ProductFilterCategory.kt | 21 ++++++++++++ .../ProductFilterCategoryConverter.kt | 14 ++++++++ .../itracker/tracker/config/WebMvcConfig.kt | 4 +-- .../controller/CoupangPartnersController.kt | 8 ++--- .../oauth/kakao/dto/KakaoMemberResponse.kt | 1 + .../member/controller/FavoriteController.kt | 8 ++--- .../request/FavoritePatchRequest.kt | 4 +-- .../tracker/member/domain/FavoriteProduct.kt | 7 ++++ .../itracker/tracker/member/domain/Member.kt | 14 +++----- .../domain/repository/FavoriteRepository.kt | 9 +++++ .../tracker/member/service/FavoriteService.kt | 14 ++++++++ .../tracker/member/service/MemberService.kt | 5 +++ .../handler/airpods/FavoriteAirpodsHandler.kt | 9 +++-- .../handler/macbook/FavoriteMacbookHandler.kt | 12 +++++-- .../response/AirpodsFavoriteResponse.kt | 6 ++-- .../response/CommonFavoriteProductModel.kt | 2 ++ .../response/MacbookFavoriteResponse.kt | 6 ++-- .../member/service/vo/FavoriteCount.kt | 9 +++++ .../tracker/member/service/vo/FavoriteInfo.kt | 4 +-- .../oauth/controller/OauthController.kt | 17 +++++++--- .../response/PhoneNumberValidateResponse.kt | 5 +++ .../oauth/repository/MemberRepository.kt | 11 ++++++ .../tracker/oauth/service/OauthService.kt | 4 +++ .../product/controller/HomeController.kt | 8 ++--- .../product/controller/ProductController.kt | 26 ++++++-------- .../tracker/product/handler/AirPodsHandler.kt | 30 +++++++++++----- .../tracker/product/handler/MacbookHandler.kt | 34 +++++++++++++------ .../product/handler/ProductHandleable.kt | 14 ++++---- .../product/handler/ProductHandlerImpl.kt | 22 ++++++------ .../product/response/CategoryResponses.kt | 4 +-- .../response/product/CommonProductModel.kt | 1 + .../product/airpods/AirPodsDetailResponse.kt | 6 +++- .../product/airpods/AirPodsResponse.kt | 8 +++-- .../product/macbook/MacbookDetailResponse.kt | 11 ++++-- .../product/macbook/MacbookResponse.kt | 8 +++-- .../tracker/product/vo/ProductInfo.kt | 14 +++++++- src/main/resources/application.yml | 1 + .../assured/ProductControllerAssuredTest.kt | 4 +-- .../assured/ProductMacbookAssuredTest.kt | 14 ++++---- 40 files changed, 296 insertions(+), 124 deletions(-) create mode 100644 src/main/kotlin/backend/itracker/crawl/common/ProductFilterCategory.kt create mode 100644 src/main/kotlin/backend/itracker/tracker/common/converter/ProductFilterCategoryConverter.kt create mode 100644 src/main/kotlin/backend/itracker/tracker/member/service/vo/FavoriteCount.kt create mode 100644 src/main/kotlin/backend/itracker/tracker/oauth/controller/response/PhoneNumberValidateResponse.kt diff --git a/src/main/kotlin/backend/itracker/crawl/common/ProductCategory.kt b/src/main/kotlin/backend/itracker/crawl/common/ProductCategory.kt index 9a9716d..e7c498e 100644 --- a/src/main/kotlin/backend/itracker/crawl/common/ProductCategory.kt +++ b/src/main/kotlin/backend/itracker/crawl/common/ProductCategory.kt @@ -1,21 +1,24 @@ package backend.itracker.crawl.common -import backend.itracker.crawl.macbook.domain.MacbookCategory - enum class ProductCategory { - MACBOOK_AIR, - MACBOOK_PRO, + MACBOOK, MAC, I_PHONE, I_PAD, AIRPODS, APPLE_WATCH; - fun toMacbookCategory(): MacbookCategory { - return when (this) { - MACBOOK_AIR -> MacbookCategory.MACBOOK_AIR - MACBOOK_PRO -> MacbookCategory.MACBOOK_PRO - else -> throw IllegalArgumentException("MacbookCategory로 변환할 수 없는 ProductCategory 입니다. category: $this") + companion object { + fun from(productFilterCategory: ProductFilterCategory): ProductCategory { + return when (productFilterCategory) { + ProductFilterCategory.MACBOOK_AIR, + ProductFilterCategory.MACBOOK_PRO -> MACBOOK + ProductFilterCategory.MAC -> MAC + ProductFilterCategory.I_PHONE -> I_PHONE + ProductFilterCategory.I_PAD -> I_PAD + ProductFilterCategory.AIRPODS -> AIRPODS + ProductFilterCategory.APPLE_WATCH -> APPLE_WATCH + } } } } diff --git a/src/main/kotlin/backend/itracker/crawl/common/ProductFilterCategory.kt b/src/main/kotlin/backend/itracker/crawl/common/ProductFilterCategory.kt new file mode 100644 index 0000000..7704762 --- /dev/null +++ b/src/main/kotlin/backend/itracker/crawl/common/ProductFilterCategory.kt @@ -0,0 +1,21 @@ +package backend.itracker.crawl.common + +import backend.itracker.crawl.macbook.domain.MacbookCategory + +enum class ProductFilterCategory { + MACBOOK_AIR, + MACBOOK_PRO, + MAC, + I_PHONE, + I_PAD, + AIRPODS, + APPLE_WATCH; + + fun toMacbookCategory(): MacbookCategory { + return when (this) { + MACBOOK_AIR -> MacbookCategory.MACBOOK_AIR + MACBOOK_PRO -> MacbookCategory.MACBOOK_PRO + else -> throw IllegalArgumentException("MacbookCategory로 변환할 수 없는 ProductCategory 입니다. category: $this") + } + } +} diff --git a/src/main/kotlin/backend/itracker/tracker/common/converter/ProductFilterCategoryConverter.kt b/src/main/kotlin/backend/itracker/tracker/common/converter/ProductFilterCategoryConverter.kt new file mode 100644 index 0000000..88f92f4 --- /dev/null +++ b/src/main/kotlin/backend/itracker/tracker/common/converter/ProductFilterCategoryConverter.kt @@ -0,0 +1,14 @@ +package backend.itracker.tracker.common.converter + +import backend.itracker.crawl.common.ProductFilterCategory +import org.springframework.core.convert.converter.Converter + +private val productFilterCategories = ProductFilterCategory.entries + +class ProductFilterCategoryConverter : Converter { + + override fun convert(source: String): ProductFilterCategory { + return productFilterCategories.firstOrNull { it.name.lowercase() == source } + ?: throw IllegalArgumentException("지원하지 않는 카테고리 입니다. category: $source") + } +} diff --git a/src/main/kotlin/backend/itracker/tracker/config/WebMvcConfig.kt b/src/main/kotlin/backend/itracker/tracker/config/WebMvcConfig.kt index 7589531..aa85205 100644 --- a/src/main/kotlin/backend/itracker/tracker/config/WebMvcConfig.kt +++ b/src/main/kotlin/backend/itracker/tracker/config/WebMvcConfig.kt @@ -1,7 +1,7 @@ package backend.itracker.tracker.config import backend.itracker.tracker.common.converter.OauthServerTypeConverter -import backend.itracker.tracker.common.converter.ProductCategoryConverter +import backend.itracker.tracker.common.converter.ProductFilterCategoryConverter import org.springframework.context.annotation.Configuration import org.springframework.format.FormatterRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @@ -10,7 +10,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer class WebMvcConfig : WebMvcConfigurer { override fun addFormatters(registry: FormatterRegistry) { - registry.addConverter(ProductCategoryConverter()) + registry.addConverter(ProductFilterCategoryConverter()) registry.addConverter(OauthServerTypeConverter()) } } diff --git a/src/main/kotlin/backend/itracker/tracker/coupang/controller/CoupangPartnersController.kt b/src/main/kotlin/backend/itracker/tracker/coupang/controller/CoupangPartnersController.kt index a202403..a92911f 100644 --- a/src/main/kotlin/backend/itracker/tracker/coupang/controller/CoupangPartnersController.kt +++ b/src/main/kotlin/backend/itracker/tracker/coupang/controller/CoupangPartnersController.kt @@ -2,7 +2,7 @@ package backend.itracker.tracker.coupang.controller import backend.itracker.crawl.airpods.service.AirPodsService import backend.itracker.crawl.common.PartnersLinkInfo -import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import backend.itracker.crawl.macbook.service.MacbookService import backend.itracker.tracker.coupang.service.CoupangPartnersService import org.springframework.http.ResponseEntity @@ -20,18 +20,18 @@ class CoupangPartnersController( @PatchMapping("/api/v1/coupang/deeplink/{category}") fun updatePartnersLink( - @PathVariable category: ProductCategory, + @PathVariable category: ProductFilterCategory, @RequestParam start: Long, @RequestParam end: Long, ): ResponseEntity { return when (category) { - ProductCategory.MACBOOK_AIR, ProductCategory.MACBOOK_PRO -> { + ProductFilterCategory.MACBOOK_AIR, ProductFilterCategory.MACBOOK_PRO -> { val partnersLink = coupangPartnersService.updateAllMacbookPartnersLink(start, end) macbookService.updateAllPartnersLink(partnersLink.map { PartnersLinkInfo(it.originalUrl, it.shortenUrl)}) ResponseEntity.ok().build() } - ProductCategory.AIRPODS -> { + ProductFilterCategory.AIRPODS -> { val partnersLink = coupangPartnersService.updateAllAirPodsPartnersLink(start, end) airPodsService.updateAllPartnersLink(partnersLink.map { PartnersLinkInfo(it.originalUrl, it.shortenUrl)}) ResponseEntity.ok().build() diff --git a/src/main/kotlin/backend/itracker/tracker/infra/oauth/kakao/dto/KakaoMemberResponse.kt b/src/main/kotlin/backend/itracker/tracker/infra/oauth/kakao/dto/KakaoMemberResponse.kt index 1d38171..1d971c6 100644 --- a/src/main/kotlin/backend/itracker/tracker/infra/oauth/kakao/dto/KakaoMemberResponse.kt +++ b/src/main/kotlin/backend/itracker/tracker/infra/oauth/kakao/dto/KakaoMemberResponse.kt @@ -19,6 +19,7 @@ data class KakaoMemberResponse( return Member( oauthId = OauthId(id.toString(), OauthServerType.KAKAO), nickname = kakaoAccount.profile.nickname, + phoneNumber = kakaoAccount.phoneNumber ?: "01000000000", profileImage = kakaoAccount.profile.profileImageUrl, ) } diff --git a/src/main/kotlin/backend/itracker/tracker/member/controller/FavoriteController.kt b/src/main/kotlin/backend/itracker/tracker/member/controller/FavoriteController.kt index 76c88ab..5380948 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/controller/FavoriteController.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/controller/FavoriteController.kt @@ -11,11 +11,7 @@ import backend.itracker.tracker.member.service.handler.response.CommonFavoritePr import backend.itracker.tracker.resolver.LoginMember import org.springframework.data.domain.PageRequest import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.ModelAttribute -import org.springframework.web.bind.annotation.PatchMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* private const val FAVORITE_DEFAULT_SIZE = 6 @@ -32,7 +28,7 @@ class FavoriteController( val favoriteInfo = request.toFavoriteInfo() val favorite = Favorite( member = member, - product = FavoriteProduct(productId = favoriteInfo.productId, productCategory = favoriteInfo.category) + product = FavoriteProduct.of(favoriteInfo.productId, favoriteInfo.category) ) favoriteService.patchFavorite(favorite) diff --git a/src/main/kotlin/backend/itracker/tracker/member/controller/request/FavoritePatchRequest.kt b/src/main/kotlin/backend/itracker/tracker/member/controller/request/FavoritePatchRequest.kt index 00281c9..2a05f72 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/controller/request/FavoritePatchRequest.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/controller/request/FavoritePatchRequest.kt @@ -1,6 +1,6 @@ package backend.itracker.tracker.member.controller.request -import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import backend.itracker.tracker.member.service.vo.FavoriteInfo data class FavoritePatchRequest( @@ -12,7 +12,7 @@ data class FavoritePatchRequest( { return FavoriteInfo( productId = productId, - category = ProductCategory.valueOf(productCategory.uppercase()) + category = ProductFilterCategory.valueOf(productCategory.uppercase()) ) } } diff --git a/src/main/kotlin/backend/itracker/tracker/member/domain/FavoriteProduct.kt b/src/main/kotlin/backend/itracker/tracker/member/domain/FavoriteProduct.kt index 1516595..e8d3111 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/domain/FavoriteProduct.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/domain/FavoriteProduct.kt @@ -1,6 +1,7 @@ package backend.itracker.tracker.member.domain import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import jakarta.persistence.Embeddable import jakarta.persistence.EnumType import jakarta.persistence.Enumerated @@ -14,6 +15,12 @@ class FavoriteProduct( val productCategory: ProductCategory, ) { + companion object { + fun of(productId: Long, productFilterCategory: ProductFilterCategory): FavoriteProduct { + return FavoriteProduct(productId, ProductCategory.from(productFilterCategory)) + } + } + override fun toString(): String { return "FavoriteProduct(productId=$productId, productCategory=$productCategory)" } diff --git a/src/main/kotlin/backend/itracker/tracker/member/domain/Member.kt b/src/main/kotlin/backend/itracker/tracker/member/domain/Member.kt index 36a6dee..5bfdbbd 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/domain/Member.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/domain/Member.kt @@ -3,15 +3,7 @@ package backend.itracker.tracker.member.domain import backend.itracker.crawl.common.BaseEntity import backend.itracker.tracker.oauth.OauthId import backend.itracker.tracker.oauth.OauthServerType -import jakarta.persistence.Column -import jakarta.persistence.Embedded -import jakarta.persistence.Entity -import jakarta.persistence.EnumType -import jakarta.persistence.Enumerated -import jakarta.persistence.FetchType -import jakarta.persistence.OneToMany -import jakarta.persistence.Table -import jakarta.persistence.UniqueConstraint +import jakarta.persistence.* @Entity @Table( @@ -26,6 +18,7 @@ class Member( val oauthId: OauthId, var nickname: String, + val phoneNumber: String, @Column(columnDefinition = "text") var profileImage: String, @@ -47,13 +40,14 @@ class Member( fun isAnonymous() = this.id == 0L override fun toString(): String { - return "Member(id='$id' oauthId=$oauthId, nickname='$nickname', profileImage='$profileImage', authType=$authType)" + return "Member(oauthId=$oauthId, nickname='$nickname', phoneNumber='$phoneNumber', profileImage='$profileImage', authType=$authType, favorites=$favorites)" } companion object { fun anonymous() = Member( oauthId = OauthId("anonymous", OauthServerType.KAKAO), nickname = "익명", + phoneNumber = "01012345678", profileImage = "익명", ) } diff --git a/src/main/kotlin/backend/itracker/tracker/member/domain/repository/FavoriteRepository.kt b/src/main/kotlin/backend/itracker/tracker/member/domain/repository/FavoriteRepository.kt index 2a7d025..7d44228 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/domain/repository/FavoriteRepository.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/domain/repository/FavoriteRepository.kt @@ -26,4 +26,13 @@ interface FavoriteRepository : JpaRepository { ): Optional fun findAllByMember(member: Member, pageable: Pageable): Page + + @Query( + """ + select count(f) + from Favorite f + where f.product = :product + """ + ) + fun findCountByProduct(@Param("product") favoriteProduct: FavoriteProduct): Long } diff --git a/src/main/kotlin/backend/itracker/tracker/member/service/FavoriteService.kt b/src/main/kotlin/backend/itracker/tracker/member/service/FavoriteService.kt index da8b03c..9c89906 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/service/FavoriteService.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/service/FavoriteService.kt @@ -1,10 +1,12 @@ package backend.itracker.tracker.member.service import backend.itracker.tracker.member.domain.Favorite +import backend.itracker.tracker.member.domain.FavoriteProduct import backend.itracker.tracker.member.domain.Member import backend.itracker.tracker.member.domain.repository.FavoriteRepository import backend.itracker.tracker.member.service.handler.FavoriteComposite import backend.itracker.tracker.member.service.handler.response.CommonFavoriteProductModel +import backend.itracker.tracker.member.service.vo.FavoriteCount import org.springframework.data.domain.Page import org.springframework.data.domain.PageImpl import org.springframework.data.domain.Pageable @@ -43,4 +45,16 @@ class FavoriteService( return PageImpl(productsContents, pageFavorites.pageable, productsContents.size.toLong()) } + + @Transactional(readOnly = true) + fun findFavoritesCountByFavoriteProduct( + favoriteProduct: FavoriteProduct + ): FavoriteCount { + val count = favoriteRepository.findCountByProduct(favoriteProduct) + return FavoriteCount( + productId = favoriteProduct.productId, + category = favoriteProduct.productCategory, + count = count + ) + } } diff --git a/src/main/kotlin/backend/itracker/tracker/member/service/MemberService.kt b/src/main/kotlin/backend/itracker/tracker/member/service/MemberService.kt index be6ca76..1ddf44e 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/service/MemberService.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/service/MemberService.kt @@ -32,4 +32,9 @@ class MemberService( fun getByOauthId(oauthId: OauthId): Member { return memberRepository.getByOauthId(oauthId) } + + @Transactional(readOnly = true) + fun isDuplicatedPhoneNumber(phoneNumber: String): Boolean { + return memberRepository.isDuplicatedPhoneNumber(phoneNumber) + } } diff --git a/src/main/kotlin/backend/itracker/tracker/member/service/handler/airpods/FavoriteAirpodsHandler.kt b/src/main/kotlin/backend/itracker/tracker/member/service/handler/airpods/FavoriteAirpodsHandler.kt index a0f50ea..73e9057 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/service/handler/airpods/FavoriteAirpodsHandler.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/service/handler/airpods/FavoriteAirpodsHandler.kt @@ -3,6 +3,7 @@ package backend.itracker.tracker.member.service.handler.airpods import backend.itracker.crawl.airpods.service.AirPodsService import backend.itracker.crawl.common.ProductCategory import backend.itracker.tracker.member.domain.Favorite +import backend.itracker.tracker.member.domain.repository.FavoriteRepository import backend.itracker.tracker.member.service.handler.FavoriteHandleable import backend.itracker.tracker.member.service.handler.response.AirpodsFavoriteResponse import backend.itracker.tracker.member.service.handler.response.CommonFavoriteProductModel @@ -10,8 +11,10 @@ import org.springframework.stereotype.Component @Component class FavoriteAirpodsHandler( - private val airpodsService: AirPodsService + private val airpodsService: AirPodsService, + private val favoriteRepository: FavoriteRepository, ) : FavoriteHandleable { + override fun supports(category: ProductCategory): Boolean { return ProductCategory.AIRPODS == category } @@ -24,7 +27,9 @@ class FavoriteAirpodsHandler( val favorite = favorites.find { it.product.productId == airpod.id } ?: throw IllegalStateException("찜한 상품을 찾을 수 없습니다.") - AirpodsFavoriteResponse.of(airpod, favorite) + val notificationCount = + favoriteRepository.findCountByProduct(favorite.product) + AirpodsFavoriteResponse.of(airpod, notificationCount, favorite) } } } diff --git a/src/main/kotlin/backend/itracker/tracker/member/service/handler/macbook/FavoriteMacbookHandler.kt b/src/main/kotlin/backend/itracker/tracker/member/service/handler/macbook/FavoriteMacbookHandler.kt index 8e5b76d..71ea575 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/service/handler/macbook/FavoriteMacbookHandler.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/service/handler/macbook/FavoriteMacbookHandler.kt @@ -3,6 +3,7 @@ package backend.itracker.tracker.member.service.handler.macbook import backend.itracker.crawl.common.ProductCategory import backend.itracker.crawl.macbook.service.MacbookService import backend.itracker.tracker.member.domain.Favorite +import backend.itracker.tracker.member.domain.repository.FavoriteRepository import backend.itracker.tracker.member.service.handler.FavoriteHandleable import backend.itracker.tracker.member.service.handler.response.CommonFavoriteProductModel import backend.itracker.tracker.member.service.handler.response.MacbookFavoriteResponse @@ -10,10 +11,12 @@ import org.springframework.stereotype.Component @Component class FavoriteMacbookHandler( - private val macbookService: MacbookService + private val macbookService: MacbookService, + private val favoriteRepository: FavoriteRepository, ) : FavoriteHandleable { + override fun supports(category: ProductCategory): Boolean { - return category == ProductCategory.MACBOOK_PRO || category == ProductCategory.MACBOOK_AIR + return category == ProductCategory.MACBOOK } override fun findAllByIds(favorites: List): List { @@ -24,7 +27,10 @@ class FavoriteMacbookHandler( val favorite = favorites.find { it.product.productId == macbook.id } ?: throw IllegalStateException("찜한 상품을 찾을 수 없습니다.") - MacbookFavoriteResponse.of(macbook, favorite) + val notificationCount = + favoriteRepository.findCountByProduct(favorite.product) + + MacbookFavoriteResponse.of(macbook, notificationCount, favorite) } } } diff --git a/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/AirpodsFavoriteResponse.kt b/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/AirpodsFavoriteResponse.kt index a720c6c..cc1ecb6 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/AirpodsFavoriteResponse.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/AirpodsFavoriteResponse.kt @@ -20,11 +20,12 @@ class AirpodsFavoriteResponse( val currentPrice: BigDecimal, val isOutOfStock: Boolean, + notificationCount: Long, createdAt: LocalDateTime -): CommonFavoriteProductModel(createdAt){ +): CommonFavoriteProductModel(notificationCount, createdAt){ companion object { - fun of(airPods: AirPods, favorite: Favorite): AirpodsFavoriteResponse { + fun of(airPods: AirPods, notificationCount: Long, favorite: Favorite): AirpodsFavoriteResponse { val name = when (airPods.category) { AirPodsCategory.AIRPODS -> "에어팟" AirPodsCategory.AIRPODS_PRO -> "에어팟 프로" @@ -43,6 +44,7 @@ class AirpodsFavoriteResponse( discountPercentage = airPods.findDiscountPercentage(), currentPrice = airPods.findCurrentPrice(), isOutOfStock = airPods.isOutOfStock(), + notificationCount = notificationCount, createdAt = favorite.createdAt ) } diff --git a/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/CommonFavoriteProductModel.kt b/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/CommonFavoriteProductModel.kt index 25560ab..a47822b 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/CommonFavoriteProductModel.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/CommonFavoriteProductModel.kt @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore import java.time.LocalDateTime abstract class CommonFavoriteProductModel( + val notificationCount: Long, + @JsonIgnore val createdAt: LocalDateTime ) diff --git a/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/MacbookFavoriteResponse.kt b/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/MacbookFavoriteResponse.kt index 08f593a..7fe79e1 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/MacbookFavoriteResponse.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/service/handler/response/MacbookFavoriteResponse.kt @@ -23,11 +23,12 @@ class MacbookFavoriteResponse( val imageUrl: String, val isOutOfStock: Boolean, + notificationCount: Long, createdAt: LocalDateTime -) : CommonFavoriteProductModel(createdAt) { +) : CommonFavoriteProductModel(notificationCount, createdAt) { companion object { - fun of(macbook: Macbook, favorite: Favorite): MacbookFavoriteResponse { + fun of(macbook: Macbook, notificationCount: Long, favorite: Favorite): MacbookFavoriteResponse { val koreanCategory = when (macbook.category) { MacbookCategory.MACBOOK_AIR -> "맥북 에어" MacbookCategory.MACBOOK_PRO -> "맥북 프로" @@ -49,6 +50,7 @@ class MacbookFavoriteResponse( label = macbook.isAllTimeLowPrice(), imageUrl = macbook.thumbnail, isOutOfStock = macbook.isOutOfStock(), + notificationCount = notificationCount, createdAt = favorite.createdAt ) } diff --git a/src/main/kotlin/backend/itracker/tracker/member/service/vo/FavoriteCount.kt b/src/main/kotlin/backend/itracker/tracker/member/service/vo/FavoriteCount.kt new file mode 100644 index 0000000..acc07e7 --- /dev/null +++ b/src/main/kotlin/backend/itracker/tracker/member/service/vo/FavoriteCount.kt @@ -0,0 +1,9 @@ +package backend.itracker.tracker.member.service.vo + +import backend.itracker.crawl.common.ProductCategory + +data class FavoriteCount( + val productId: Long, + val category: ProductCategory, + val count: Long +) diff --git a/src/main/kotlin/backend/itracker/tracker/member/service/vo/FavoriteInfo.kt b/src/main/kotlin/backend/itracker/tracker/member/service/vo/FavoriteInfo.kt index ad201bc..ebe80e3 100644 --- a/src/main/kotlin/backend/itracker/tracker/member/service/vo/FavoriteInfo.kt +++ b/src/main/kotlin/backend/itracker/tracker/member/service/vo/FavoriteInfo.kt @@ -1,8 +1,8 @@ package backend.itracker.tracker.member.service.vo -import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory data class FavoriteInfo( val productId: Long, - val category: ProductCategory + val category: ProductFilterCategory ) diff --git a/src/main/kotlin/backend/itracker/tracker/oauth/controller/OauthController.kt b/src/main/kotlin/backend/itracker/tracker/oauth/controller/OauthController.kt index ead0c84..4ec88d4 100644 --- a/src/main/kotlin/backend/itracker/tracker/oauth/controller/OauthController.kt +++ b/src/main/kotlin/backend/itracker/tracker/oauth/controller/OauthController.kt @@ -1,7 +1,9 @@ package backend.itracker.tracker.oauth.controller +import backend.itracker.tracker.common.response.SingleData import backend.itracker.tracker.oauth.OauthServerType import backend.itracker.tracker.oauth.RedirectType +import backend.itracker.tracker.oauth.controller.response.PhoneNumberValidateResponse import backend.itracker.tracker.oauth.service.OauthService import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -10,17 +12,15 @@ import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController -@RequestMapping("/api/v1/oauth") class OauthController( private val oauthService: OauthService ) { - @GetMapping("/{oauthServerType}") + @GetMapping("/api/v1/oauth/{oauthServerType}") fun redirectAuthCodeRequestUrl( @PathVariable oauthServerType: OauthServerType, request: HttpServletRequest, @@ -39,7 +39,7 @@ class OauthController( return ResponseEntity.status(HttpStatus.FOUND).build() } - @GetMapping("/login/{oauthServerType}") + @GetMapping("/api/v1/oauth/login/{oauthServerType}") fun login( @PathVariable oauthServerType: OauthServerType, @RequestParam("code") code: String, @@ -58,4 +58,13 @@ class OauthController( .header(HttpHeaders.AUTHORIZATION, accessToken) .build() } + + @GetMapping("/api/v1/login/validate") + fun validatePhoneNumber( + @RequestParam("phoneNumber") phoneNumber: String, + ): ResponseEntity> { + val isDuplicatedPhoneNumber = oauthService.validatePhoneNumber(phoneNumber) + + return ResponseEntity.ok(SingleData(PhoneNumberValidateResponse(isDuplicatedPhoneNumber))) + } } diff --git a/src/main/kotlin/backend/itracker/tracker/oauth/controller/response/PhoneNumberValidateResponse.kt b/src/main/kotlin/backend/itracker/tracker/oauth/controller/response/PhoneNumberValidateResponse.kt new file mode 100644 index 0000000..8fc129f --- /dev/null +++ b/src/main/kotlin/backend/itracker/tracker/oauth/controller/response/PhoneNumberValidateResponse.kt @@ -0,0 +1,5 @@ +package backend.itracker.tracker.oauth.controller.response + +data class PhoneNumberValidateResponse( + val isDuplicatedPhoneNumber: Boolean +) diff --git a/src/main/kotlin/backend/itracker/tracker/oauth/repository/MemberRepository.kt b/src/main/kotlin/backend/itracker/tracker/oauth/repository/MemberRepository.kt index 76eb2ed..6d76619 100644 --- a/src/main/kotlin/backend/itracker/tracker/oauth/repository/MemberRepository.kt +++ b/src/main/kotlin/backend/itracker/tracker/oauth/repository/MemberRepository.kt @@ -3,6 +3,8 @@ package backend.itracker.tracker.oauth.repository import backend.itracker.tracker.member.domain.Member import backend.itracker.tracker.oauth.OauthId import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param import java.util.* import kotlin.jvm.optionals.getOrNull @@ -12,4 +14,13 @@ fun MemberRepository.getByOauthId(oauthId: OauthId) = findByOauthId(oauthId).get interface MemberRepository : JpaRepository { fun findByOauthId(oauthId: OauthId): Optional + + @Query( + """ + select count(m) > 0 + from Member m + where m.phoneNumber = :phoneNumber + """ + ) + fun isDuplicatedPhoneNumber(@Param("phoneNumber") phoneNumber: String): Boolean } diff --git a/src/main/kotlin/backend/itracker/tracker/oauth/service/OauthService.kt b/src/main/kotlin/backend/itracker/tracker/oauth/service/OauthService.kt index 5fec254..e9f1adc 100644 --- a/src/main/kotlin/backend/itracker/tracker/oauth/service/OauthService.kt +++ b/src/main/kotlin/backend/itracker/tracker/oauth/service/OauthService.kt @@ -53,4 +53,8 @@ class OauthService( val oauthId = jwtDecoder.parseOauthId(authorizationHeader) return memberService.getByOauthId(oauthId) } + + fun validatePhoneNumber(phoneNumber: String): Boolean { + return memberService.isDuplicatedPhoneNumber(phoneNumber) + } } diff --git a/src/main/kotlin/backend/itracker/tracker/product/controller/HomeController.kt b/src/main/kotlin/backend/itracker/tracker/product/controller/HomeController.kt index 2ce8270..7b2d887 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/controller/HomeController.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/controller/HomeController.kt @@ -1,6 +1,6 @@ package backend.itracker.tracker.product.controller -import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import backend.itracker.tracker.common.response.Pages import backend.itracker.tracker.product.handler.ProductHandlerImpl import backend.itracker.tracker.product.response.product.CommonProductModel @@ -20,11 +20,11 @@ class HomeController( @RequestParam(defaultValue = "10") limit: Int ): ResponseEntity> { val macbookAirs = - productHandler.findTopDiscountPercentageProducts(ProductCategory.MACBOOK_AIR, Limit(limit)) + productHandler.findTopDiscountPercentageProducts(ProductFilterCategory.MACBOOK_AIR, Limit(limit)) val macbookPros = - productHandler.findTopDiscountPercentageProducts(ProductCategory.MACBOOK_PRO, Limit(limit)) + productHandler.findTopDiscountPercentageProducts(ProductFilterCategory.MACBOOK_PRO, Limit(limit)) val airPods = - productHandler.findTopDiscountPercentageProducts(ProductCategory.AIRPODS, Limit(limit)) + productHandler.findTopDiscountPercentageProducts(ProductFilterCategory.AIRPODS, Limit(limit)) val allProducts = macbookAirs + macbookPros + airPods return ResponseEntity.ok(Pages.withPagination(allProducts.sortedBy { it.discountPercentage() } diff --git a/src/main/kotlin/backend/itracker/tracker/product/controller/ProductController.kt b/src/main/kotlin/backend/itracker/tracker/product/controller/ProductController.kt index 23094b4..f79a2b4 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/controller/ProductController.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/controller/ProductController.kt @@ -1,6 +1,6 @@ package backend.itracker.tracker.product.controller -import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import backend.itracker.tracker.common.request.PageParams import backend.itracker.tracker.common.response.Pages import backend.itracker.tracker.common.response.SingleData @@ -16,13 +16,9 @@ import backend.itracker.tracker.product.vo.ProductInfo import backend.itracker.tracker.resolver.AnonymousMember import org.springframework.data.domain.PageRequest import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.ModelAttribute -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* -private val productCategories = ProductCategory.entries +private val productCategories = ProductFilterCategory.entries @RestController class ProductController( @@ -31,7 +27,7 @@ class ProductController( @GetMapping("/api/v1/products/{category}") fun findTopDiscountPercentageProductsByCategory( - @PathVariable category: ProductCategory, + @PathVariable category: ProductFilterCategory, @RequestParam(defaultValue = "5") limit: Int, ): ResponseEntity> { val products = productHandler.findTopDiscountPercentageProducts(category, Limit(limit)) @@ -45,16 +41,16 @@ class ProductController( @GetMapping("/api/v1/products/{category}/filter") fun findProductFilter( - @PathVariable category: ProductCategory, - @RequestParam filterConditon: Map + @PathVariable category: ProductFilterCategory, + @RequestParam filterCondition: Map ): ResponseEntity> { - val filter = productHandler.findFilter(category, ProductFilter(filterConditon)) + val filter = productHandler.findFilter(category, ProductFilter(filterCondition)) return ResponseEntity.ok(SingleData(filter)) } @GetMapping("/api/v1/products/{category}/search") fun findFilterdProducts( - @PathVariable category: ProductCategory, + @PathVariable category: ProductFilterCategory, @RequestParam filterCondition: Map, @ModelAttribute pageParams: PageParams, ): ResponseEntity> { @@ -69,12 +65,12 @@ class ProductController( } @GetMapping("/api/v1/products/{category}/{productId}") - fun findFilterdProductDetail( + fun findFilteredProductDetail( @AnonymousMember member: Member, - @PathVariable category: ProductCategory, + @PathVariable category: ProductFilterCategory, @PathVariable productId: Long, ): ResponseEntity { - val product = productHandler.findProductById(ProductInfo(category, productId), member) + val product = productHandler.findProductById(ProductInfo.of(category, productId), member) return ResponseEntity.ok(product) } diff --git a/src/main/kotlin/backend/itracker/tracker/product/handler/AirPodsHandler.kt b/src/main/kotlin/backend/itracker/tracker/product/handler/AirPodsHandler.kt index d1a2333..28eb4ba 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/handler/AirPodsHandler.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/handler/AirPodsHandler.kt @@ -2,6 +2,7 @@ package backend.itracker.tracker.product.handler import backend.itracker.crawl.airpods.service.AirPodsService import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import backend.itracker.tracker.member.domain.FavoriteProduct import backend.itracker.tracker.member.domain.Member import backend.itracker.tracker.member.domain.repository.FavoriteRepository @@ -24,23 +25,28 @@ class AirPodsHandler( private val airPodsService: AirPodsService, private val favoriteRepository: FavoriteRepository, ) : ProductHandleable { - override fun supports(productCategory: ProductCategory): Boolean { - return ProductCategory.AIRPODS == productCategory + override fun supports(productFilterCategory: ProductFilterCategory): Boolean { + return ProductFilterCategory.AIRPODS == productFilterCategory } override fun findTopDiscountPercentageProducts( - productCategory: ProductCategory, + productFilterCategory: ProductFilterCategory, limit: Int ): List { val airpods = airPodsService.findAllFetch() - val contents = airpods.map { AirPodsResponse.from(it) } + val contents = airpods.map { + AirPodsResponse.of( + it, + favoriteRepository.findCountByProduct(FavoriteProduct(it.id, ProductCategory.AIRPODS)) + ) + } .sortedBy { it.discountPercentage } .take(limit) return contents } - override fun findFilter(productCategory: ProductCategory, filterCondition: ProductFilter): CommonFilterModel { + override fun findFilter(productFilterCategory: ProductFilterCategory, filterCondition: ProductFilter): CommonFilterModel { throw NotSupportedException("AirPodsHandler는 필터링를 지원하지 않습니다.") } @@ -50,12 +56,17 @@ class AirPodsHandler( * @return 현재 전체 에어팟을 반환 중 */ override fun findFilteredProductsOrderByDiscountRate( - category: ProductCategory, + category: ProductFilterCategory, filter: ProductFilter, pageable: Pageable ): Page { val airpods = airPodsService.findAllFetch() - val contents = airpods.map { AirPodsResponse.from(it) } + val contents = airpods.map { + AirPodsResponse.of( + it, + favoriteRepository.findCountByProduct(FavoriteProduct(it.id, ProductCategory.AIRPODS)) + ) + } .sortedBy { it.discountPercentage } return PageImpl(contents, PageRequest.of(0, airpods.size), airpods.size.toLong()) @@ -68,7 +79,10 @@ class AirPodsHandler( member.id, FavoriteProduct(productInfo.productId, productInfo.productCategory) ).isPresent + val notificationCount = favoriteRepository.findCountByProduct( + FavoriteProduct(productInfo.productId, productInfo.productCategory) + ) - return AirPodsDetailResponse.of(airPods, isFavorite) + return AirPodsDetailResponse.of(airPods, notificationCount, isFavorite) } } diff --git a/src/main/kotlin/backend/itracker/tracker/product/handler/MacbookHandler.kt b/src/main/kotlin/backend/itracker/tracker/product/handler/MacbookHandler.kt index b7d03f2..3139978 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/handler/MacbookHandler.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/handler/MacbookHandler.kt @@ -1,6 +1,7 @@ package backend.itracker.tracker.product.handler import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import backend.itracker.crawl.macbook.domain.Macbook import backend.itracker.crawl.macbook.service.MacbookService import backend.itracker.crawl.macbook.service.dto.MacbookFilterCondition @@ -27,23 +28,28 @@ class MacbookHandler( private val favoriteRepository: FavoriteRepository, ) : ProductHandleable { - override fun supports(productCategory: ProductCategory): Boolean { - return productCategory == ProductCategory.MACBOOK_AIR || - productCategory == ProductCategory.MACBOOK_PRO + override fun supports(productFilterCategory: ProductFilterCategory): Boolean { + return productFilterCategory == ProductFilterCategory.MACBOOK_AIR || + productFilterCategory == ProductFilterCategory.MACBOOK_PRO } override fun findTopDiscountPercentageProducts( - productCategory: ProductCategory, + productFilterCategory: ProductFilterCategory, limit: Int ): List { - val macbooks = macbookService.findAllFetchByCategory(productCategory.toMacbookCategory()) - return macbooks.map { MacbookResponse.from(it) } + val macbooks = macbookService.findAllFetchByCategory(productFilterCategory.toMacbookCategory()) + return macbooks.map { + MacbookResponse.of( + it, + favoriteRepository.findCountByProduct(FavoriteProduct(it.id, ProductCategory.MACBOOK)) + ) + } .sortedBy { it.discountPercentage } .take(limit) } override fun findFilter( - category: ProductCategory, + category: ProductFilterCategory, filter: ProductFilter ): CommonFilterModel { val macbooks = macbookService.findAllByCategoryAndFilter(category.toMacbookCategory(), MacbookFilterCondition(filter.value)) @@ -52,7 +58,7 @@ class MacbookHandler( } override fun findFilteredProductsOrderByDiscountRate( - category: ProductCategory, + category: ProductFilterCategory, filter: ProductFilter, pageable: Pageable, ): Page { @@ -71,7 +77,12 @@ class MacbookHandler( return emptyList() } - return macbooks.map { MacbookResponse.from(it) } + return macbooks.map { + MacbookResponse.of( + it, + favoriteRepository.findCountByProduct(FavoriteProduct(it.id, ProductCategory.MACBOOK)) + ) + } .sortedBy { it.discountPercentage } .slice(startElementNumber until lastElementNumber) } @@ -83,7 +94,10 @@ class MacbookHandler( member.id, FavoriteProduct(productInfo.productId, productInfo.productCategory) ).isPresent + val notificationCount = favoriteRepository.findCountByProduct( + FavoriteProduct(productInfo.productId, productInfo.productCategory) + ) - return MacbookDetailResponse.of(macbook, isFavorite) + return MacbookDetailResponse.of(macbook, notificationCount, isFavorite) } } diff --git a/src/main/kotlin/backend/itracker/tracker/product/handler/ProductHandleable.kt b/src/main/kotlin/backend/itracker/tracker/product/handler/ProductHandleable.kt index 1168097..ff24c19 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/handler/ProductHandleable.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/handler/ProductHandleable.kt @@ -1,6 +1,6 @@ package backend.itracker.tracker.product.handler -import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import backend.itracker.tracker.member.domain.Member import backend.itracker.tracker.product.response.filter.CommonFilterModel import backend.itracker.tracker.product.response.product.CommonProductDetailModel @@ -13,23 +13,23 @@ import org.springframework.data.domain.Pageable interface ProductHandleable { - fun supports(productCategory: ProductCategory): Boolean + fun supports(productFilterCategory: ProductFilterCategory): Boolean /** - * @param productCategory 상품 카테고리 -> 맥북에서만 사용 + * @param productFilterCategory 상품 카테고리 -> 맥북에서만 사용 */ - fun findTopDiscountPercentageProducts(productCategory: ProductCategory, limit: Int): List + fun findTopDiscountPercentageProducts(productFilterCategory: ProductFilterCategory, limit: Int): List /** - * @param productCategory 상품 카테고리 -> 맥북에서만 사용 + * @param productFilterCategory 상품 카테고리 -> 맥북에서만 사용 */ - fun findFilter(productCategory: ProductCategory, filterCondition: ProductFilter): CommonFilterModel + fun findFilter(productFilterCategory: ProductFilterCategory, filterCondition: ProductFilter): CommonFilterModel /** * @param category 상품 카테고리 -> 맥북에서만 사용 */ fun findFilteredProductsOrderByDiscountRate( - category: ProductCategory, + category: ProductFilterCategory, filter: ProductFilter, pageable: Pageable, ): Page diff --git a/src/main/kotlin/backend/itracker/tracker/product/handler/ProductHandlerImpl.kt b/src/main/kotlin/backend/itracker/tracker/product/handler/ProductHandlerImpl.kt index 9a81018..66b178a 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/handler/ProductHandlerImpl.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/handler/ProductHandlerImpl.kt @@ -1,6 +1,6 @@ package backend.itracker.tracker.product.handler -import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import backend.itracker.tracker.member.domain.Member import backend.itracker.tracker.product.response.filter.CommonFilterModel import backend.itracker.tracker.product.response.product.CommonProductDetailModel @@ -18,27 +18,27 @@ class ProductHandlerImpl( ) { fun findTopDiscountPercentageProducts( - productCategory: ProductCategory, + productFilterCategory: ProductFilterCategory, limit: Limit ): List { - val productHandler = (productHandlers.find { it.supports(productCategory) } - ?: throw IllegalArgumentException("지원하지 않는 카테고리 입니다. category: $productCategory")) + val productHandler = (productHandlers.find { it.supports(productFilterCategory) } + ?: throw IllegalArgumentException("지원하지 않는 카테고리 입니다. category: $productFilterCategory")) - return productHandler.findTopDiscountPercentageProducts(productCategory, limit.value) + return productHandler.findTopDiscountPercentageProducts(productFilterCategory, limit.value) } fun findFilter( - productCategory: ProductCategory, + productFilterCategory: ProductFilterCategory, productFilter: ProductFilter, ): CommonFilterModel { - val productHandler = productHandlers.find { it.supports(productCategory) } - ?: throw IllegalArgumentException("핸들러가 지원하지 않는 카테고리 입니다. category: $productCategory") + val productHandler = productHandlers.find { it.supports(productFilterCategory) } + ?: throw IllegalArgumentException("핸들러가 지원하지 않는 카테고리 입니다. category: $productFilterCategory") - return productHandler.findFilter(productCategory, productFilter) + return productHandler.findFilter(productFilterCategory, productFilter) } fun findFilteredProducts( - category: ProductCategory, + category: ProductFilterCategory, productFilter: ProductFilter, pageable: PageRequest ): Page { @@ -52,7 +52,7 @@ class ProductHandlerImpl( productInfo: ProductInfo, anonymousMember: Member ): CommonProductDetailModel { - val productHandler = productHandlers.find { it.supports(productInfo.productCategory) } + val productHandler = productHandlers.find { it.supports(productInfo.productFilterCategory) } ?: throw IllegalArgumentException("핸들러가 지원하지 않는 카테고리 입니다. category: ${productInfo.productCategory}") return productHandler.findProductById(productInfo, anonymousMember) diff --git a/src/main/kotlin/backend/itracker/tracker/product/response/CategoryResponses.kt b/src/main/kotlin/backend/itracker/tracker/product/response/CategoryResponses.kt index 57ac10c..ac8d840 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/response/CategoryResponses.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/response/CategoryResponses.kt @@ -1,11 +1,11 @@ package backend.itracker.tracker.product.response -import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import kotlin.enums.EnumEntries data class CategoryResponses( val categories: List ) { - constructor(categories: EnumEntries) : this(categories = categories.map { it.name.lowercase() }) + constructor(categories: EnumEntries) : this(categories = categories.map { it.name.lowercase() }) } diff --git a/src/main/kotlin/backend/itracker/tracker/product/response/product/CommonProductModel.kt b/src/main/kotlin/backend/itracker/tracker/product/response/product/CommonProductModel.kt index bb5345e..dd3b459 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/response/product/CommonProductModel.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/response/product/CommonProductModel.kt @@ -18,6 +18,7 @@ abstract class CommonProductDetailModel( val allTimeHighPrice: BigDecimal, val allTimeLowPrice: BigDecimal, val averagePrice: BigDecimal, + val notificationCount: Long, val priceInfos: List ) { diff --git a/src/main/kotlin/backend/itracker/tracker/product/response/product/airpods/AirPodsDetailResponse.kt b/src/main/kotlin/backend/itracker/tracker/product/response/product/airpods/AirPodsDetailResponse.kt index ca8e84c..1861d6f 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/response/product/airpods/AirPodsDetailResponse.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/response/product/airpods/AirPodsDetailResponse.kt @@ -25,6 +25,7 @@ class AirPodsDetailResponse( allTimeHighPrice: BigDecimal, allTimeLowPrice: BigDecimal, averagePrice: BigDecimal, + notificationCount: Long, priceInfos: List, ) : CommonProductDetailModel( isFavorite, @@ -33,12 +34,14 @@ class AirPodsDetailResponse( allTimeHighPrice, allTimeLowPrice, averagePrice, + notificationCount, priceInfos ) { companion object { fun of( airPods: AirPods, - isFavorite: Boolean = false + notificationCount: Long, + isFavorite: Boolean = false, ): CommonProductDetailModel { val name = when (airPods.category) { AirPodsCategory.AIRPODS -> "에어팟" @@ -63,6 +66,7 @@ class AirPodsDetailResponse( allTimeHighPrice = airPods.findAllTimeHighPrice(), allTimeLowPrice = airPods.findAllTimeLowPrice(), averagePrice = airPods.findAveragePrice(), + notificationCount = notificationCount, priceInfos = airPods.getRecentPricesByPeriod(SIX_MONTH).airPodsPrices .map { CommonPriceInfo.of(it.createdAt, it.currentPrice) } ) diff --git a/src/main/kotlin/backend/itracker/tracker/product/response/product/airpods/AirPodsResponse.kt b/src/main/kotlin/backend/itracker/tracker/product/response/product/airpods/AirPodsResponse.kt index 7ecea6f..1ddfde1 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/response/product/airpods/AirPodsResponse.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/response/product/airpods/AirPodsResponse.kt @@ -17,11 +17,12 @@ data class AirPodsResponse( val imageUrl: String, val discountPercentage: Int, val currentPrice: BigDecimal, - val isOutOfStock: Boolean + val isOutOfStock: Boolean, + val notificationCount: Long, ) : CommonProductModel { companion object { - fun from(airPods: AirPods): AirPodsResponse { + fun of(airPods: AirPods, notificationCount: Long): AirPodsResponse { val name = when (airPods.category) { AirPodsCategory.AIRPODS -> "에어팟" AirPodsCategory.AIRPODS_PRO -> "에어팟 프로" @@ -39,7 +40,8 @@ data class AirPodsResponse( imageUrl = airPods.thumbnail, discountPercentage = airPods.findDiscountPercentage(), currentPrice = airPods.findCurrentPrice(), - isOutOfStock = airPods.isOutOfStock() + isOutOfStock = airPods.isOutOfStock(), + notificationCount = notificationCount, ) } } diff --git a/src/main/kotlin/backend/itracker/tracker/product/response/product/macbook/MacbookDetailResponse.kt b/src/main/kotlin/backend/itracker/tracker/product/response/product/macbook/MacbookDetailResponse.kt index 5ef85cd..77cd4d5 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/response/product/macbook/MacbookDetailResponse.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/response/product/macbook/MacbookDetailResponse.kt @@ -28,6 +28,7 @@ class MacbookDetailResponse( allTimeHighPrice: BigDecimal, allTimeLowPrice: BigDecimal, averagePrice: BigDecimal, + notificationCount: Long, priceInfos: List, ) : CommonProductDetailModel( isFavorite, @@ -36,11 +37,16 @@ class MacbookDetailResponse( allTimeHighPrice, allTimeLowPrice, averagePrice, - priceInfos + notificationCount, + priceInfos, ) { companion object { - fun of(macbook: Macbook, isFavorite: Boolean = false): MacbookDetailResponse { + fun of( + macbook: Macbook, + notificationCount: Long, + isFavorite: Boolean = false + ): MacbookDetailResponse { val koreanCategory = when (macbook.category) { MacbookCategory.MACBOOK_AIR -> "맥북 에어" MacbookCategory.MACBOOK_PRO -> "맥북 프로" @@ -67,6 +73,7 @@ class MacbookDetailResponse( coupangUrl = macbook.partnersLink.ifBlank { macbook.productLink }, isFavorite = isFavorite, isOutOfStock = macbook.isOutOfStock(), + notificationCount = notificationCount, priceInfos = macbook.getRecentPricesByPeriod(SIX_MONTH).macbookPrices .map { CommonPriceInfo.of(it.createdAt, it.currentPrice) } ) diff --git a/src/main/kotlin/backend/itracker/tracker/product/response/product/macbook/MacbookResponse.kt b/src/main/kotlin/backend/itracker/tracker/product/response/product/macbook/MacbookResponse.kt index f08ca8f..aa22425 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/response/product/macbook/MacbookResponse.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/response/product/macbook/MacbookResponse.kt @@ -20,11 +20,12 @@ data class MacbookResponse( val color: String, val label: Boolean, val imageUrl: String, - val isOutOfStock: Boolean + val isOutOfStock: Boolean, + val notificationCount: Long, ) : CommonProductModel { companion object { - fun from(macbook: Macbook): MacbookResponse { + fun of(macbook: Macbook, notificationCount: Long): MacbookResponse { val koreanCategory = when (macbook.category) { MacbookCategory.MACBOOK_AIR -> "맥북 에어" MacbookCategory.MACBOOK_PRO -> "맥북 프로" @@ -45,7 +46,8 @@ data class MacbookResponse( currentPrice = macbook.findCurrentPrice().setScale(0), label = macbook.isAllTimeLowPrice(), imageUrl = macbook.thumbnail, - isOutOfStock = macbook.isOutOfStock() + isOutOfStock = macbook.isOutOfStock(), + notificationCount = notificationCount, ) } } diff --git a/src/main/kotlin/backend/itracker/tracker/product/vo/ProductInfo.kt b/src/main/kotlin/backend/itracker/tracker/product/vo/ProductInfo.kt index 759897f..a8d6891 100644 --- a/src/main/kotlin/backend/itracker/tracker/product/vo/ProductInfo.kt +++ b/src/main/kotlin/backend/itracker/tracker/product/vo/ProductInfo.kt @@ -1,8 +1,20 @@ package backend.itracker.tracker.product.vo import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory data class ProductInfo( val productCategory: ProductCategory, + val productFilterCategory: ProductFilterCategory, val productId: Long, -) +) { + companion object { + fun of(category: ProductFilterCategory, productId: Long): ProductInfo { + return ProductInfo( + productCategory = ProductCategory.from(category), + productFilterCategory = category, + productId = productId + ) + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d59d156..dc92023 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,6 +13,7 @@ spring: logging: level: org.hibernate.SQL: debug + org.hibernate.orm.jdbc.bind: TRACE cors: allowed-origins: http://localhost:3000 diff --git a/src/test/kotlin/backend/itracker/tracker/product/assured/ProductControllerAssuredTest.kt b/src/test/kotlin/backend/itracker/tracker/product/assured/ProductControllerAssuredTest.kt index c47239e..d1b3625 100644 --- a/src/test/kotlin/backend/itracker/tracker/product/assured/ProductControllerAssuredTest.kt +++ b/src/test/kotlin/backend/itracker/tracker/product/assured/ProductControllerAssuredTest.kt @@ -1,7 +1,7 @@ package backend.itracker.tracker.product.assured import backend.itracker.config.AssuredTestConfig -import backend.itracker.crawl.common.ProductCategory +import backend.itracker.crawl.common.ProductFilterCategory import backend.itracker.tracker.product.response.CategoryResponses import io.restassured.common.mapper.TypeRef import org.assertj.core.api.Assertions.assertThat @@ -16,7 +16,7 @@ class ProductControllerAssuredTest : AssuredTestConfig() { .`as`(object : TypeRef() {}) // then - val expected = ProductCategory.entries.map { it.name.lowercase() }.toList() + val expected = ProductFilterCategory.entries.map { it.name.lowercase() }.toList() assertThat(response.categories).isEqualTo(expected) } } diff --git a/src/test/kotlin/backend/itracker/tracker/product/assured/ProductMacbookAssuredTest.kt b/src/test/kotlin/backend/itracker/tracker/product/assured/ProductMacbookAssuredTest.kt index 45be93e..2d8c8ba 100644 --- a/src/test/kotlin/backend/itracker/tracker/product/assured/ProductMacbookAssuredTest.kt +++ b/src/test/kotlin/backend/itracker/tracker/product/assured/ProductMacbookAssuredTest.kt @@ -92,11 +92,11 @@ class ProductMacbookAssuredTest : AssuredTestConfig() { }) val expected = listOf( - MacbookResponse.from(first), - MacbookResponse.from(second), - MacbookResponse.from(third), - MacbookResponse.from(fourth), - MacbookResponse.from(fifth), + MacbookResponse.of(first, 0), + MacbookResponse.of(second, 0), + MacbookResponse.of(third, 0), + MacbookResponse.of(fourth, 0), + MacbookResponse.of(fifth, 0), ) // when @@ -131,7 +131,7 @@ class ProductMacbookAssuredTest : AssuredTestConfig() { .`as`(object : TypeRef() {}) // then - assertThat(response).isEqualTo(MacbookDetailResponse.of(expected)) + assertThat(response).isEqualTo(MacbookDetailResponse.of(expected, 0)) } @ParameterizedTest @@ -169,7 +169,7 @@ class ProductMacbookAssuredTest : AssuredTestConfig() { // then assertAll( { assertThat(response.data).hasSize(1) }, - { assertThat(response.data).containsExactly(MacbookResponse.from(expected)) } + { assertThat(response.data).containsExactly(MacbookResponse.of(expected, 0)) } ) }