diff --git a/src/main/java/com/zerozero/store/domain/model/Store.java b/src/main/java/com/zerozero/store/domain/model/Store.java index ff189ea..8ade43f 100644 --- a/src/main/java/com/zerozero/store/domain/model/Store.java +++ b/src/main/java/com/zerozero/store/domain/model/Store.java @@ -47,7 +47,7 @@ public class Store extends BaseEntity { private UUID userId; - public static Store of(UUID userId, StoreSearchResponse store, List images) { + public static Store create(UUID userId, StoreSearchResponse store, List images) { return Store.builder() .kakaoId(store.id()) .name(store.placeName()) diff --git a/src/main/java/com/zerozero/store/domain/repository/StoreRepository.java b/src/main/java/com/zerozero/store/domain/repository/StoreRepository.java new file mode 100644 index 0000000..1454107 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/repository/StoreRepository.java @@ -0,0 +1,33 @@ +package com.zerozero.store.domain.repository; + +import com.zerozero.store.domain.model.Store; +import com.zerozero.store.domain.value.GeoLocation; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface StoreRepository extends JpaRepository { + + Integer countStoresByUserId(UUID userId); + + @Query(value = """ + SELECT user_rank.r + FROM ( + SELECT u.id AS user_id, + RANK() OVER (ORDER BY COUNT(s.id) DESC) AS r + FROM user u + JOIN store s ON u.id = s.user_id + GROUP BY u.id + ) AS user_rank + WHERE user_rank.user_id = :userId + """, nativeQuery = true) + Optional findStoreUserRank(@Param("userId") UUID userId); + + Store findByNameAndGeoLocation(String name, GeoLocation geoLocation); + + List findAllByUserIdAndDeleted(UUID userId, boolean deleted); +} diff --git a/src/main/java/com/zerozero/store/domain/request/StoreLocationRequest.java b/src/main/java/com/zerozero/store/domain/request/StoreLocationRequest.java new file mode 100644 index 0000000..61a4f8d --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/request/StoreLocationRequest.java @@ -0,0 +1,8 @@ +package com.zerozero.store.domain.request; + +public record StoreLocationRequest( + double longitude, + double latitude, + String accessToken +) { +} diff --git a/src/main/java/com/zerozero/store/domain/response/StoreResponse.java b/src/main/java/com/zerozero/store/domain/response/StoreResponse.java new file mode 100644 index 0000000..029bb7d --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/response/StoreResponse.java @@ -0,0 +1,99 @@ +package com.zerozero.store.domain.response; + +import com.zerozero.image.domain.model.Image; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Builder +public record StoreResponse( + + @Schema(description = "판매점 ID", example = "8e3006f4-3a16-11ef-9454-0242ac120002") + UUID id, + + @Schema(description = "카카오 ID", example = "25770215") + String kakaoId, + + @Schema(description = "판매점 이름", example = "제로 피자") + String name, + + @Schema(description = "판매 종류", example = "음식점 > 한식 > 육류,고기") + String category, + + @Schema(description = "전화번호", example = "02-525-6692") + String phone, + + @Schema(description = "판매점 주소", example = "서울특별시 서초구 서초동") + String address, + + @Schema(description = "판매점 도로명 주소", example = "서울특별시 서초구 서초대로50길 82 정원빌딩") + String roadAddress, + + @Schema(description = "판매점 x좌표(경도)", example = "127.01275515884753") + String longitude, + + @Schema(description = "판매점 y좌표(위도)", example = "37.49206032952165") + String latitude, + + @Schema(description = "제로음료 판매 여부", example = "true") + boolean status, + + @Schema(description = "제로음료 등록 이미지 목록") + List images, + + @Schema(description = "판매점 상세페이지 URL", example = "http://place.map.kakao.com/25770215") + String placeUrl +) { + public static StoreResponse from(com.zerozero.store.domain.model.Store store) { + return StoreResponse.builder() + .id(store.getId()) + .kakaoId(store.getKakaoId()) + .name(store.getName()) + .category(store.getCategory()) + .phone(store.getPhone()) + .address(store.getAddress().getAddress()) + .roadAddress(store.getAddress().getRoadAddress()) + .longitude(store.getGeoLocation().getLongitude()) + .latitude(store.getGeoLocation().getLatitude()) + .status(store.isStatus()) + .images(store.getImages().stream().map(Image::getUrl).collect(Collectors.toList())) + .placeUrl(store.getPlaceUrl()) + .build(); + } + + public static StoreResponse from(com.zerozero.store.infrastructure.mongodb.Store store) { + return StoreResponse.builder() + .id(store.getStoreId()) + .kakaoId(store.getKakaoId()) + .name(store.getName()) + .category(store.getCategory()) + .phone(store.getPhone()) + .address(store.getAddress()) + .roadAddress(store.getRoadAddress()) + .longitude(store.getLongitude()) + .latitude(store.getLatitude()) + .status(store.isStatus()) + .placeUrl(store.getPlaceUrl()) + .build(); + } + + public static StoreResponse of(StoreSearchResponse storeSearchResponse, UUID storeId, boolean status) { + return StoreResponse.builder() + .id(storeId) + .kakaoId(storeSearchResponse.id()) + .name(storeSearchResponse.placeName()) + .category(storeSearchResponse.categoryName()) + .phone(storeSearchResponse.phone()) + .address(storeSearchResponse.addressName()) + .roadAddress(storeSearchResponse.roadAddressName()) + .longitude(storeSearchResponse.longitude()) + .latitude(storeSearchResponse.latitude()) + .status(status) + .placeUrl(storeSearchResponse.placeUrl()) + .build(); + } + +} diff --git a/src/main/java/com/zerozero/store/domain/response/StoreSearchResponse.java b/src/main/java/com/zerozero/store/domain/response/StoreSearchResponse.java new file mode 100644 index 0000000..57a16e8 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/response/StoreSearchResponse.java @@ -0,0 +1,31 @@ +package com.zerozero.store.domain.response; + +import com.zerozero.store.infrastructure.kakao.search.response.KakaoSearchResponse.Document; + +public record StoreSearchResponse( + String id, + String placeName, + String categoryName, + String phone, + String addressName, + String roadAddressName, + String longitude, + String latitude, + String placeUrl, + String distance +) { + public static StoreSearchResponse from(Document document) { + return new StoreSearchResponse( + document.getId(), + document.getPlaceName(), + document.getCategoryName(), + document.getPhone(), + document.getAddressName(), + document.getRoadAddressName(), + document.getX(), + document.getY(), + document.getPlaceUrl(), + document.getDistance() + ); + } +} diff --git a/src/main/java/com/zerozero/store/domain/response/StoreUserRankResponse.java b/src/main/java/com/zerozero/store/domain/response/StoreUserRankResponse.java new file mode 100644 index 0000000..8870222 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/response/StoreUserRankResponse.java @@ -0,0 +1,10 @@ +package com.zerozero.store.domain.response; + +public record StoreUserRankResponse( + int rank, + int storeReportCount +) { + public static StoreUserRankResponse of(int rank, int storeReportCount) { + return new StoreUserRankResponse(rank, storeReportCount); + } +} diff --git a/src/main/java/com/zerozero/store/domain/service/CreateStoreMongoUseCase.java b/src/main/java/com/zerozero/store/domain/service/CreateStoreMongoUseCase.java new file mode 100644 index 0000000..af7f680 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/service/CreateStoreMongoUseCase.java @@ -0,0 +1,29 @@ +package com.zerozero.store.domain.service; + +import com.zerozero.store.domain.model.Store; +import com.zerozero.store.domain.repository.StoreRepository; +import com.zerozero.store.exception.StoreErrorType; +import com.zerozero.store.exception.StoreException; +import com.zerozero.store.infrastructure.mongodb.StoreMongoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Transactional +public class CreateStoreMongoUseCase { + + private final StoreRepository storeRepository; + + private final StoreMongoRepository storeMongoRepository; + + public void execute(UUID storeId) { + Store store = storeRepository.findById(storeId).orElseThrow(() -> new StoreException(StoreErrorType.NOT_EXIST_STORE)); + com.zerozero.store.infrastructure.mongodb.Store storeMongo = com.zerozero.store.infrastructure.mongodb.Store.of(store); + storeMongoRepository.save(storeMongo); + } + +} diff --git a/src/main/java/com/zerozero/store/domain/service/CreateStoreUseCase.java b/src/main/java/com/zerozero/store/domain/service/CreateStoreUseCase.java new file mode 100644 index 0000000..d5c1a78 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/service/CreateStoreUseCase.java @@ -0,0 +1,51 @@ +package com.zerozero.store.domain.service; + +import com.zerozero.store.domain.model.Store; +import com.zerozero.store.domain.repository.StoreRepository; +import com.zerozero.store.domain.response.StoreSearchResponse; +import com.zerozero.store.exception.StoreErrorType; +import com.zerozero.store.exception.StoreException; +import com.zerozero.store.infrastructure.rabbitmq.CreateStoreMessageProducer; +import com.zerozero.store.infrastructure.rabbitmq.CreateStoreQueueProperty; +import com.zerozero.store.presentation.request.CreateStoreRequest; +import com.zerozero.user.domain.model.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Transactional +public class CreateStoreUseCase { + + private final StoreRepository storeRepository; + + private final StoreSearcher storeSearcher; + + private final CreateStoreQueueProperty createStoreQueueProperty; + + public UUID execute(CreateStoreRequest createStoreRequest, User user) { + List storeSearchResponses = storeSearcher.search(createStoreRequest.placeName()); + + StoreSearchResponse storeSearchResponse = storeSearchResponses.stream() + .filter(store -> + createStoreRequest.placeName().equals(store.placeName()) && + createStoreRequest.longitude().equals(store.longitude()) && + createStoreRequest.latitude().equals(store.latitude())) + .findFirst() + .orElseThrow(() -> new StoreException(StoreErrorType.NOT_EXIST_STORE)); + + Store store = Store.create(user.getId(), storeSearchResponse, createStoreRequest.images()); + storeRepository.save(store); + + UUID storeId = store.getId(); + CreateStoreMessageProducer createStoreMessageProducer = new CreateStoreMessageProducer(createStoreQueueProperty, storeId); + createStoreMessageProducer.publishMessage(); + + return storeId; + } + +} diff --git a/src/main/java/com/zerozero/store/domain/service/GetStoreUserRankUseCase.java b/src/main/java/com/zerozero/store/domain/service/GetStoreUserRankUseCase.java new file mode 100644 index 0000000..d1ad423 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/service/GetStoreUserRankUseCase.java @@ -0,0 +1,21 @@ +package com.zerozero.store.domain.service; + +import com.zerozero.store.domain.repository.StoreRepository; +import com.zerozero.store.domain.response.StoreUserRankResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class GetStoreUserRankUseCase { + + private final StoreRepository storeRepository; + + public StoreUserRankResponse execute(UUID userId) { + int storeUserRank = storeRepository.findStoreUserRank(userId).orElse(0); + int storeCount = storeRepository.countStoresByUserId(userId); + return StoreUserRankResponse.of(storeUserRank, storeCount); + } +} diff --git a/src/main/java/com/zerozero/store/domain/service/ReadNearbyStoresUseCase.java b/src/main/java/com/zerozero/store/domain/service/ReadNearbyStoresUseCase.java new file mode 100644 index 0000000..3e400d2 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/service/ReadNearbyStoresUseCase.java @@ -0,0 +1,51 @@ +package com.zerozero.store.domain.service; + +import com.zerozero.auth.exception.AuthErrorType; +import com.zerozero.auth.exception.AuthException; +import com.zerozero.core.util.JwtUtil; +import com.zerozero.store.domain.request.StoreLocationRequest; +import com.zerozero.store.domain.response.StoreResponse; +import com.zerozero.store.infrastructure.mongodb.Store; +import com.zerozero.store.infrastructure.mongodb.StoreMongoRepository; +import com.zerozero.user.domain.model.User; +import com.zerozero.user.domain.repository.UserRepository; +import com.zerozero.user.exception.UserErrorType; +import com.zerozero.user.exception.UserException; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Log4j2 +public class ReadNearbyStoresUseCase { + + private final JwtUtil jwtUtil; + + private final UserRepository userRepository; + + private final StoreMongoRepository storeMongoRepository; + + public static final Double DEFAULT_RADIUS = 1000.0; + + public List execute(StoreLocationRequest storeLocationRequest) { + String accessToken = storeLocationRequest.accessToken(); + if (jwtUtil.isTokenExpired(accessToken)) { + log.error("[ReadNearbyStoresUseCase] Expired access token"); + throw new AuthException(AuthErrorType.EXPIRED_TOKEN); + } + UUID userId = jwtUtil.extractUserId(accessToken); + User user = userRepository.findById(userId).orElseThrow(() -> new UserException(UserErrorType.NOT_EXIST_USER)); + List mongoStores = storeMongoRepository.findStoresWithinCoordinatesRadius(storeLocationRequest.longitude(), storeLocationRequest.latitude(), DEFAULT_RADIUS); + return mongoStores.stream() + .map(StoreResponse::from) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/com/zerozero/store/domain/service/ReadStoreInfoUseCase.java b/src/main/java/com/zerozero/store/domain/service/ReadStoreInfoUseCase.java new file mode 100644 index 0000000..ff60e2c --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/service/ReadStoreInfoUseCase.java @@ -0,0 +1,26 @@ +package com.zerozero.store.domain.service; + +import com.zerozero.store.domain.model.Store; +import com.zerozero.store.domain.repository.StoreRepository; +import com.zerozero.store.domain.response.StoreResponse; +import com.zerozero.store.exception.StoreErrorType; +import com.zerozero.store.exception.StoreException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ReadStoreInfoUseCase { + + private final StoreRepository storeRepository; + + public StoreResponse execute(UUID storeId) { + Store store = storeRepository.findById(storeId).orElseThrow(() -> new StoreException(StoreErrorType.NOT_EXIST_STORE)); + return StoreResponse.from(store); + } + +} diff --git a/src/main/java/com/zerozero/store/domain/service/ReadUserStoresUseCase.java b/src/main/java/com/zerozero/store/domain/service/ReadUserStoresUseCase.java new file mode 100644 index 0000000..6c954c0 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/service/ReadUserStoresUseCase.java @@ -0,0 +1,27 @@ +package com.zerozero.store.domain.service; + +import com.zerozero.store.domain.repository.StoreRepository; +import com.zerozero.store.domain.response.StoreResponse; +import com.zerozero.user.domain.model.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ReadUserStoresUseCase { + + private final StoreRepository storeRepository; + + public List execute(User user) { + return storeRepository.findAllByUserIdAndDeleted(user.getId(), false) + .stream() + .map(StoreResponse::from) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/com/zerozero/store/domain/service/SearchNearbyStoresUseCase.java b/src/main/java/com/zerozero/store/domain/service/SearchNearbyStoresUseCase.java new file mode 100644 index 0000000..470b034 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/service/SearchNearbyStoresUseCase.java @@ -0,0 +1,49 @@ +package com.zerozero.store.domain.service; + +import com.zerozero.store.domain.model.Store; +import com.zerozero.store.domain.repository.StoreRepository; +import com.zerozero.store.domain.response.StoreResponse; +import com.zerozero.store.domain.response.StoreSearchResponse; +import com.zerozero.store.domain.value.GeoLocation; +import com.zerozero.store.presentation.request.StoreSearchRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class SearchNearbyStoresUseCase { + + private final StoreRepository storeRepository; + + private final StoreSearcher storeSearcher; + + public List execute(StoreSearchRequest storeSearchRequest) { + List storeSearchResponses = storeSearcher.searchByLocation( + storeSearchRequest.query(), storeSearchRequest.longitude(), storeSearchRequest.latitude() + ); + + return storeSearchResponses.stream() + .map(storeSearchResponse -> { + Store store = storeRepository.findByNameAndGeoLocation( + storeSearchResponse.placeName(), GeoLocation.of(storeSearchResponse.longitude(), storeSearchResponse.latitude()) + ); + + if (store == null) { + return StoreResponse.of(storeSearchResponse, null, false); + } + + return StoreResponse.of( + storeSearchResponse, + store.getId(), + store.isStatus() + ); + }) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/com/zerozero/store/domain/service/SearchStoreUseCase.java b/src/main/java/com/zerozero/store/domain/service/SearchStoreUseCase.java new file mode 100644 index 0000000..c3f3f45 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/service/SearchStoreUseCase.java @@ -0,0 +1,46 @@ +package com.zerozero.store.domain.service; + +import com.zerozero.store.domain.model.Store; +import com.zerozero.store.domain.repository.StoreRepository; +import com.zerozero.store.domain.response.StoreResponse; +import com.zerozero.store.domain.response.StoreSearchResponse; +import com.zerozero.store.domain.value.GeoLocation; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class SearchStoreUseCase { + + private final StoreRepository storeRepository; + + private final StoreSearcher storeSearcher; + + public List execute(String query) { + List storeSearchResponses = storeSearcher.search(query); + + return storeSearchResponses.stream() + .map(storeSearchResponse -> { + Store store = storeRepository.findByNameAndGeoLocation( + storeSearchResponse.placeName(), GeoLocation.of(storeSearchResponse.longitude(), storeSearchResponse.latitude()) + ); + + if (store == null) { + return StoreResponse.of(storeSearchResponse, null, false); + } + + return StoreResponse.of( + storeSearchResponse, + store.getId(), + store.isStatus() + ); + }) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/com/zerozero/store/domain/service/StoreSearcher.java b/src/main/java/com/zerozero/store/domain/service/StoreSearcher.java new file mode 100644 index 0000000..e137d01 --- /dev/null +++ b/src/main/java/com/zerozero/store/domain/service/StoreSearcher.java @@ -0,0 +1,12 @@ +package com.zerozero.store.domain.service; + +import com.zerozero.store.domain.response.StoreSearchResponse; + +import java.util.List; + +public interface StoreSearcher { + + List search(String query); + + List searchByLocation(String query, double longitude, double latitude); +}