Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/main/java/eatda/controller/store/StoreController.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ public ResponseEntity<ImagesResponse> getStoreImages(@PathVariable long storeId)
}

@GetMapping("/api/shops")
public ResponseEntity<StoresResponse> getStores(@RequestParam @Min(1) @Max(50) int size) {
return ResponseEntity.ok(storeService.getStores(size));
public ResponseEntity<StoresResponse> getStores(@RequestParam @Min(1) @Max(50) int size,
@RequestParam(required = false) String category) {
return ResponseEntity.ok(storeService.getStores(size, category));
}

@GetMapping("/api/shops/{storeId}")
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/eatda/domain/store/StoreCategory.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public enum StoreCategory {
CHINESE("중식"),
JAPANESE("일식"),
WESTERN("양식"),
CAFE("카페"),
CAFE("카페/디저트"),
OTHER("기타");

private final String categoryName;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/eatda/repository/store/StoreRepository.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package eatda.repository.store;

import eatda.domain.store.Store;
import eatda.domain.store.StoreCategory;
import eatda.exception.BusinessErrorCode;
import eatda.exception.BusinessException;
import java.util.List;
Expand All @@ -19,4 +20,6 @@ default Store getById(Long id) {
Optional<Store> findByKakaoId(String kakaoId);

List<Store> findAllByOrderByCreatedAtDesc(Pageable pageable);

List<Store> findAllByCategoryOrderByCreatedAtDesc(StoreCategory category, Pageable pageable);
}
14 changes: 12 additions & 2 deletions src/main/java/eatda/service/store/StoreService.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
import eatda.controller.store.StoreSearchResponses;
import eatda.controller.store.StoresResponse;
import eatda.domain.store.Store;
import eatda.domain.store.StoreCategory;
import eatda.repository.store.CheerRepository;
import eatda.repository.store.StoreRepository;
import eatda.storage.image.ImageStorage;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -36,13 +38,21 @@ public StoreResponse getStore(long storeId) {
}

// TODO : N+1 문제 해결
public StoresResponse getStores(int size) {
return storeRepository.findAllByOrderByCreatedAtDesc(Pageable.ofSize(size))
public StoresResponse getStores(int size, @Nullable String category) {
return findStores(size, category)
.stream()
.map(store -> new StorePreviewResponse(store, getStoreImageUrl(store).orElse(null)))
.collect(collectingAndThen(toList(), StoresResponse::new));
}

private List<Store> findStores(int size, @Nullable String category) {
if (category == null || category.isBlank()) {
return storeRepository.findAllByOrderByCreatedAtDesc(Pageable.ofSize(size));
}
return storeRepository.findAllByCategoryOrderByCreatedAtDesc(
StoreCategory.from(category), Pageable.ofSize(size));
}

public ImagesResponse getStoreImages(long storeId) {
Store store = storeRepository.getById(storeId);
List<String> imageUrls = cheerRepository.findAllImageKey(store)
Expand Down
43 changes: 39 additions & 4 deletions src/test/java/eatda/controller/store/StoreControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import eatda.controller.BaseControllerTest;
import eatda.domain.member.Member;
import eatda.domain.store.Store;
import eatda.domain.store.StoreCategory;
import java.time.LocalDateTime;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -43,12 +44,14 @@ class GetStore {
class GetStores {

@Test
void 음식점_목록을_최신순으로_조회한다() {
void 모든_카테고리의_음식점_목록을_최신순으로_조회한다() {
Member member = memberGenerator.generate("111");
LocalDateTime startAt = LocalDateTime.of(2025, 7, 26, 1, 0, 0);
Store store1 = storeGenerator.generate("111", "서울 강남구 대치동 896-33", startAt);
Store store2 = storeGenerator.generate("222", "서울 강남구 대치동 896-34", startAt.plusHours(1));
Store store3 = storeGenerator.generate("333", "서울 강남구 대치동 896-35", startAt.plusHours(2));
Store store1 = storeGenerator.generate("112", "서울 강남구 대치동 896-33", StoreCategory.KOREAN, startAt);
Store store2 = storeGenerator.generate("113", "서울 성북구 석관동 123-45", StoreCategory.OTHER,
startAt.plusHours(1));
Store store3 = storeGenerator.generate("114", "서울 강남구 역삼동 678-90", StoreCategory.KOREAN,
startAt.plusHours(2));
cheerGenerator.generateCommon(member, store1, "image-key-1");
cheerGenerator.generateCommon(member, store2, "image-key-2");
cheerGenerator.generateCommon(member, store3, "image-key-3");
Expand All @@ -69,6 +72,38 @@ class GetStores {
() -> assertThat(response.stores().get(1).id()).isEqualTo(store2.getId())
);
}

@Test
void 특정_카테고리의_음식점_목록을_최신순으로_조회한다() {
Member member = memberGenerator.generate("111");
LocalDateTime startAt = LocalDateTime.of(2025, 7, 26, 1, 0, 0);
Store store1 = storeGenerator.generate("112", "서울 강남구 대치동 896-33", StoreCategory.CAFE, startAt);
Store store2 = storeGenerator.generate("113", "서울 성북구 석관동 123-45", StoreCategory.OTHER,
startAt.plusHours(1));
Store store3 = storeGenerator.generate("114", "서울 강남구 역삼동 678-90", StoreCategory.CAFE,
startAt.plusHours(2));
cheerGenerator.generateCommon(member, store1, "image-key-1");
cheerGenerator.generateCommon(member, store2, "image-key-2");
cheerGenerator.generateCommon(member, store3, "image-key-3");

int size = 2;
StoreCategory category = StoreCategory.CAFE;

StoresResponse response = given()
.queryParam("size", size)
.queryParam("category", category.getCategoryName())
.when()
.get("/api/shops")
.then()
.statusCode(200)
.extract().as(StoresResponse.class);

assertAll(
() -> assertThat(response.stores()).hasSize(size),
() -> assertThat(response.stores().get(0).id()).isEqualTo(store3.getId()),
() -> assertThat(response.stores().get(1).id()).isEqualTo(store1.getId())
);
}
}

@Nested
Expand Down
18 changes: 12 additions & 6 deletions src/test/java/eatda/document/store/StoreDocumentTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package eatda.document.store;


import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
Expand All @@ -23,6 +22,7 @@
import eatda.document.RestDocsRequest;
import eatda.document.RestDocsResponse;
import eatda.document.Tag;
import eatda.domain.store.StoreCategory;
import eatda.exception.BusinessErrorCode;
import eatda.exception.BusinessException;
import io.restassured.http.ContentType;
Expand Down Expand Up @@ -101,7 +101,9 @@ class GetStores {
.tag(Tag.STORE_API)
.summary("음식점 목록 조회")
.queryParameter(
parameterWithName("size").description("조회할 음식점 개수 (최소 1, 최대 50)")
parameterWithName("size").description("조회할 음식점 개수 (최소 1, 최대 50)"),
parameterWithName("category")
.description("음식점 카테고리(기본값: 전체) (한식,중식,일식,양식,디저트/카페,기타)").optional()
);

RestDocsResponse responseDocument = response()
Expand All @@ -117,13 +119,14 @@ class GetStores {

@Test
void 음식점_목록_최신순으로_조회() {
int size = 2;
StoreCategory category = StoreCategory.CAFE;
StoresResponse response = new StoresResponse(List.of(
new StorePreviewResponse(2L, "https://example.image", "농민백암순대", "강남구", "대치동", "한식"),
new StorePreviewResponse(1L, "https://example.image", "석관동떡볶이", "성북구", "석관동", "한식")
));
doReturn(response).when(storeService).getStores(anyInt());
doReturn(response).when(storeService).getStores(size, category.getCategoryName());

int size = 2;
var document = document("store/get", 200)
.request(requestDocument)
.response(responseDocument)
Expand All @@ -132,16 +135,18 @@ class GetStores {
given(document)
.contentType(ContentType.JSON)
.queryParam("size", size)
.queryParam("category", category.getCategoryName())
.when().get("/api/shops")
.then().statusCode(200);
}

@EnumSource(value = BusinessErrorCode.class, names = {"PRESIGNED_URL_GENERATION_FAILED"})
@ParameterizedTest
void 음식점_목록_조회_실패(BusinessErrorCode errorCode) {
doThrow(new BusinessException(errorCode)).when(storeService).getStores(anyInt());

int size = 2;
StoreCategory category = StoreCategory.CAFE;
doThrow(new BusinessException(errorCode)).when(storeService).getStores(size, category.getCategoryName());

var document = document("store/get", errorCode)
.request(requestDocument)
.response(ERROR_RESPONSE)
Expand All @@ -150,6 +155,7 @@ class GetStores {
given(document)
.contentType(ContentType.JSON)
.queryParam("size", size)
.queryParam("category", category.getCategoryName())
.when().get("/api/shops")
.then().statusCode(errorCode.getStatus().value());
}
Expand Down
28 changes: 19 additions & 9 deletions src/test/java/eatda/fixture/StoreGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,26 @@ public StoreGenerator(StoreRepository storeRepository) {
}

public Store generate(String kakaoId, String lotNumberAddress) {
Store store = Store.builder()
Store store = create(kakaoId, lotNumberAddress, DEFAULT_CATEGORY);
return storeRepository.save(store);
}

public Store generate(String kakaoId, String lotNumberAddress, LocalDateTime createdAt) {
Store store = create(kakaoId, lotNumberAddress, DEFAULT_CATEGORY);
DomainUtils.setCreatedAt(store, createdAt);
return storeRepository.save(store);
}

public Store generate(String kakaoId, String lotNumberAddress, StoreCategory category, LocalDateTime createdAt) {
Store store = create(kakaoId, lotNumberAddress, category);
DomainUtils.setCreatedAt(store, createdAt);
return storeRepository.save(store);
}

private Store create(String kakaoId, String lotNumberAddress, StoreCategory category) {
return Store.builder()
.kakaoId(kakaoId)
.category(DEFAULT_CATEGORY)
.category(category)
.phoneNumber(DEFAULT_PHONE_NUMBER)
.name(DEFAULT_NAME)
.placeUrl(DEFAULT_PLACE_URL)
Expand All @@ -37,12 +54,5 @@ public Store generate(String kakaoId, String lotNumberAddress) {
.latitude(DEFAULT_LATITUDE)
.longitude(DEFAULT_LONGITUDE)
.build();
return storeRepository.save(store);
}

public Store generate(String kakaoId, String lotNumberAddress, LocalDateTime createdAt) {
Store store = generate(kakaoId, lotNumberAddress);
DomainUtils.setCreatedAt(store, createdAt);
return storeRepository.save(store);
}
}
38 changes: 32 additions & 6 deletions src/test/java/eatda/service/store/StoreServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import eatda.controller.store.StoreResponse;
import eatda.domain.member.Member;
import eatda.domain.store.Store;
import eatda.domain.store.StoreCategory;
import eatda.exception.BusinessErrorCode;
import eatda.exception.BusinessException;
import eatda.service.BaseServiceTest;
Expand Down Expand Up @@ -59,26 +60,51 @@ class GetStore {
class GetStores {

@Test
void 음식점_목록을_최신순으로_조회한다() {
void 모든_카테고리의_음식점_목록을_최신순으로_조회한다() {
Member member = memberGenerator.generate("111");
LocalDateTime startAt = LocalDateTime.of(2025, 7, 26, 1, 0, 0);
Store store1 = storeGenerator.generate("농민백암순대", "서울 강남구 대치동 896-33", startAt);
Store store2 = storeGenerator.generate("석관동떡볶이", "서울 성북구 석관동 123-45", startAt.plusHours(1));
Store store3 = storeGenerator.generate("강남순대국", "서울 강남구 역삼동 678-90", startAt.plusHours(2));
Store store1 = storeGenerator.generate("농민백암순대", "서울 강남구 대치동 896-33", StoreCategory.KOREAN, startAt);
Store store2 = storeGenerator.generate("석관동떡볶이", "서울 성북구 석관동 123-45", StoreCategory.OTHER,
startAt.plusHours(1));
Store store3 = storeGenerator.generate("강남순대국", "서울 강남구 역삼동 678-90", StoreCategory.KOREAN,
startAt.plusHours(2));
cheerGenerator.generateCommon(member, store1, "image-key-1");
cheerGenerator.generateCommon(member, store2, "image-key-2");
cheerGenerator.generateCommon(member, store3, "image-key-3");

int size = 2;

var response = storeService.getStores(size);
var response = storeService.getStores(size, null);

assertAll(
() -> assertThat(response.stores()).hasSize(size),
() -> assertThat(response.stores().get(0).id()).isEqualTo(store3.getId()),
() -> assertThat(response.stores().get(1).id()).isEqualTo(store2.getId())
);
}

@Test
void 특정_카테고리의_음식점_목록을_최신순으로_조회한다() {
Member member = memberGenerator.generate("111");
LocalDateTime startAt = LocalDateTime.of(2025, 7, 26, 1, 0, 0);
Store store1 = storeGenerator.generate("112", "서울 강남구 대치동 896-33", StoreCategory.KOREAN, startAt);
Store store2 = storeGenerator.generate("113", "서울 성북구 석관동 123-45", StoreCategory.OTHER,
startAt.plusHours(1));
Store store3 = storeGenerator.generate("114", "서울 강남구 역삼동 678-90", StoreCategory.KOREAN,
startAt.plusHours(2));
cheerGenerator.generateCommon(member, store1, "image-key-1");
cheerGenerator.generateCommon(member, store2, "image-key-2");
cheerGenerator.generateCommon(member, store3, "image-key-3");
int size = 2;
StoreCategory category = StoreCategory.KOREAN;

var response = storeService.getStores(size, category.getCategoryName());

assertAll(
() -> assertThat(response.stores()).hasSize(size),
() -> assertThat(response.stores().get(0).id()).isEqualTo(store3.getId()),
() -> assertThat(response.stores().get(1).id()).isEqualTo(store1.getId())
);
}
}

@Nested
Expand Down