Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 장소 입력자동화 정보 DB 저장 #110

Merged
merged 16 commits into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.piikii.application.domain.generic.ThumbnailLinks
data class OriginPlace(
val id: Long?,
val name: String,
val originMapId: Long,
val originMapId: OriginMapId,
KimDoubleB marked this conversation as resolved.
Show resolved Hide resolved
val url: String,
val thumbnailLinks: ThumbnailLinks,
val address: String? = null,
Expand All @@ -18,3 +18,6 @@ data class OriginPlace(
val category: String?,
val origin: Origin,
)

@JvmInline
value class OriginMapId(val value: Long)
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ class OriginPlaceService(
ExceptionCode.NOT_SUPPORT_AUTO_COMPLETE_URL,
"No AutoComplete client found for $url",
)
val placeId = originPlaceAutoCompleteClient.extractPlaceId(plainUrl)
return originPlaceAutoCompleteClient.getAutoCompletedPlace(url = plainUrl, placeId = placeId)
val originMapId = originPlaceAutoCompleteClient.extractOriginMapId(plainUrl)
return originPlaceQueryPort.findByOriginMapId(originMapId)
?: originPlaceAutoCompleteClient.getAutoCompletedPlace(url = plainUrl, originMapId = originMapId)
.let { originPlaceCommandPort.save(it) }
}

private fun getUrlOfRemovedParameters(url: String): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
package com.piikii.application.port.output.persistence

import com.piikii.application.domain.place.OriginMapId
import com.piikii.application.domain.place.OriginPlace

interface OriginPlaceQueryPort {
fun retrieve(id: Long): OriginPlace

fun retrieveAll(ids: List<Long>): List<OriginPlace>
fun findByOriginMapId(originMapId: OriginMapId): OriginPlace?
}

