diff --git a/src/main/java/com/earseo/sight/controller/SightAdminController.java b/src/main/java/com/earseo/sight/controller/SightAdminController.java index 9cfb28f..799c939 100644 --- a/src/main/java/com/earseo/sight/controller/SightAdminController.java +++ b/src/main/java/com/earseo/sight/controller/SightAdminController.java @@ -3,6 +3,7 @@ import com.earseo.sight.common.BaseResponse; import com.earseo.sight.dto.request.CurationCreateRequest; import com.earseo.sight.dto.request.CurationUpdateRequest; +import com.earseo.sight.dto.request.InitRequest; import com.earseo.sight.dto.response.CurationDeleteResponse; import com.earseo.sight.dto.response.CurationResponse; import com.earseo.sight.service.CurationService; @@ -28,9 +29,13 @@ public class SightAdminController { private final CurationService curationService; @PostMapping("/init") - public ResponseEntity> initSight() { - initService.initSight(); - initService.initDocent(); + public ResponseEntity> initSight( + @RequestBody + @Valid + InitRequest request + ) { + initService.initSight(request.lang()); + initService.initDocent(request.lang()); return ResponseEntity.ok(BaseResponse.ok(null)); } diff --git a/src/main/java/com/earseo/sight/controller/SightController.java b/src/main/java/com/earseo/sight/controller/SightController.java index 50064e6..1b52f49 100644 --- a/src/main/java/com/earseo/sight/controller/SightController.java +++ b/src/main/java/com/earseo/sight/controller/SightController.java @@ -122,7 +122,7 @@ public ResponseEntity> getSightRectangle( Double maxLatitude ) { - return ResponseEntity.ok(BaseResponse.ok(sightService.getMapRectangle(minLongitude, minLatitude, maxLongitude, maxLatitude))); + return ResponseEntity.ok(BaseResponse.ok(sightService.getMapRectangle(minLongitude, minLatitude, maxLongitude, maxLatitude, "ko"))); } @@ -217,7 +217,7 @@ public ResponseEntity> getSightCircle( @DecimalMax(value = "39", message = "위도는 39 이하여야 합니다") Double latitude ) { - return ResponseEntity.ok(BaseResponse.ok(sightService.getMapCircle(meters, longitude, latitude))); + return ResponseEntity.ok(BaseResponse.ok(sightService.getMapCircle(meters, longitude, latitude, "ko"))); } @Operation( @@ -321,7 +321,7 @@ public ResponseEntity> getSightDetail( @RequestHeader(value = "X-USER-ID", required = false) Long memberId ) { - return ResponseEntity.ok(BaseResponse.ok(sightService.getSightDetailInfo(id, longitude, latitude, memberId))); + return ResponseEntity.ok(BaseResponse.ok(sightService.getSightDetailInfo(id, longitude, latitude, memberId, "ko"))); } @Operation( @@ -591,7 +591,7 @@ public ResponseEntity> searchTitle( Integer limit ) { return ResponseEntity.ok(BaseResponse.ok( - sightService.searchSight(keyword, longitude, latitude, minLongitude, minLatitude, maxLongitude, maxLatitude, limit) + sightService.searchSight(keyword, longitude, latitude, minLongitude, minLatitude, maxLongitude, maxLatitude, limit, "ko") )); } } diff --git a/src/main/java/com/earseo/sight/controller/SightUserController.java b/src/main/java/com/earseo/sight/controller/SightUserController.java index 1675a2d..b6eb89f 100644 --- a/src/main/java/com/earseo/sight/controller/SightUserController.java +++ b/src/main/java/com/earseo/sight/controller/SightUserController.java @@ -91,7 +91,7 @@ public ResponseEntity> addBookmark( @NotBlank(message = "관광지 ID는 필수입니다") String sightId ) { - return ResponseEntity.ok(BaseResponse.ok(bookmarkService.addBookmark(memberId, sightId))); + return ResponseEntity.ok(BaseResponse.ok(bookmarkService.addBookmark(memberId, sightId, "ko"))); } @Operation( diff --git a/src/main/java/com/earseo/sight/dto/etl/SightItemDto.java b/src/main/java/com/earseo/sight/dto/etl/SightItemDto.java index ab05f45..f2f1564 100644 --- a/src/main/java/com/earseo/sight/dto/etl/SightItemDto.java +++ b/src/main/java/com/earseo/sight/dto/etl/SightItemDto.java @@ -1,8 +1,8 @@ package com.earseo.sight.dto.etl; public record SightItemDto(String contentId, String contentTypeId, String cat1, - String cat2, String cat3, String ocat1, String ocat2, String ocat3, - String outl, String title, String addr1, String addr2, String addr3, + String cat2, String cat1Code, String cat2Code, String title, + String addr1, String addr2, String addr3, Double mapX, Double mapY, String modifiedtime, String tel, Integer mLevel, String overview, String originImgUrl, String smallImgUrl, String usetime, String restdate, String parking, String usefee) { } diff --git a/src/main/java/com/earseo/sight/dto/internal/SightMetaResponse.java b/src/main/java/com/earseo/sight/dto/internal/SightMetaResponse.java index fe2a55f..d324299 100644 --- a/src/main/java/com/earseo/sight/dto/internal/SightMetaResponse.java +++ b/src/main/java/com/earseo/sight/dto/internal/SightMetaResponse.java @@ -22,7 +22,7 @@ public static SightMetaResponse toDto(SightMetaDto dto) { dto.mapY(), dto.mapX(), dto.docentUrl(), - Theme.valueOf(dto.cat1()) + Theme.valueOf(dto.theme()) ); } } diff --git a/src/main/java/com/earseo/sight/dto/projection/CurationSightItemDto.java b/src/main/java/com/earseo/sight/dto/projection/CurationSightItemDto.java index 0caafd1..98665c6 100644 --- a/src/main/java/com/earseo/sight/dto/projection/CurationSightItemDto.java +++ b/src/main/java/com/earseo/sight/dto/projection/CurationSightItemDto.java @@ -3,7 +3,10 @@ public record CurationSightItemDto( String contentId, String title, - String cat2, + String subTheme, + String originImgUrl, + Double mapX, + Double mapY, Double distance, String addr3 ) { diff --git a/src/main/java/com/earseo/sight/dto/projection/SearchSightItemDto.java b/src/main/java/com/earseo/sight/dto/projection/SearchSightItemDto.java index 948d16b..b7a0a2d 100644 --- a/src/main/java/com/earseo/sight/dto/projection/SearchSightItemDto.java +++ b/src/main/java/com/earseo/sight/dto/projection/SearchSightItemDto.java @@ -3,7 +3,7 @@ public record SearchSightItemDto( String contentId, String title, - String cat2, + String subTheme, String addr3, Double mapX, Double mapY, diff --git a/src/main/java/com/earseo/sight/dto/projection/SightDetailItemDto.java b/src/main/java/com/earseo/sight/dto/projection/SightDetailItemDto.java index e40cb77..e5d8e5c 100644 --- a/src/main/java/com/earseo/sight/dto/projection/SightDetailItemDto.java +++ b/src/main/java/com/earseo/sight/dto/projection/SightDetailItemDto.java @@ -2,9 +2,9 @@ public record SightDetailItemDto( String contentId, - String cat1, - String cat2, - String outl, + String theme, + String subTheme, + String overview, String title, String addr1, String addr3, diff --git a/src/main/java/com/earseo/sight/dto/projection/SightMapItemDto.java b/src/main/java/com/earseo/sight/dto/projection/SightMapItemDto.java index 560b353..29400f0 100644 --- a/src/main/java/com/earseo/sight/dto/projection/SightMapItemDto.java +++ b/src/main/java/com/earseo/sight/dto/projection/SightMapItemDto.java @@ -1,10 +1,12 @@ package com.earseo.sight.dto.projection; +import com.earseo.sight.entity.Theme; + public record SightMapItemDto( String contentId, String title, Double mapX, Double mapY, - String cat1 + String theme ) { } diff --git a/src/main/java/com/earseo/sight/dto/projection/SightMetaDto.java b/src/main/java/com/earseo/sight/dto/projection/SightMetaDto.java index bb0cb77..ca97cf0 100644 --- a/src/main/java/com/earseo/sight/dto/projection/SightMetaDto.java +++ b/src/main/java/com/earseo/sight/dto/projection/SightMetaDto.java @@ -1,5 +1,7 @@ package com.earseo.sight.dto.projection; +import com.earseo.sight.entity.Theme; + public record SightMetaDto( String contentId, String title, @@ -8,6 +10,6 @@ public record SightMetaDto( Double mapY, Double mapX, String docentUrl, - String cat1 + String theme ) { } diff --git a/src/main/java/com/earseo/sight/dto/request/InitRequest.java b/src/main/java/com/earseo/sight/dto/request/InitRequest.java new file mode 100644 index 0000000..832cb3f --- /dev/null +++ b/src/main/java/com/earseo/sight/dto/request/InitRequest.java @@ -0,0 +1,6 @@ +package com.earseo.sight.dto.request; + +public record InitRequest( + String lang +) { +} diff --git a/src/main/java/com/earseo/sight/dto/response/CurationSightResponse.java b/src/main/java/com/earseo/sight/dto/response/CurationSightResponse.java index 393ebe7..c4ba485 100644 --- a/src/main/java/com/earseo/sight/dto/response/CurationSightResponse.java +++ b/src/main/java/com/earseo/sight/dto/response/CurationSightResponse.java @@ -1,5 +1,6 @@ package com.earseo.sight.dto.response; +import ch.hsr.geohash.GeoHash; import com.earseo.sight.dto.projection.CurationSightItemDto; import com.earseo.sight.entity.SubTheme; import io.swagger.v3.oas.annotations.media.Schema; @@ -14,19 +15,35 @@ public record CurationSightResponse( @Schema(description = "관광지 하위 테마 (코드)", example = "CU01") SubTheme subTheme, + @Schema(description = "대표 이미지 URL", example = "https://example.com/image.jpg") + String imgUrl, + + @Schema(description = "경/위도") + PathPoint point, + @Schema(description = "현재 위치로부터의 직선 거리 (미터)", example = "1234.56") Double distance, @Schema(description = "주소 요약 (구/동 단위)", example = "서울 종로구") - String address + String address, + + @Schema(description = "이야기 스팟 조회용 GeoHash", example = "wydm6dqkm") + String geoHash ) { public static CurationSightResponse toDto(CurationSightItemDto dto) { return new CurationSightResponse( dto.contentId(), dto.title(), - SubTheme.valueOf(dto.cat2()), + SubTheme.valueOf(dto.subTheme()), + dto.originImgUrl(), + new PathPoint(dto.mapX(), dto.mapY()), dto.distance(), - dto.addr3() + dto.addr3(), + getGeoHash(dto.mapX(), dto.mapY()) ); } + + private static String getGeoHash(double longitude, double latitude) { + return GeoHash.withCharacterPrecision(latitude, longitude, 9).toBase32(); + } } diff --git a/src/main/java/com/earseo/sight/dto/response/PathPoint.java b/src/main/java/com/earseo/sight/dto/response/PathPoint.java new file mode 100644 index 0000000..3d32dcd --- /dev/null +++ b/src/main/java/com/earseo/sight/dto/response/PathPoint.java @@ -0,0 +1,12 @@ +package com.earseo.sight.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record PathPoint( + @Schema(description = "경도", example = "127.9768") + Double longitude, + + @Schema(description = "위도", example = "37.5759") + Double latitude +) { +} diff --git a/src/main/java/com/earseo/sight/dto/response/SearchSightResponse.java b/src/main/java/com/earseo/sight/dto/response/SearchSightResponse.java index a44d0c8..0f45877 100644 --- a/src/main/java/com/earseo/sight/dto/response/SearchSightResponse.java +++ b/src/main/java/com/earseo/sight/dto/response/SearchSightResponse.java @@ -30,7 +30,7 @@ public static SearchSightResponse toDto(SearchSightItemDto dto){ return new SearchSightResponse( dto.contentId(), dto.title(), - SubTheme.valueOf(dto.cat2()), + SubTheme.valueOf(dto.subTheme()), dto.addr3(), dto.mapX(), dto.mapY(), diff --git a/src/main/java/com/earseo/sight/dto/response/SightDetailInfoResponse.java b/src/main/java/com/earseo/sight/dto/response/SightDetailInfoResponse.java index 2b952fd..6f4562a 100644 --- a/src/main/java/com/earseo/sight/dto/response/SightDetailInfoResponse.java +++ b/src/main/java/com/earseo/sight/dto/response/SightDetailInfoResponse.java @@ -18,7 +18,7 @@ public record SightDetailInfoResponse( SubTheme subTheme, @Schema(description = "관광지 개요/설명", example = "조선시대 왕궁으로 500년 역사를 간직하고 있습니다") - String outl, + String overview, @Schema(description = "관광지 이름", example = "경복궁") String title, @@ -68,9 +68,9 @@ public record SightDetailInfoResponse( public static SightDetailInfoResponse toDto(SightDetailItemDto dto, List curationList) { return new SightDetailInfoResponse( dto.contentId(), - Theme.valueOf(dto.cat1()), - SubTheme.valueOf(dto.cat2()), - dto.outl(), + Theme.valueOf(dto.theme()), + SubTheme.valueOf(dto.subTheme()), + dto.overview(), dto.title(), dto.addr1(), dto.addr3(), diff --git a/src/main/java/com/earseo/sight/entity/Sight.java b/src/main/java/com/earseo/sight/entity/EnSight.java similarity index 68% rename from src/main/java/com/earseo/sight/entity/Sight.java rename to src/main/java/com/earseo/sight/entity/EnSight.java index 19af697..89c7209 100644 --- a/src/main/java/com/earseo/sight/entity/Sight.java +++ b/src/main/java/com/earseo/sight/entity/EnSight.java @@ -12,7 +12,8 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class Sight { +@Table(name = "en_sight") +public class EnSight { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -20,31 +21,13 @@ public class Sight { @Column(name = "content_id", nullable = false, unique = true) private String contentId; - @Column(name = "content_type_id") - private String contentTypeId; - @Enumerated(EnumType.STRING) - @Column(name = "cat1") - private Theme cat1; + @Column(name = "theme") + private Theme theme; @Enumerated(EnumType.STRING) - @Column(name = "cat2") - private SubTheme cat2; - - @Column(name = "cat3") - private String cat3; - - @Column(name = "ocat1") - private String ocat1; - - @Column(name = "ocat2") - private String ocat2; - - @Column(name = "ocat3") - private String ocat3; - - @Column(name = "outl", columnDefinition = "TEXT") - private String outl; + @Column(name = "sub_theme") + private SubTheme subTheme; @Column(name = "title") private String title; @@ -64,24 +47,15 @@ public class Sight { @Column(name = "map_y") private Double mapY; - @Column(name = "modifiedtime") - private String modifiedtime; - @Column(name = "tel", columnDefinition = "TEXT") private String tel; - @Column(name = "m_level") - private Integer mLevel; - @Column(name = "overview", columnDefinition = "TEXT") private String overview; @Column(name = "origin_img_url", columnDefinition = "TEXT") private String originImgUrl; - @Column(name = "small_img_url", columnDefinition = "TEXT") - private String smallImgUrl; - @Column(name = "use_time", columnDefinition = "TEXT") private String usetime; diff --git a/src/main/java/com/earseo/sight/entity/KoSight.java b/src/main/java/com/earseo/sight/entity/KoSight.java new file mode 100644 index 0000000..2085d2f --- /dev/null +++ b/src/main/java/com/earseo/sight/entity/KoSight.java @@ -0,0 +1,73 @@ +package com.earseo.sight.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.locationtech.jts.geom.Point; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "ko_sight") +public class KoSight { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "content_id", nullable = false, unique = true) + private String contentId; + + @Enumerated(EnumType.STRING) + @Column(name = "theme") + private Theme theme; + + @Enumerated(EnumType.STRING) + @Column(name = "sub_theme") + private SubTheme subTheme; + + @Column(name = "title") + private String title; + + @Column(name = "addr1") + private String addr1; + + @Column(name = "addr2") + private String addr2; + + @Column(name = "addr3") + private String addr3; + + @Column(name = "map_x") + private Double mapX; + + @Column(name = "map_y") + private Double mapY; + + @Column(name = "tel", columnDefinition = "TEXT") + private String tel; + + @Column(name = "overview", columnDefinition = "TEXT") + private String overview; + + @Column(name = "origin_img_url", columnDefinition = "TEXT") + private String originImgUrl; + + @Column(name = "use_time", columnDefinition = "TEXT") + private String usetime; + + @Column(name = "rest_date", columnDefinition = "TEXT") + private String restdate; + + @Column(name = "parking", columnDefinition = "TEXT") + private String parking; + + @Column(name = "use_fee", columnDefinition = "TEXT") + private String usefee; + + @Column(name = "geom", columnDefinition = "geometry(Point,4326)") + private Point geom; +} diff --git a/src/main/java/com/earseo/sight/repository/SightRepository.java b/src/main/java/com/earseo/sight/repository/EnSightRepository.java similarity index 82% rename from src/main/java/com/earseo/sight/repository/SightRepository.java rename to src/main/java/com/earseo/sight/repository/EnSightRepository.java index 6fc5651..68bd057 100644 --- a/src/main/java/com/earseo/sight/repository/SightRepository.java +++ b/src/main/java/com/earseo/sight/repository/EnSightRepository.java @@ -1,18 +1,20 @@ package com.earseo.sight.repository; import com.earseo.sight.dto.projection.*; -import com.earseo.sight.entity.Sight; +import com.earseo.sight.entity.EnSight; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; -public interface SightRepository extends JpaRepository { +public interface EnSightRepository extends JpaRepository { + + boolean existsByContentId(String contentId); @Query(value = """ - SELECT s.content_id, s.title, s.map_x, s.map_y, s.cat1 - FROM sight s + SELECT s.content_id, s.title, s.map_x, s.map_y, s.theme + FROM en_sight s WHERE ST_Intersects( s.geom, ST_MakeEnvelope(:minLongitude, :minLatitude, :maxLongitude, :maxLatitude, 4326) @@ -26,8 +28,8 @@ List findByRectangle( ); @Query(value = """ - SELECT s.content_id, s.title, s.map_x, s.map_y, s.cat1 - FROM sight s + SELECT s.content_id, s.title, s.map_x, s.map_y, s.theme + FROM en_sight s WHERE ST_DWithin( s.geom::geography, ST_SetSRID(ST_MakePoint(:longitude, :latitude), 4326)::geography, @@ -41,7 +43,7 @@ List findByRadius( ); @Query(value = """ - SELECT s.content_id, s.cat1, s.cat2, s.outl, s.title, s.addr1, s.addr3, s.map_x, s.map_y, + SELECT s.content_id, s.theme, s.sub_theme, s.overview, s.title, s.addr1, s.addr3, s.map_x, s.map_y, s.tel, s.origin_img_url, s.use_time, s.rest_date, s.parking, s.use_fee, ST_Distance( s.geom::geography, @@ -49,7 +51,7 @@ List findByRadius( ) as distance, d.docent_url, CASE WHEN sb.id IS NOT NULL THEN true ELSE false END as is_bookmarked - FROM sight s + FROM en_sight s LEFT JOIN docent d ON d.content_id = s.content_id LEFT JOIN sight_bookmark sb ON sb.content_id = s.content_id AND sb.member_id = :memberId WHERE s.content_id = :contentId @@ -63,21 +65,21 @@ SightDetailItemDto findByContentId( @Query(value = """ SELECT - s.content_id, s.title, s.addr3, s.origin_img_url, s.map_y, s.map_x, d.docent_url, s.cat1 - FROM sight s + s.content_id, s.title, s.addr3, s.origin_img_url, s.map_y, s.map_x, d.docent_url, s.theme + FROM en_sight s LEFT JOIN docent d ON d.content_id = s.content_id WHERE s.content_id IN :ids """, nativeQuery = true) List findByContentId(@Param("ids") List ids); @Query(value = """ - SELECT s.content_id, s.title, s.cat2, + SELECT s.content_id, s.title, s.sub_theme, s.origin_img_url, s.map_x, s.map_y, ST_Distance( s.geom::geography, ST_SetSRID(ST_MakePoint(:longitude, :latitude), 4326)::geography ) as distance, s.addr3 - FROM sight s + FROM en_sight s JOIN curation_sight cs ON cs.sight_content_id = s.content_id WHERE cs.curation_id = :curationId ORDER BY cs.id @@ -88,16 +90,16 @@ List findByCurationId( @Param("latitude") Double latitude ); - List findAllByContentIdIn(List sightIds); + List findAllByContentIdIn(List sightIds); @Query(value = """ - SELECT s.content_id, s.title, s.cat2, s.addr3, s.map_x, s.map_y, + SELECT s.content_id, s.title, s.sub_theme, s.addr3, s.map_x, s.map_y, ST_Distance( s.geom::geography, ST_SetSRID(ST_MakePoint(:longitude, :latitude), 4326)::geography ) as distance - FROM sight s + FROM en_sight s WHERE ST_Intersects( s.geom, ST_MakeEnvelope(:minLongitude, :minLatitude, :maxLongitude, :maxLatitude, 4326) diff --git a/src/main/java/com/earseo/sight/repository/KoSightRepository.java b/src/main/java/com/earseo/sight/repository/KoSightRepository.java new file mode 100644 index 0000000..57d71ec --- /dev/null +++ b/src/main/java/com/earseo/sight/repository/KoSightRepository.java @@ -0,0 +1,123 @@ +package com.earseo.sight.repository; + +import com.earseo.sight.dto.projection.*; +import com.earseo.sight.entity.KoSight; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface KoSightRepository extends JpaRepository { + + boolean existsByContentId(String contentId); + + @Query(value = """ + SELECT s.content_id, s.title, s.map_x, s.map_y, s.theme + FROM ko_sight s + WHERE ST_Intersects( + s.geom, + ST_MakeEnvelope(:minLongitude, :minLatitude, :maxLongitude, :maxLatitude, 4326) + ) + """, nativeQuery = true) + List findByRectangle( + @Param("minLongitude") Double minLongitude, + @Param("minLatitude") Double minLatitude, + @Param("maxLongitude") Double maxLongitude, + @Param("maxLatitude") Double maxLatitude + ); + + @Query(value = """ + SELECT s.content_id, s.title, s.map_x, s.map_y, s.theme + FROM ko_sight s + WHERE ST_DWithin( + s.geom::geography, + ST_SetSRID(ST_MakePoint(:longitude, :latitude), 4326)::geography, + :meters + ) + """, nativeQuery = true) + List findByRadius( + @Param("meters") Double meters, + @Param("longitude") Double longitude, + @Param("latitude") Double latitude + ); + + @Query(value = """ + SELECT s.content_id, s.theme, s.sub_theme, s.overview, s.title, s.addr1, s.addr3, s.map_x, s.map_y, + s.tel, s.origin_img_url, s.use_time, s.rest_date, s.parking, s.use_fee, + ST_Distance( + s.geom::geography, + ST_SetSRID(ST_MakePoint(:longitude, :latitude), 4326)::geography + ) as distance, + d.docent_url, + CASE WHEN sb.id IS NOT NULL THEN true ELSE false END as is_bookmarked + FROM ko_sight s + LEFT JOIN docent d ON d.content_id = s.content_id + LEFT JOIN sight_bookmark sb ON sb.content_id = s.content_id AND sb.member_id = :memberId + WHERE s.content_id = :contentId + """, nativeQuery = true) + SightDetailItemDto findByContentId( + @Param("contentId") String contentId, + @Param("longitude") Double longitude, + @Param("latitude") Double latitude, + @Param("memberId") Long memberId + ); + + @Query(value = """ + SELECT + s.content_id, s.title, s.addr3, s.origin_img_url, s.map_y, s.map_x, d.docent_url, s.theme + FROM ko_sight s + LEFT JOIN docent d ON d.content_id = s.content_id + WHERE s.content_id IN :ids + """, nativeQuery = true) + List findByContentId(@Param("ids") List ids); + + @Query(value = """ + SELECT s.content_id, s.title, s.sub_theme, s.origin_img_url, s.map_x, s.map_y, + ST_Distance( + s.geom::geography, + ST_SetSRID(ST_MakePoint(:longitude, :latitude), 4326)::geography + ) as distance, + s.addr3 + FROM ko_sight s + JOIN curation_sight cs ON cs.sight_content_id = s.content_id + WHERE cs.curation_id = :curationId + ORDER BY cs.id + """, nativeQuery = true) + List findByCurationId( + @Param("curationId") Long curationId, + @Param("longitude") Double longitude, + @Param("latitude") Double latitude + ); + + List findAllByContentIdIn(List sightIds); + + + @Query(value = """ + SELECT s.content_id, s.title, s.sub_theme, s.addr3, s.map_x, s.map_y, + ST_Distance( + s.geom::geography, + ST_SetSRID(ST_MakePoint(:longitude, :latitude), 4326)::geography + ) as distance + FROM ko_sight s + WHERE ST_Intersects( + s.geom, + ST_MakeEnvelope(:minLongitude, :minLatitude, :maxLongitude, :maxLatitude, 4326) + ) AND + :keyword IS NULL OR + :keyword = '' OR + s.title ILIKE '%' || :keyword || '%' + ORDER BY distance + LIMIT :limit + """, nativeQuery = true) + List findByKeywordAndRectangle( + @Param("keyword") String keyword, + @Param("longitude") Double longitude, + @Param("latitude") Double latitude, + @Param("minLongitude") Double minLongitude, + @Param("minLatitude") Double minLatitude, + @Param("maxLongitude") Double maxLongitude, + @Param("maxLatitude") Double maxLatitude, + @Param("limit") Integer limit + ); +} diff --git a/src/main/java/com/earseo/sight/service/BookmarkService.java b/src/main/java/com/earseo/sight/service/BookmarkService.java index 0093f93..18fe3f0 100644 --- a/src/main/java/com/earseo/sight/service/BookmarkService.java +++ b/src/main/java/com/earseo/sight/service/BookmarkService.java @@ -1,10 +1,14 @@ package com.earseo.sight.service; +import com.earseo.sight.common.exception.BaseException; +import com.earseo.sight.common.exception.SightError; import com.earseo.sight.dto.response.BookmarkList; import com.earseo.sight.dto.response.BookmarkResponse; import com.earseo.sight.dto.response.BookmarkStatusResponse; import com.earseo.sight.entity.SightBookmark; import com.earseo.sight.repository.BookmarkRepository; +import com.earseo.sight.repository.EnSightRepository; +import com.earseo.sight.repository.KoSightRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,10 +19,22 @@ @RequiredArgsConstructor public class BookmarkService { + private final KoSightRepository koSightRepository; + private final EnSightRepository enSightRepository; private final BookmarkRepository bookmarkRepository; @Transactional - public BookmarkStatusResponse addBookmark(Long memberId, String sightId) { + public BookmarkStatusResponse addBookmark(Long memberId, String sightId, String lang) { + boolean exists; + if (lang.equals("en")) { + exists = enSightRepository.existsByContentId(sightId); + } else { + exists = koSightRepository.existsByContentId(sightId); + } + + if (!exists) { + throw new BaseException(SightError.SIGHT_NOT_FOUND); + } bookmarkRepository.insertBookmark(memberId, sightId); return new BookmarkStatusResponse(true, sightId); } diff --git a/src/main/java/com/earseo/sight/service/CurationService.java b/src/main/java/com/earseo/sight/service/CurationService.java index 26babd5..2739ac4 100644 --- a/src/main/java/com/earseo/sight/service/CurationService.java +++ b/src/main/java/com/earseo/sight/service/CurationService.java @@ -8,10 +8,10 @@ import com.earseo.sight.dto.response.*; import com.earseo.sight.entity.Curation; import com.earseo.sight.entity.CurationSight; -import com.earseo.sight.entity.Sight; +import com.earseo.sight.entity.KoSight; import com.earseo.sight.repository.CurationRepository; import com.earseo.sight.repository.CurationSightRepository; -import com.earseo.sight.repository.SightRepository; +import com.earseo.sight.repository.KoSightRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,7 +26,7 @@ public class CurationService { private final CurationRepository curationRepository; private final CurationSightRepository curationSightRepository; - private final SightRepository sightRepository; + private final KoSightRepository koSightRepository; @Transactional(readOnly = true) public CurationList getCurationList() { @@ -53,7 +53,7 @@ public CurationSightList getCurationSight(Long curationId, Double longitude, Dou () -> new BaseException(SightError.CURATION_NOT_FOUND) ); - List curationSights = sightRepository.findByCurationId(curationId, longitude, latitude); + List curationSights = koSightRepository.findByCurationId(curationId, longitude, latitude); List curationSightResponses = curationSights.stream() .map(CurationSightResponse::toDto) .toList(); @@ -67,7 +67,7 @@ public CurationSightList getCurationSight(Long curationId, Double longitude, Dou @Transactional public CurationResponse createCuration(CurationCreateRequest request) { - List sights = sightRepository.findAllByContentIdIn(request.contentIds()); + List sights = koSightRepository.findAllByContentIdIn(request.contentIds()); if (sights.size() != request.contentIds().size()) { throw new BaseException(SightError.SIGHT_NOT_FOUND); } diff --git a/src/main/java/com/earseo/sight/service/InitService.java b/src/main/java/com/earseo/sight/service/InitService.java index 1ed63cc..65a60c9 100644 --- a/src/main/java/com/earseo/sight/service/InitService.java +++ b/src/main/java/com/earseo/sight/service/InitService.java @@ -2,12 +2,10 @@ import com.earseo.sight.dto.etl.DocentItemDto; import com.earseo.sight.dto.etl.SightItemDto; -import com.earseo.sight.entity.Docent; -import com.earseo.sight.entity.Sight; -import com.earseo.sight.entity.SubTheme; -import com.earseo.sight.entity.Theme; +import com.earseo.sight.entity.*; import com.earseo.sight.repository.DocentRepository; -import com.earseo.sight.repository.SightRepository; +import com.earseo.sight.repository.EnSightRepository; +import com.earseo.sight.repository.KoSightRepository; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -15,6 +13,7 @@ import lombok.RequiredArgsConstructor; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Point; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; @@ -25,15 +24,20 @@ @RequiredArgsConstructor public class InitService { - private final SightRepository sightRepository; + private final KoSightRepository koSightRepository; + private final EnSightRepository enSightRepository; private final DocentRepository docentRepository; private final GeometryFactory geometryFactory = new GeometryFactory(); private final ObjectMapper objectMapper; + @Value("${cloud.aws.cloudfront.domain}") + private String cloudFrontDomain; + @Transactional - public void initSight() { + public void initSight(String lang) { RestClient client = RestClient.create(); - String cdnUrl = "https://cdn.earseo.click/core/master/master_data_.json"; + String s3Key = String.format("core/master/master_data_%s_.json", lang); + String cdnUrl = getCloudFrontDomain(s3Key); try { JsonNode jsonContent = client.get() @@ -47,20 +51,30 @@ public void initSight() { } ); - List sights = dtos.stream() - .map(this::convertSight) - .collect(Collectors.toList()); + if(lang.equals("en")) { + List sights = dtos.stream() + .map(this::convertEnSight) + .toList(); + + enSightRepository.saveAll(sights); + } else { + List sights = dtos.stream() + .map(this::convertKoSight) + .toList(); + + koSightRepository.saveAll(sights); + } - sightRepository.saveAll(sights); } catch (Exception e) { throw new RuntimeException(e); } } @Transactional - public void initDocent() { + public void initDocent(String lang) { RestClient client = RestClient.create(); - String cdnUrl = "https://cdn.earseo.click/core/docent/docent_data.json"; + String s3Key = String.format("core/master/docent_data.json", lang); + String cdnUrl = getCloudFrontDomain(s3Key); try { JsonNode jsonContent = client.get() @@ -84,34 +98,52 @@ public void initDocent() { } } - private Sight convertSight(SightItemDto dto) { + private KoSight convertKoSight(SightItemDto dto) { Point geom = null; if (dto.mapX() != null && dto.mapY() != null) { geom = geometryFactory.createPoint(new org.locationtech.jts.geom.Coordinate(dto.mapX(), dto.mapY())); } - return Sight.builder(). + return KoSight.builder(). contentId(dto.contentId()) - .contentTypeId(dto.contentTypeId()) - .cat1(Theme.valueOf(dto.cat1())) - .cat2(SubTheme.valueOf(dto.cat2())) - .cat3(dto.cat3()) - .ocat1(dto.ocat1()) - .ocat2(dto.ocat2()) - .ocat3(dto.ocat3()) - .outl(dto.outl()) + .theme(Theme.valueOf(dto.cat1Code())) + .subTheme(SubTheme.valueOf(dto.cat2Code())) + .title(dto.title()) + .addr1(dto.addr1()) + .addr2(dto.addr2()) + .addr3(dto.addr3()) + .mapX(dto.mapX()) + .mapY(dto.mapY()) + .tel(dto.tel()) + .overview(dto.overview()) + .originImgUrl(dto.originImgUrl()) + .usetime(dto.usetime()) + .restdate(dto.restdate()) + .parking(dto.parking()) + .usefee(dto.usefee()) + .geom(geom) + .build(); + } + + private EnSight convertEnSight(SightItemDto dto) { + Point geom = null; + if (dto.mapX() != null && dto.mapY() != null) { + geom = geometryFactory.createPoint(new org.locationtech.jts.geom.Coordinate(dto.mapX(), dto.mapY())); + } + + return EnSight.builder(). + contentId(dto.contentId()) + .theme(Theme.valueOf(dto.cat1Code())) + .subTheme(SubTheme.valueOf(dto.cat2Code())) .title(dto.title()) .addr1(dto.addr1()) .addr2(dto.addr2()) .addr3(dto.addr3()) .mapX(dto.mapX()) .mapY(dto.mapY()) - .modifiedtime(dto.modifiedtime()) .tel(dto.tel()) - .mLevel(dto.mLevel()) .overview(dto.overview()) .originImgUrl(dto.originImgUrl()) - .smallImgUrl(dto.smallImgUrl()) .usetime(dto.usetime()) .restdate(dto.restdate()) .parking(dto.parking()) @@ -127,4 +159,8 @@ private Docent convertDocent(DocentItemDto dto) { .docentUrl(dto.docentUrl()) .build(); } + + private String getCloudFrontDomain(String s3Key){ + return String.format("%s/%s", cloudFrontDomain, s3Key); + } } diff --git a/src/main/java/com/earseo/sight/service/SightService.java b/src/main/java/com/earseo/sight/service/SightService.java index ed30ab9..107a59a 100644 --- a/src/main/java/com/earseo/sight/service/SightService.java +++ b/src/main/java/com/earseo/sight/service/SightService.java @@ -12,7 +12,8 @@ import com.earseo.sight.entity.Theme; import com.earseo.sight.repository.CurationRepository; import com.earseo.sight.repository.DocentRepository; -import com.earseo.sight.repository.SightRepository; +import com.earseo.sight.repository.EnSightRepository; +import com.earseo.sight.repository.KoSightRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -23,16 +24,22 @@ public class SightService { private static final int GEOHASH_PRECISION = 9; - private final SightRepository sightRepository; + private final KoSightRepository koSightRepository; + private final EnSightRepository enSightRepository; private final DocentRepository docentRepository; private final CurationRepository curationRepository; - public SightMapInfoList getMapRectangle(Double minLongitude, Double minLatitude, Double maxLongitude, Double maxLatitude) { + public SightMapInfoList getMapRectangle(Double minLongitude, Double minLatitude, Double maxLongitude, Double maxLatitude, String lang) { if (minLongitude >= maxLongitude || minLatitude >= maxLatitude) { throw new BaseException(SightError.INVALID_COORDINATE_RANGE); } - List sights = sightRepository.findByRectangle(minLongitude, minLatitude, maxLongitude, maxLatitude); + List sights; + if (lang.equals("en")) { + sights = enSightRepository.findByRectangle(minLongitude, minLatitude, maxLongitude, maxLatitude); + } else { + sights = koSightRepository.findByRectangle(minLongitude, minLatitude, maxLongitude, maxLatitude); + } List sightInfos = sights.stream().map( item -> new SightInfoResponse( @@ -40,16 +47,21 @@ public SightMapInfoList getMapRectangle(Double minLongitude, Double minLatitude, item.title(), item.mapX(), item.mapY(), - Theme.valueOf(item.cat1()), + Theme.valueOf(item.theme()), getGeoHash(item.mapX(), item.mapY())) ).toList(); return new SightMapInfoList(sightInfos); } - public SightMapInfoList getMapCircle(Double meters, Double longitude, Double latitude) { + public SightMapInfoList getMapCircle(Double meters, Double longitude, Double latitude, String lang) { - List sights = sightRepository.findByRadius(meters, longitude, latitude); + List sights; + if (lang.equals("en")) { + sights = enSightRepository.findByRadius(meters, longitude, latitude); + } else { + sights = koSightRepository.findByRadius(meters, longitude, latitude); + } List sightInfos = sights.stream().map( item -> new SightInfoResponse( @@ -57,22 +69,26 @@ public SightMapInfoList getMapCircle(Double meters, Double longitude, Double lat item.title(), item.mapX(), item.mapY(), - Theme.valueOf(item.cat1()), + Theme.valueOf(item.theme()), getGeoHash(item.mapX(), item.mapY())) ).toList(); return new SightMapInfoList(sightInfos); } - public SightDetailInfoResponse getSightDetailInfo(String id, Double longitude, Double latitude, Long memberId) { + public SightDetailInfoResponse getSightDetailInfo(String id, Double longitude, Double latitude, Long memberId, String lang) { - SightDetailItemDto dto = sightRepository.findByContentId(id, longitude, latitude, memberId); + SightDetailItemDto dto; + if (lang.equals("en")) { + dto = enSightRepository.findByContentId(id, longitude, latitude, memberId); + } else { + dto = koSightRepository.findByContentId(id, longitude, latitude, memberId); + } if (dto == null) { throw new BaseException(SightError.SIGHT_NOT_FOUND); } - List curations = curationRepository.findAllBySightContentId(id); List curationResponses = curations.stream() .map( @@ -99,12 +115,17 @@ public DocentResponse getDocent(String sightId) { public SearchSightList searchSight( String keyword, Double longitude, Double latitude, Double minLongitude, Double minLatitude, - Double maxLongitude, Double maxLatitude, Integer limit) { + Double maxLongitude, Double maxLatitude, Integer limit, String lang) { if (minLongitude >= maxLongitude || minLatitude >= maxLatitude) { throw new BaseException(SightError.INVALID_COORDINATE_RANGE); } - List sights = sightRepository.findByKeywordAndRectangle(keyword, longitude, latitude, minLongitude, minLatitude, maxLongitude, maxLatitude, limit); + List sights; + if (lang.equals("en")) { + sights = enSightRepository.findByKeywordAndRectangle(keyword, longitude, latitude, minLongitude, minLatitude, maxLongitude, maxLatitude, limit); + } else { + sights = koSightRepository.findByKeywordAndRectangle(keyword, longitude, latitude, minLongitude, minLatitude, maxLongitude, maxLatitude, limit); + } List sightResponses = sights.stream() .map(SearchSightResponse::toDto) .toList(); diff --git a/src/main/java/com/earseo/sight/service/internal/InternalSightService.java b/src/main/java/com/earseo/sight/service/internal/InternalSightService.java index 332b59b..49b1b57 100644 --- a/src/main/java/com/earseo/sight/service/internal/InternalSightService.java +++ b/src/main/java/com/earseo/sight/service/internal/InternalSightService.java @@ -2,7 +2,7 @@ import com.earseo.sight.dto.internal.SightMetaResponse; import com.earseo.sight.dto.projection.SightMetaDto; -import com.earseo.sight.repository.SightRepository; +import com.earseo.sight.repository.KoSightRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,11 +16,11 @@ @RequiredArgsConstructor public class InternalSightService { - private final SightRepository sightRepository; + private final KoSightRepository koSightRepository; @Transactional(readOnly = true) public List getSightByIds(List ids) { - List dtos = sightRepository.findByContentId(ids); + List dtos = koSightRepository.findByContentId(ids); Map orderMap = new HashMap<>(); for (int i = 0; i < ids.size(); i++) { diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 9a5cf63..e0d14d6 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -64,6 +64,13 @@ management: tracing: enabled: false +cloud: + aws: + region: + static: ap-northeast-2 + cloudfront: + domain: ${AWS_CLOUDFRONT_DOMAIN} + logging: level: org.hibernate.SQL: ${LOGGING_LEVEL_ORG_HIBERNATE_SQL:debug} diff --git a/src/main/resources/application-test.yaml b/src/main/resources/application-test.yaml index 034ae9c..9a38e4a 100644 --- a/src/main/resources/application-test.yaml +++ b/src/main/resources/application-test.yaml @@ -57,6 +57,13 @@ management: tracing: enabled: false +cloud: + aws: + region: + static: ap-northeast-2 + cloudfront: + domain: ${AWS_CLOUDFRONT_DOMAIN:cdn.example.com} + logging: level: org.hibernate.SQL: ${LOGGING_LEVEL_ORG_HIBERNATE_SQL:debug}