diff --git a/src/main/kotlin/com/retrip/map/application/in/request/LocationCreateRequest.kt b/src/main/kotlin/com/retrip/map/application/in/request/LocationCreateRequest.kt index c594a43..0642155 100644 --- a/src/main/kotlin/com/retrip/map/application/in/request/LocationCreateRequest.kt +++ b/src/main/kotlin/com/retrip/map/application/in/request/LocationCreateRequest.kt @@ -6,22 +6,12 @@ import io.swagger.v3.oas.annotations.media.Schema @Schema(description = "장소 생성 Request") data class LocationCreateRequest( val name: String, - val category: String, - val description: String?, - val telephone: String?, - val address: String?, - val roadAddress: String?, val latitude: Double, val longitude: Double, ) { fun to(): Location { return Location.create( name, - category, - description, - telephone, - address, - roadAddress, latitude, longitude ) diff --git a/src/main/kotlin/com/retrip/map/application/in/request/LocationDetailCreateRequest.kt b/src/main/kotlin/com/retrip/map/application/in/request/LocationDetailCreateRequest.kt new file mode 100644 index 0000000..88eb59f --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/request/LocationDetailCreateRequest.kt @@ -0,0 +1,29 @@ +package com.retrip.map.application.`in`.request + +import com.retrip.map.domain.entity.LocationDetail +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(description = "장소 상세 생성 Request") +data class LocationDetailCreateRequest( + val name: String, + val category: String, + val description: String?, + val telephone: String?, + val address: String?, + val roadAddress: String?, + val latitude: Double, + val longitude: Double, +) { + fun to(): LocationDetail { + return LocationDetail.create( + name, + category, + description, + telephone, + address, + roadAddress, + latitude, + longitude + ) + } +} diff --git a/src/main/kotlin/com/retrip/map/application/in/request/LocationDetailUpdateRequest.kt b/src/main/kotlin/com/retrip/map/application/in/request/LocationDetailUpdateRequest.kt new file mode 100644 index 0000000..15b42bf --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/request/LocationDetailUpdateRequest.kt @@ -0,0 +1,16 @@ +package com.retrip.map.application.`in`.request + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(description = "장소 상세 수정 Request") +data class LocationDetailUpdateRequest( + val name: String, + val category: String, + val description: String?, + val telephone: String?, + val address: String?, + val roadAddress: String?, + val latitude: Double, + val longitude: Double, +) { +} diff --git a/src/main/kotlin/com/retrip/map/application/in/request/LocationUpdateRequest.kt b/src/main/kotlin/com/retrip/map/application/in/request/LocationUpdateRequest.kt index 34ff40f..d60e45a 100644 --- a/src/main/kotlin/com/retrip/map/application/in/request/LocationUpdateRequest.kt +++ b/src/main/kotlin/com/retrip/map/application/in/request/LocationUpdateRequest.kt @@ -1,16 +1,10 @@ package com.retrip.map.application.`in`.request -import com.retrip.map.domain.entity.Location import io.swagger.v3.oas.annotations.media.Schema @Schema(description = "장소 수정 Request") data class LocationUpdateRequest( val name: String, - val category: String, - val description: String?, - val telephone: String?, - val address: String?, - val roadAddress: String?, val latitude: Double, val longitude: Double, ) { diff --git a/src/main/kotlin/com/retrip/map/application/in/response/LocationCreateResponse.kt b/src/main/kotlin/com/retrip/map/application/in/response/LocationCreateResponse.kt index 0108b76..57c766c 100644 --- a/src/main/kotlin/com/retrip/map/application/in/response/LocationCreateResponse.kt +++ b/src/main/kotlin/com/retrip/map/application/in/response/LocationCreateResponse.kt @@ -3,21 +3,12 @@ package com.retrip.map.application.`in`.response import io.swagger.v3.oas.annotations.media.Schema import java.util.UUID +@Schema(description = "장소 생성 응답") data class LocationCreateResponse( @Schema(description = "장소 id") val id: UUID, @Schema(description = "장소명") val name: String, - @Schema(description = "장소 카테고리") - val category: String, - @Schema(description = "장소 설명") - val description: String?, - @Schema(description = "장소 전화번호") - val telephone: String?, - @Schema(description = "장소 주소") - val address: String?, - @Schema(description = "장소 도로명 주소") - val roadAddress: String?, @Schema(description = "장소 위도") val latitude: Double?, @Schema(description = "장소 경도") diff --git a/src/main/kotlin/com/retrip/map/application/in/response/LocationDetailCreateResponse.kt b/src/main/kotlin/com/retrip/map/application/in/response/LocationDetailCreateResponse.kt new file mode 100644 index 0000000..36aa5d3 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/response/LocationDetailCreateResponse.kt @@ -0,0 +1,27 @@ +package com.retrip.map.application.`in`.response + +import io.swagger.v3.oas.annotations.media.Schema +import java.util.UUID + +@Schema(description = "장소 상세 생성 응답") +data class LocationDetailCreateResponse( + @Schema(description = "장소 상세 id") + val id: UUID, + @Schema(description = "장소 상세명") + val name: String, + @Schema(description = "장소 상세 카테고리") + val category: String, + @Schema(description = "장소 상세 설명") + val description: String?, + @Schema(description = "장소 상세 전화번호") + val telephone: String?, + @Schema(description = "장소 상세 주소") + val address: String?, + @Schema(description = "장소 상세 도로명 주소") + val roadAddress: String?, + @Schema(description = "장소 상세 위도") + val latitude: Double?, + @Schema(description = "장소 상세 경도") + val longitude: Double?, +) { +} diff --git a/src/main/kotlin/com/retrip/map/application/in/response/LocationDetailResponse.kt b/src/main/kotlin/com/retrip/map/application/in/response/LocationDetailResponse.kt new file mode 100644 index 0000000..47e8e63 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/response/LocationDetailResponse.kt @@ -0,0 +1,27 @@ +package com.retrip.map.application.`in`.response + +import io.swagger.v3.oas.annotations.media.Schema +import java.util.UUID + +@Schema(description = "장소 상세 조회 Response") +data class LocationDetailResponse( + @Schema(description = "장소 상세 id") + val id: UUID, + @Schema(description = "장소 상세명") + val name: String, + @Schema(description = "장소 상세 카테고리") + val category: String, + @Schema(description = "장소 상세 설명") + val description: String?, + @Schema(description = "장소 상세 전화번호") + val telephone: String?, + @Schema(description = "장소 상세 주소") + val address: String?, + @Schema(description = "장소 상세 도로명 주소") + val roadAddress: String?, + @Schema(description = "장소 상세 위도") + val latitude: Double?, + @Schema(description = "장소 상세 경도") + val longitude: Double?, +) { +} diff --git a/src/main/kotlin/com/retrip/map/application/in/response/LocationDetailUpdateResponse.kt b/src/main/kotlin/com/retrip/map/application/in/response/LocationDetailUpdateResponse.kt new file mode 100644 index 0000000..5b74715 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/response/LocationDetailUpdateResponse.kt @@ -0,0 +1,27 @@ +package com.retrip.map.application.`in`.response + +import io.swagger.v3.oas.annotations.media.Schema +import java.util.UUID + +@Schema(description = "장소 상세 업데이트 Response") +data class LocationDetailUpdateResponse( + @Schema(description = "장소 상세 id") + val id: UUID, + @Schema(description = "장소 상세명") + val name: String, + @Schema(description = "장소 상세 카테고리") + val category: String, + @Schema(description = "장소 상세 설명") + val description: String?, + @Schema(description = "장소 상세 전화번호") + val telephone: String?, + @Schema(description = "장소 상세 주소") + val address: String?, + @Schema(description = "장소 상세 도로명 주소") + val roadAddress: String?, + @Schema(description = "장소 상세 위도") + val latitude: Double?, + @Schema(description = "장소 상세 경도") + val longitude: Double?, +) { +} diff --git a/src/main/kotlin/com/retrip/map/application/in/response/LocationResponse.kt b/src/main/kotlin/com/retrip/map/application/in/response/LocationResponse.kt index 6c6bb6f..e79bd38 100644 --- a/src/main/kotlin/com/retrip/map/application/in/response/LocationResponse.kt +++ b/src/main/kotlin/com/retrip/map/application/in/response/LocationResponse.kt @@ -9,16 +9,6 @@ data class LocationResponse( val id: UUID, @Schema(description = "장소명") val name: String, - @Schema(description = "장소 카테고리") - val category: String, - @Schema(description = "장소 설명") - val description: String?, - @Schema(description = "장소 전화번호") - val telephone: String?, - @Schema(description = "장소 주소") - val address: String?, - @Schema(description = "장소 도로명 주소") - val roadAddress: String?, @Schema(description = "장소 위도") val latitude: Double?, @Schema(description = "장소 경도") diff --git a/src/main/kotlin/com/retrip/map/application/in/response/LocationSearchResponse.kt b/src/main/kotlin/com/retrip/map/application/in/response/LocationSearchResponse.kt new file mode 100644 index 0000000..eace4b9 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/response/LocationSearchResponse.kt @@ -0,0 +1,17 @@ +package com.retrip.map.application.`in`.response + +import io.swagger.v3.oas.annotations.media.Schema +import java.util.UUID + +@Schema(description = "장소 빠른 조회 Response") +data class LocationSearchResponse( + @Schema(description = "장소 id") + val id: UUID, + @Schema(description = "장소명") + val name: String, + @Schema(description = "장소 위도") + val latitude: Double?, + @Schema(description = "장소 경도") + val longitude: Double?, +) { +} diff --git a/src/main/kotlin/com/retrip/map/application/in/response/LocationUpdateResponse.kt b/src/main/kotlin/com/retrip/map/application/in/response/LocationUpdateResponse.kt index 9d45629..47dbf1f 100644 --- a/src/main/kotlin/com/retrip/map/application/in/response/LocationUpdateResponse.kt +++ b/src/main/kotlin/com/retrip/map/application/in/response/LocationUpdateResponse.kt @@ -3,21 +3,12 @@ package com.retrip.map.application.`in`.response import io.swagger.v3.oas.annotations.media.Schema import java.util.UUID +@Schema(description = "장소 업데이트 Response") data class LocationUpdateResponse( @Schema(description = "장소 id") val id: UUID, @Schema(description = "장소명") val name: String, - @Schema(description = "장소 카테고리") - val category: String, - @Schema(description = "장소 설명") - val description: String?, - @Schema(description = "장소 전화번호") - val telephone: String?, - @Schema(description = "장소 주소") - val address: String?, - @Schema(description = "장소 도로명 주소") - val roadAddress: String?, @Schema(description = "장소 위도") val latitude: Double?, @Schema(description = "장소 경도") diff --git a/src/main/kotlin/com/retrip/map/application/in/service/LocationDetailIndexService.kt b/src/main/kotlin/com/retrip/map/application/in/service/LocationDetailIndexService.kt new file mode 100644 index 0000000..a891712 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/service/LocationDetailIndexService.kt @@ -0,0 +1,17 @@ +package com.retrip.map.application.`in`.service + +import com.retrip.map.application.`in`.usecase.LocationIndexUseCase +import com.retrip.map.application.out.repository.LocationDetailElasticRepository +import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDetailDocument +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +@Transactional +class LocationDetailIndexService( + private val locationDetailElasticRepository: LocationDetailElasticRepository +) : LocationIndexUseCase { + override fun indexLocationDetailDocuments(documents: List?) { + documents?.let { locationDetailElasticRepository.saveAll(it) } + } +} diff --git a/src/main/kotlin/com/retrip/map/application/in/service/LocationDetailService.kt b/src/main/kotlin/com/retrip/map/application/in/service/LocationDetailService.kt new file mode 100644 index 0000000..44163a3 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/service/LocationDetailService.kt @@ -0,0 +1,79 @@ +package com.retrip.map.application.`in`.service + +import co.elastic.clients.elasticsearch.core.search.ContextBuilders.location +import com.retrip.map.application.`in`.request.LocationDetailCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailUpdateRequest +import com.retrip.map.application.`in`.response.LocationDetailCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailResponse +import com.retrip.map.application.`in`.response.LocationDetailUpdateResponse +import com.retrip.map.application.`in`.usecase.LocationDetailUseCase +import com.retrip.map.application.out.repository.LocationDetailQueryRepository +import com.retrip.map.application.out.repository.LocationDetailRepository +import com.retrip.map.domain.exception.LocationDetailNotFoundException +import com.retrip.map.domain.exception.common.RequireException +import lombok.RequiredArgsConstructor +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.* + +@Service +@RequiredArgsConstructor +@Transactional +class LocationDetailService( + val locationDetailRepository: LocationDetailRepository, + val locationDetailQueryRepository: LocationDetailQueryRepository +) : LocationDetailUseCase { + + @Transactional(readOnly = true) + override fun getLocationDetail(id: UUID?, page: Pageable): Page { + return locationDetailQueryRepository.findLocationDetails(id, page) + } + + override fun createLocationDetail(request: LocationDetailCreateRequest): LocationDetailCreateResponse { + val locationDetail = locationDetailRepository.save(request.to()) + return LocationDetailCreateResponse( + locationDetail.id ?: throw LocationDetailNotFoundException(), + locationDetail.name?.value ?: throw RequireException(), + locationDetail.category?.value ?: throw RequireException(), + locationDetail.description?.value, + locationDetail.telephone, + locationDetail.address?.address, + locationDetail.address?.roadAddress, + locationDetail.geoPoint?.latitude, + locationDetail.geoPoint?.longitude, + ) + } + + override fun updateLocationDetail(id: UUID, request: LocationDetailUpdateRequest): LocationDetailUpdateResponse { + val location = locationDetailRepository.findByIdOrNull(id) ?: throw LocationDetailNotFoundException() + location.update( + request.name, + request.category, + request.description, + request.telephone, + request.address, + request.roadAddress, + request.latitude, + request.longitude, + ) + return LocationDetailUpdateResponse( + location.id ?: throw LocationDetailNotFoundException(), + location.name?.value ?: throw RequireException(), + location.category?.value ?: throw RequireException(), + location.description?.value, + location.telephone, + location.address?.address, + location.address?.roadAddress, + location.geoPoint?.latitude, + location.geoPoint?.longitude, + ) + } + + override fun deleteLocationDetail(locationDetailId: UUID) { + locationDetailRepository.deleteById(locationDetailId) + } +} + diff --git a/src/main/kotlin/com/retrip/map/application/in/service/LocationIndexService.kt b/src/main/kotlin/com/retrip/map/application/in/service/LocationIndexService.kt deleted file mode 100644 index 9be94d7..0000000 --- a/src/main/kotlin/com/retrip/map/application/in/service/LocationIndexService.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.retrip.map.application.`in`.service - -import com.retrip.map.application.`in`.usecase.LocationIndexUseCase -import com.retrip.map.application.out.repository.LocationElasticRepository -import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDocument -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -@Transactional -class LocationIndexService( - private val locationElasticRepository: LocationElasticRepository -) : LocationIndexUseCase { - override fun indexLocationDocuments(documents: List?) { - documents?.let { locationElasticRepository.saveAll(it) } - } -} diff --git a/src/main/kotlin/com/retrip/map/application/in/service/LocationSearchService.kt b/src/main/kotlin/com/retrip/map/application/in/service/LocationSearchService.kt new file mode 100644 index 0000000..29a7214 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/service/LocationSearchService.kt @@ -0,0 +1,55 @@ +package com.retrip.map.application.`in`.service + +import com.retrip.map.application.`in`.request.LocationCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailUpdateRequest +import com.retrip.map.application.`in`.request.LocationUpdateRequest +import com.retrip.map.application.`in`.response.LocationCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailResponse +import com.retrip.map.application.`in`.response.LocationDetailUpdateResponse +import com.retrip.map.application.`in`.response.LocationResponse +import com.retrip.map.application.`in`.response.LocationSearchResponse +import com.retrip.map.application.`in`.response.LocationUpdateResponse +import com.retrip.map.application.`in`.usecase.LocationDetailUseCase +import com.retrip.map.application.`in`.usecase.LocationSearchUseCase +import com.retrip.map.application.`in`.usecase.LocationUseCase +import com.retrip.map.application.out.repository.LocationDetailQueryRepository +import com.retrip.map.application.out.repository.LocationDetailRepository +import com.retrip.map.application.out.repository.LocationElasticRepository +import com.retrip.map.application.out.repository.LocationQueryRepository +import com.retrip.map.application.out.repository.LocationRepository +import com.retrip.map.domain.exception.LocationDetailNotFoundException +import com.retrip.map.domain.exception.LocationNotFoundException +import com.retrip.map.domain.exception.common.RequireException +import lombok.RequiredArgsConstructor +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.data.jpa.domain.AbstractPersistable_.id +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.util.* + +@Service +@RequiredArgsConstructor +@Transactional +class LocationSearchService( + val locationElasticRepository: LocationElasticRepository +) : LocationSearchUseCase { + + @Transactional(readOnly = true) + override fun getLocation(name: String?, page: Pageable): Page { + val locations = locationElasticRepository.findByNameContaining(name, page) + return locations.map { + LocationSearchResponse( + id = it.id, + name = it.name, + latitude = it.latitude, + longitude = it.longitude, + ) + } + } + +} + diff --git a/src/main/kotlin/com/retrip/map/application/in/service/LocationService.kt b/src/main/kotlin/com/retrip/map/application/in/service/LocationService.kt index 4f9d207..51f2b72 100644 --- a/src/main/kotlin/com/retrip/map/application/in/service/LocationService.kt +++ b/src/main/kotlin/com/retrip/map/application/in/service/LocationService.kt @@ -1,18 +1,30 @@ package com.retrip.map.application.`in`.service import com.retrip.map.application.`in`.request.LocationCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailUpdateRequest import com.retrip.map.application.`in`.request.LocationUpdateRequest import com.retrip.map.application.`in`.response.LocationCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailResponse +import com.retrip.map.application.`in`.response.LocationDetailUpdateResponse import com.retrip.map.application.`in`.response.LocationResponse import com.retrip.map.application.`in`.response.LocationUpdateResponse +import com.retrip.map.application.`in`.usecase.LocationDetailUseCase import com.retrip.map.application.`in`.usecase.LocationUseCase +import com.retrip.map.application.out.repository.LocationDetailQueryRepository +import com.retrip.map.application.out.repository.LocationDetailRepository +import com.retrip.map.application.out.repository.LocationElasticRepository import com.retrip.map.application.out.repository.LocationQueryRepository import com.retrip.map.application.out.repository.LocationRepository +import com.retrip.map.domain.exception.LocationDetailNotFoundException import com.retrip.map.domain.exception.LocationNotFoundException import com.retrip.map.domain.exception.common.RequireException +import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDocument import lombok.RequiredArgsConstructor import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable +import org.springframework.data.jpa.domain.AbstractPersistable_.id import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -23,6 +35,7 @@ import java.util.* @Transactional class LocationService( val locationRepository: LocationRepository, + val locationElasticRepository: LocationElasticRepository, val locationQueryRepository: LocationQueryRepository ) : LocationUseCase { @@ -33,14 +46,10 @@ class LocationService( override fun createLocation(request: LocationCreateRequest): LocationCreateResponse { val location = locationRepository.save(request.to()) + locationElasticRepository.save(LocationDocument.of(location)) return LocationCreateResponse( location.id ?: throw LocationNotFoundException(), location.name?.value ?: throw RequireException(), - location.category?.value ?: throw RequireException(), - location.description?.value, - location.telephone, - location.address?.address, - location.address?.roadAddress, location.geoPoint?.latitude, location.geoPoint?.longitude, ) @@ -48,24 +57,16 @@ class LocationService( override fun updateLocation(id: UUID, request: LocationUpdateRequest): LocationUpdateResponse { val location = locationRepository.findByIdOrNull(id) ?: throw LocationNotFoundException() + locationElasticRepository.deleteById(id) location.update( request.name, - request.category, - request.description, - request.telephone, - request.address, - request.roadAddress, request.latitude, request.longitude, ) + locationElasticRepository.save(LocationDocument.of(location)) return LocationUpdateResponse( - location.id ?: throw LocationNotFoundException(), + location.id ?: throw LocationDetailNotFoundException(), location.name?.value ?: throw RequireException(), - location.category?.value ?: throw RequireException(), - location.description?.value, - location.telephone, - location.address?.address, - location.address?.roadAddress, location.geoPoint?.latitude, location.geoPoint?.longitude, ) @@ -73,6 +74,7 @@ class LocationService( override fun deleteLocation(locationId: UUID) { locationRepository.deleteById(locationId) + locationElasticRepository.deleteById(locationId) } } diff --git a/src/main/kotlin/com/retrip/map/application/in/usecase/LocationDetailUseCase.kt b/src/main/kotlin/com/retrip/map/application/in/usecase/LocationDetailUseCase.kt new file mode 100644 index 0000000..46923da --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/usecase/LocationDetailUseCase.kt @@ -0,0 +1,17 @@ +package com.retrip.map.application.`in`.usecase + +import com.retrip.map.application.`in`.request.LocationDetailCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailUpdateRequest +import com.retrip.map.application.`in`.response.LocationDetailCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailResponse +import com.retrip.map.application.`in`.response.LocationDetailUpdateResponse +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import java.util.UUID + +interface LocationDetailUseCase { + fun createLocationDetail(request: LocationDetailCreateRequest): LocationDetailCreateResponse + fun updateLocationDetail(id: UUID, request: LocationDetailUpdateRequest): LocationDetailUpdateResponse + fun deleteLocationDetail(locationDetailId: UUID) + fun getLocationDetail(id: UUID?, page: Pageable): Page +} diff --git a/src/main/kotlin/com/retrip/map/application/in/usecase/LocationIndexUseCase.kt b/src/main/kotlin/com/retrip/map/application/in/usecase/LocationIndexUseCase.kt index d5f43c4..3a83821 100644 --- a/src/main/kotlin/com/retrip/map/application/in/usecase/LocationIndexUseCase.kt +++ b/src/main/kotlin/com/retrip/map/application/in/usecase/LocationIndexUseCase.kt @@ -1,7 +1,7 @@ package com.retrip.map.application.`in`.usecase -import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDocument +import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDetailDocument interface LocationIndexUseCase { - fun indexLocationDocuments(documents: List?) + fun indexLocationDetailDocuments(documents: List?) } diff --git a/src/main/kotlin/com/retrip/map/application/in/usecase/LocationSearchUseCase.kt b/src/main/kotlin/com/retrip/map/application/in/usecase/LocationSearchUseCase.kt new file mode 100644 index 0000000..ed7aa00 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/in/usecase/LocationSearchUseCase.kt @@ -0,0 +1,15 @@ +package com.retrip.map.application.`in`.usecase + +import com.retrip.map.application.`in`.request.LocationCreateRequest +import com.retrip.map.application.`in`.request.LocationUpdateRequest +import com.retrip.map.application.`in`.response.LocationCreateResponse +import com.retrip.map.application.`in`.response.LocationResponse +import com.retrip.map.application.`in`.response.LocationSearchResponse +import com.retrip.map.application.`in`.response.LocationUpdateResponse +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import java.util.* + +interface LocationSearchUseCase { + fun getLocation(name: String?, page: Pageable): Page +} diff --git a/src/main/kotlin/com/retrip/map/application/in/usecase/LocationUseCase.kt b/src/main/kotlin/com/retrip/map/application/in/usecase/LocationUseCase.kt index f65dc79..afb5f72 100644 --- a/src/main/kotlin/com/retrip/map/application/in/usecase/LocationUseCase.kt +++ b/src/main/kotlin/com/retrip/map/application/in/usecase/LocationUseCase.kt @@ -7,7 +7,7 @@ import com.retrip.map.application.`in`.response.LocationResponse import com.retrip.map.application.`in`.response.LocationUpdateResponse import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable -import java.util.UUID +import java.util.* interface LocationUseCase { fun createLocation(request: LocationCreateRequest): LocationCreateResponse diff --git a/src/main/kotlin/com/retrip/map/application/out/repository/LocationDetailElasticRepository.kt b/src/main/kotlin/com/retrip/map/application/out/repository/LocationDetailElasticRepository.kt new file mode 100644 index 0000000..c9a0d9f --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/out/repository/LocationDetailElasticRepository.kt @@ -0,0 +1,11 @@ +package com.retrip.map.application.out.repository + +import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDetailDocument +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository +import org.springframework.stereotype.Repository +import java.util.UUID + +@Repository +interface LocationDetailElasticRepository: ElasticsearchRepository { + fun findFirstByOrderByEditedAtDesc(): LocationDetailDocument? +} diff --git a/src/main/kotlin/com/retrip/map/application/out/repository/LocationDetailQueryRepository.kt b/src/main/kotlin/com/retrip/map/application/out/repository/LocationDetailQueryRepository.kt new file mode 100644 index 0000000..7930f54 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/out/repository/LocationDetailQueryRepository.kt @@ -0,0 +1,13 @@ +package com.retrip.map.application.out.repository + +import com.retrip.map.application.`in`.response.LocationDetailResponse +import com.retrip.map.domain.entity.LocationDetail +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import java.time.LocalDateTime +import java.util.UUID + +interface LocationDetailQueryRepository { + fun findLocationDetails(id: UUID?, page: Pageable): Page + fun findLocationDetailsByEditedAt( editedAt: LocalDateTime): List +} diff --git a/src/main/kotlin/com/retrip/map/application/out/repository/LocationDetailRepository.kt b/src/main/kotlin/com/retrip/map/application/out/repository/LocationDetailRepository.kt new file mode 100644 index 0000000..a95de9f --- /dev/null +++ b/src/main/kotlin/com/retrip/map/application/out/repository/LocationDetailRepository.kt @@ -0,0 +1,8 @@ +package com.retrip.map.application.out.repository + +import com.retrip.map.domain.entity.LocationDetail +import org.springframework.data.jpa.repository.JpaRepository +import java.util.UUID + +interface LocationDetailRepository: JpaRepository { +} diff --git a/src/main/kotlin/com/retrip/map/application/out/repository/LocationElasticRepository.kt b/src/main/kotlin/com/retrip/map/application/out/repository/LocationElasticRepository.kt index 1d2e2e3..ca695ca 100644 --- a/src/main/kotlin/com/retrip/map/application/out/repository/LocationElasticRepository.kt +++ b/src/main/kotlin/com/retrip/map/application/out/repository/LocationElasticRepository.kt @@ -1,11 +1,14 @@ package com.retrip.map.application.out.repository import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDocument +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable import org.springframework.data.elasticsearch.repository.ElasticsearchRepository import org.springframework.stereotype.Repository import java.util.UUID @Repository interface LocationElasticRepository: ElasticsearchRepository { - fun findFirstByOrderByEditedAtDesc(): LocationDocument? + // 이름 포함 검색 + 페이징 + fun findByNameContaining(name: String?, pageable: Pageable): Page } diff --git a/src/main/kotlin/com/retrip/map/application/out/repository/LocationQueryRepository.kt b/src/main/kotlin/com/retrip/map/application/out/repository/LocationQueryRepository.kt index 55b5798..a34c31d 100644 --- a/src/main/kotlin/com/retrip/map/application/out/repository/LocationQueryRepository.kt +++ b/src/main/kotlin/com/retrip/map/application/out/repository/LocationQueryRepository.kt @@ -1,14 +1,13 @@ package com.retrip.map.application.out.repository +import com.retrip.map.application.`in`.response.LocationDetailResponse import com.retrip.map.application.`in`.response.LocationResponse -import com.retrip.map.domain.entity.Location +import com.retrip.map.domain.entity.LocationDetail import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable -import java.time.LocalDate import java.time.LocalDateTime import java.util.UUID interface LocationQueryRepository { fun findLocations(id: UUID?, page: Pageable): Page - fun findLocationsByEditedAt( editedAt: LocalDateTime): List } diff --git a/src/main/kotlin/com/retrip/map/domain/entity/Location.kt b/src/main/kotlin/com/retrip/map/domain/entity/Location.kt index e35e294..b7fd3ff 100644 --- a/src/main/kotlin/com/retrip/map/domain/entity/Location.kt +++ b/src/main/kotlin/com/retrip/map/domain/entity/Location.kt @@ -1,8 +1,7 @@ package com.retrip.map.domain.entity -import com.retrip.map.domain.vo.LocationAddress -import com.retrip.map.domain.vo.LocationCategory -import com.retrip.map.domain.vo.LocationDescription +import com.retrip.map.domain.vo.LocationDetailGeoPoint +import com.retrip.map.domain.vo.LocationDetailName import com.retrip.map.domain.vo.LocationGeoPoint import com.retrip.map.domain.vo.LocationName import jakarta.persistence.Column @@ -28,18 +27,6 @@ class Location( @Embedded var name: LocationName? = null, - @Embedded - var category: LocationCategory? = null, - - @Embedded - var description: LocationDescription? = null, - - @Column(name = "telephone") - var telephone: String? = null, - - @Embedded - var address: LocationAddress? = null, - @Embedded var geoPoint: LocationGeoPoint? = null, @@ -48,40 +35,22 @@ class Location( ): BaseEntity() { fun update( name: String, - category: String, - description: String?, - telephone: String?, - address: String?, - roadAddress: String?, latitude: Double, longitude: Double ) { this.name = LocationName(name) - this.category = LocationCategory(category) - this.description = LocationDescription(description) - this.telephone = telephone - this.address = LocationAddress(address, roadAddress) this.geoPoint = LocationGeoPoint(latitude, longitude) } companion object { fun create( name: String, - category: String, - description: String?, - telephone: String?, - address: String?, - roadAddress: String?, latitude: Double, longitude: Double ): Location { return Location( id = UUID.randomUUID(), name = LocationName(name), - category = LocationCategory(category), - description = LocationDescription(description), - telephone = telephone, - address = LocationAddress(address, roadAddress), geoPoint = LocationGeoPoint(latitude, longitude) ) } diff --git a/src/main/kotlin/com/retrip/map/domain/entity/LocationDetail.kt b/src/main/kotlin/com/retrip/map/domain/entity/LocationDetail.kt new file mode 100644 index 0000000..e7c01cb --- /dev/null +++ b/src/main/kotlin/com/retrip/map/domain/entity/LocationDetail.kt @@ -0,0 +1,89 @@ +package com.retrip.map.domain.entity + +import com.retrip.map.domain.vo.LocationDetailAddress +import com.retrip.map.domain.vo.LocationDetailCategory +import com.retrip.map.domain.vo.LocationDetailDescription +import com.retrip.map.domain.vo.LocationDetailGeoPoint +import com.retrip.map.domain.vo.LocationDetailName +import jakarta.persistence.Column +import jakarta.persistence.Embedded +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import jakarta.persistence.Version +import lombok.AccessLevel +import lombok.NoArgsConstructor +import lombok.Setter +import java.util.* + +@Entity +@Table(name = "location-details") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Setter(value = AccessLevel.PROTECTED) +class LocationDetail( + @Id + @Column(columnDefinition = "varbinary(16)") + val id: UUID? = null, + + @Embedded + var name: LocationDetailName? = null, + + @Embedded + var category: LocationDetailCategory? = null, + + @Embedded + var description: LocationDetailDescription? = null, + + @Column(name = "telephone") + var telephone: String? = null, + + @Embedded + var address: LocationDetailAddress? = null, + + @Embedded + var geoPoint: LocationDetailGeoPoint? = null, + + @Version + private val version: Long? = null, +): BaseEntity() { + fun update( + name: String, + category: String, + description: String?, + telephone: String?, + address: String?, + roadAddress: String?, + latitude: Double, + longitude: Double + ) { + this.name = LocationDetailName(name) + this.category = LocationDetailCategory(category) + this.description = LocationDetailDescription(description) + this.telephone = telephone + this.address = LocationDetailAddress(address, roadAddress) + this.geoPoint = LocationDetailGeoPoint(latitude, longitude) + } + + companion object { + fun create( + name: String, + category: String, + description: String?, + telephone: String?, + address: String?, + roadAddress: String?, + latitude: Double, + longitude: Double + ): LocationDetail { + return LocationDetail( + id = UUID.randomUUID(), + name = LocationDetailName(name), + category = LocationDetailCategory(category), + description = LocationDetailDescription(description), + telephone = telephone, + address = LocationDetailAddress(address, roadAddress), + geoPoint = LocationDetailGeoPoint(latitude, longitude) + ) + } + } +} diff --git a/src/main/kotlin/com/retrip/map/domain/exception/LocationDetailNotFoundException.kt b/src/main/kotlin/com/retrip/map/domain/exception/LocationDetailNotFoundException.kt new file mode 100644 index 0000000..2896020 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/domain/exception/LocationDetailNotFoundException.kt @@ -0,0 +1,12 @@ +package com.retrip.map.domain.exception + +import com.retrip.map.domain.exception.common.BusinessException +import com.retrip.map.domain.exception.common.ErrorCode + +class LocationDetailNotFoundException : BusinessException { + constructor(): super(ErrorCode.LOCATION_DETAIL_NOT_FOUND) {} + constructor(errorCode: ErrorCode): super(errorCode) {} + constructor(message: String): super(ErrorCode.LOCATION_DETAIL_NOT_FOUND, message) {} + constructor(errorCode: ErrorCode, message: String): super(errorCode, message) {} + +} diff --git a/src/main/kotlin/com/retrip/map/domain/exception/LocationNotFoundException.kt b/src/main/kotlin/com/retrip/map/domain/exception/LocationNotFoundException.kt index adfb953..1c56103 100644 --- a/src/main/kotlin/com/retrip/map/domain/exception/LocationNotFoundException.kt +++ b/src/main/kotlin/com/retrip/map/domain/exception/LocationNotFoundException.kt @@ -2,7 +2,6 @@ package com.retrip.map.domain.exception import com.retrip.map.domain.exception.common.BusinessException import com.retrip.map.domain.exception.common.ErrorCode -import jakarta.persistence.EntityNotFoundException class LocationNotFoundException : BusinessException { constructor(): super(ErrorCode.LOCATION_NOT_FOUND) {} diff --git a/src/main/kotlin/com/retrip/map/domain/exception/common/ErrorCode.kt b/src/main/kotlin/com/retrip/map/domain/exception/common/ErrorCode.kt index ebde91b..562d6e8 100644 --- a/src/main/kotlin/com/retrip/map/domain/exception/common/ErrorCode.kt +++ b/src/main/kotlin/com/retrip/map/domain/exception/common/ErrorCode.kt @@ -17,4 +17,5 @@ enum class ErrorCode( ILLEGAL_STATE(BAD_REQUEST, "Common-005", "Illegal state"), LOCATION_NOT_FOUND(BAD_REQUEST, "Location-001", "로케이션 엔티티를 찾을 수 없습니다."), + LOCATION_DETAIL_NOT_FOUND(BAD_REQUEST, "Location-002", "로케이션 디테일 엔티티를 찾을 수 없습니다."), } diff --git a/src/main/kotlin/com/retrip/map/domain/vo/LocationAddress.kt b/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailAddress.kt similarity index 97% rename from src/main/kotlin/com/retrip/map/domain/vo/LocationAddress.kt rename to src/main/kotlin/com/retrip/map/domain/vo/LocationDetailAddress.kt index 6237ea7..cebfc2c 100644 --- a/src/main/kotlin/com/retrip/map/domain/vo/LocationAddress.kt +++ b/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailAddress.kt @@ -5,7 +5,7 @@ import jakarta.persistence.Embeddable @Embeddable -class LocationAddress() { +class LocationDetailAddress() { @Column(name = "address") var address: String? = null diff --git a/src/main/kotlin/com/retrip/map/domain/vo/LocationCategory.kt b/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailCategory.kt similarity index 92% rename from src/main/kotlin/com/retrip/map/domain/vo/LocationCategory.kt rename to src/main/kotlin/com/retrip/map/domain/vo/LocationDetailCategory.kt index 03f98ae..31420a3 100644 --- a/src/main/kotlin/com/retrip/map/domain/vo/LocationCategory.kt +++ b/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailCategory.kt @@ -5,7 +5,7 @@ import jakarta.persistence.Embeddable @Embeddable -class LocationCategory() { +class LocationDetailCategory() { @Column(name = "category") var value: String = "" diff --git a/src/main/kotlin/com/retrip/map/domain/vo/LocationDescription.kt b/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailDescription.kt similarity index 94% rename from src/main/kotlin/com/retrip/map/domain/vo/LocationDescription.kt rename to src/main/kotlin/com/retrip/map/domain/vo/LocationDetailDescription.kt index 5be2f8d..1bb0ac8 100644 --- a/src/main/kotlin/com/retrip/map/domain/vo/LocationDescription.kt +++ b/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailDescription.kt @@ -5,7 +5,7 @@ import jakarta.persistence.Embeddable @Embeddable -class LocationDescription() { +class LocationDetailDescription() { @Column(name = "description") var value: String? = null diff --git a/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailGeoPoint.kt b/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailGeoPoint.kt new file mode 100644 index 0000000..58f2fab --- /dev/null +++ b/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailGeoPoint.kt @@ -0,0 +1,27 @@ +package com.retrip.map.domain.vo + +import jakarta.persistence.Column +import jakarta.persistence.Embeddable + + +@Embeddable +class LocationDetailGeoPoint() { + @Column(name = "latitude") + var latitude: Double = 0.0 + + @Column(name = "longitude") + var longitude: Double = 0.0 + + constructor(latitude: Double, longitude: Double) : this() { + validate(latitude, longitude) + this.latitude = latitude + this.longitude = longitude + } + + private fun validate(latitude: Double, longitude: Double) { + if (latitude == 0.0 && longitude == 0.0) { + throw IllegalArgumentException("장소 위도, 경도가 모두 0일 수 없습니다.") + } + } + +} diff --git a/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailName.kt b/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailName.kt new file mode 100644 index 0000000..1cbe5e9 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/domain/vo/LocationDetailName.kt @@ -0,0 +1,20 @@ +package com.retrip.map.domain.vo + +import jakarta.persistence.Column +import jakarta.persistence.Embeddable + + +@Embeddable +class LocationDetailName() { + @Column(name = "name") + var value: String = "" + + constructor(value: String) : this() { + validate(value) + this.value = value + } + + private fun validate(value: String) { + require(value.isNotBlank()) { "장소명은 필수입니다." } + } +} diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapBatchConfig.kt b/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapBatchConfig.kt index 71611d8..67ddffe 100644 --- a/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapBatchConfig.kt +++ b/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapBatchConfig.kt @@ -1,10 +1,9 @@ package com.retrip.map.infra.adapter.`in`.batch -import com.retrip.map.domain.entity.Location -import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDocument +import com.retrip.map.domain.entity.LocationDetail +import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDetailDocument import org.springframework.batch.core.Job import org.springframework.batch.core.Step -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing import org.springframework.batch.core.job.builder.JobBuilder import org.springframework.batch.core.launch.support.RunIdIncrementer import org.springframework.batch.core.repository.JobRepository @@ -41,13 +40,11 @@ class MapBatchConfig( jobRepository: JobRepository, transactionManager: PlatformTransactionManager ): Step { - return StepBuilder("map-step", jobRepository) - .chunk, List>(10, transactionManager) + return StepBuilder("detail-location-step", jobRepository) + .chunk, List>(10, transactionManager) .reader(reader) .processor(processor) .writer(writer) .build() } - - } diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapProcessor.kt b/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapProcessor.kt index 65d5fdb..213bf86 100644 --- a/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapProcessor.kt +++ b/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapProcessor.kt @@ -1,15 +1,14 @@ package com.retrip.map.infra.adapter.`in`.batch -import com.retrip.map.domain.entity.Location -import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDocument +import com.retrip.map.domain.entity.LocationDetail +import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDetailDocument import org.springframework.batch.item.ItemProcessor import org.springframework.stereotype.Component @Component class MapProcessor() - : ItemProcessor, List> { - override fun process(item: List): List? { - return item.map { LocationDocument.of(it) } - + : ItemProcessor, List> { + override fun process(item: List): List? { + return item.map { LocationDetailDocument.of(it) } } } diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapReader.kt b/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapReader.kt index 1d1b55e..538f1fd 100644 --- a/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapReader.kt +++ b/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapReader.kt @@ -1,21 +1,25 @@ package com.retrip.map.infra.adapter.`in`.batch -import co.elastic.clients.elasticsearch.ElasticsearchClient -import com.retrip.map.application.out.repository.LocationElasticRepository -import com.retrip.map.application.out.repository.LocationQueryRepository -import com.retrip.map.domain.entity.Location +import com.retrip.map.application.out.repository.LocationDetailElasticRepository +import com.retrip.map.application.out.repository.LocationDetailQueryRepository +import com.retrip.map.domain.entity.LocationDetail import org.springframework.batch.item.ItemReader import org.springframework.stereotype.Component -import java.time.LocalDate +import java.time.Instant import java.time.LocalDateTime +import java.time.ZoneOffset @Component class MapReader( - private val locationQueryRepository: LocationQueryRepository, - private val locationElasticRepository: LocationElasticRepository -): ItemReader> { - override fun read(): List? { - val lastUpdateDocument = locationElasticRepository.findFirstByOrderByEditedAtDesc() - return locationQueryRepository.findLocationsByEditedAt(lastUpdateDocument?.editedAt ?: LocalDateTime.now()) + private val locationDetailQueryRepository: LocationDetailQueryRepository, + private val locationDetailElasticRepository: LocationDetailElasticRepository +) : ItemReader> { + override fun read(): List? { + val lastUpdateDocument = locationDetailElasticRepository.findFirstByOrderByEditedAtDesc() + val editedAt = + lastUpdateDocument?.editedAt?.let { LocalDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneOffset.UTC) } + ?: LocalDateTime.now() + + return locationDetailQueryRepository.findLocationDetailsByEditedAt(editedAt) } } diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapWriter.kt b/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapWriter.kt index 1fb1c6c..ae57c61 100644 --- a/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapWriter.kt +++ b/src/main/kotlin/com/retrip/map/infra/adapter/in/batch/MapWriter.kt @@ -1,7 +1,7 @@ package com.retrip.map.infra.adapter.`in`.batch import com.retrip.map.application.`in`.usecase.LocationIndexUseCase -import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDocument +import com.retrip.map.infra.adapter.out.search.elasticsearch.entity.LocationDetailDocument import org.springframework.batch.item.Chunk import org.springframework.batch.item.ItemWriter import org.springframework.stereotype.Component @@ -9,9 +9,8 @@ import org.springframework.stereotype.Component @Component class MapWriter( val locationIndexUseCase: LocationIndexUseCase -) : ItemWriter> { - override fun write(chunk: Chunk?>) { - chunk.items.forEach { documents -> locationIndexUseCase.indexLocationDocuments(documents) } - println("TEST 완료") +) : ItemWriter> { + override fun write(chunk: Chunk?>) { + chunk.items.forEach { documents -> locationIndexUseCase.indexLocationDetailDocuments(documents) } } } diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/in/presentation/LocationController.kt b/src/main/kotlin/com/retrip/map/infra/adapter/in/presentation/LocationController.kt index 1003edc..52bb80c 100644 --- a/src/main/kotlin/com/retrip/map/infra/adapter/in/presentation/LocationController.kt +++ b/src/main/kotlin/com/retrip/map/infra/adapter/in/presentation/LocationController.kt @@ -1,10 +1,16 @@ package com.retrip.map.infra.adapter.`in`.presentation import com.retrip.map.application.`in`.request.LocationCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailUpdateRequest import com.retrip.map.application.`in`.request.LocationUpdateRequest import com.retrip.map.application.`in`.response.LocationCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailResponse +import com.retrip.map.application.`in`.response.LocationDetailUpdateResponse import com.retrip.map.application.`in`.response.LocationResponse import com.retrip.map.application.`in`.response.LocationUpdateResponse +import com.retrip.map.application.`in`.usecase.LocationDetailUseCase import com.retrip.map.application.`in`.usecase.LocationUseCase import com.retrip.map.infra.adapter.`in`.presentation.common.ApiResponse import io.swagger.v3.oas.annotations.media.Schema diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/in/presentation/LocationDetailController.kt b/src/main/kotlin/com/retrip/map/infra/adapter/in/presentation/LocationDetailController.kt new file mode 100644 index 0000000..ee04bf5 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/infra/adapter/in/presentation/LocationDetailController.kt @@ -0,0 +1,72 @@ +package com.retrip.map.infra.adapter.`in`.presentation + +import com.retrip.map.application.`in`.request.LocationDetailCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailUpdateRequest +import com.retrip.map.application.`in`.response.LocationDetailCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailResponse +import com.retrip.map.application.`in`.response.LocationDetailUpdateResponse +import com.retrip.map.application.`in`.usecase.LocationDetailUseCase +import com.retrip.map.infra.adapter.`in`.presentation.common.ApiResponse +import io.swagger.v3.oas.annotations.media.Schema +import lombok.RequiredArgsConstructor +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.data.web.PageableDefault +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import java.util.* + +@RestController +@RequiredArgsConstructor +@RequestMapping("/location-detail") +class LocationDetailController( + private val locationDetailUseCase: LocationDetailUseCase +) { + + @GetMapping("") + @Schema(description = "장소 상세 전체 조회") + fun getLocationDetail( + @RequestParam(name = "locationDetailId") id: UUID?, + @PageableDefault(size = 10, page = 0) page: Pageable + ): ApiResponse> { + val result = locationDetailUseCase.getLocationDetail(id, page) + return ApiResponse.ok(result) + } + + @PostMapping("") + @Schema(description = "장소 상세 등록") + fun createLocationDetail( + @RequestBody request: LocationDetailCreateRequest + ): ApiResponse { + val result = locationDetailUseCase.createLocationDetail(request) + return ApiResponse.create(result) + } + + @PutMapping("/{locationDetailId}") + @Schema(description = "장소 상세 수정") + fun updateLocationDetail( + @PathVariable locationDetailId: UUID, + @RequestBody request: LocationDetailUpdateRequest + ): ApiResponse { + val result = locationDetailUseCase.updateLocationDetail(locationDetailId, request) + return ApiResponse.ok(result) + } + + @DeleteMapping("/{locationDetailId}") + @Schema(description = "장소 상세 삭제") + fun deleteLocationDetail( + @PathVariable locationDetailId: UUID + ): ApiResponse { + locationDetailUseCase.deleteLocationDetail(locationDetailId) + return ApiResponse.noContent() + } + + +} diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/in/presentation/LocationSearchController.kt b/src/main/kotlin/com/retrip/map/infra/adapter/in/presentation/LocationSearchController.kt new file mode 100644 index 0000000..318e52b --- /dev/null +++ b/src/main/kotlin/com/retrip/map/infra/adapter/in/presentation/LocationSearchController.kt @@ -0,0 +1,51 @@ +package com.retrip.map.infra.adapter.`in`.presentation + +import com.retrip.map.application.`in`.request.LocationCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailCreateRequest +import com.retrip.map.application.`in`.request.LocationDetailUpdateRequest +import com.retrip.map.application.`in`.request.LocationUpdateRequest +import com.retrip.map.application.`in`.response.LocationCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailCreateResponse +import com.retrip.map.application.`in`.response.LocationDetailResponse +import com.retrip.map.application.`in`.response.LocationDetailUpdateResponse +import com.retrip.map.application.`in`.response.LocationResponse +import com.retrip.map.application.`in`.response.LocationSearchResponse +import com.retrip.map.application.`in`.response.LocationUpdateResponse +import com.retrip.map.application.`in`.usecase.LocationDetailUseCase +import com.retrip.map.application.`in`.usecase.LocationSearchUseCase +import com.retrip.map.application.`in`.usecase.LocationUseCase +import com.retrip.map.infra.adapter.`in`.presentation.common.ApiResponse +import io.swagger.v3.oas.annotations.media.Schema +import lombok.RequiredArgsConstructor +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.data.jpa.domain.AbstractPersistable_.id +import org.springframework.data.web.PageableDefault +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import java.util.* + +@RestController +@RequiredArgsConstructor +@RequestMapping("/location-search") +class LocationSearchController( + private val locationSearchUseCase: LocationSearchUseCase +) { + + @GetMapping("") + @Schema(description = "장소 검색 엔진 조회") + fun getLocation( + @PageableDefault(size = 10, page = 0) page: Pageable, + @RequestParam(name = "name", required = false) name: String? + ): ApiResponse> { + val result = locationSearchUseCase.getLocation(name, page) + return ApiResponse.ok(result) + } +} diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/out/persistence/mysql/query/LocationDetailQuerydslRepository.kt b/src/main/kotlin/com/retrip/map/infra/adapter/out/persistence/mysql/query/LocationDetailQuerydslRepository.kt new file mode 100644 index 0000000..8641dff --- /dev/null +++ b/src/main/kotlin/com/retrip/map/infra/adapter/out/persistence/mysql/query/LocationDetailQuerydslRepository.kt @@ -0,0 +1,68 @@ +package com.retrip.map.infra.adapter.out.persistence.mysql.query + +import com.querydsl.core.types.Predicate +import com.querydsl.core.types.Projections +import com.querydsl.jpa.impl.JPAQueryFactory +import com.retrip.map.application.`in`.response.LocationDetailResponse +import com.retrip.map.application.out.repository.LocationDetailQueryRepository +import com.retrip.map.domain.entity.LocationDetail +import com.retrip.map.domain.entity.QLocationDetail.locationDetail +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageImpl +import org.springframework.data.domain.Pageable +import org.springframework.stereotype.Repository +import java.time.LocalDateTime +import java.util.* + + +@Repository +class LocationDetailQuerydslRepository( + private val query: JPAQueryFactory +) : LocationDetailQueryRepository { + + + override fun findLocationDetails(id: UUID?, page: Pageable): Page { + val locationDetails = query.select( + Projections.constructor( + LocationDetailResponse::class.java, + locationDetail.id, + locationDetail.name.value, + locationDetail.category.value, + locationDetail.description.value, + locationDetail.telephone, + locationDetail.address.address, + locationDetail.address.roadAddress, + locationDetail.geoPoint.latitude, + locationDetail.geoPoint.longitude + ) + ).from(locationDetail) + .where( + eqLocation(id) + ) + .offset(page.offset) + .limit(page.pageSize.toLong()) + .orderBy(locationDetail.createdAt.desc()) + .fetch() + val count = query + .select(locationDetail.count()) + .from(locationDetail) + .where( + eqLocation(id) + ) + .fetchOne() + return PageImpl(locationDetails, page, count ?: 0) + } + + override fun findLocationDetailsByEditedAt(editedAt: LocalDateTime): List { + return query.selectFrom(locationDetail) + .where( + locationDetail.editedAt.gt(editedAt) + ).fetch() + } + + + + private fun eqLocation(id: UUID?): Predicate? { + return id?.let { locationDetail.id.eq(it) } + } +} diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/out/persistence/mysql/query/LocationQuerydslRepository.kt b/src/main/kotlin/com/retrip/map/infra/adapter/out/persistence/mysql/query/LocationQuerydslRepository.kt index fa2cb6b..a276ba9 100644 --- a/src/main/kotlin/com/retrip/map/infra/adapter/out/persistence/mysql/query/LocationQuerydslRepository.kt +++ b/src/main/kotlin/com/retrip/map/infra/adapter/out/persistence/mysql/query/LocationQuerydslRepository.kt @@ -5,14 +5,12 @@ import com.querydsl.core.types.Projections import com.querydsl.jpa.impl.JPAQueryFactory import com.retrip.map.application.`in`.response.LocationResponse import com.retrip.map.application.out.repository.LocationQueryRepository -import com.retrip.map.domain.entity.Location import com.retrip.map.domain.entity.QLocation.location +import com.retrip.map.domain.entity.QLocationDetail.locationDetail import org.springframework.data.domain.Page import org.springframework.data.domain.PageImpl import org.springframework.data.domain.Pageable import org.springframework.stereotype.Repository -import java.time.LocalDate -import java.time.LocalDateTime import java.util.* @@ -20,17 +18,14 @@ import java.util.* class LocationQuerydslRepository( private val query: JPAQueryFactory ) : LocationQueryRepository { + + override fun findLocations(id: UUID?, page: Pageable): Page { val locations = query.select( Projections.constructor( LocationResponse::class.java, location.id, location.name.value, - location.category.value, - location.description.value, - location.telephone, - location.address.address, - location.address.roadAddress, location.geoPoint.latitude, location.geoPoint.longitude ) @@ -52,14 +47,7 @@ class LocationQuerydslRepository( return PageImpl(locations, page, count ?: 0) } - override fun findLocationsByEditedAt(editedAt: LocalDateTime): List { - return query.selectFrom(location) - .where( - location.editedAt.gt(editedAt) - ).fetch() - } - private fun eqLocation(id: UUID?): Predicate? { - return id?.let { location.id.eq(it) } + return id?.let { locationDetail.id.eq(it) } } } diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/out/search/elasticsearch/entity/LocationDetailDocument.kt b/src/main/kotlin/com/retrip/map/infra/adapter/out/search/elasticsearch/entity/LocationDetailDocument.kt new file mode 100644 index 0000000..f23d556 --- /dev/null +++ b/src/main/kotlin/com/retrip/map/infra/adapter/out/search/elasticsearch/entity/LocationDetailDocument.kt @@ -0,0 +1,62 @@ +package com.retrip.map.infra.adapter.out.search.elasticsearch.entity + +import com.retrip.map.domain.entity.LocationDetail +import com.retrip.map.domain.exception.LocationDetailNotFoundException +import com.retrip.map.domain.exception.common.RequireException +import jakarta.persistence.Id +import org.springframework.data.elasticsearch.annotations.Document +import org.springframework.data.elasticsearch.annotations.Field +import org.springframework.data.elasticsearch.annotations.FieldType +import org.springframework.data.elasticsearch.annotations.Mapping +import org.springframework.data.elasticsearch.annotations.Setting +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.util.* + +@Document(indexName = "location-details") +@Setting(settingPath = "elasticsearch/settings/setting.json") +@Mapping(mappingPath = "elasticsearch/mappings/mapping-location-detail.json") +data class LocationDetailDocument( + + @Id + val id: UUID, + @Field(type = FieldType.Text, analyzer = "korean") + val name: String, + @Field(type = FieldType.Keyword) + val category: String, + @Field(type = FieldType.Keyword) + val description: String?, + @Field(type = FieldType.Keyword) + val telephone: String?, + @Field(type = FieldType.Keyword) + val address: String?, + @Field(type = FieldType.Keyword) + val roadAddress: String?, + @Field(type = FieldType.Double) + val latitude: Double?, + @Field(type = FieldType.Double) + val longitude: Double?, + @Field(type = FieldType.Date) + val createdAt: Long? = null, + @Field(type = FieldType.Date) + val editedAt: Long? = null, +) { + companion object { + fun of(locationDetail: LocationDetail): LocationDetailDocument { + return LocationDetailDocument( + locationDetail.id ?: throw LocationDetailNotFoundException(), + locationDetail.name?.value ?: throw RequireException(), + locationDetail.category?.value ?: throw RequireException(), + locationDetail.description?.value, + locationDetail.telephone, + locationDetail.address?.address, + locationDetail.address?.roadAddress, + locationDetail.geoPoint?.latitude, + locationDetail.geoPoint?.longitude, + locationDetail.createdAt?.toInstant(ZoneOffset.UTC)?.toEpochMilli(), + locationDetail.editedAt?.toInstant(ZoneOffset.UTC)?.toEpochMilli() + ) + } + } + +} diff --git a/src/main/kotlin/com/retrip/map/infra/adapter/out/search/elasticsearch/entity/LocationDocument.kt b/src/main/kotlin/com/retrip/map/infra/adapter/out/search/elasticsearch/entity/LocationDocument.kt index 3d1bb33..66db247 100644 --- a/src/main/kotlin/com/retrip/map/infra/adapter/out/search/elasticsearch/entity/LocationDocument.kt +++ b/src/main/kotlin/com/retrip/map/infra/adapter/out/search/elasticsearch/entity/LocationDocument.kt @@ -1,6 +1,7 @@ package com.retrip.map.infra.adapter.out.search.elasticsearch.entity import com.retrip.map.domain.entity.Location +import com.retrip.map.domain.entity.QLocationDetail.locationDetail import com.retrip.map.domain.exception.LocationNotFoundException import com.retrip.map.domain.exception.common.RequireException import jakarta.persistence.Id @@ -10,50 +11,35 @@ import org.springframework.data.elasticsearch.annotations.FieldType import org.springframework.data.elasticsearch.annotations.Mapping import org.springframework.data.elasticsearch.annotations.Setting import java.time.LocalDateTime +import java.time.ZoneOffset import java.util.* -@Document(indexName = "locations") +@Document(indexName = "location") @Setting(settingPath = "elasticsearch/settings/setting.json") -@Mapping(mappingPath = "elasticsearch/mappings/mapping.json") +@Mapping(mappingPath = "elasticsearch/mappings/mapping-location.json") data class LocationDocument( - @Id val id: UUID, @Field(type = FieldType.Text, analyzer = "korean") val name: String, - @Field(type = FieldType.Keyword) - val category: String, - @Field(type = FieldType.Keyword) - val description: String?, - @Field(type = FieldType.Keyword) - val telephone: String?, - @Field(type = FieldType.Keyword) - val address: String?, - @Field(type = FieldType.Keyword) - val roadAddress: String?, @Field(type = FieldType.Double) val latitude: Double?, @Field(type = FieldType.Double) val longitude: Double?, @Field(type = FieldType.Date) - val createdAt: LocalDateTime?, + val createdAt: Long?, @Field(type = FieldType.Date) - val editedAt: LocalDateTime?, + val editedAt: Long?, ) { companion object { fun of(location: Location): LocationDocument { return LocationDocument( location.id ?: throw LocationNotFoundException(), location.name?.value ?: throw RequireException(), - location.category?.value ?: throw RequireException(), - location.description?.value, - location.telephone, - location.address?.address, - location.address?.roadAddress, location.geoPoint?.latitude, location.geoPoint?.longitude, - location.createdAt, - location.editedAt + location.createdAt?.toInstant(ZoneOffset.UTC)?.toEpochMilli(), + location.editedAt?.toInstant(ZoneOffset.UTC)?.toEpochMilli() ) } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7191076..fcf843c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,6 +15,7 @@ spring: batch: job: name: "retrip-map" + enabled: false jdbc: initialize-schema: always jpa: diff --git a/src/main/resources/elasticsearch/mappings/mapping-location-detail.json b/src/main/resources/elasticsearch/mappings/mapping-location-detail.json new file mode 100644 index 0000000..47a50d5 --- /dev/null +++ b/src/main/resources/elasticsearch/mappings/mapping-location-detail.json @@ -0,0 +1,40 @@ +{ + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "text", + "analyzer": "korean" + }, + "category": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "telephone": { + "type": "keyword" + }, + "address": { + "type": "keyword" + }, + "roadAddress": { + "type": "keyword" + }, + "latitude": { + "type": "double" + }, + "longitude": { + "type": "double" + }, + "createdAt": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "editedAt": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + } + } +} diff --git a/src/main/resources/elasticsearch/mappings/mapping-location.json b/src/main/resources/elasticsearch/mappings/mapping-location.json new file mode 100644 index 0000000..440c27a --- /dev/null +++ b/src/main/resources/elasticsearch/mappings/mapping-location.json @@ -0,0 +1,25 @@ +{ + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "text", + "analyzer": "korean" + }, + "latitude": { + "type": "double" + }, + "longitude": { + "type": "double" + }, + "createdAt": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "editedAt": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + } + } +} diff --git a/src/main/resources/elasticsearch/mappings/mapping.json b/src/main/resources/elasticsearch/mappings/mapping.json deleted file mode 100644 index 7e0952a..0000000 --- a/src/main/resources/elasticsearch/mappings/mapping.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "properties": { - "name": { - "type": "text", - "analyzer": "korean" - } - } -}