Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.wayble.server.explore.controller;

import com.wayble.server.common.response.CommonResponse;
import com.wayble.server.explore.dto.facility.WaybleFacilityConditionDto;
import com.wayble.server.explore.dto.facility.WaybleFacilityResponseDto;
import com.wayble.server.explore.service.WaybleFacilityDocumentService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequiredArgsConstructor
@Validated
@RequestMapping("/api/v1/facilities/search")
public class WaybleFacilitySearchController {
private final WaybleFacilityDocumentService waybleFacilityDocumentService;

@GetMapping("")
public CommonResponse<List<WaybleFacilityResponseDto>> findNearbyFacilities(
@Valid @ModelAttribute WaybleFacilityConditionDto conditionDto
) {
return CommonResponse.success(waybleFacilityDocumentService.findNearbyFacilityDocuments(conditionDto));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.wayble.server.explore.dto.facility;

import com.wayble.server.explore.entity.FacilityType;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;

public record WaybleFacilityConditionDto(
@DecimalMin(value = "-90.0", message = "위도는 -90.0 이상이어야 합니다.")
@DecimalMax(value = "90.0", message = "위도는 90.0 이하여야 합니다.")
@NotNull(message = "위도 입력은 필수입니다.")
Double latitude,

@DecimalMin(value = "-180.0", message = "경도는 -180.0 이상이어야 합니다.")
@DecimalMax(value = "180.0", message = "경도는 180.0 이하여야 합니다.")
@NotNull(message = "경도 입력은 필수입니다.")
Double longitude,

FacilityType facilityType
) {
}
Comment on lines +19 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Null 허용 시 NPE 위험: facilityType에 @NotNull 추가 필요

WaybleFacilityQuerySearchRepository에서 condition.facilityType().name()을 사용하는 것으로 보입니다. facilityType이 null이면 NPE가 발생합니다. NotNull 제약을 추가하세요.

다음 수정안을 제안합니다:

-        FacilityType facilityType
+        @NotNull(message = "시설 타입 입력은 필수입니다.")
+        FacilityType facilityType

테스트에 facilityType 누락 케이스(Negative)도 추가해 주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FacilityType facilityType
) {
}
// src/main/java/com/wayble/server/explore/dto/facility/WaybleFacilityConditionDto.java
import javax.validation.constraints.NotNull; // ← make sure this import is present
public class WaybleFacilityConditionDto {
// …
public WaybleFacilityConditionDto(
@NotNull(message = "시설 타입 입력은 필수입니다.")
FacilityType facilityType
) {
this.facilityType = facilityType;
}
// …
}
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/explore/dto/facility/WaybleFacilityConditionDto.java
around lines 19-21, facilityType is nullable which risks NPE when calling
condition.facilityType().name(); add a @NotNull annotation to the facilityType
field (and import the correct javax/jakarta validation package used by the
project) so validation fails early, update constructor/getter if needed to
retain the annotation, and add a unit test that covers the negative case where
facilityType is missing to ensure validation prevents the NPE.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.wayble.server.explore.dto.facility;

import com.wayble.server.explore.entity.FacilityType;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;

@Builder
public record WaybleFacilityRegisterDto (
@DecimalMin(value = "-90.0", message = "위도는 -90.0 이상이어야 합니다.")
@DecimalMax(value = "90.0", message = "위도는 90.0 이하여야 합니다.")
@NotNull(message = "위도 입력은 필수입니다.")
Double latitude,

@DecimalMin(value = "-180.0", message = "경도는 -180.0 이상이어야 합니다.")
@DecimalMax(value = "180.0", message = "경도는 180.0 이하여야 합니다.")
@NotNull(message = "경도 입력은 필수입니다.")
Double longitude,

FacilityType facilityType
){
}
Comment on lines +21 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

등록 시 facilityType 필수값 보장 필요

등록 DTO에서도 facilityType 누락을 방지하기 위해 @NotNull을 추가하세요.

-        FacilityType facilityType
+        @NotNull(message = "시설 타입 입력은 필수입니다.")
+        FacilityType facilityType
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FacilityType facilityType
){
}
@NotNull(message = "시설 타입 입력은 필수입니다.")
FacilityType facilityType
){
}
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/explore/dto/facility/WaybleFacilityRegisterDto.java
around lines 21-23, the facilityType field in the registration DTO is missing a
nullability constraint; add the @NotNull annotation to the facilityType
declaration (and the corresponding import, e.g.
javax.validation.constraints.NotNull or jakarta.validation.constraints.NotNull
depending on the project), ensuring the constructor/field carries the annotation
so validation will reject requests without facilityType; run/adjust any
validation tests or configuration if needed to enable request-level validation.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.wayble.server.explore.dto.facility;

import com.wayble.server.explore.entity.FacilityType;
import com.wayble.server.explore.entity.WaybleFacilityDocument;
import lombok.AccessLevel;
import lombok.Builder;

