Skip to content

Commit ec732e5

Browse files
authored
[Feat] 가게 이미지 조회 API 구현
2 parents aee2827 + d886c55 commit ec732e5

File tree

8 files changed

+200
-7
lines changed

8 files changed

+200
-7
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package eatda.controller.store;
2+
3+
import java.util.List;
4+
5+
public record ImagesResponse(List<String> imageUrls) {
6+
}

src/main/java/eatda/controller/store/StoreController.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public class StoreController {
1717

1818
private final StoreService storeService;
1919

20+
@GetMapping("/api/shops/{storeId}/images")
21+
public ResponseEntity<ImagesResponse> getStoreImages(@PathVariable long storeId) {
22+
return ResponseEntity.ok(storeService.getStoreImages(storeId));
23+
}
24+
2025
@GetMapping("/api/shops")
2126
public ResponseEntity<StoresResponse> getStores(@RequestParam @Min(1) @Max(50) int size) {
2227
return ResponseEntity.ok(storeService.getStores(size));

src/main/java/eatda/repository/store/CheerRepository.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@
77
import java.util.List;
88
import java.util.Optional;
99
import org.springframework.data.domain.Pageable;
10+
import org.springframework.data.jpa.repository.JpaRepository;
1011
import org.springframework.data.jpa.repository.Query;
11-
import org.springframework.data.repository.Repository;
1212

13-
public interface CheerRepository extends Repository<Cheer, Long> {
14-
15-
Cheer save(Cheer cheer);
13+
public interface CheerRepository extends JpaRepository<Cheer, Long> {
1614

1715
List<Cheer> findAllByOrderByCreatedAtDesc(Pageable pageable);
1816

@@ -23,6 +21,12 @@ public interface CheerRepository extends Repository<Cheer, Long> {
2321
LIMIT 1""")
2422
Optional<ImageKey> findRecentImageKey(Store store);
2523

24+
@Query("""
25+
SELECT c.imageKey FROM Cheer c
26+
WHERE c.store = :store AND c.imageKey IS NOT NULL
27+
ORDER BY c.createdAt DESC""")
28+
List<ImageKey> findAllImageKey(Store store);
29+
2630
int countByMember(Member member);
2731

2832
boolean existsByMemberAndStoreKakaoId(Member member, String storeKakaoId);

src/main/java/eatda/service/store/StoreService.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import eatda.client.map.MapClient;
77
import eatda.client.map.StoreSearchResult;
8+
import eatda.controller.store.ImagesResponse;
89
import eatda.controller.store.StorePreviewResponse;
910
import eatda.controller.store.StoreResponse;
1011
import eatda.controller.store.StoreSearchResponses;
@@ -42,6 +43,15 @@ public StoresResponse getStores(int size) {
4243
.collect(collectingAndThen(toList(), StoresResponse::new));
4344
}
4445

46+
public ImagesResponse getStoreImages(long storeId) {
47+
Store store = storeRepository.getById(storeId);
48+
List<String> imageUrls = cheerRepository.findAllImageKey(store)
49+
.stream()
50+
.map(imageStorage::getPreSignedUrl)
51+
.toList();
52+
return new ImagesResponse(imageUrls);
53+
}
54+
4555
private Optional<String> getStoreImageUrl(Store store) {
4656
return cheerRepository.findRecentImageKey(store)
4757
.map(imageStorage::getPreSignedUrl);

src/test/java/eatda/controller/store/StoreControllerTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,42 @@ class GetStores {
6969
}
7070
}
7171

72+
@Nested
73+
class GetStoreImages {
74+
75+
@Test
76+
void 음식점_이미지들을_조회한다() {
77+
Member member = memberGenerator.generate("111");
78+
Store store = storeGenerator.generate("농민백암순대", "서울 강남구 대치동 896-33");
79+
cheerGenerator.generateCommon(member, store, "image-key-1");
80+
cheerGenerator.generateCommon(member, store, "image-key-2");
81+
cheerGenerator.generateCommon(member, store, "image-key-3");
82+
83+
ImagesResponse response = given()
84+
.when()
85+
.get("/api/shops/{storeId}/images", store.getId())
86+
.then()
87+
.statusCode(200)
88+
.extract().as(ImagesResponse.class);
89+
90+
assertThat(response.imageUrls()).hasSize(3);
91+
}
92+
93+
@Test
94+
void 음식점_이미지가_없다면_빈_리스트를_반환한다() {
95+
Store store = storeGenerator.generate("농민백암순대", "서울 강남구 대치동 896-33");
96+
97+
ImagesResponse response = given()
98+
.when()
99+
.get("/api/shops/{storeId}/images", store.getId())
100+
.then()
101+
.statusCode(200)
102+
.extract().as(ImagesResponse.class);
103+
104+
assertThat(response.imageUrls()).isEmpty();
105+
}
106+
}
107+
72108
@Nested
73109
class SearchStores {
74110

src/test/java/eatda/document/store/StoreDocumentTest.java

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
1414
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
1515

16+
import eatda.controller.store.ImagesResponse;
1617
import eatda.controller.store.StorePreviewResponse;
1718
import eatda.controller.store.StoreResponse;
1819
import eatda.controller.store.StoreSearchResponse;
@@ -36,6 +37,7 @@ public class StoreDocumentTest extends BaseDocumentTest {
3637

3738
@Nested
3839
class GetStore {
40+
3941
RestDocsRequest requestDocument = request()
4042
.tag(Tag.STORE_API)
4143
.summary("음식점 정보 조회")
@@ -122,7 +124,7 @@ class GetStores {
122124
doReturn(response).when(storeService).getStores(anyInt());
123125

124126
int size = 2;
125-
var document = document("store/get-stores", 200)
127+
var document = document("store/get", 200)
126128
.request(requestDocument)
127129
.response(responseDocument)
128130
.build();
@@ -140,7 +142,7 @@ class GetStores {
140142
doThrow(new BusinessException(errorCode)).when(storeService).getStores(anyInt());
141143

142144
int size = 2;
143-
var document = document("store/get-stores", errorCode)
145+
var document = document("store/get", errorCode)
144146
.request(requestDocument)
145147
.response(ERROR_RESPONSE)
146148
.build();
@@ -153,6 +155,63 @@ class GetStores {
153155
}
154156
}
155157

158+
@Nested
159+
class GetStoreImages {
160+
161+
RestDocsRequest requestDocument = request()
162+
.tag(Tag.STORE_API)
163+
.summary("가게 이미지 조회")
164+
.pathParameter(
165+
parameterWithName("storeId").description("음식점 ID")
166+
);
167+
168+
RestDocsResponse responseDocument = response()
169+
.responseBodyField(
170+
fieldWithPath("imageUrls").type(ARRAY).description("음식점 이미지 URL 목록")
171+
);
172+
173+
@Test
174+
void 음식점_이미지들을_조회() {
175+
long storeId = 1L;
176+
ImagesResponse response = new ImagesResponse(List.of(
177+
"https://example.image/1.jpg",
178+
"https://example.image/2.jpg",
179+
"https://example.image/3.jpg"
180+
));
181+
doReturn(response).when(storeService).getStoreImages(storeId);
182+
183+
var document = document("store/get-images", 200)
184+
.request(requestDocument)
185+
.response(responseDocument)
186+
.build();
187+
188+
given(document)
189+
.contentType(ContentType.JSON)
190+
.pathParam("storeId", storeId)
191+
.when().get("/api/shops/{storeId}/images")
192+
.then().statusCode(200);
193+
}
194+
195+
@EnumSource(value = BusinessErrorCode.class, names = {"STORE_NOT_FOUND"})
196+
@ParameterizedTest
197+
void 음식점_이미지_조회_실패(BusinessErrorCode errorCode) {
198+
long storeId = 1L;
199+
doThrow(new BusinessException(errorCode)).when(storeService).getStoreImages(storeId);
200+
201+
var document = document("get-images", errorCode)
202+
.request(requestDocument)
203+
.response(ERROR_RESPONSE)
204+
.build();
205+
206+
given(document)
207+
.contentType(ContentType.JSON)
208+
.pathParam("storeId", storeId)
209+
.when().get("/api/shops/{storeId}/images")
210+
.then().statusCode(errorCode.getStatus().value());
211+
}
212+
213+
}
214+
156215
@Nested
157216
class SearchStores {
158217

src/test/java/eatda/repository/store/CheerRepositoryTest.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import eatda.domain.member.Member;
77
import eatda.domain.store.Store;
88
import eatda.repository.BaseRepositoryTest;
9+
import java.util.List;
910
import java.util.Optional;
1011
import org.junit.jupiter.api.Nested;
1112
import org.junit.jupiter.api.Test;
@@ -26,7 +27,9 @@ class FindRecentImageKey {
2627

2728
Optional<ImageKey> imageKey = cheerRepository.findRecentImageKey(store);
2829

29-
assertThat(imageKey).contains(new ImageKey("image-key-2"));
30+
assertThat(imageKey)
31+
.map(ImageKey::getValue)
32+
.contains("image-key-2");
3033
}
3134

3235
@Test
@@ -42,4 +45,37 @@ class FindRecentImageKey {
4245
assertThat(imageKey).isEmpty();
4346
}
4447
}
48+
49+
@Nested
50+
class FindAllImageKey {
51+
52+
@Test
53+
void 응원들_중_모든_null이_아닌_이미지_키를_조회한다() throws InterruptedException {
54+
Member member = memberGenerator.generate("111");
55+
Store store = storeGenerator.generate("농민백암순대", "서울 강남구 대치동 896-33");
56+
cheerGenerator.generateCommon(member, store, "image-key-1");
57+
Thread.sleep(5);
58+
cheerGenerator.generateCommon(member, store, "image-key-2");
59+
cheerGenerator.generateCommon(member, store, null);
60+
61+
List<ImageKey> imageKeys = cheerRepository.findAllImageKey(store);
62+
63+
assertThat(imageKeys)
64+
.extracting(ImageKey::getValue)
65+
.containsExactly("image-key-2", "image-key-1");
66+
}
67+
68+
@Test
69+
void 응원들의_이미지가_모두_비어있다면_빈_리스트를_반환한다() {
70+
Member member = memberGenerator.generate("111");
71+
Store store = storeGenerator.generate("농민백암순대", "서울 강남구 대치동 896-33");
72+
cheerGenerator.generateCommon(member, store, null);
73+
cheerGenerator.generateCommon(member, store, null);
74+
cheerGenerator.generateCommon(member, store, null);
75+
76+
List<ImageKey> imageKeys = cheerRepository.findAllImageKey(store);
77+
78+
assertThat(imageKeys).isEmpty();
79+
}
80+
}
4581
}

src/test/java/eatda/service/store/StoreServiceTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import static org.mockito.Mockito.doReturn;
88

99
import eatda.client.map.StoreSearchResult;
10+
import eatda.controller.store.ImagesResponse;
1011
import eatda.controller.store.StoreResponse;
1112
import eatda.domain.member.Member;
1213
import eatda.domain.store.Store;
@@ -78,6 +79,42 @@ class GetStores {
7879
}
7980
}
8081

82+
@Nested
83+
class GetStoreImages {
84+
85+
@Test
86+
void 음식점_이미지들을_조회한다() {
87+
Member member = memberGenerator.generate("111");
88+
Store store = storeGenerator.generate("농민백암순대", "서울 강남구 대치동 896-33");
89+
cheerGenerator.generateCommon(member, store, "image-key-1");
90+
cheerGenerator.generateCommon(member, store, "image-key-2");
91+
cheerGenerator.generateCommon(member, store, "image-key-3");
92+
93+
ImagesResponse response = storeService.getStoreImages(store.getId());
94+
95+
assertThat(response.imageUrls()).hasSize(3);
96+
}
97+
98+
@Test
99+
void 음식점_이미지가_없다면_빈_리스트를_반환한다() {
100+
Store store = storeGenerator.generate("농민백암순대", "서울 강남구 대치동 896-33");
101+
102+
ImagesResponse response = storeService.getStoreImages(store.getId());
103+
104+
assertThat(response.imageUrls()).isEmpty();
105+
}
106+
107+
@Test
108+
void 음식점이_존재하지_않으면_예외를_발생시킨다() {
109+
long nonExistentStoreId = 999L;
110+
111+
BusinessException exception = assertThrows(BusinessException.class,
112+
() -> storeService.getStoreImages(nonExistentStoreId));
113+
114+
assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.STORE_NOT_FOUND);
115+
}
116+
}
117+
81118
@Nested
82119
class SearchStores {
83120

0 commit comments

Comments
 (0)