interface OriginPlaceCommandPort {
fun save(originPlace: OriginPlace): OriginPlace

fun update(
originPlace: OriginPlace,
id: Long,
)

fun delete(id: Long)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.piikii.application.port.output.web

import com.piikii.application.domain.place.OriginMapId
import com.piikii.application.domain.place.OriginPlace

interface OriginPlaceAutoCompleteClient {
fun isAutoCompleteSupportedUrl(url: String): Boolean

fun extractPlaceId(url: String): String
fun extractOriginMapId(url: String): OriginMapId

fun getAutoCompletedPlace(
url: String,
placeId: String,
originMapId: OriginMapId,
): OriginPlace
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.piikii.output.persistence.postgresql.adapter

import com.piikii.application.domain.place.OriginMapId
import com.piikii.application.domain.place.OriginPlace
import com.piikii.application.port.output.persistence.OriginPlaceCommandPort
import com.piikii.application.port.output.persistence.OriginPlaceQueryPort
import com.piikii.output.persistence.postgresql.persistence.entity.OriginPlaceEntity
import com.piikii.output.persistence.postgresql.persistence.repository.OriginPlaceRepository
import jakarta.persistence.EntityNotFoundException
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional

Expand All @@ -16,34 +16,11 @@ class OriginPlaceAdapter(
) : OriginPlaceCommandPort, OriginPlaceQueryPort {
@Transactional
override fun save(originPlace: OriginPlace): OriginPlace {
val entity = OriginPlaceEntity.from(originPlace)
originPlaceRepository.save(entity)
return entity.toDomain()
return originPlaceRepository.save(OriginPlaceEntity.from(originPlace))
.toDomain()
}

@Transactional
override fun update(
originPlace: OriginPlace,
id: Long,
) {
TODO("Not yet implemented")
}

@Transactional
override fun delete(id: Long) {
TODO("Not yet implemented")
}

override fun retrieve(id: Long): OriginPlace {
val originPlaceEntity = originPlaceRepository.findById(id)
if (originPlaceEntity.isPresent) {
return originPlaceEntity.get().toDomain()
}
// TODO 예외 정의
throw EntityNotFoundException()
}

override fun retrieveAll(ids: List<Long>): List<OriginPlace> {
TODO("Not yet implemented")
override fun findByOriginMapId(originMapId: OriginMapId): OriginPlace? {
return originPlaceRepository.findByOriginMapId(originMapId)?.toDomain()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.piikii.output.persistence.postgresql.persistence.entity

import com.piikii.application.domain.generic.Origin
import com.piikii.application.domain.generic.ThumbnailLinks
import com.piikii.application.domain.place.OriginMapId
import com.piikii.application.domain.place.OriginPlace
import com.piikii.output.persistence.postgresql.persistence.common.BaseEntity
import jakarta.persistence.Column
Expand All @@ -19,8 +20,8 @@ import org.hibernate.annotations.SQLRestriction
@SQLDelete(sql = "UPDATE piikii.origin_place SET is_deleted = true WHERE id = ?")
@DynamicUpdate
class OriginPlaceEntity(
@Column(name = "origin_map_id", nullable = false)
val originMapId: Long,
@Column(name = "origin_map_id", nullable = false, unique = true)
val originMapId: OriginMapId,
thguss marked this conversation as resolved.
Show resolved Hide resolved
@Column(name = "name", length = 255, nullable = false)
var name: String,
@Column(name = "url", nullable = false, length = 255)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.piikii.output.persistence.postgresql.persistence.repository

import com.piikii.application.domain.place.OriginMapId
import com.piikii.output.persistence.postgresql.persistence.entity.OriginPlaceEntity
import org.springframework.data.jpa.repository.JpaRepository

interface OriginPlaceRepository : JpaRepository<OriginPlaceEntity, Long>
interface OriginPlaceRepository : JpaRepository<OriginPlaceEntity, Long> {
fun findByOriginMapId(originMapId: OriginMapId): OriginPlaceEntity?
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
package com.piikii.output.web.avocado.adapter

import com.piikii.application.domain.place.OriginMapId
import com.piikii.application.domain.place.OriginPlace
import com.piikii.application.port.output.web.OriginPlaceAutoCompleteClient
import com.piikii.common.exception.ExceptionCode
import com.piikii.common.exception.PiikiiException
import com.piikii.output.web.avocado.parser.AvocadoPlaceIdParserStrategy
import com.piikii.output.web.avocado.parser.AvocadoOriginMapIdParserStrategy
import org.springframework.stereotype.Component
import org.springframework.web.client.RestClient
import org.springframework.web.client.body

@Component
class AvocadoPlaceAutoCompleteClient(
private val avocadoPlaceIdParserStrategy: AvocadoPlaceIdParserStrategy,
private val avocadoOriginMapIdParserStrategy: AvocadoOriginMapIdParserStrategy,
private val avocadoApiClient: RestClient,
) : OriginPlaceAutoCompleteClient {
override fun isAutoCompleteSupportedUrl(url: String): Boolean {
return avocadoPlaceIdParserStrategy.getParserBySupportedUrl(url) != null
return avocadoOriginMapIdParserStrategy.getParserBySupportedUrl(url) != null
}

override fun extractPlaceId(url: String): String {
return avocadoPlaceIdParserStrategy.getParserBySupportedUrl(url)?.parsePlaceId(url)
override fun extractOriginMapId(url: String): OriginMapId {
return avocadoOriginMapIdParserStrategy.getParserBySupportedUrl(url)?.parseOriginMapId(url)
?: throw PiikiiException(ExceptionCode.NOT_SUPPORT_AUTO_COMPLETE_URL)
}

override fun getAutoCompletedPlace(
url: String,
placeId: String,
originMapId: OriginMapId,
): OriginPlace {
return avocadoApiClient.get()
.uri("/$placeId")
.uri("/${originMapId.value}")
.retrieve()
.body<AvocadoPlaceInfoResponse>()
?.toOriginPlace(url)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import com.piikii.application.domain.generic.Origin
import com.piikii.application.domain.generic.ThumbnailLinks
import com.piikii.application.domain.place.OriginMapId
import com.piikii.application.domain.place.OriginPlace

@JsonIgnoreProperties(ignoreUnknown = true)
Expand All @@ -27,7 +28,7 @@ data class AvocadoPlaceInfoResponse(
fun toOriginPlace(url: String): OriginPlace {
return OriginPlace(
id = null,
originMapId = id,
originMapId = OriginMapId(id),
name = name,
url = url,
thumbnailLinks = ThumbnailLinks(images ?: emptyList()),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
package com.piikii.output.web.avocado.parser

import com.piikii.application.domain.place.OriginMapId
import com.piikii.output.web.avocado.config.AvocadoProperties
import org.springframework.stereotype.Component
import org.springframework.web.client.RestClient

@Component
class AvocadoPlaceIdParserStrategy(private val parsers: List<AvocadoPlaceIdParser>) {
fun getParserBySupportedUrl(url: String): AvocadoPlaceIdParser? {
class AvocadoOriginMapIdParserStrategy(private val parsers: List<AvocadoOriginMapIdParser>) {
fun getParserBySupportedUrl(url: String): AvocadoOriginMapIdParser? {
return parsers.find { it.pattern().matches(url) }
}
}

interface AvocadoPlaceIdParser {
interface AvocadoOriginMapIdParser {
fun pattern(): Regex

fun parsePlaceId(url: String): String?
fun parseOriginMapId(url: String): OriginMapId?

/**
* Regex를 이용해 OriginMapId 반환
* - Regex Match 결과로부터 첫 번째 값을 꺼내 OriginMapId 변환 및 반환
*
* @return OriginMapId
*/
thguss marked this conversation as resolved.
Show resolved Hide resolved
fun MatchResult?.parseFromMatchResult(): OriginMapId? {
return this?.groupValues
?.getOrNull(1)
?.toLongOrNull()
?.let { OriginMapId(it) }
}
}

@Component
class MapPlaceIdParser(properties: AvocadoProperties) : AvocadoPlaceIdParser {
class MapUrlIdParser(properties: AvocadoProperties) : AvocadoOriginMapIdParser {
private val patternRegex: Regex = "${properties.url.regex.web}$PLACE_ID_REGEX".toRegex()
private val parseRegex: Regex = "${properties.url.regex.web}($PLACE_ID_REGEX)".toRegex()

override fun pattern(): Regex {
return patternRegex
}

override fun parsePlaceId(url: String): String? {
return parseRegex.find(url)?.groupValues?.get(1)
override fun parseOriginMapId(url: String): OriginMapId? {
return parseRegex.find(url).parseFromMatchResult()
}

companion object {
Expand All @@ -36,9 +50,9 @@ class MapPlaceIdParser(properties: AvocadoProperties) : AvocadoPlaceIdParser {
}

@Component
class SharePlaceIdParser(
class ShareUrlIdParser(
properties: AvocadoProperties,
) : AvocadoPlaceIdParser {
) : AvocadoOriginMapIdParser {
private val regex: Regex = properties.url.regex.share.toRegex()
private val idParameterRegex: Regex = "id=(\\d+)".toRegex()
private val client: RestClient = RestClient.builder().build()
Expand All @@ -47,14 +61,14 @@ class SharePlaceIdParser(
return regex
}

override fun parsePlaceId(url: String): String? {
override fun parseOriginMapId(url: String): OriginMapId? {
val response =
client.get().uri(url)
.retrieve()
.toEntity(Map::class.java)
if (response.statusCode.is3xxRedirection && response.headers.location != null) {
val extractedId = idParameterRegex.find(response.headers.location.toString())
return extractedId?.groupValues?.get(1)
return idParameterRegex.find(response.headers.location.toString())
.parseFromMatchResult()
}
return null
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.piikii.output.web.avocado

import com.piikii.application.domain.place.OriginMapId
import com.piikii.output.web.avocado.adapter.AvocadoPlaceAutoCompleteClient
import com.piikii.output.web.avocado.parser.MapPlaceIdParser
import com.piikii.output.web.avocado.parser.SharePlaceIdParser
import com.piikii.output.web.avocado.parser.MapUrlIdParser
import com.piikii.output.web.avocado.parser.ShareUrlIdParser
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
Expand All @@ -17,10 +18,10 @@ import org.springframework.test.context.ContextConfiguration
@ContextConfiguration(classes = [TestConfiguration::class])
class AvocadoPlaceAutoCompleteClientTest {
@Autowired
lateinit var sharePlaceIdParser: SharePlaceIdParser
lateinit var sharePlaceIdParser: ShareUrlIdParser

@Autowired
lateinit var mapPlaceIdParser: MapPlaceIdParser
lateinit var mapPlaceIdParser: MapUrlIdParser

@Autowired
lateinit var avocadoPlaceAutoCompleteClient: AvocadoPlaceAutoCompleteClient
Expand All @@ -29,7 +30,7 @@ class AvocadoPlaceAutoCompleteClientTest {
fun sharePlaceIdParserTest() {
val url = "주소를 입력하세요"

val parse = sharePlaceIdParser.parsePlaceId(url)
val parse = sharePlaceIdParser.parseOriginMapId(url)
println("parse = $parse")
}

Expand All @@ -40,16 +41,16 @@ class AvocadoPlaceAutoCompleteClientTest {
val pattern = mapPlaceIdParser.pattern()
assertThat(pattern.matches(url)).isTrue()

val parse = mapPlaceIdParser.parsePlaceId(url)
val parse = mapPlaceIdParser.parseOriginMapId(url)
println("parse = $parse")
}

@Test
fun getAutoCompletedPlaceTest() {
val url = "주소를 입력하세요"
val id = "id를 입력하세요"
val id = 123L

val originPlace = avocadoPlaceAutoCompleteClient.getAutoCompletedPlace(url, id)
val originPlace = avocadoPlaceAutoCompleteClient.getAutoCompletedPlace(url, OriginMapId(id))
println("originPlace = $originPlace")
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
package com.piikii.output.web.lemon.adapter

import com.piikii.application.domain.place.OriginMapId
import com.piikii.application.domain.place.OriginPlace
import com.piikii.application.port.output.web.OriginPlaceAutoCompleteClient
import com.piikii.common.exception.ExceptionCode
import com.piikii.common.exception.PiikiiException
import com.piikii.output.web.lemon.parser.LemonPlaceIdParser
import com.piikii.output.web.lemon.parser.LemonOriginMapIdParser
import org.springframework.stereotype.Component
import org.springframework.web.client.RestClient
import org.springframework.web.client.body

@Component
class LemonPlaceAutoCompleteClient(
private val lemonPlaceIdParser: LemonPlaceIdParser,
private val lemonOriginMapIdParser: LemonOriginMapIdParser,
private val lemonApiClient: RestClient,
) : OriginPlaceAutoCompleteClient {
override fun isAutoCompleteSupportedUrl(url: String): Boolean {
return lemonPlaceIdParser.isAutoCompleteSupportedUrl(url)
return lemonOriginMapIdParser.isAutoCompleteSupportedUrl(url)
}

override fun extractPlaceId(url: String): String {
return lemonPlaceIdParser.parse(url)
override fun extractOriginMapId(url: String): OriginMapId {
return lemonOriginMapIdParser.parseOriginMapId(url)
?: throw PiikiiException(ExceptionCode.NOT_SUPPORT_AUTO_COMPLETE_URL)
}

override fun getAutoCompletedPlace(
url: String,
placeId: String,
originMapId: OriginMapId,
): OriginPlace {
return lemonApiClient.get()
.uri("/$placeId")
.uri("/${originMapId.value}")
.retrieve()
.body<LemonPlaceInfoResponse>()
?.toOriginPlace(url)
Expand Down
Loading