Skip to content

Commit

Permalink
feature: 모든 상점 조회 API에 혜택 상세 필드 추가 (#1121)
Browse files Browse the repository at this point in the history
* feat: 모든 상점 조회시 혜택 필드 추가

* refactor: 리뷰 반영

---------

Co-authored-by: 송선권 <[email protected]>
Co-authored-by: Hwang HyeonSik <[email protected]>
Co-authored-by: 배진호 <[email protected]>
Co-authored-by: 김성재 <[email protected]>
Co-authored-by: 신관규 <[email protected]>
Co-authored-by: HyeonsuLee <[email protected]>
  • Loading branch information
7 people authored Dec 6, 2024
1 parent f9a8828 commit f3fecd8
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@

import in.koreatech.koin.domain.shop.model.shop.Shop;
import in.koreatech.koin.global.domain.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -34,9 +36,14 @@ public class BenefitCategoryMap extends BaseEntity {
@JoinColumn(name = "benefit_id", referencedColumnName = "id", nullable = false)
private BenefitCategory benefitCategory;

@Size(min = 2, max = 20)
@Column(name = "detail")
private String detail;

@Builder
public BenefitCategoryMap(Shop shop, BenefitCategory benefitCategory) {
public BenefitCategoryMap(Shop shop, BenefitCategory benefitCategory, String detail) {
this.shop = shop;
this.benefitCategory = benefitCategory;
this.detail = detail;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;

import in.koreatech.koin.domain.benefit.model.BenefitCategoryMap;
Expand All @@ -10,5 +11,12 @@ public interface BenefitCategoryMapRepository extends Repository<BenefitCategory

List<BenefitCategoryMap> findAllByBenefitCategoryId(Integer benefitCategoryId);

@Query("""
SELECT bcm FROM BenefitCategoryMap bcm
JOIN FETCH bcm.shop s
JOIN FETCH bcm.benefitCategory bc
""")
List<BenefitCategoryMap> findAllWithFetchJoin();

BenefitCategoryMap save(BenefitCategoryMap benefitCategoryMap);
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public static ShopsResponseV2 from(
ShopsSortCriteria sortBy,
List<ShopsFilterCriteria> shopsFilterCriterias,
LocalDateTime now,
String query
String query,
Map<Integer, List<String>> benefitDetail
) {
List<InnerShopResponse> innerShopResponses = shops.stream()
.filter(queryPredicate(query))
Expand All @@ -53,7 +54,8 @@ public static ShopsResponseV2 from(
shopInfo.durationEvent(),
it.isOpen(now),
shopInfo.averageRate(),
shopInfo.reviewCount()
shopInfo.reviewCount(),
benefitDetail.getOrDefault(it.id(), List.of())
);
})
.filter(ShopsFilterCriteria.createCombinedFilter(shopsFilterCriterias))
Expand Down Expand Up @@ -101,7 +103,10 @@ public record InnerShopResponse(
double averageRate,

@Schema(example = "10", description = "리뷰 개수", requiredMode = REQUIRED)
long reviewCount
long reviewCount,

@Schema(example = "['배달비 무료', '콜라 서비스']", description = "혜택 설명", requiredMode = NOT_REQUIRED)
List<String> benefitDetails
) {

@JsonNaming(value = SnakeCaseStrategy.class)
Expand Down Expand Up @@ -138,7 +143,8 @@ public static InnerShopResponse from(
Boolean isEvent,
Boolean isOpen,
Double averageRate,
Long reviewCount
Long reviewCount,
List<String> benefitDetails
) {
return new InnerShopResponse(
shop.shopCategories().stream().map(ShopCategoryCache::id).toList(),
Expand All @@ -159,7 +165,8 @@ public static InnerShopResponse from(
isEvent,
isOpen,
averageRate,
reviewCount
reviewCount,
benefitDetails
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static in.koreatech.koin.global.domain.notification.model.NotificationSubscribeType.REVIEW_PROMPT;

import in.koreatech.koin.domain.benefit.model.BenefitCategoryMap;
import in.koreatech.koin.domain.benefit.repository.BenefitCategoryMapRepository;
import in.koreatech.koin.domain.shop.cache.ShopsCacheService;
import in.koreatech.koin.domain.shop.cache.dto.ShopsCache;
import in.koreatech.koin.domain.shop.dto.shop.ShopsFilterCriteria;
Expand All @@ -24,6 +26,8 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
Expand All @@ -43,6 +47,7 @@ public class ShopService {
private final ShopCustomRepository shopCustomRepository;
private final NotificationSubscribeRepository notificationSubscribeRepository;
private final ShopReviewNotificationRedisRepository shopReviewNotificationRedisRepository;
private final BenefitCategoryMapRepository benefitCategoryMapRepository;

public ShopResponse getShop(Integer shopId) {
Shop shop = shopRepository.getById(shopId);
Expand All @@ -63,23 +68,37 @@ public ShopCategoriesResponse getShopsCategories() {
}

public ShopsResponseV2 getShopsV2(
ShopsSortCriteria sortBy,
List<ShopsFilterCriteria> filterCriteria,
String query
ShopsSortCriteria sortBy,
List<ShopsFilterCriteria> filterCriteria,
String query
) {
if (filterCriteria.contains(null)) {
throw KoinIllegalArgumentException.withDetail("유효하지 않은 필터입니다.");
}
ShopsCache shopCaches = shopsCache.findAllShopCache();
LocalDateTime now = LocalDateTime.now(clock);
Map<Integer, ShopInfo> shopInfoMap = shopCustomRepository.findAllShopInfo(now);
List<BenefitCategoryMap> benefitCategorys = benefitCategoryMapRepository.findAllWithFetchJoin();
Map<Integer, List<String>> benefitDetailMap = new HashMap<>(benefitCategorys.size());
benefitCategorys.forEach(benefitCategory -> {
int shopId = benefitCategory.getShop().getId();
String benefitDetail = benefitCategory.getDetail();
if (benefitDetailMap.containsKey(shopId)) {
benefitDetailMap.get(shopId).add(benefitDetail);
} else {
List<String> details = new ArrayList<>();
details.add(benefitDetail);
benefitDetailMap.put(shopId, details);
}
});
return ShopsResponseV2.from(
shopCaches.shopCaches(),
shopInfoMap,
sortBy,
filterCriteria,
now,
query
shopCaches.shopCaches(),
shopInfoMap,
sortBy,
filterCriteria,
now,
query,
benefitDetailMap
);
}

Expand All @@ -88,9 +107,9 @@ public void publishCallNotification(Integer shopId, Integer studentId) {

if (isSubscribeReviewNotification(studentId)) {
ShopReviewNotification shopReviewNotification = ShopReviewNotification.builder()
.shopId(shopId)
.studentId(studentId)
.build();
.shopId(shopId)
.studentId(studentId)
.build();

double score = LocalDateTime.now(clock).plusHours(1).toEpochSecond(ZoneOffset.UTC);
shopReviewNotificationRedisRepository.save(shopReviewNotification, score);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE `shop_benefit_category_map`
ADD COLUMN `detail` VARCHAR(20);
69 changes: 69 additions & 0 deletions src/test/java/in/koreatech/koin/acceptance/ShopApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import in.koreatech.koin.domain.benefit.model.BenefitCategory;
import in.koreatech.koin.fixture.BenefitCategoryFixture;
import in.koreatech.koin.fixture.BenefitCategoryMapFixture;
import java.time.LocalDate;

import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -40,6 +43,12 @@
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ShopApiTest extends AcceptanceTest {

@Autowired
private BenefitCategoryFixture benefitCategoryFixture;

@Autowired
private BenefitCategoryMapFixture benefitCategoryMapFixture;

@Autowired
private UserFixture userFixture;

Expand Down Expand Up @@ -1897,4 +1906,64 @@ void setUp() {
)
.andExpect(status().isOk());
}

@Test
void 리뷰를_조회하면_혜택_정보가_조회된다() throws Exception {
Shop 영업중인_티바 = shopFixture.영업중인_티바(owner);
shopReviewFixture.리뷰_4점(익명_학생, 영업중인_티바);

shopReviewFixture.리뷰_4점(익명_학생, 마슬랜);
shopReviewFixture.리뷰_4점(익명_학생, 마슬랜);
// 2024-01-15 12:00 월요일 기준
boolean 마슬랜_영업여부 = true;
boolean 티바_영업여부 = true;

BenefitCategory 최소주문금액_무료 = benefitCategoryFixture.최소주문금액_무료();
BenefitCategory 서비스_증정 = benefitCategoryFixture.서비스_증정();
benefitCategoryMapFixture.설명이_포함된_혜택_추가(영업중인_티바, 최소주문금액_무료, "무료");
benefitCategoryMapFixture.설명이_포함된_혜택_추가(영업중인_티바, 서비스_증정, "콜라");
mockMvc.perform(
get("/v2/shops")
.queryParam("sorter", "COUNT_DESC")
)
.andExpect(status().isOk())
.andExpect(content().json(String.format("""
{
"count": 2,
"shops": [
{
"category_ids": [
\s
],
"delivery": true,
"id": 1,
"name": "마슬랜 치킨",
"pay_bank": true,
"pay_card": true,
"phone": "010-7574-1212",
"is_event": false,
"is_open": %s,
"average_rate": 4.0,
"review_count": 2,
"benefit_details": []
},{
"category_ids": [
\s
],
"delivery": true,
"id": 2,
"name": "티바",
"pay_bank": true,
"pay_card": true,
"phone": "010-7788-9900",
"is_event": false,
"is_open": %s,
"average_rate": 4.0,
"review_count": 1,
"benefit_details": ["무료", "콜라"]
}
]
}
""", 티바_영업여부, 마슬랜_영업여부)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@ public BenefitCategoryMapFixture(
.benefitCategory(benefitCategory)
.build());
}

public BenefitCategoryMap 설명이_포함된_혜택_추가(Shop shop, BenefitCategory benefitCategory, String detail) {
return benefitCategoryMapRepository.save(BenefitCategoryMap.builder()
.shop(shop)
.benefitCategory(benefitCategory)
.detail(detail)
.build());
}
}

0 comments on commit f3fecd8

Please sign in to comment.