Skip to content

Commit

Permalink
FEAT: (#88) 지도에서 판매점 조회 시 반경 2km 내 판매점만 조회한다
Browse files Browse the repository at this point in the history
  • Loading branch information
anxi01 committed Aug 13, 2024
1 parent 3c3a6cb commit 6ac3ff8
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package com.zerozero.store.application;

import com.zerozero.core.application.BaseRequest;
import com.zerozero.core.application.BaseResponse;
import com.zerozero.core.application.BaseUseCase;
import com.zerozero.core.domain.entity.Store;
import com.zerozero.core.domain.entity.User;
import com.zerozero.core.domain.infra.repository.StoreJPARepository;
import com.zerozero.core.domain.infra.repository.UserJPARepository;
import com.zerozero.core.domain.vo.AccessToken;
import com.zerozero.core.exception.DomainException;
import com.zerozero.core.exception.error.BaseErrorCode;
import com.zerozero.core.util.JwtUtil;
import com.zerozero.external.kakao.search.application.RequestKakaoKeywordSearchUseCase;
import com.zerozero.external.kakao.search.application.RequestKakaoKeywordSearchUseCase.RequestKakaoKeywordSearchRequest;
import com.zerozero.external.kakao.search.dto.KeywordSearchResponse;
import com.zerozero.external.kakao.search.dto.KeywordSearchResponse.Document;
import com.zerozero.store.application.SearchNearbyStoresUseCase.SearchNearbyStoresRequest;
import com.zerozero.store.application.SearchNearbyStoresUseCase.SearchNearbyStoresResponse;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Log4j2
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class SearchNearbyStoresUseCase implements BaseUseCase<SearchNearbyStoresRequest, SearchNearbyStoresResponse> {

private final JwtUtil jwtUtil;

private final RequestKakaoKeywordSearchUseCase requestKakaoKeywordSearchUseCase;

private final UserJPARepository userJPARepository;

private final StoreJPARepository storeJPARepository;

private static final Integer DEFAULT_RADIUS = 2000;

@Override
public SearchNearbyStoresResponse execute(SearchNearbyStoresRequest request) {
if (request == null || !request.isValid()) {
log.error("[SearchNearbyStoresUseCase] Invalid request");
return SearchNearbyStoresResponse.builder()
.success(false)
.errorCode(SearchNearbyStoresErrorCode.NOT_EXIST_SEARCH_CONDITION)
.build();
}
AccessToken accessToken = request.getAccessToken();
if (jwtUtil.isTokenExpired(accessToken.getToken())) {
log.error("[SearchNearbyStoresUseCase] Expired access token");
return SearchNearbyStoresResponse.builder()
.success(false)
.errorCode(SearchNearbyStoresErrorCode.EXPIRED_TOKEN)
.build();
}
String userEmail = jwtUtil.extractUsername(accessToken.getToken());
User user = userJPARepository.findByEmail(userEmail);
if (user == null) {
log.error("[SearchNearbyStoresUseCase] not found user with email {}", userEmail);
return SearchNearbyStoresResponse.builder()
.success(false)
.errorCode(SearchNearbyStoresErrorCode.NOT_EXIST_USER)
.build();
}
KeywordSearchResponse keywordSearchResponse = requestKakaoKeywordSearchUseCase.execute(
RequestKakaoKeywordSearchRequest.builder()
.query(request.getQuery())
.longitude(String.valueOf(request.getLongitude()))
.latitude(String.valueOf(request.getLatitude()))
.radius(DEFAULT_RADIUS)
.build())
.getKeywordSearchResponse();
if (keywordSearchResponse == null || keywordSearchResponse.getMeta().getTotalCount() == 0) {
log.error("[SearchNearbyStoresUseCase] Search response is null");
return SearchNearbyStoresResponse.builder()
.success(false)
.errorCode(SearchNearbyStoresErrorCode.NOT_EXIST_SEARCH_RESPONSE)
.build();
}
List<Document> filteredItems = Arrays.stream(keywordSearchResponse.getDocuments()).peek(item -> {
boolean isSelling = storeJPARepository.existsByNameAndLongitudeAndLatitudeAndStatusIsTrue(
item.getPlaceName(), item.getX(), item.getY());
if (isSelling) {
item.setStatus(true);
Store store = storeJPARepository.findByNameAndLongitudeAndLatitudeAndStatusIsTrue(item.getPlaceName(),
item.getX(), item.getY());
item.setStoreId(store.getId());
}
}).collect(Collectors.toList());
return SearchNearbyStoresResponse.builder()
.stores(filteredItems.stream()
.map(com.zerozero.core.domain.vo.Store::of)
.collect(Collectors.toList()))
.build();
}

@Getter
@RequiredArgsConstructor
public enum SearchNearbyStoresErrorCode implements BaseErrorCode<DomainException> {
NOT_EXIST_SEARCH_CONDITION(HttpStatus.BAD_REQUEST, "검색 조건이 올바르지 않습니다."),
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."),
NOT_EXIST_USER(HttpStatus.BAD_REQUEST, "존재하지 않는 사용자입니다."),
NOT_EXIST_SEARCH_RESPONSE(HttpStatus.BAD_REQUEST, "검색 응답이 존재하지 않습니다.");

private final HttpStatus httpStatus;

private final String message;

@Override
public DomainException toException() {
return new DomainException(httpStatus, this);
}
}

@ToString
@Getter
@Setter
@SuperBuilder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class SearchNearbyStoresResponse extends BaseResponse<SearchNearbyStoresErrorCode> {

private List<com.zerozero.core.domain.vo.Store> stores;
}

@ToString
@Getter
@Setter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class SearchNearbyStoresRequest implements BaseRequest {

private String query;

private Double longitude;

private Double latitude;

private AccessToken accessToken;

@Override
public boolean isValid() {
return query != null && longitude != null && latitude != null && accessToken != null;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.zerozero.store.presentation;

import com.zerozero.configuration.swagger.ApiErrorCode;
import com.zerozero.core.application.BaseRequest;
import com.zerozero.core.application.BaseResponse;
import com.zerozero.core.domain.vo.AccessToken;
import com.zerozero.core.domain.vo.Store;
import com.zerozero.core.exception.error.GlobalErrorCode;
import com.zerozero.store.application.SearchNearbyStoresUseCase;
import com.zerozero.store.application.SearchNearbyStoresUseCase.SearchNearbyStoresErrorCode;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import java.util.Optional;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@Tag(name = "Store", description = "판매점")
public class SearchNearbyStoresController {

private final SearchNearbyStoresUseCase searchNearbyStoresUseCase;

@Operation(
summary = "[메인페이지] 판매점 검색 API",
description = "메인페이지에서 등록할 판매점을 쿼리와 경,위도를 통해 반경 2KM 내 판매점을 검색합니다.",
operationId = "/store/search/nearby"
)
@ApiErrorCode({GlobalErrorCode.class, SearchNearbyStoresErrorCode.class})
@GetMapping("/store/search/nearby")
public ResponseEntity<SearchNearbyStoresResponse> searchNearbyStores(@ParameterObject SearchNearbyStoresRequest request,
@Parameter(hidden = true) AccessToken accessToken) {
SearchNearbyStoresUseCase.SearchNearbyStoresResponse searchNearbyStoresResponse = searchNearbyStoresUseCase.execute(
SearchNearbyStoresUseCase.SearchNearbyStoresRequest.builder()
.query(request.getQuery())
.longitude(request.getLongitude())
.latitude(request.getLatitude())
.accessToken(accessToken)
.build());
if (searchNearbyStoresResponse == null || !searchNearbyStoresResponse.isSuccess()) {
Optional.ofNullable(searchNearbyStoresResponse)
.map(BaseResponse::getErrorCode)
.ifPresentOrElse(errorCode -> {
throw errorCode.toException();
}, () -> {
throw GlobalErrorCode.INTERNAL_ERROR.toException();
});
}
return ResponseEntity.ok(SearchNearbyStoresResponse.builder().stores(searchNearbyStoresResponse.getStores()).build());
}

@ToString
@Getter
@Setter
@SuperBuilder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Schema(description = "판매점 검색 응답")
public static class SearchNearbyStoresResponse extends BaseResponse<GlobalErrorCode> {

@Schema(description = "판매점 목록")
private List<Store> stores;
}

@ToString
@Getter
@Setter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Schema(description = "판매점 검색 요청")
public static class SearchNearbyStoresRequest implements BaseRequest {

@Schema(description = "판매점 검색 쿼리", example = "꿉당")
private String query;

@Schema(description = "사용자 경도", example = "127.01727639915623")
private Double longitude;

@Schema(description = "사용자 위도", example = "37.4839596934158")
private Double latitude;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public class SearchStoreController {
private final SearchStoreUseCase searchStoreUseCase;

@Operation(
summary = "판매점 검색 API",
description = "등록할 판매점을 쿼리를 통해 검색합니다.",
summary = "[가게제보] 판매점 검색 API",
description = "가게제보 페이지에서 등록할 판매점을 쿼리를 통해 검색합니다.",
operationId = "/store/search"
)
@ApiErrorCode({GlobalErrorCode.class, SearchStoreErrorCode.class})
Expand Down

0 comments on commit 6ac3ff8

Please sign in to comment.