@Builder(access = AccessLevel.PRIVATE)
public record WaybleFacilityResponseDto(
Double latitude,

Double longitude,

FacilityType facilityType
) {
public static WaybleFacilityResponseDto from(WaybleFacilityDocument facilityDocument) {
return WaybleFacilityResponseDto.builder()
.latitude(facilityDocument.getLocation().getLat())
.longitude(facilityDocument.getLocation().getLon())
.facilityType(facilityDocument.getFacilityType())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.wayble.server.explore.entity;

public enum FacilityType {
ELEVATOR,
RAMP
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.wayble.server.explore.entity;

import com.wayble.server.direction.entity.transportation.Facility;
import com.wayble.server.explore.dto.facility.WaybleFacilityRegisterDto;
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;

@ToString
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "wayble_facility_document", createIndex = true)
public class WaybleFacilityDocument {
@Id
@Field(name = "id")
private String id;

@GeoPointField
private GeoPoint location;

private FacilityType facilityType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.wayble.server.explore.repository.facility;

import com.wayble.server.explore.entity.WaybleFacilityDocument;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface WaybleFacilityDocumentRepository extends ElasticsearchRepository<WaybleFacilityDocument, String> {
List<WaybleFacilityDocument> findAll();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.wayble.server.explore.repository.facility;

import co.elastic.clients.elasticsearch._types.GeoLocation;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import com.wayble.server.explore.dto.facility.WaybleFacilityConditionDto;
import com.wayble.server.explore.dto.facility.WaybleFacilityResponseDto;
import com.wayble.server.explore.entity.WaybleFacilityDocument;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
@RequiredArgsConstructor
public class WaybleFacilityQuerySearchRepository {

private final ElasticsearchOperations operations;
private static final IndexCoordinates INDEX = IndexCoordinates.of("wayble_facility_document");
private static final int LIMIT = 50;

/**
* 위도, 경도, 시설 타입을 바탕으로 WaybleFacilityDocument를 거리순으로 N개 반환
*/
public List<WaybleFacilityResponseDto> findNearbyFacilitiesByType(
WaybleFacilityConditionDto condition) {

double radius = 10.0; // 기본 반경 5km
String radiusWithUnit = radius + "km";

// 시설 타입에 따른 쿼리 조건 생성
Query query = Query.of(q -> q
.bool(b -> {
// 시설 타입 조건 추가
if (condition.facilityType() != null) {
b.must(m -> m
.term(t -> t
.field("facilityType.keyword")
.value(condition.facilityType().name())
)
);
}

// 위치 기반 필터: 중심 좌표 기준 반경 필터링
b.filter(f -> f
.geoDistance(gd -> gd
.field("location")
.location(loc -> loc
.latlon(ll -> ll
.lat(condition.latitude())
.lon(condition.longitude())
)
)
.distance(radiusWithUnit)
)
);

return b;
})
);

// 거리 기준 오름차순 정렬
SortOptions geoSort = SortOptions.of(s -> s
.geoDistance(gds -> gds
.field("location")
.location(GeoLocation.of(gl -> gl
.latlon(ll -> ll
.lat(condition.latitude())
.lon(condition.longitude())
)
))
.order(SortOrder.Asc)
)
);

// Elasticsearch 쿼리 구성
NativeQuery nativeQuery = NativeQuery.builder()
.withQuery(query)
.withSort(geoSort)
.withPageable(PageRequest.of(0, LIMIT))
.build();

// 검색 수행
SearchHits<WaybleFacilityDocument> hits =
operations.search(nativeQuery, WaybleFacilityDocument.class, INDEX);

// 결과를 Document 리스트로 반환
return hits.stream()
.map(hit -> {
WaybleFacilityDocument doc = hit.getContent();
return WaybleFacilityResponseDto.from(doc);
})
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.wayble.server.explore.service;

import com.wayble.server.explore.dto.facility.WaybleFacilityConditionDto;
import com.wayble.server.explore.dto.facility.WaybleFacilityResponseDto;
import com.wayble.server.explore.repository.facility.WaybleFacilityQuerySearchRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Comment on lines +6 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

트랜잭션 어노테이션 일관성/적용 범위 조정

  • jakarta.transaction.Transactional 보다는 Spring 관리형 트랜잭션(org.springframework.transaction.annotation.Transactional) 사용을 권장합니다.
  • 읽기 전용 서비스이므로 readOnly = true 지정 또는 제거가 적절합니다(ES 조회는 트랜잭션 불필요).

다음 수정안을 제안합니다:

-import jakarta.transaction.Transactional;
+import org.springframework.transaction.annotation.Transactional;
@@
-@Transactional
+@Transactional(readOnly = true)
 public class WaybleFacilityDocumentService {

Also applies to: 12-15

🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/explore/service/WaybleFacilityDocumentService.java
around lines 6-8 (also applies to lines 12-15), replace the import of
jakarta.transaction.Transactional with Spring's
org.springframework.transaction.annotation.Transactional and adjust the
annotation usage: for this read-only ES query service either annotate the
class/methods with @Transactional(readOnly = true) or remove transactional
annotations entirely if Elasticsearch operations do not require a transaction;
ensure imports and annotations are updated consistently across the indicated
lines.


import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class WaybleFacilityDocumentService {

private final WaybleFacilityQuerySearchRepository waybleFacilityQuerySearchRepository;

public List<WaybleFacilityResponseDto> findNearbyFacilityDocuments(WaybleFacilityConditionDto dto) {
return waybleFacilityQuerySearchRepository.findNearbyFacilitiesByType(dto);
}
}
Loading