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
@@ -1,28 +1,43 @@
// src/main/java/com/be/sportizebe/domain/facility/controller/SportsFacilityController.java
package com.be.sportizebe.domain.facility.controller;

import com.be.sportizebe.domain.facility.dto.FacilityNearResponse;
import com.be.sportizebe.domain.facility.dto.request.FacilityMarkerRequest;
import com.be.sportizebe.domain.facility.dto.request.FacilityNearRequest;
import com.be.sportizebe.domain.facility.dto.response.FacilityMarkerResponse;
import com.be.sportizebe.domain.facility.dto.response.FacilityNearResponse;
import com.be.sportizebe.domain.facility.service.SportsFacilityService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import jakarta.validation.Valid;
import java.util.List;

@Tag(name = "Sports Facility", description = "체육시설 조회 API")
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/facilities")
public class SportsFacilityController {

private final SportsFacilityService sportsFacilityService;

@Operation(summary = "주변 체육시설 상세 목록", description = "현재 위치(lat/lng) 기준 반경 내 체육시설을 거리순으로 조회합니다.")
@GetMapping("/near")
public List<FacilityNearResponse> near(
@RequestParam double lat,
@RequestParam double lng,
@RequestParam(defaultValue = "3000") int radiusM,
@RequestParam(defaultValue = "50") int limit,
@RequestParam(required = false) String type
@ParameterObject @Valid @ModelAttribute FacilityNearRequest request
) {
return sportsFacilityService.getNear(request);
}

@Operation(summary = "지도 마커 목록", description = "지도 중심 좌표(centerLat/centerLng) 기준 반경 내 체육시설 마커 정보를 조회합니다.")
@GetMapping("/markers")
public List<FacilityMarkerResponse> markers(
@ParameterObject @Valid @ModelAttribute FacilityMarkerRequest request
) {
return sportsFacilityService.getNear(lat, lng, radiusM, limit, type);
return sportsFacilityService.getMarkers(request);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// src/main/java/com/be/sportizebe/domain/facility/dto/request/FacilityMarkerRequest.java
package com.be.sportizebe.domain.facility.dto.request;

import com.be.sportizebe.domain.facility.entity.FacilityType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class FacilityMarkerRequest {

@Schema(description = "지도 중심 위도", example = "37.2869", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "지도 중심 위도(centerLat)는 필수입니다")
@DecimalMin(value = "-90.0", message = "위도는 -90.0 이상이어야 합니다")
@DecimalMax(value = "90.0", message = "위도는 90.0 이하여야 합니다")
private Double lat;

@Schema(description = "지도 중심 경도", example = "127.0095", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "지도 중심 경도(centerLng)는 필수입니다")
@DecimalMin(value = "-180.0", message = "경도는 -180.0 이상이어야 합니다")
@DecimalMax(value = "180.0", message = "경도는 180.0 이하여야 합니다")
private Double lng;

@Schema(description = "반경(미터)", example = "5000", defaultValue = "5000")
@Min(value = 100, message = "반경은 최소 100m 이상이어야 합니다")
@Max(value = 20000, message = "반경은 최대 20km까지 가능합니다")
private Integer radiusM = 5000;

@Schema(description = "조회 개수 제한", example = "200", defaultValue = "200")
@Min(value = 1, message = "limit는 최소 1 이상이어야 합니다")
@Max(value = 500, message = "limit는 최대 500까지 가능합니다")
private Integer limit = 200;

@Schema(description = "종목 필터(선택)", example = "SOCCER", nullable = true)
private FacilityType type;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// src/main/java/com/be/sportizebe/domain/facility/dto/request/FacilityNearRequest.java
package com.be.sportizebe.domain.facility.dto.request;

import com.be.sportizebe.domain.facility.entity.FacilityType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class FacilityNearRequest {

@Schema(description = "위도", example = "37.2662", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "위도(lat)는 필수입니다")
@DecimalMin(value = "-90.0", message = "위도는 -90.0 이상이어야 합니다")
@DecimalMax(value = "90.0", message = "위도는 90.0 이하여야 합니다")
private Double lat;

@Schema(description = "경도", example = "127.0006", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "경도(lng)는 필수입니다")
@DecimalMin(value = "-180.0", message = "경도는 -180.0 이상이어야 합니다")
@DecimalMax(value = "180.0", message = "경도는 180.0 이하여야 합니다")
private Double lng;

@Schema(description = "반경(미터)", example = "3000", defaultValue = "3000")
@Min(value = 100, message = "반경은 최소 100m 이상이어야 합니다")
@Max(value = 10000, message = "반경은 최대 10km까지 가능합니다")
private Integer radiusM = 3000;

@Schema(description = "조회 개수 제한", example = "50", defaultValue = "50")
@Min(value = 1, message = "limit는 최소 1 이상이어야 합니다")
@Max(value = 100, message = "limit는 최대 100까지 가능합니다")
private Integer limit = 50;

@Schema(description = "종목 필터(선택)", example = "BASKETBALL", nullable = true)
private FacilityType type;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// src/main/java/com/be/sportizebe/domain/facility/dto/response/FacilityMarkerResponse.java
package com.be.sportizebe.domain.facility.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class FacilityMarkerResponse {

@Schema(description = "시설 ID", example = "1")
private Long id;

@Schema(description = "시설명", example = "수원종합운동장")
private String facilityName;

@Schema(description = "종목", example = "SOCCER")
private String facilityType;

@Schema(description = "위도", example = "37.2869")
private double lat;

@Schema(description = "경도", example = "127.0095")
private double lng;

@Schema(description = "중심으로부터 거리(미터)", example = "1200")
private int distanceM;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// src/main/java/com/be/sportizebe/domain/facility/dto/response/FacilityNearResponse.java
package com.be.sportizebe.domain.facility.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class FacilityNearResponse {

@Schema(description = "시설 ID", example = "1")
private Long id;

@Schema(description = "시설명", example = "수원종합운동장")
private String facilityName;

@Schema(description = "소개", example = "수원시 대표 종합 스포츠 시설 (축구, 육상 등)")
private String introduce;

@Schema(description = "썸네일 URL", example = "https://example.com/suwon-stadium.jpg")
private String thumbnailUrl;

@Schema(description = "종목", example = "SOCCER")
private String facilityType;

@Schema(description = "거리(미터)", example = "2178")
private int distanceM;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.be.sportizebe.domain.facility.mapper;

import com.be.sportizebe.domain.facility.dto.FacilityNearResponse;
import com.be.sportizebe.domain.facility.repository.FacilityNearProjection;
import com.be.sportizebe.domain.facility.dto.response.FacilityMarkerResponse;
import com.be.sportizebe.domain.facility.dto.response.FacilityNearResponse;
import com.be.sportizebe.domain.facility.repository.projection.FacilityMarkerProjection;
import com.be.sportizebe.domain.facility.repository.projection.FacilityNearProjection;

public interface FacilityMapper {

public static FacilityNearResponse toNearResponse(FacilityNearProjection p){
static FacilityNearResponse toNearResponse(FacilityNearProjection p){
return FacilityNearResponse.builder()
.id(p.getId())
.facilityName(p.getFacilityName())
Expand All @@ -15,4 +17,14 @@ public static FacilityNearResponse toNearResponse(FacilityNearProjection p){
.distanceM((int) Math.round(p.getDistanceM()))
.build();
}

static FacilityMarkerResponse toMarkerResponse(FacilityMarkerProjection p){
return FacilityMarkerResponse.builder()
.id(p.getId())
.facilityName(p.getFacilityName())
.facilityType(p.getFacilityType())
.lat(p.getLat())
.lng(p.getLng())
.build();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package com.be.sportizebe.domain.facility.repository;

import com.be.sportizebe.domain.facility.entity.FacilityType;
import com.be.sportizebe.domain.facility.entity.SportsFacility;
import org.springframework.data.jpa.repository.JpaRepository;
import com.be.sportizebe.domain.facility.repository.projection.FacilityMarkerProjection;
import com.be.sportizebe.domain.facility.repository.projection.FacilityNearProjection;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface SportsFacilityRepository extends JpaRepository<SportsFacility, Long> {

// Java에서 거리를 계산하는게 아니라, DB가 돌린 결과 "숫자"를 받아오는 거다.
public interface SportsFacilityRepository extends Repository<SportsFacility, Long> {
// 주변 가까운 체육시설 조회 쿼리
@Query(value = """
SELECT
sf.id AS id,
Expand All @@ -33,7 +37,36 @@ WHERE ST_DWithin(
List<FacilityNearProjection> findNear(
@Param("lat") double lat,
@Param("lng") double lng,
@Param("radiusM") long radiusM,
@Param("radiusM") int radiusM,
@Param("limit") int limit,
@Param("type") String type
);
// 마커 전용 쿼리
@Query(value = """
SELECT
sf.id AS id,
sf.facility_name AS facilityName,
sf.facility_type AS facilityType,
ST_Y(sf.location::geometry) AS lat,
ST_X(sf.location::geometry) AS lng,
ST_Distance(
sf.location,
ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography
) AS distanceM
FROM sports_facilities sf
WHERE ST_DWithin(
sf.location,
ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography,
:radiusM
)
AND (:type IS NULL OR sf.facility_type = :type)
ORDER BY distanceM
LIMIT :limit
""", nativeQuery = true)
List<FacilityMarkerProjection> findMarkersNear(
@Param("lat") double lat,
@Param("lng") double lng,
@Param("radiusM") int radiusM,
@Param("limit") int limit,
@Param("type") String type
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.be.sportizebe.domain.facility.repository.projection;

public interface FacilityMarkerProjection {
Long getId();
String getFacilityName();
String getFacilityType();
Double getLat();
Double getLng();
Double getDistanceM();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.be.sportizebe.domain.facility.repository.projection;

public interface FacilityNearProjection {
// 쿼리 결과를 "필요한 컬럼 + 계산컬럼"만 깔끔하게 받기 위한 인터페이스
// 인터페이스로 받아버리면
Long getId();
String getFacilityName();
String getIntroduce();
String getFacilityType();
String getThumbnailUrl();
Double getDistanceM();
}
Loading