Skip to content

Commit

Permalink
Merge branch 'main' into feat/102-log-level-setting
Browse files Browse the repository at this point in the history
  • Loading branch information
Hanjaemo authored Jul 6, 2024
2 parents aa2d668 + 0617f04 commit beddf2f
Show file tree
Hide file tree
Showing 18 changed files with 274 additions and 128 deletions.
1 change: 1 addition & 0 deletions appspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ files:
- source: / # 수정 버전의 모든 파일이 인스턴스에 복사된다.
destination: /home/ubuntu/collecting_box
overwrite: yes # 현재 배포 중인 애플리케이션 수정 버전의 파일 버전이 인스턴스에 이미 있는 버전을 대체한다.
file_exists_behavior: OVERWRITE

permissions:
- object: /
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies {
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
implementation "com.querydsl:querydsl-sql-spatial:${dependencyManagement.importedProperties['querydsl.version']}"

// hibernate-spatial
implementation 'org.hibernate:hibernate-spatial:6.4.4.Final'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public enum ErrorCode {
INVALID_BEAN(BAD_REQUEST, "유효하지 않은 데이터입니다."),
MISSING_REQUEST_PARAM(BAD_REQUEST, "필수 요청 파라미터가 존재하지 않습니다."),
MISMATCH_REQUEST_PARAM(BAD_REQUEST, "요청 파라미터가 유효하지 않습니다."),
OUT_OF_RANGE_LONGITUDE(BAD_REQUEST, "경도는 -180 이상 180 이하의 값이어야 합니다."),
OUT_OF_RANGE_LATITUDE(BAD_REQUEST, "위도는 -90 이상 90 이하의 값이어야 합니다."),
NOT_NULL_LONGITUDE(BAD_REQUEST, "경도 값은 필수입니다."),
NOT_NULL_LATITUDE(BAD_REQUEST, "위도 값은 필수입니다."),

// 404
NOT_FOUND_COLLECTING_BOX(NOT_FOUND, "해당 수거함이 존재하지 않습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,28 @@ public class GlobalExceptionHandler {

@ExceptionHandler(CollectingBoxException.class)
public ApiResponse<Object> handleCollectingBoxException(CollectingBoxException e) {
log.error(e.message());
return ApiResponse.error(e.errorCode(), e.message());
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse<Object> methodValidException(MethodArgumentNotValidException e) {
public ApiResponse<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
log.error(message);
return ApiResponse.error(INVALID_BEAN, message);
}

@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(BAD_REQUEST)
public ApiResponse<Object> handleMissingParams(MissingServletRequestParameterException e) {
public ApiResponse<Object> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
log.error(e.getMessage());
return ApiResponse.error(MISSING_REQUEST_PARAM, MISSING_REQUEST_PARAM.getMessage());
}

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
@ResponseStatus(BAD_REQUEST)
public ApiResponse<Object> handleMissingParams(MethodArgumentTypeMismatchException e) {
public ApiResponse<Object> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
log.error(e.getMessage());
return ApiResponse.error(MISMATCH_REQUEST_PARAM, MISSING_REQUEST_PARAM.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
package contest.collectingbox.module.collectingbox.application;

import contest.collectingbox.global.exception.CollectingBoxException;
import contest.collectingbox.global.exception.ErrorCode;
import contest.collectingbox.global.utils.GeometryUtil;
import contest.collectingbox.module.collectingbox.domain.CollectingBox;
import contest.collectingbox.module.collectingbox.domain.CollectingBoxRepository;
import contest.collectingbox.module.collectingbox.domain.Tag;
import contest.collectingbox.module.collectingbox.domain.Tags;
import contest.collectingbox.module.collectingbox.domain.repository.CollectingBoxRepository;
import contest.collectingbox.module.collectingbox.dto.CollectingBoxDetailResponse;
import contest.collectingbox.module.collectingbox.dto.CollectingBoxResponse;
import contest.collectingbox.module.location.domain.DongInfo;
import contest.collectingbox.module.location.domain.DongInfoRepository;
import contest.collectingbox.module.location.domain.GeoPoint;
import lombok.RequiredArgsConstructor;
import org.locationtech.jts.geom.Point;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

import static contest.collectingbox.global.exception.ErrorCode.NOT_FOUND_COLLECTING_BOX;

@Service
@RequiredArgsConstructor
public class CollectingBoxService {
Expand All @@ -32,19 +27,8 @@ public class CollectingBoxService {
private int radius;

@Transactional(readOnly = true)
public List<CollectingBoxResponse> findCollectingBoxesWithinArea(final Double latitude,
final Double longitude,
final List<Tag> tags) {
if (tags.isEmpty()) {
throw new CollectingBoxException(ErrorCode.NOT_SELECTED_TAG);
}

Point center = GeometryUtil.toPoint(longitude, latitude);

return collectingBoxRepository.findAllWithinArea(center, radius, tags)
.stream()
.map(CollectingBoxResponse::fromEntity)
.collect(Collectors.toList());
public List<CollectingBoxResponse> findCollectingBoxesWithinArea(final GeoPoint center, final Tags tags) {
return collectingBoxRepository.findAllWithinArea(center, radius, tags);
}

@Transactional(readOnly = true)
Expand All @@ -53,11 +37,7 @@ public CollectingBoxDetailResponse findBoxDetailById(Long collectionId) {
}

@Transactional(readOnly = true)
public List<CollectingBoxResponse> searchCollectingBoxes(final String query, final List<Tag> tags) {
if (tags.isEmpty()) {
throw new CollectingBoxException(ErrorCode.NOT_SELECTED_TAG);
}

public List<CollectingBoxResponse> searchCollectingBoxes(final String query, final Tags tags) {
// '강남구'
if (query.endsWith("구")) {
return searchBySigunguNm(query, tags);
Expand All @@ -74,16 +54,17 @@ public List<CollectingBoxResponse> searchCollectingBoxes(final String query, fin
return searchByDongNm(splitQuery[1], tags);
}

private List<CollectingBoxResponse> searchBySigunguNm(String query, List<Tag> tags) {
private List<CollectingBoxResponse> searchBySigunguNm(String query, Tags tags) {
return dongInfoRepository.findAllBySigunguNm(query).stream()
.flatMap(dongInfo -> collectingBoxRepository.findAllByDongInfoAndTags(dongInfo, tags).stream())
.flatMap(dongInfo ->
collectingBoxRepository.findAllByDongInfoAndTags(dongInfo, tags.getTags()).stream())
.map(CollectingBoxResponse::fromEntity)
.collect(Collectors.toList());
}

private List<CollectingBoxResponse> searchByDongNm(String dongNm, List<Tag> tags) {
private List<CollectingBoxResponse> searchByDongNm(String dongNm, Tags tags) {
DongInfo dongInfo = dongInfoRepository.findByDongNm(dongNm);
List<CollectingBox> boxes = collectingBoxRepository.findAllByDongInfoAndTags(dongInfo, tags);
List<CollectingBox> boxes = collectingBoxRepository.findAllByDongInfoAndTags(dongInfo, tags.getTags());
return boxes.stream()
.map(CollectingBoxResponse::fromEntity)
.collect(Collectors.toList());
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package contest.collectingbox.module.collectingbox.domain;

import contest.collectingbox.global.exception.CollectingBoxException;

import java.util.List;

import static contest.collectingbox.global.exception.ErrorCode.NOT_SELECTED_TAG;

public class Tags {
private final List<Tag> tags;

public Tags(List<Tag> tags) {
if (tags == null || tags.isEmpty()) {
throw new CollectingBoxException(NOT_SELECTED_TAG);
}
this.tags = tags;
}

public List<Tag> getTags() {
return List.copyOf(tags);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package contest.collectingbox.module.collectingbox.domain;
package contest.collectingbox.module.collectingbox.domain.repository;

import contest.collectingbox.module.collectingbox.domain.CollectingBox;
import contest.collectingbox.module.collectingbox.domain.Tag;
import contest.collectingbox.module.location.domain.DongInfo;
import org.locationtech.jts.geom.Point;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand All @@ -10,13 +11,6 @@

public interface CollectingBoxRepository extends JpaRepository<CollectingBox, Long>, CollectingBoxRepositoryCustom {

@Query("select c from CollectingBox c join fetch c.location l " +
"where function('st_contains', function('st_buffer', :center, :radius), l.point) and " +
"c.tag in :tags")
List<CollectingBox> findAllWithinArea(@Param("center") Point center,
@Param("radius") int radius,
@Param("tags") List<Tag> tags);

@Query("select c from CollectingBox c join c.location l where l.dongInfo = :dongInfo and c.tag in :tags")
List<CollectingBox> findAllByDongInfoAndTags(@Param("dongInfo") DongInfo dongInfo,
@Param("tags") List<Tag> tags);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package contest.collectingbox.module.collectingbox.domain.repository;

import contest.collectingbox.module.collectingbox.domain.Tags;
import contest.collectingbox.module.collectingbox.dto.CollectingBoxDetailResponse;
import contest.collectingbox.module.collectingbox.dto.CollectingBoxResponse;
import contest.collectingbox.module.location.domain.GeoPoint;

import java.util.List;

public interface CollectingBoxRepositoryCustom {
CollectingBoxDetailResponse findDetailById(Long id);

List<CollectingBoxResponse> findAllWithinArea(GeoPoint center, int radius, Tags tags);
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,57 @@
package contest.collectingbox.module.collectingbox.domain;

import static contest.collectingbox.global.exception.ErrorCode.NOT_FOUND_COLLECTING_BOX;
import static contest.collectingbox.module.collectingbox.domain.QCollectingBox.collectingBox;
import static contest.collectingbox.module.location.domain.QLocation.location;
import static contest.collectingbox.module.review.domain.QReview.*;
package contest.collectingbox.module.collectingbox.domain.repository;

import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.querydsl.spatial.locationtech.jts.JTSGeometryExpressions;
import contest.collectingbox.global.exception.CollectingBoxException;
import contest.collectingbox.global.utils.GeometryUtil;
import contest.collectingbox.module.collectingbox.domain.Tags;
import contest.collectingbox.module.collectingbox.dto.CollectingBoxDetailResponse;
import contest.collectingbox.module.collectingbox.dto.CollectingBoxResponse;
import contest.collectingbox.module.collectingbox.dto.QCollectingBoxDetailResponse;
import contest.collectingbox.module.review.dto.ReviewResponse;
import contest.collectingbox.module.collectingbox.dto.QCollectingBoxResponse;
import contest.collectingbox.module.location.domain.GeoPoint;
import contest.collectingbox.module.review.dto.QReviewResponse;
import contest.collectingbox.module.review.dto.ReviewResponse;
import jakarta.persistence.EntityManager;
import org.locationtech.jts.geom.Point;

import java.util.List;

import static contest.collectingbox.global.exception.ErrorCode.NOT_FOUND_COLLECTING_BOX;
import static contest.collectingbox.module.collectingbox.domain.QCollectingBox.collectingBox;
import static contest.collectingbox.module.location.domain.QLocation.location;
import static contest.collectingbox.module.review.domain.QReview.review;

public class CollectingBoxRepositoryImpl implements CollectingBoxRepositoryCustom {

private final JPAQueryFactory queryFactory;

public CollectingBoxRepositoryImpl(EntityManager em) {
this.queryFactory = new JPAQueryFactory(em);
}

@Override
public CollectingBoxDetailResponse findDetailById(Long id) {
public List<CollectingBoxResponse> findAllWithinArea(GeoPoint center, int radius, Tags tags) {
Point centerPoint = GeometryUtil.toPoint(center.getLongitude(), center.getLatitude());
return queryFactory
.select(new QCollectingBoxResponse(
collectingBox.id,
Expressions.stringTemplate("function('st_longitude', {0})", location.point)
.castToNum(double.class),
Expressions.stringTemplate("function('st_latitude', {0})", location.point)
.castToNum(double.class),
collectingBox.tag
))
.from(collectingBox)
.join(collectingBox.location, location)
.where(JTSGeometryExpressions.asJTSGeometry(centerPoint).buffer(radius).contains(location.point),
collectingBox.tag.in(tags.getTags()))
.fetch();
}

@Override
public CollectingBoxDetailResponse findDetailById(Long id) {
CollectingBoxDetailResponse response = queryFactory.select(new QCollectingBoxDetailResponse(
location.point,
location.name, location.roadName,
Expand All @@ -48,7 +76,7 @@ public CollectingBoxDetailResponse findDetailById(Long id) {
.fetch();

response.setReviews(reviews);

return response;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package contest.collectingbox.module.collectingbox.dto;

import com.querydsl.core.annotations.QueryProjection;
import contest.collectingbox.module.collectingbox.domain.CollectingBox;
import contest.collectingbox.module.collectingbox.domain.Tag;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -16,22 +18,30 @@ public class CollectingBoxResponse {
@Schema(description = "수거함 ID", example = "2571")
private Long id;

@Schema(description = "위도", example = "37.4888178446615")
private Double latitude;

@Schema(description = "경도", example = " 126.902998281977")
private Double longitude;

@Schema(description = "위도", example = "37.4888178446615")
private Double latitude;

@Schema(description = "태그", example = "CLOTHES",
allowableValues = {"CLOTHES", "LAMP_BATTERY", "MEDICINE", "TRASH"})
private String tag;
private Tag tag;

@QueryProjection
public CollectingBoxResponse(Long id, double longitude, double latitude, Tag tag) {
this.id = id;
this.longitude = longitude;
this.latitude = latitude;
this.tag = tag;
}

public static CollectingBoxResponse fromEntity(CollectingBox collectingBox) {
return CollectingBoxResponse.builder()
.id(collectingBox.getId())
.latitude(collectingBox.getLocation().latitude())
.longitude(collectingBox.getLocation().longitude())
.tag(collectingBox.getTag().name())
.tag(collectingBox.getTag())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import contest.collectingbox.global.common.ApiResponse;
import contest.collectingbox.module.collectingbox.application.CollectingBoxService;
import contest.collectingbox.module.collectingbox.domain.Tag;
import contest.collectingbox.module.collectingbox.domain.Tags;
import contest.collectingbox.module.collectingbox.dto.CollectingBoxDetailResponse;
import contest.collectingbox.module.collectingbox.dto.CollectingBoxResponse;
import contest.collectingbox.module.location.domain.GeoPoint;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

Expand All @@ -20,18 +20,17 @@ public class CollectingBoxController {

private final CollectingBoxService collectingBoxService;

@Operation(summary = "수거함 목록 조회", description = "위도와 경도를 기준으로 200m 반경에 위치한 수거함 목록을 조회합니다.")
@Operation(summary = "주변 수거함 목록 조회", description = "위도와 경도를 기준으로 주변 600m 반경에 위치한 수거함 목록을 조회합니다.")
@GetMapping
public ApiResponse<List<CollectingBoxResponse>> findCollectingBoxesWithinArea(@RequestParam final Double latitude,
@RequestParam final Double longitude,
@RequestParam final List<Tag> tags) {
return ApiResponse.ok(collectingBoxService.findCollectingBoxesWithinArea(latitude, longitude, tags));
public ApiResponse<List<CollectingBoxResponse>> findCollectingBoxesWithinArea(final GeoPoint center,
final Tags tags) {
return ApiResponse.ok(collectingBoxService.findCollectingBoxesWithinArea(center, tags));
}

@Operation(summary = "지역별 수거함 검색", description = "구/동 단위로 검색한 주소에 위치한 수거함 목록을 조회합니다.")
@GetMapping("/search")
public ApiResponse<List<CollectingBoxResponse>> searchCollectingBoxes(@RequestParam final String query,
@RequestParam final List<Tag> tags) {
final Tags tags) {
return ApiResponse.ok(collectingBoxService.searchCollectingBoxes(query, tags));
}

Expand Down
Loading

0 comments on commit beddf2f

Please sign in to comment.