diff --git a/src/main/java/in/koreatech/koin/domain/shop/controller/ShopApi.java b/src/main/java/in/koreatech/koin/domain/shop/controller/ShopApi.java index 3916581ac9..434edc249d 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/controller/ShopApi.java +++ b/src/main/java/in/koreatech/koin/domain/shop/controller/ShopApi.java @@ -1,6 +1,7 @@ package in.koreatech.koin.domain.shop.controller; import static in.koreatech.koin.domain.user.model.UserType.*; +import static in.koreatech.koin.global.code.ApiResponseCode.*; import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH; import java.util.List; @@ -16,6 +17,7 @@ import in.koreatech.koin.domain.shop.dto.shop.ShopsFilterCriteriaV3; import in.koreatech.koin.domain.shop.dto.shop.ShopsSortCriteria; import in.koreatech.koin.domain.shop.dto.shop.ShopsSortCriteriaV3; +import in.koreatech.koin.domain.shop.dto.shop.response.OpenShopsCountResponse; import in.koreatech.koin.domain.shop.dto.shop.response.ShopCategoriesResponse; import in.koreatech.koin.domain.shop.dto.shop.response.ShopResponse; import in.koreatech.koin.domain.shop.dto.shop.response.ShopResponseV2; @@ -24,7 +26,6 @@ import in.koreatech.koin.domain.shop.dto.shop.response.ShopsResponseV2; import in.koreatech.koin.domain.shop.dto.shop.response.ShopsResponseV3; import in.koreatech.koin.global.auth.Auth; -import in.koreatech.koin.global.code.ApiResponseCode; import in.koreatech.koin.global.code.ApiResponseCodes; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -56,7 +57,7 @@ ResponseEntity getShopById( - openTime, closeTime 응답값 추가 """) @ApiResponseCodes({ - ApiResponseCode.OK + OK }) @GetMapping("/v2/shops/{id}") ResponseEntity getShopByIdV2( @@ -87,6 +88,13 @@ ResponseEntity getShopSummary( @GetMapping("/shops") ResponseEntity getShops(); + @ApiResponseCodes({ + OK + }) + @Operation(summary = "현재 영업 중인 상점 개수 조회") + @GetMapping("/shops/open/count") + ResponseEntity getOpenShopsCount(); + @ApiResponses( value = { @ApiResponse(responseCode = "200"), diff --git a/src/main/java/in/koreatech/koin/domain/shop/controller/ShopController.java b/src/main/java/in/koreatech/koin/domain/shop/controller/ShopController.java index 9a1a5fbda7..63af016029 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/controller/ShopController.java +++ b/src/main/java/in/koreatech/koin/domain/shop/controller/ShopController.java @@ -18,6 +18,7 @@ import in.koreatech.koin.domain.shop.dto.shop.ShopsFilterCriteriaV3; import in.koreatech.koin.domain.shop.dto.shop.ShopsSortCriteria; import in.koreatech.koin.domain.shop.dto.shop.ShopsSortCriteriaV3; +import in.koreatech.koin.domain.shop.dto.shop.response.OpenShopsCountResponse; import in.koreatech.koin.domain.shop.dto.shop.response.ShopCategoriesResponse; import in.koreatech.koin.domain.shop.dto.shop.response.ShopResponse; import in.koreatech.koin.domain.shop.dto.shop.response.ShopResponseV2; @@ -68,6 +69,12 @@ public ResponseEntity getShops() { return ResponseEntity.ok(shopsResponse); } + @GetMapping("/shops/open/count") + public ResponseEntity getOpenShopsCount() { + OpenShopsCountResponse response = shopService.getOpenShopsCount(); + return ResponseEntity.ok(response); + } + @GetMapping("/shops/categories") public ResponseEntity getShopsCategories() { ShopCategoriesResponse shopCategoriesResponse = shopService.getShopsCategories(); diff --git a/src/main/java/in/koreatech/koin/domain/shop/dto/shop/response/OpenShopsCountResponse.java b/src/main/java/in/koreatech/koin/domain/shop/dto/shop/response/OpenShopsCountResponse.java new file mode 100644 index 0000000000..0b655c2dd6 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/shop/dto/shop/response/OpenShopsCountResponse.java @@ -0,0 +1,11 @@ +package in.koreatech.koin.domain.shop.dto.shop.response; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record OpenShopsCountResponse( + @Schema(example = "24", 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 9868921741..5521f2af03 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 @@ -4,6 +4,7 @@ import in.koreatech.koin.domain.shop.exception.ShopNotFoundException; import in.koreatech.koin.domain.shop.model.shop.Shop; import java.time.LocalDate; +import java.time.LocalTime; import java.util.List; import java.util.Map; import java.util.Optional; @@ -36,6 +37,41 @@ default Shop getById(Integer shopId) { """) List findAll(); + @Query(""" + SELECT COUNT(DISTINCT s.id) + FROM Shop s + JOIN s.shopOpens so + WHERE s.isDeleted = false + AND so.isDeleted = false + AND so.closed = false + AND ( + ( + so.dayOfWeek = :currentDayOfWeek + AND ( + ( + so.closeTime > so.openTime + AND so.openTime <= :currentTime + AND so.closeTime >= :currentTime + ) + OR ( + so.closeTime <= so.openTime + AND so.openTime <= :currentTime + ) + ) + ) + OR ( + so.dayOfWeek = :previousDayOfWeek + AND so.closeTime <= so.openTime + AND so.closeTime >= :currentTime + ) + ) + """) + Long countOpenShops( + @Param("currentDayOfWeek") String currentDayOfWeek, + @Param("previousDayOfWeek") String previousDayOfWeek, + @Param("currentTime") LocalTime currentTime + ); + @Query(""" SELECT s FROM Shop s diff --git a/src/main/java/in/koreatech/koin/domain/shop/service/ShopService.java b/src/main/java/in/koreatech/koin/domain/shop/service/ShopService.java index 8b474c53a3..5a6f7fb656 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/service/ShopService.java +++ b/src/main/java/in/koreatech/koin/domain/shop/service/ShopService.java @@ -6,9 +6,11 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.time.format.TextStyle; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import org.springframework.data.domain.Sort; @@ -24,6 +26,7 @@ import in.koreatech.koin.domain.shop.dto.shop.ShopsFilterCriteriaV3; import in.koreatech.koin.domain.shop.dto.shop.ShopsSortCriteria; import in.koreatech.koin.domain.shop.dto.shop.ShopsSortCriteriaV3; +import in.koreatech.koin.domain.shop.dto.shop.response.OpenShopsCountResponse; import in.koreatech.koin.domain.shop.dto.shop.response.ShopCategoriesResponse; import in.koreatech.koin.domain.shop.dto.shop.response.ShopResponse; import in.koreatech.koin.domain.shop.dto.shop.response.ShopResponseV2; @@ -82,6 +85,14 @@ public ShopsResponse getShops() { return ShopsResponse.from(shops, eventDuration, now); } + public OpenShopsCountResponse getOpenShopsCount() { + LocalDateTime now = LocalDateTime.now(clock); + String currentDayOfWeek = getDayOfWeek(now); + String previousDayOfWeek = getDayOfWeek(now.minusDays(1)); + Long count = shopRepository.countOpenShops(currentDayOfWeek, previousDayOfWeek, now.toLocalTime()); + return new OpenShopsCountResponse(Math.toIntExact(count)); + } + public ShopCategoriesResponse getShopsCategories() { List shopCategories = shopCategoryRepository.findAll(Sort.by("orderIndex")); return ShopCategoriesResponse.from(shopCategories); @@ -178,4 +189,8 @@ public void publishCallNotification(Integer shopId, Integer studentId) { private boolean isSubscribeReviewNotification(Integer studentId) { return notificationSubscribeRepository.existsByUserIdAndSubscribeTypeAndDetailTypeIsNull(studentId, REVIEW_PROMPT); } + + private String getDayOfWeek(LocalDateTime dateTime) { + return dateTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.US).toUpperCase(); + } } 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 361fb2423d..951c69de5c 100644 --- a/src/test/java/in/koreatech/koin/acceptance/domain/ShopApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/domain/ShopApiTest.java @@ -429,6 +429,22 @@ void setUp() { """, 마슬랜_영업여부, 신전_떡볶이_영업여부))); } + @Test + void 현재_영업중인_상점_개수를_조회한다() throws Exception { + shopFixture.영업중이_아닌_신전_떡볶이(owner); + + // 2024-01-15 12:00 월요일 기준 + mockMvc.perform( + get("/shops/open/count") + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "count": 1 + } + """)); + } + @Test void 상점의_정렬된_모든_카테고리_조회() throws Exception { shopCategoryFixture.카테고리_일반음식(shopParentCategory_가게); // 카테고리_치킨이 먼저 생성됨