diff --git a/src/main/java/in/koreatech/koin/domain/shop/controller/ShopEventApi.java b/src/main/java/in/koreatech/koin/domain/shop/controller/ShopEventApi.java index ccbe2d44dc..70a0aa589b 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/controller/ShopEventApi.java +++ b/src/main/java/in/koreatech/koin/domain/shop/controller/ShopEventApi.java @@ -1,7 +1,11 @@ package in.koreatech.koin.domain.shop.controller; +import static in.koreatech.koin.global.code.ApiResponseCode.OK; + +import in.koreatech.koin.domain.shop.dto.event.response.ShopEventCountResponse; import in.koreatech.koin.domain.shop.dto.event.response.ShopEventsWithBannerUrlResponse; import in.koreatech.koin.domain.shop.dto.event.response.ShopEventsWithThumbnailUrlResponse; +import in.koreatech.koin.global.code.ApiResponseCodes; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -40,4 +44,11 @@ ResponseEntity getShopEvents( @Operation(summary = "모든 상점의 모든 이벤트 조회") @GetMapping("/shops/events") ResponseEntity getShopAllEvent(); + + @ApiResponseCodes({ + OK + }) + @Operation(summary = "이벤트 진행 중인 상점 개수 조회") + @GetMapping("/shops/events/count") + ResponseEntity getEventShopCount(); } diff --git a/src/main/java/in/koreatech/koin/domain/shop/controller/ShopEventController.java b/src/main/java/in/koreatech/koin/domain/shop/controller/ShopEventController.java index b4b6f3371e..e7d96eb2e0 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/controller/ShopEventController.java +++ b/src/main/java/in/koreatech/koin/domain/shop/controller/ShopEventController.java @@ -1,9 +1,9 @@ package in.koreatech.koin.domain.shop.controller; +import in.koreatech.koin.domain.shop.dto.event.response.ShopEventCountResponse; import in.koreatech.koin.domain.shop.dto.event.response.ShopEventsWithBannerUrlResponse; import in.koreatech.koin.domain.shop.dto.event.response.ShopEventsWithThumbnailUrlResponse; import in.koreatech.koin.domain.shop.service.ShopEventService; -import in.koreatech.koin.domain.shop.service.ShopService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -29,4 +29,10 @@ public ResponseEntity getShopAllEvent() { var response = shopEventService.getAllEvents(); return ResponseEntity.ok(response); } + + @GetMapping("/shops/events/count") + public ResponseEntity getEventShopCount() { + var response = shopEventService.getEventShopCount(); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/in/koreatech/koin/domain/shop/dto/event/response/ShopEventCountResponse.java b/src/main/java/in/koreatech/koin/domain/shop/dto/event/response/ShopEventCountResponse.java new file mode 100644 index 0000000000..b187d06fd6 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/shop/dto/event/response/ShopEventCountResponse.java @@ -0,0 +1,11 @@ +package in.koreatech.koin.domain.shop.dto.event.response; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record ShopEventCountResponse( + @Schema(example = "5", description = "이벤트 진행 중인 상점 개수", requiredMode = REQUIRED) + Integer count +) { +} diff --git a/src/main/java/in/koreatech/koin/domain/shop/repository/shop/ShopRepository.java b/src/main/java/in/koreatech/koin/domain/shop/repository/shop/ShopRepository.java index 5521f2af03..667f2b5d71 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/repository/shop/ShopRepository.java +++ b/src/main/java/in/koreatech/koin/domain/shop/repository/shop/ShopRepository.java @@ -82,6 +82,17 @@ Long countOpenShops( """) List findAllWithEventArticles(); + @Query(""" + SELECT COUNT(DISTINCT s.id) + FROM Shop s + JOIN s.eventArticles e + WHERE s.isDeleted = false + AND e.isDeleted = false + AND e.startDate <= :now + AND e.endDate >= :now + """) + Long countShopsWithOngoingEvent(@Param("now") LocalDate now); + @Query(""" SELECT new in.koreatech.koin.domain.shop.dto.shop.ShopNotificationQueryResponse( s.id, diff --git a/src/main/java/in/koreatech/koin/domain/shop/service/ShopEventService.java b/src/main/java/in/koreatech/koin/domain/shop/service/ShopEventService.java index 96e2cffb5c..ce6a2f9c24 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/service/ShopEventService.java +++ b/src/main/java/in/koreatech/koin/domain/shop/service/ShopEventService.java @@ -1,10 +1,12 @@ package in.koreatech.koin.domain.shop.service; +import in.koreatech.koin.domain.shop.dto.event.response.ShopEventCountResponse; import in.koreatech.koin.domain.shop.dto.event.response.ShopEventsWithBannerUrlResponse; import in.koreatech.koin.domain.shop.dto.event.response.ShopEventsWithThumbnailUrlResponse; import in.koreatech.koin.domain.shop.model.shop.Shop; import in.koreatech.koin.domain.shop.repository.shop.ShopRepository; import java.time.Clock; +import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -27,4 +29,10 @@ public ShopEventsWithBannerUrlResponse getAllEvents() { List shops = shopRepository.findAllWithEventArticles(); return ShopEventsWithBannerUrlResponse.of(shops, clock); } + + public ShopEventCountResponse getEventShopCount() { + LocalDate now = LocalDate.now(clock); + Long count = shopRepository.countShopsWithOngoingEvent(now); + return new ShopEventCountResponse(Math.toIntExact(count)); + } } diff --git a/src/test/java/in/koreatech/koin/acceptance/domain/ShopApiTest.java b/src/test/java/in/koreatech/koin/acceptance/domain/ShopApiTest.java index 951c69de5c..d825e75b56 100644 --- a/src/test/java/in/koreatech/koin/acceptance/domain/ShopApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/domain/ShopApiTest.java @@ -576,6 +576,26 @@ void setUp() { """)); } + @Test + void 이벤트_진행중인_상점_개수를_조회한다() throws Exception { + Shop 영업중인_티바 = shopFixture.영업중인_티바(owner); + Shop 영업중이_아닌_신전떡볶이 = shopFixture.영업중이_아닌_신전_떡볶이(owner); + eventArticleFixture.할인_이벤트(마슬랜, LocalDate.now(clock).minusDays(3), LocalDate.now(clock).plusDays(3)); + eventArticleFixture.참여_이벤트(마슬랜, LocalDate.now(clock).minusDays(3), LocalDate.now(clock).plusDays(3)); + eventArticleFixture.참여_이벤트(영업중인_티바, LocalDate.now(clock), LocalDate.now(clock).plusDays(10)); + eventArticleFixture.할인_이벤트(영업중이_아닌_신전떡볶이, LocalDate.now(clock).minusDays(10), LocalDate.now(clock).minusDays(1)); + + mockMvc.perform( + get("/shops/events/count") + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "count": 2 + } + """)); + } + @Test void 리뷰_평점순으로_정렬하여_모든_상점을_조회한다() throws Exception { Shop 영업중인_티바 = shopFixture.영업중인_티바(owner);