From 3a850daa8108a5e1890addd025b09fc6e81e7a4f Mon Sep 17 00:00:00 2001 From: Diger Date: Mon, 26 Aug 2024 23:39:10 +0900 Subject: [PATCH 1/4] fix: URL Validate --- .../request/PlaceAutoCompleteUrlRequest.kt | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/request/PlaceAutoCompleteUrlRequest.kt b/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/request/PlaceAutoCompleteUrlRequest.kt index fed5218c..6ee67a5b 100644 --- a/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/request/PlaceAutoCompleteUrlRequest.kt +++ b/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/request/PlaceAutoCompleteUrlRequest.kt @@ -1,10 +1,22 @@ package com.piikii.application.port.input.dto.request +import com.piikii.common.exception.ExceptionCode +import com.piikii.common.exception.PiikiiException import io.swagger.v3.oas.annotations.media.Schema -import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.NotBlank +import java.net.URI data class PlaceAutoCompleteUrlRequest( - @field:NotNull(message = "URL 입력은 필수 입니다.") + @field:NotBlank(message = "URL 입력은 필수 입니다.") @field:Schema(description = "장소 자동완성을 위한 지도 URL", example = "https://test.com/1231421") - val url: String, -) + var url: String, +) { + init { + val validatedUrl = + "(https://\\S+)".toRegex().find(url)?.value ?: throw PiikiiException( + exceptionCode = ExceptionCode.ILLEGAL_ARGUMENT_EXCEPTION, + detailMessage = "URL이 요청 형식에 맞지 않습니다. : $url", + ) + url = URI(validatedUrl).toString() + } +} From 80d415431c803a93f0232c5cf4c40aceaca41184 Mon Sep 17 00:00:00 2001 From: Diger Date: Mon, 26 Aug 2024 23:53:13 +0900 Subject: [PATCH 2/4] fix: Lemon URL Parsing Regex --- .../kotlin/com/piikii/output/web/lemon/config/LemonConfig.kt | 1 + .../piikii/output/web/lemon/parser/LemonOriginMapIdParser.kt | 2 ++ .../lemon/src/main/resources/lemon-config/application.yml | 1 + 3 files changed, 4 insertions(+) diff --git a/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/config/LemonConfig.kt b/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/config/LemonConfig.kt index 30dec31e..9c4848db 100644 --- a/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/config/LemonConfig.kt +++ b/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/config/LemonConfig.kt @@ -29,5 +29,6 @@ data class LemonUrl( data class Regex( val web: String, val mobileWeb: String, + val mobileApp: String, ) } diff --git a/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/parser/LemonOriginMapIdParser.kt b/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/parser/LemonOriginMapIdParser.kt index e39f7847..507e2302 100644 --- a/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/parser/LemonOriginMapIdParser.kt +++ b/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/parser/LemonOriginMapIdParser.kt @@ -14,6 +14,7 @@ class LemonOriginMapIdParser( listOf( "${properties.url.regex.web}($ORIGIN_MAP_IP_REGEX)".toRegex(), "${properties.url.regex.mobileWeb}($ORIGIN_MAP_IP_REGEX)".toRegex(), + "${properties.url.regex.mobileApp}($ORIGIN_MAP_IP_REGEX_CHAR)".toRegex(), ) fun isAutoCompleteSupportedUrl(url: String): Boolean = regexes.any { it.matches(url) } @@ -29,5 +30,6 @@ class LemonOriginMapIdParser( companion object { const val ORIGIN_MAP_IP_REGEX = "\\d+" + const val ORIGIN_MAP_IP_REGEX_CHAR = "\\w+" } } diff --git a/piikii-output-web/lemon/src/main/resources/lemon-config/application.yml b/piikii-output-web/lemon/src/main/resources/lemon-config/application.yml index 3511c715..dff594fa 100644 --- a/piikii-output-web/lemon/src/main/resources/lemon-config/application.yml +++ b/piikii-output-web/lemon/src/main/resources/lemon-config/application.yml @@ -3,4 +3,5 @@ lemon: regex: web: ${LEMON_WEB_URL_REGEX} mobile-web: ${LEMON_MOBILE_WEB_URL_REGEX} + mobile-app: ${LEMON_MOBILE_APP_URL_REGEX} api: ${LEMON_API_URL} From 56ee38ca80539a1680837a31a2ac0bc8e9881413 Mon Sep 17 00:00:00 2001 From: Diger Date: Fri, 30 Aug 2024 00:11:10 +0900 Subject: [PATCH 3/4] style: Regex Pattern to Const --- .../port/input/dto/request/PlaceAutoCompleteUrlRequest.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/request/PlaceAutoCompleteUrlRequest.kt b/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/request/PlaceAutoCompleteUrlRequest.kt index 6ee67a5b..c4ad6077 100644 --- a/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/request/PlaceAutoCompleteUrlRequest.kt +++ b/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/request/PlaceAutoCompleteUrlRequest.kt @@ -13,10 +13,14 @@ data class PlaceAutoCompleteUrlRequest( ) { init { val validatedUrl = - "(https://\\S+)".toRegex().find(url)?.value ?: throw PiikiiException( + Regex(REGEX_PATTERN).find(url)?.value ?: throw PiikiiException( exceptionCode = ExceptionCode.ILLEGAL_ARGUMENT_EXCEPTION, detailMessage = "URL이 요청 형식에 맞지 않습니다. : $url", ) url = URI(validatedUrl).toString() } + + companion object { + private const val REGEX_PATTERN = "(https://\\S+)" + } } From 10ca06f1814a3a692ad8ffd0d678b20b56dfafdf Mon Sep 17 00:00:00 2001 From: Diger Date: Tue, 3 Sep 2024 23:25:12 +0900 Subject: [PATCH 4/4] feat: Add Lemon Redirect Parser --- .../adapter/LemonPlaceAutoCompleteAdapter.kt | 8 +- .../lemon/adapter/LemonPlaceInfoResponse.kt | 136 +++++++++--------- .../lemon/parser/LemonOriginMapIdParser.kt | 75 +++++++--- 3 files changed, 121 insertions(+), 98 deletions(-) diff --git a/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/adapter/LemonPlaceAutoCompleteAdapter.kt b/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/adapter/LemonPlaceAutoCompleteAdapter.kt index 22d2e55a..b4eb8c1e 100644 --- a/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/adapter/LemonPlaceAutoCompleteAdapter.kt +++ b/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/adapter/LemonPlaceAutoCompleteAdapter.kt @@ -5,22 +5,22 @@ import com.piikii.application.domain.place.OriginPlace import com.piikii.application.port.output.web.OriginPlaceAutoCompletePort import com.piikii.common.exception.ExceptionCode import com.piikii.common.exception.PiikiiException -import com.piikii.output.web.lemon.parser.LemonOriginMapIdParser +import com.piikii.output.web.lemon.parser.LemonOriginMapIdParserStrategy import org.springframework.stereotype.Component import org.springframework.web.client.RestClient import org.springframework.web.client.body @Component class LemonPlaceAutoCompleteAdapter( - private val lemonOriginMapIdParser: LemonOriginMapIdParser, + private val lemonOriginMapIdParserStrategy: LemonOriginMapIdParserStrategy, private val lemonApiClient: RestClient, ) : OriginPlaceAutoCompletePort { override fun isAutoCompleteSupportedUrl(url: String): Boolean { - return lemonOriginMapIdParser.isAutoCompleteSupportedUrl(url) + return lemonOriginMapIdParserStrategy.getParserBySupportedUrl(url) != null } override fun extractOriginMapId(url: String): OriginMapId { - return lemonOriginMapIdParser.parseOriginMapId(url) + return lemonOriginMapIdParserStrategy.getParserBySupportedUrl(url)?.parseOriginMapId(url) ?: throw PiikiiException(ExceptionCode.NOT_SUPPORT_AUTO_COMPLETE_URL) } diff --git a/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/adapter/LemonPlaceInfoResponse.kt b/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/adapter/LemonPlaceInfoResponse.kt index 6c720a18..07e0d646 100644 --- a/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/adapter/LemonPlaceInfoResponse.kt +++ b/piikii-output-web/lemon/src/main/kotlin/com/piikii/output/web/lemon/adapter/LemonPlaceInfoResponse.kt @@ -10,15 +10,16 @@ import com.piikii.application.domain.place.OriginPlace @JsonIgnoreProperties(ignoreUnknown = true) data class LemonPlaceInfoResponse( - val isMapUser: String?, - val isExist: Boolean?, - val basicInfo: BasicInfo, + @JsonProperty("ismapuser") val isMapUser: String?, + @JsonProperty("isexist") val isExist: Boolean?, + @JsonProperty("basicinfo") val basicInfo: BasicInfo?, val comment: Comment?, - val menuInfo: MenuInfo, - val photo: Photo, + @JsonProperty("menuinfo") val menuInfo: MenuInfo?, + val photo: Photo?, ) { fun toOriginPlace(url: String): OriginPlace { - val fullAddress = "${basicInfo.address.region.newaddrfullname} ${basicInfo.address.newaddr.newaddrfull}".trim() + requireNotNull(basicInfo) { "BasicInfo is required" } + val fullAddress = "${basicInfo.address.region.newAddrFullName} ${basicInfo.address.newAddr.newAddrFull}".trim() return OriginPlace( id = LongTypeId(0L), name = basicInfo.name, @@ -40,83 +41,70 @@ data class LemonPlaceInfoResponse( @JsonIgnoreProperties(ignoreUnknown = true) data class BasicInfo( val cid: Long, - @JsonProperty("placenamefull") - val name: String, - @JsonProperty("mainphotourl") - val mainPhotoUrl: String, - @JsonProperty("phonenum") - val phoneNumber: String?, + @JsonProperty("placenamefull") val name: String, + @JsonProperty("mainphotourl") val mainPhotoUrl: String, + @JsonProperty("phonenum") val phoneNumber: String?, val address: Address, val homepage: String?, val category: Category, val feedback: Feedback, - val openHour: OpenHour, + @JsonProperty("openhour") val openHour: OpenHour, val tags: List?, - @JsonProperty("x") - val longitude: Double?, - @JsonProperty("y") - val latitude: Double?, + @JsonProperty("x") val longitude: Double?, + @JsonProperty("y") val latitude: Double?, ) @JsonIgnoreProperties(ignoreUnknown = true) data class Address( - val newaddr: NewAddress, + @JsonProperty("newaddr") val newAddr: NewAddress, val region: Region, - val addrbunho: String? = null, + @JsonProperty("addrbunho") val addrBunho: String?, ) @JsonIgnoreProperties(ignoreUnknown = true) data class NewAddress( - val newaddrfull: String, - val bsizonno: String, + @JsonProperty("newaddrfull") val newAddrFull: String, + @JsonProperty("bsizonno") val bsiZonNo: String, ) @JsonIgnoreProperties(ignoreUnknown = true) data class Region( val name3: String, - val fullname: String, - val newaddrfullname: String, + @JsonProperty("fullname") val fullName: String, + @JsonProperty("newaddrfullname") val newAddrFullName: String, ) @JsonIgnoreProperties(ignoreUnknown = true) data class Category( - @JsonProperty("catename") - val firstCategoryName: String, - @JsonProperty("cate1name") - val secondCategoryName: String, + @JsonProperty("catename") val firstCategoryName: String, + @JsonProperty("cate1name") val secondCategoryName: String, ) @JsonIgnoreProperties(ignoreUnknown = true) data class Feedback( - @JsonProperty("scoresum") - val sumOfScore: Int, - @JsonProperty("scorecnt") - val countOfScore: Int, - @JsonProperty("blogrvwcnt") - val countOfBlogReview: Int, - @JsonProperty("comntcnt") - val countOfReviewComment: Int, - @JsonProperty("allphotocnt") - val countOfAllPhoto: Int, - @JsonProperty("reviewphotocnt") - val countOfPhotoReview: Int, + @JsonProperty("scoresum") val sumOfScore: Int = 0, + @JsonProperty("scorecnt") val countOfScore: Int = 0, + @JsonProperty("blogrvwcnt") val countOfBlogReview: Int = 0, + @JsonProperty("comntcnt") val countOfReviewComment: Int = 0, + @JsonProperty("allphotocnt") val countOfAllPhoto: Int = 0, + @JsonProperty("reviewphotocnt") val countOfPhotoReview: Int = 0, ) { fun calculateStarGrade(): Double? = if (countOfScore > 0) sumOfScore.toDouble() / countOfScore else null } @JsonIgnoreProperties(ignoreUnknown = true) data class OpenHour( - val periodList: List?, - val offdayList: List?, + @JsonProperty("periodlist") val periodList: List?, + @JsonProperty("offdaylist") val offDayList: List?, ) { fun toPrintFormat(): String? { - val openingHour = - periodList?.first { it.periodName == OPEN_HOUR_PERIOD_NAME } - ?.toPrintFormat() - val offdaySchedule = - offdayList?.map { it.toPrintFormat() } - ?.joinToString { JOINER } - return if (openingHour == null && offdaySchedule == null) null else "$openingHour$JOINER$offdaySchedule" + val openingHour = periodList?.firstOrNull { it.periodName == OPEN_HOUR_PERIOD_NAME }?.toPrintFormat() + val offDaySchedule = offDayList?.mapNotNull { it.toPrintFormat() }?.joinToString(JOINER) + return if (openingHour == null && offDaySchedule.isNullOrEmpty()) { + null + } else { + "$openingHour$JOINER$offDaySchedule".trim() + } } companion object { @@ -127,37 +115,41 @@ data class LemonPlaceInfoResponse( @JsonIgnoreProperties(ignoreUnknown = true) data class Period( - val periodName: String, - val timeList: List