Skip to content

Commit

Permalink
5/29 배포 작업 (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
cookienc authored May 29, 2024
1 parent d9a29ca commit dd91fa4
Show file tree
Hide file tree
Showing 20 changed files with 521 additions and 8 deletions.
52 changes: 52 additions & 0 deletions src/main/kotlin/backend/itracker/crawl/airpods/domain/AirPods.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package backend.itracker.crawl.airpods.domain

import backend.itracker.crawl.common.BaseEntity
import jakarta.persistence.Column
import jakarta.persistence.Embedded
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.Table

@Entity
@Table(name = "airpods")
class AirPods(
val coupangId: Long,
val company: String,
val releaseYear: Int,
val generation: Int,
val canWirelessCharging: Boolean,
val chargingType: String,
val color: String,

@Enumerated(EnumType.STRING)
val category: AirPodsCategory,

@Column(columnDefinition = "TEXT")
val name: String,

@Column(columnDefinition = "TEXT")
val productLink: String,

@Column(columnDefinition = "TEXT")
val thumbnail: String,

@Embedded
val prices: AirPodsPrices = AirPodsPrices(),

id: Long = 0L
) : BaseEntity(id) {

fun addAllPrices(prices: AirPodsPrices) {
prices.airPodsPrices.forEach(this::addPrice)
}

fun addPrice(airPodsPrice: AirPodsPrice) {
prices.add(airPodsPrice)
airPodsPrice.changeAirPods(this)
}

override fun toString(): String {
return "AirPods(coupangId=$coupangId, company='$company', releaseYear=$releaseYear, generation=$generation, canWirelessCharging=$canWirelessCharging, chargingType='$chargingType', color='$color', category=$category, name='$name', productLink='$productLink', thumbnail='$thumbnail')"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package backend.itracker.crawl.airpods.domain

enum class AirPodsCategory {
AIRPODS,
AIRPODS_PRO,
AIRPODS_MAX
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package backend.itracker.crawl.airpods.domain

import backend.itracker.crawl.common.BaseEntity
import jakarta.persistence.Entity
import jakarta.persistence.FetchType
import jakarta.persistence.ForeignKey
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.Table
import java.math.BigDecimal

@Entity
@Table(name = "airpods_price")
class AirPodsPrice(
val discountPercentage: Int,
val basePrice: BigDecimal,
val currentPrice: BigDecimal,
val isOutOfStock: Boolean,
id: Long = 0L
) : BaseEntity(id) {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "airpods_id", nullable = false, foreignKey = ForeignKey(name = "fk_airpods_price_airpods_id_ref_airpods_id")
)
var airPods: AirPods? = null

fun changeAirPods(airPods: AirPods) {
this.airPods = airPods
}

override fun toString(): String {
return "AirPodsPrice(discountPercentage=$discountPercentage, basePrice=$basePrice, currentPrice=$currentPrice, isOutOfStock=$isOutOfStock)"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package backend.itracker.crawl.airpods.domain

import jakarta.persistence.CascadeType
import jakarta.persistence.Embeddable
import jakarta.persistence.OneToMany

@Embeddable
class AirPodsPrices(
@OneToMany(mappedBy = "airPods", cascade = [CascadeType.PERSIST])
val airPodsPrices: MutableList<AirPodsPrice> = mutableListOf()
) {

fun add(targetAirPodsPrice: AirPodsPrice) {
airPodsPrices.add(targetAirPodsPrice)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package backend.itracker.crawl.airpods.domain.repository

import backend.itracker.crawl.airpods.domain.AirPods
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import java.util.*

interface AirPodsRepository : JpaRepository<AirPods, Long> {

@Query(
"""
select a
from AirPods a
join fetch a.prices
where a.coupangId = :coupangId
"""
)
fun findByCoupangId(@Param("coupangId") coupangId: Long): Optional<AirPods>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package backend.itracker.crawl.airpods.service

import backend.itracker.crawl.airpods.domain.AirPods
import backend.itracker.crawl.airpods.domain.repository.AirPodsRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Transactional
@Service
class AirPodsService(
private val airPodsRepository: AirPodsRepository
) {

fun saveAll(airPodses: List<AirPods>) {
for (airPods in airPodses) {
val maybeAirPods = airPodsRepository.findByCoupangId(airPods.coupangId)
if (maybeAirPods.isEmpty) {
airPodsRepository.save(airPods)
continue
}
maybeAirPods.get().addAllPrices(airPods.prices)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package backend.itracker.crawl.service

import backend.itracker.crawl.airpods.domain.AirPods
import backend.itracker.crawl.ipad.domain.Ipad
import backend.itracker.crawl.mac.domain.Mac
import backend.itracker.crawl.macbook.domain.Macbook
Expand Down Expand Up @@ -40,6 +41,12 @@ class CrawlService(
return crawlMapper.toMac(products)
}

fun crawlAirPods(): List<AirPods> {
val url = getCrawlUrl(CrawlTargetCategory.AIRPODS)
val products = crawler.crawl(url)
return crawlMapper.toAirPods(products)
}

private fun getCrawlUrl(category: CrawlTargetCategory): String {
return "$CRAWL_URL${category.categoryId}"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package backend.itracker.crawl.service.mapper

import backend.itracker.crawl.airpods.domain.AirPods
import backend.itracker.crawl.ipad.domain.Ipad
import backend.itracker.crawl.mac.domain.Mac
import backend.itracker.crawl.macbook.domain.Macbook
import backend.itracker.crawl.service.mapper.airpods.AirPodsMapper
import backend.itracker.crawl.service.mapper.ipad.IpadMappers
import backend.itracker.crawl.service.mapper.mac.MacMappers
import backend.itracker.crawl.service.mapper.macbook.MacbookMappers
Expand All @@ -17,7 +19,8 @@ class CrawlMapper(
private val macbookMappers: MacbookMappers,
private val ipadMappers: IpadMappers,
private val appleWatchMappers: AppleWatchMappers,
private val macMappers: MacMappers
private val macMappers: MacMappers,
private val airPodsMapper: AirPodsMapper,
) {

fun toMacbook(products: Map<String, DefaultProduct>): List<Macbook> {
Expand All @@ -43,4 +46,10 @@ class CrawlMapper(

return macMappers.toDomain(filteredProducts)
}

fun toAirPods(products: Map<String, DefaultProduct>): List<AirPods> {
val filteredProducts = products.values.filter { it.isAirPods() }

return airPodsMapper.toDomain(filteredProducts)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package backend.itracker.crawl.service.mapper.airpods

import backend.itracker.crawl.airpods.domain.AirPods
import backend.itracker.crawl.airpods.domain.AirPodsCategory
import backend.itracker.crawl.service.response.AirPodsCrawlResponse
import backend.itracker.crawl.service.vo.DefaultProduct
import org.springframework.stereotype.Component

private const val AIR_PODS_GEN_2 = "AirPods 2세대"

@Component
class AirPodsGen2Mapper : AirPodsMappingComponent {

override fun supports(subCategory: String): Boolean {
return AIR_PODS_GEN_2 == subCategory
}

override fun toDomain(product: DefaultProduct): AirPods {
val names = product.name.split(",")
.map { it.trim() }
.toList()

val title = names[0].split(" ")
val company = title[0]
val releaseYear = "2019"
val category = AirPodsCategory.AIRPODS
val generation = title[2].replace("세대", "")
val canWirelessCharging = title[3] == "유선"
val chargingType = "Lighting 8-pin"

return AirPodsCrawlResponse(
coupangId = product.productId,
name = product.name,
company = company,
releaseYear = releaseYear,
category = category,
generation = generation,
canWirelessCharging = canWirelessCharging,
chargingType = chargingType,
productLink = product.productLink,
thumbnail = product.thumbnailLink,
basePrice = product.price.basePrice,
discountPercentage = product.price.discountPercentage,
currentPrice = product.price.discountPrice,
isOutOfStock = product.price.isOutOfStock
).toDomain()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package backend.itracker.crawl.service.mapper.airpods

import backend.itracker.crawl.airpods.domain.AirPods
import backend.itracker.crawl.airpods.domain.AirPodsCategory
import backend.itracker.crawl.service.response.AirPodsCrawlResponse
import backend.itracker.crawl.service.vo.DefaultProduct
import org.springframework.stereotype.Component

private const val AIR_PODS_GEN_3 = "AirPods 3세대"

@Component
class AirPodsGen3Mapper : AirPodsMappingComponent {

override fun supports(subCategory: String): Boolean {
return AIR_PODS_GEN_3 == subCategory
}

override fun toDomain(product: DefaultProduct): AirPods {
val names = product.name.split(",")
.map { it.trim() }
.toList()

val title = names[0].split(" ")
val company = title[0]
val releaseYear = title[1]
val category = AirPodsCategory.AIRPODS
val generation = title[3].replace("세대", "")
val canWirelessCharging = title[4] != "유선"
val chargingType = "Lighting 8-pin"

return AirPodsCrawlResponse(
coupangId = product.productId,
name = product.name,
company = company,
releaseYear = releaseYear,
category = category,
generation = generation,
canWirelessCharging = canWirelessCharging,
chargingType = chargingType,
productLink = product.productLink,
thumbnail = product.thumbnailLink,
basePrice = product.price.basePrice,
discountPercentage = product.price.discountPercentage,
currentPrice = product.price.discountPrice,
isOutOfStock = product.price.isOutOfStock
).toDomain()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package backend.itracker.crawl.service.mapper.airpods

import backend.itracker.crawl.airpods.domain.AirPods
import backend.itracker.crawl.exception.CrawlException
import backend.itracker.crawl.service.vo.DefaultProduct
import org.springframework.stereotype.Component

@Component
class AirPodsMapper(
private val airPodsMappers: List<AirPodsMappingComponent>
) {

fun toDomain(filteredProducts: List<DefaultProduct>): List<AirPods> {
val airPodses = mutableListOf<AirPods>()
for (product in filteredProducts) {
try {
for (airPodsMapper in airPodsMappers) {
if (airPodsMapper.supports(product.subCategory)) {
airPodses.add(airPodsMapper.toDomain(product))
}
}
} catch (e: Exception) {
throw CrawlException(
"""
|AirPods Mapping 중에 에러가 발생했습니다.
|name : ${product.name},
|subcategory : ${product.subCategory},
|error: ${e.stackTraceToString()}
|""".trimMargin()
)
}
}

return airPodses
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package backend.itracker.crawl.service.mapper.airpods

import backend.itracker.crawl.airpods.domain.AirPods
import backend.itracker.crawl.service.vo.DefaultProduct

interface AirPodsMappingComponent {

fun supports(subCategory: String): Boolean

fun toDomain(product: DefaultProduct): AirPods
}
Loading

0 comments on commit dd91fa4

Please sign in to comment.