-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FEAT: (#88) 지도에서 판매점 조회 시 반경 2km 내 판매점만 조회한다
- Loading branch information
Showing
3 changed files
with
261 additions
and
2 deletions.
There are no files selected for viewing
162 changes: 162 additions & 0 deletions
162
src/main/java/com/zerozero/store/application/SearchNearbyStoresUseCase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
|
||
} |
97 changes: 97 additions & 0 deletions
97
src/main/java/com/zerozero/store/presentation/SearchNearbyStoresController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters