Skip to content

Commit c58bb6d

Browse files
authored
[Feat] 가게에 달린 응원들 조회 API 구현
2 parents ec732e5 + 5e820ca commit c58bb6d

File tree

8 files changed

+169
-0
lines changed

8 files changed

+169
-0
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.springframework.http.HttpStatus;
99
import org.springframework.http.ResponseEntity;
1010
import org.springframework.web.bind.annotation.GetMapping;
11+
import org.springframework.web.bind.annotation.PathVariable;
1112
import org.springframework.web.bind.annotation.PostMapping;
1213
import org.springframework.web.bind.annotation.RequestParam;
1314
import org.springframework.web.bind.annotation.RequestPart;
@@ -34,4 +35,11 @@ public ResponseEntity<CheersResponse> getCheers(@RequestParam @Min(1) @Max(50) i
3435
CheersResponse response = cheerService.getCheers(size);
3536
return ResponseEntity.ok(response);
3637
}
38+
39+
@GetMapping("/api/shops/{storeId}/cheers")
40+
public ResponseEntity<CheersInStoreResponse> getCheersByStoreId(@PathVariable Long storeId,
41+
@RequestParam @Min(1) @Max(50) int size) {
42+
CheersInStoreResponse response = cheerService.getCheersByStoreId(storeId, size);
43+
return ResponseEntity.ok(response);
44+
}
3745
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package eatda.controller.store;
2+
3+
import eatda.domain.store.Cheer;
4+
5+
public record CheerInStoreResponse(
6+
long id,
7+
long memberId,
8+
String memberNickname,
9+
String description
10+
) {
11+
12+
public CheerInStoreResponse(Cheer cheer) {
13+
this(
14+
cheer.getId(),
15+
cheer.getMember().getId(),
16+
cheer.getMember().getNickname(),
17+
cheer.getDescription()
18+
);
19+
}
20+
}
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 CheersInStoreResponse(List<CheerInStoreResponse> cheers) {
6+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public interface CheerRepository extends JpaRepository<Cheer, Long> {
1414

1515
List<Cheer> findAllByOrderByCreatedAtDesc(Pageable pageable);
1616

17+
List<Cheer> findAllByStoreOrderByCreatedAtDesc(Store store, Pageable pageable);
18+
1719
@Query("""
1820
SELECT c.imageKey FROM Cheer c
1921
WHERE c.store = :store AND c.imageKey IS NOT NULL

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import eatda.client.map.MapClient;
44
import eatda.client.map.StoreSearchResult;
5+
import eatda.controller.store.CheerInStoreResponse;
56
import eatda.controller.store.CheerPreviewResponse;
67
import eatda.controller.store.CheerRegisterRequest;
78
import eatda.controller.store.CheerResponse;
9+
import eatda.controller.store.CheersInStoreResponse;
810
import eatda.controller.store.CheersResponse;
911
import eatda.domain.Image;
1012
import eatda.domain.ImageDomain;
@@ -74,4 +76,15 @@ private CheersResponse toCheersResponse(List<Cheer> cheers) {
7476
imageStorage.getPreSignedUrl(cheer.getImageKey())))
7577
.toList());
7678
}
79+
80+
@Transactional(readOnly = true)
81+
public CheersInStoreResponse getCheersByStoreId(Long storeId, int size) {
82+
Store store = storeRepository.getById(storeId);
83+
List<Cheer> cheers = cheerRepository.findAllByStoreOrderByCreatedAtDesc(store, Pageable.ofSize(size));
84+
85+
List<CheerInStoreResponse> cheersResponse = cheers.stream()
86+
.map(CheerInStoreResponse::new)
87+
.toList(); // TODO N+1 문제 해결
88+
return new CheersInStoreResponse(cheersResponse);
89+
}
7790
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,33 @@ class GetCheers {
8585
() -> assertThat(firstResponse.cheerId()).isEqualTo(cheer3.getId())
8686
);
8787
}
88+
89+
@Nested
90+
class GetCheersByStoreId {
91+
92+
@Test
93+
void 가게_아이디로_응원을_조회한다() throws InterruptedException {
94+
Member member1 = memberGenerator.generateRegisteredMember("123", "[email protected]", "1234", "01012341234");
95+
Member member2 = memberGenerator.generateRegisteredMember("124", "[email protected]", "1235", "01012341235");
96+
Store store = storeGenerator.generate("123", "서울시 강남구 역삼동 123-45");
97+
Cheer cheer1 = cheerGenerator.generateCommon(member1, store);
98+
Thread.sleep(5);
99+
Cheer cheer2 = cheerGenerator.generateCommon(member2, store);
100+
101+
CheersInStoreResponse response = given()
102+
.when()
103+
.queryParam("size", 2)
104+
.get("/api/shops/{storeId}/cheers", store.getId())
105+
.then()
106+
.statusCode(200)
107+
.extract().as(CheersInStoreResponse.class);
108+
109+
assertAll(
110+
() -> assertThat(response.cheers()).hasSize(2),
111+
() -> assertThat(response.cheers().get(0).id()).isEqualTo(cheer2.getId()),
112+
() -> assertThat(response.cheers().get(1).id()).isEqualTo(cheer1.getId())
113+
);
114+
}
115+
}
88116
}
89117
}

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
1515
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
1616

17+
import eatda.controller.store.CheerInStoreResponse;
1718
import eatda.controller.store.CheerPreviewResponse;
1819
import eatda.controller.store.CheerRegisterRequest;
1920
import eatda.controller.store.CheerResponse;
21+
import eatda.controller.store.CheersInStoreResponse;
2022
import eatda.controller.store.CheersResponse;
2123
import eatda.document.BaseDocumentTest;
2224
import eatda.document.RestDocsRequest;
@@ -191,4 +193,68 @@ class GetCheers {
191193
.then().statusCode(errorCode.getStatus().value());
192194
}
193195
}
196+
197+
@Nested
198+
class GetCheersByStoreId {
199+
200+
RestDocsRequest requestDocument = request()
201+
.tag(Tag.STORE_API)
202+
.summary("가게별 응원 검색")
203+
.pathParameter(
204+
parameterWithName("storeId").description("가게 ID")
205+
)
206+
.queryParameter(
207+
parameterWithName("size").description("조회 개수 (최소 1, 최대 50)")
208+
);
209+
210+
RestDocsResponse responseDocument = response()
211+
.responseBodyField(
212+
fieldWithPath("cheers").type(ARRAY).description("응원 검색 결과"),
213+
fieldWithPath("cheers[].id").type(NUMBER).description("응원 ID"),
214+
fieldWithPath("cheers[].memberId").type(NUMBER).description("응원 작성자 회원 ID"),
215+
fieldWithPath("cheers[].memberNickname").type(STRING).description("응원 작성자 닉네임"),
216+
fieldWithPath("cheers[].description").type(STRING).description("응원 내용")
217+
);
218+
219+
@Test
220+
void 가게별_응원_검색_성공() {
221+
Long storeId = 1L;
222+
int size = 2;
223+
CheersInStoreResponse responses = new CheersInStoreResponse(List.of(
224+
new CheerInStoreResponse(20L, 5L, "커찬", "너무 맛있어요!"),
225+
new CheerInStoreResponse(10L, 3L, "찬커", "너무 매워요! 하지만 맛있어요!")
226+
));
227+
doReturn(responses).when(cheerService).getCheersByStoreId(storeId, size);
228+
229+
var document = document("cheer/get-store-id", 200)
230+
.request(requestDocument)
231+
.response(responseDocument)
232+
.build();
233+
234+
given(document)
235+
.contentType(ContentType.JSON)
236+
.queryParam("size", size)
237+
.when().get("/api/shops/{storeId}/cheers", storeId)
238+
.then().statusCode(200);
239+
}
240+
241+
@EnumSource(value = BusinessErrorCode.class, names = {"STORE_NOT_FOUND"})
242+
@ParameterizedTest
243+
void 가게별_응원_검색_실패(BusinessErrorCode errorCode) {
244+
Long storeId = 1L;
245+
int size = 2;
246+
doThrow(new BusinessException(errorCode)).when(cheerService).getCheersByStoreId(eq(storeId), anyInt());
247+
248+
var document = document("cheer/get-store-id", errorCode)
249+
.request(requestDocument)
250+
.response(ERROR_RESPONSE)
251+
.build();
252+
253+
given(document)
254+
.contentType(ContentType.JSON)
255+
.queryParam("size", size)
256+
.when().get("/api/shops/{storeId}/cheers", storeId)
257+
.then().statusCode(errorCode.getStatus().value());
258+
}
259+
}
194260
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import eatda.client.map.StoreSearchResult;
1010
import eatda.controller.store.CheerRegisterRequest;
1111
import eatda.controller.store.CheerResponse;
12+
import eatda.controller.store.CheersInStoreResponse;
1213
import eatda.controller.store.CheersResponse;
1314
import eatda.domain.member.Member;
1415
import eatda.domain.store.Cheer;
@@ -146,5 +147,30 @@ class GetCheers {
146147
() -> assertThat(response.cheers().get(1).cheerId()).isEqualTo(cheer2.getId())
147148
);
148149
}
150+
151+
@Nested
152+
class GetCheersByStoreId {
153+
154+
@Test
155+
void 요청한_가게의_응원을_최신순으로_반환한다() throws InterruptedException {
156+
Member member1 = memberGenerator.generateRegisteredMember("123", "[email protected]", "1234", "01012341234");
157+
Member member2 = memberGenerator.generateRegisteredMember("124", "[email protected]", "1235", "01012341235");
158+
Member member3 = memberGenerator.generateRegisteredMember("125", "[email protected]", "1236", "01012341236");
159+
Store store = storeGenerator.generate("123", "서울시 강남구 역삼동 123-45");
160+
Cheer cheer1 = cheerGenerator.generateCommon(member1, store);
161+
Thread.sleep(5);
162+
Cheer cheer2 = cheerGenerator.generateCommon(member2, store);
163+
Thread.sleep(5);
164+
Cheer cheer3 = cheerGenerator.generateCommon(member3, store);
165+
166+
CheersInStoreResponse response = cheerService.getCheersByStoreId(store.getId(), 2);
167+
168+
assertAll(
169+
() -> assertThat(response.cheers()).hasSize(2),
170+
() -> assertThat(response.cheers().get(0).id()).isEqualTo(cheer3.getId()),
171+
() -> assertThat(response.cheers().get(1).id()).isEqualTo(cheer2.getId())
172+
);
173+
}
174+
}
149175
}
150176
}

0 commit comments

Comments
 (0)