diff --git a/analytics/src/main/java/com/ktds/hi/analytics/biz/usecase/out/ExternalReviewPort.java b/analytics/src/main/java/com/ktds/hi/analytics/biz/usecase/out/ExternalReviewPort.java index 6fb78b1..266bd11 100644 --- a/analytics/src/main/java/com/ktds/hi/analytics/biz/usecase/out/ExternalReviewPort.java +++ b/analytics/src/main/java/com/ktds/hi/analytics/biz/usecase/out/ExternalReviewPort.java @@ -17,6 +17,7 @@ public interface ExternalReviewPort { * 최근 리뷰 데이터 조회 */ List getRecentReviews(Long storeId, Integer days); + /** * 리뷰 개수 조회 diff --git a/review/src/main/java/com/ktds/hi/review/biz/service/ReviewInteractor.java b/review/src/main/java/com/ktds/hi/review/biz/service/ReviewInteractor.java index cc1db37..7cdede3 100644 --- a/review/src/main/java/com/ktds/hi/review/biz/service/ReviewInteractor.java +++ b/review/src/main/java/com/ktds/hi/review/biz/service/ReviewInteractor.java @@ -96,7 +96,29 @@ public class ReviewInteractor implements CreateReviewUseCase, DeleteReviewUseCas .build()) .collect(Collectors.toList()); } - + + @Override + public List getStoreRecentReviews(Long storeId, Integer page, Integer size, Integer days) { + Pageable pageable = PageRequest.of(page != null ? page : 0, size == null || size == 0 ? 1000 : size); + + LocalDateTime cutoffDate = LocalDateTime.now().minusDays(days); + Page reviews = reviewRepository.findRecentReviewsByStoreId(storeId, pageable, cutoffDate); + + return reviews.stream() + .filter(review -> review.getStatus() == ReviewStatus.ACTIVE) + .map(review -> ReviewListResponse.builder() + .reviewId(review.getId()) + .memberNickname(review.getMemberNickname()) + .rating(review.getRating()) + .content(review.getContent()) + .imageUrls(review.getImageUrls()) + .likeCount(review.getLikeCount()) + .dislikeCount(review.getDislikeCount()) + .createdAt(review.getCreatedAt()) + .build()) + .collect(Collectors.toList()); + } + @Override @Transactional(readOnly = true) public ReviewDetailResponse getReviewDetail(Long reviewId) { diff --git a/review/src/main/java/com/ktds/hi/review/biz/usecase/in/GetReviewUseCase.java b/review/src/main/java/com/ktds/hi/review/biz/usecase/in/GetReviewUseCase.java index 750972b..f33eef5 100644 --- a/review/src/main/java/com/ktds/hi/review/biz/usecase/in/GetReviewUseCase.java +++ b/review/src/main/java/com/ktds/hi/review/biz/usecase/in/GetReviewUseCase.java @@ -15,6 +15,11 @@ public interface GetReviewUseCase { * 매장 리뷰 목록 조회 */ List getStoreReviews(Long storeId, Integer page, Integer size); + + /** + * 매장 최근 리뷰 목록 조회 + */ + List getStoreRecentReviews(Long storeId, Integer page, Integer size, Integer days); /** * 리뷰 상세 조회 diff --git a/review/src/main/java/com/ktds/hi/review/biz/usecase/out/ReviewRepository.java b/review/src/main/java/com/ktds/hi/review/biz/usecase/out/ReviewRepository.java index d6d0fa5..c2772e1 100644 --- a/review/src/main/java/com/ktds/hi/review/biz/usecase/out/ReviewRepository.java +++ b/review/src/main/java/com/ktds/hi/review/biz/usecase/out/ReviewRepository.java @@ -3,7 +3,9 @@ package com.ktds.hi.review.biz.usecase.out; import com.ktds.hi.review.biz.domain.Review; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.query.Param; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -34,6 +36,11 @@ public interface ReviewRepository { */ Page findReviewsByStoreIdOrderByCreatedAtDesc(Long storeId, Pageable pageable); + /** + * 매장 ID와 일자 데이터로 최근 리뷰 목록 조회 + */ + Page findRecentReviewsByStoreId(Long storeId, Pageable pageable, @Param("cutoffDate") LocalDateTime cutoffDate); + /** * 회원 ID로 리뷰 목록 조회 */ diff --git a/review/src/main/java/com/ktds/hi/review/infra/controller/ReviewController.java b/review/src/main/java/com/ktds/hi/review/infra/controller/ReviewController.java index c65da71..597707c 100644 --- a/review/src/main/java/com/ktds/hi/review/infra/controller/ReviewController.java +++ b/review/src/main/java/com/ktds/hi/review/infra/controller/ReviewController.java @@ -52,6 +52,24 @@ public class ReviewController { List reviews = getReviewUseCase.getStoreReviews(storeId, page, size); return ResponseEntity.ok(reviews); } + + /** + * 최근 매장 리뷰 목록 조회 API + */ + @GetMapping("/stores/recent/{storeId}") + @Operation(summary = "매장 최근 리뷰 목록 조회", description = "특정 매장의 최근 리뷰 목록을 조회합니다.") + public ResponseEntity> getStoreRecentReviews( + @PathVariable Long storeId, + @RequestParam(defaultValue = "0") Integer page, + @RequestParam(defaultValue = "0") Integer size, + @RequestParam Integer days + ) { + + List reviews = getReviewUseCase.getStoreRecentReviews(storeId, page, size, days); + return ResponseEntity.ok(reviews); + } + + /** * 리뷰 상세 조회 API diff --git a/review/src/main/java/com/ktds/hi/review/infra/gateway/ReviewRepositoryAdapter.java b/review/src/main/java/com/ktds/hi/review/infra/gateway/ReviewRepositoryAdapter.java index b23100e..b00cf54 100644 --- a/review/src/main/java/com/ktds/hi/review/infra/gateway/ReviewRepositoryAdapter.java +++ b/review/src/main/java/com/ktds/hi/review/infra/gateway/ReviewRepositoryAdapter.java @@ -10,6 +10,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; +import java.time.LocalDateTime; import java.util.Optional; /** @@ -47,7 +48,17 @@ public class ReviewRepositoryAdapter implements ReviewRepository { pageable); return entities.map(this::toDomain); } - + + @Override + public Page findRecentReviewsByStoreId(Long storeId, Pageable pageable, + LocalDateTime cutoffDate) { + + Page entities = reviewJpaRepository.findRecentReviewsByStoreId(storeId, ReviewStatus.ACTIVE, + cutoffDate,pageable); + return entities.map(this::toDomain); + + } + @Override public Page findReviewsByMemberId(Long memberId, Pageable pageable) { Page entities = reviewJpaRepository.findByMemberIdAndStatus(memberId, ReviewStatus.ACTIVE, pageable); diff --git a/review/src/main/java/com/ktds/hi/review/infra/gateway/repository/ReviewJpaRepository.java b/review/src/main/java/com/ktds/hi/review/infra/gateway/repository/ReviewJpaRepository.java index a3e18f5..dd326f0 100644 --- a/review/src/main/java/com/ktds/hi/review/infra/gateway/repository/ReviewJpaRepository.java +++ b/review/src/main/java/com/ktds/hi/review/infra/gateway/repository/ReviewJpaRepository.java @@ -1,46 +1,58 @@ -package com.ktds.hi.review.infra.gateway.repository; - -import com.ktds.hi.review.biz.domain.ReviewStatus; -import com.ktds.hi.review.infra.gateway.entity.ReviewEntity; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -/** - * 리뷰 JPA 리포지토리 인터페이스 - * 리뷰 데이터의 CRUD 작업을 담당 - */ -@Repository -public interface ReviewJpaRepository extends JpaRepository { - - /** - * 매장 ID와 상태로 리뷰 목록 조회 - */ - Page findByStoreIdAndStatus(Long storeId, ReviewStatus status, Pageable pageable); - - /** - * 회원 ID와 상태로 리뷰 목록 조회 - */ - Page findByMemberIdAndStatus(Long memberId, ReviewStatus status, Pageable pageable); - - /** - * 리뷰 ID와 회원 ID로 리뷰 조회 - */ - Optional findByIdAndMemberId(Long id, Long memberId); - - - /** - * ✅ 수정: 매장ID + 내용으로 중복 리뷰 체크 - */ - boolean existsByStoreIdAndContent(Long storeId, String content); - - /** - * 대안: 외부 닉네임으로만 중복 체크 (더 간단한 방법) - */ -// boolean existsByStoreIdAndExternalNickname(Long storeId, String externalNickname); -} +package com.ktds.hi.review.infra.gateway.repository; + +import com.ktds.hi.review.biz.domain.ReviewStatus; +import com.ktds.hi.review.infra.gateway.entity.ReviewEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.Optional; + +/** + * 리뷰 JPA 리포지토리 인터페이스 + * 리뷰 데이터의 CRUD 작업을 담당 + */ +@Repository +public interface ReviewJpaRepository extends JpaRepository { + + /** + * 매장 ID와 상태로 리뷰 목록 조회 + */ + Page findByStoreIdAndStatus(Long storeId, ReviewStatus status, Pageable pageable); + + /** + * 회원 ID와 상태로 리뷰 목록 조회 + */ + Page findByMemberIdAndStatus(Long memberId, ReviewStatus status, Pageable pageable); + + @Query("SELECT r FROM ReviewEntity r WHERE r.storeId = :storeId " + + "AND r.status = :status " + + "AND (CAST(:cutoffDate AS TIMESTAMP) IS NULL OR r.createdAt >= :cutoffDate) " + + "ORDER BY r.createdAt DESC") + Page findRecentReviewsByStoreId( + @Param("storeId") Long storeId, + @Param("status") ReviewStatus status, + @Param("cutoffDate") LocalDateTime cutoffDate, + Pageable pageable + ); + + /** + * 리뷰 ID와 회원 ID로 리뷰 조회 + */ + Optional findByIdAndMemberId(Long id, Long memberId); + + + /** + * ✅ 수정: 매장ID + 내용으로 중복 리뷰 체크 + */ + boolean existsByStoreIdAndContent(Long storeId, String content); + + /** + * 대안: 외부 닉네임으로만 중복 체크 (더 간단한 방법) + */ +// boolean existsByStoreIdAndExternalNickname(Long storeId, String externalNickname); +} diff --git a/store/build.gradle b/store/build.gradle index 0bdee20..8a5118f 100644 --- a/store/build.gradle +++ b/store/build.gradle @@ -5,4 +5,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'com.azure:azure-messaging-eventhubs:5.15.0' + + implementation 'com.azure:azure-storage-blob:12.22.1' + implementation 'com.azure:azure-storage-common:12.21.1' } diff --git a/store/src/main/java/com/ktds/hi/store/config/AzureStorageConfig.java b/store/src/main/java/com/ktds/hi/store/config/AzureStorageConfig.java new file mode 100644 index 0000000..c3cdd75 --- /dev/null +++ b/store/src/main/java/com/ktds/hi/store/config/AzureStorageConfig.java @@ -0,0 +1,32 @@ +package com.ktds.hi.store.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; + +import lombok.extern.slf4j.Slf4j; + +/** + * Azure Blob Storage 설정 + * + * @author 하이오더 개발팀 + * @version 1.0.0 + */ +@Slf4j +// @Configuration +public class AzureStorageConfig { + + @Value("${azure.storage.connection-string}") + private String connectionString; + + @Bean + public BlobServiceClient blobServiceClient() { + log.info("Azure Blob Storage 클라이언트 초기화"); + return new BlobServiceClientBuilder() + .connectionString(connectionString) + .buildClient(); + } +} \ No newline at end of file