Skip to content

Commit 903ceb6

Browse files
authored
Merge pull request #180 from YAPP-Github/feat/PRODUCT-261
[Feat] '최신 응원 조회 API'에 검색 조건 추가
2 parents a8a2960 + 596ea00 commit 903ceb6

File tree

11 files changed

+399
-35
lines changed

11 files changed

+399
-35
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package eatda.controller.cheer;
22

3+
import eatda.controller.store.SearchDistrict;
34
import eatda.controller.web.auth.LoginMember;
45
import eatda.domain.ImageDomain;
6+
import eatda.domain.cheer.CheerTagName;
7+
import eatda.domain.store.StoreCategory;
58
import eatda.domain.store.StoreSearchResult;
69
import eatda.service.cheer.CheerService;
710
import eatda.service.store.StoreSearchService;
811
import jakarta.validation.constraints.Max;
912
import jakarta.validation.constraints.Min;
13+
import java.util.List;
1014
import lombok.RequiredArgsConstructor;
1115
import org.springframework.http.HttpStatus;
1216
import org.springframework.http.ResponseEntity;
@@ -38,8 +42,12 @@ public ResponseEntity<CheerResponse> registerCheer(@RequestBody CheerRegisterReq
3842

3943
@GetMapping("/api/cheer")
4044
public ResponseEntity<CheersResponse> getCheers(@RequestParam(defaultValue = "0") @Min(0) int page,
41-
@RequestParam(defaultValue = "5") @Min(1) @Max(50) int size) {
42-
CheersResponse response = cheerService.getCheers(page, size);
45+
@RequestParam(defaultValue = "5") @Min(1) @Max(50) int size,
46+
@RequestParam(required = false) StoreCategory category,
47+
@RequestParam(required = false) List<CheerTagName> tag,
48+
@RequestParam(required = false) List<SearchDistrict> location) {
49+
CheerSearchParameters searchParameters = new CheerSearchParameters(page, size, category, tag, location);
50+
CheersResponse response = cheerService.getCheers(searchParameters);
4351
return ResponseEntity.ok(response);
4452
}
4553

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package eatda.controller.cheer;
2+
3+
import eatda.controller.store.SearchDistrict;
4+
import eatda.domain.cheer.CheerTagName;
5+
import eatda.domain.store.District;
6+
import eatda.domain.store.StoreCategory;
7+
import java.util.Collections;
8+
import java.util.List;
9+
import lombok.Getter;
10+
import org.springframework.lang.Nullable;
11+
12+
public class CheerSearchParameters {
13+
14+
@Getter
15+
private final int page;
16+
@Getter
17+
private final int size;
18+
@Nullable
19+
private final StoreCategory category;
20+
private final List<CheerTagName> tag;
21+
private final List<SearchDistrict> location;
22+
23+
public CheerSearchParameters(int page,
24+
int size,
25+
@Nullable StoreCategory category,
26+
@Nullable List<CheerTagName> tag,
27+
@Nullable List<SearchDistrict> location) {
28+
this.page = page;
29+
this.size = size;
30+
this.category = category;
31+
this.tag = tag != null ? tag : Collections.emptyList();
32+
this.location = location != null ? location : Collections.emptyList();
33+
}
34+
35+
@Nullable
36+
public StoreCategory getCategory() {
37+
return category;
38+
}
39+
40+
public List<CheerTagName> getCheerTagNames() {
41+
return tag;
42+
}
43+
44+
public List<District> getDistricts() {
45+
return location.stream()
46+
.flatMap(district -> district.getDistricts().stream())
47+
.distinct()
48+
.toList();
49+
}
50+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package eatda.controller.store;
2+
3+
import eatda.domain.store.District;
4+
import java.util.List;
5+
import lombok.Getter;
6+
7+
@Getter
8+
public enum SearchDistrict {
9+
10+
GANGNAM("강남/역삼/선릉", List.of(District.GANGNAM)),
11+
KONDAE("건대/성수/서울숲/왕십리", List.of(District.SEONGDONG, District.GWANGJIN)),
12+
GEUMHO("금호/옥수/신당", List.of(District.JUNG)),
13+
HAPJEONG("합정/망원/홍대", List.of(District.MAPO)),
14+
SINCHON("신촌/이대", List.of(District.SEODAEMUN)),
15+
MYEONGDONG("명동/을지로/충무로", List.of(District.DONGDAEMUN, District.SEONGBUK)),
16+
SEOCHON("서촌/북촌/삼청", List.of(District.JONGNO)),
17+
DAECHI("대치/논현/서초", List.of(District.SEOCHO)),
18+
YONGSAN("용산/이태원/한남", List.of(District.YONGSAN, District.DONGJAK)),
19+
GEUMCHEON("금천/도봉/노원", List.of(District.GEUMCHEON, District.DOBONG, District.NOWON)),
20+
YEONGDEUNGPO("영등포/여의도", List.of(District.YEONGDEUNGPO)),
21+
JAMSIL("잠실/송파", List.of(District.SONGPA)),
22+
JONGRO("종로/광화문", List.of(District.JONGNO)),
23+
MAGOK("마곡/목동/강서", List.of(District.GANGSEO, District.YANGCHEON)),
24+
GURO("구로/서울대입구", List.of(District.GURO, District.GWANAK)),
25+
;
26+
27+
private String displayName;
28+
private List<District> districts;
29+
30+
SearchDistrict(String displayName, List<District> districts) {
31+
this.displayName = displayName;
32+
this.districts = districts;
33+
}
34+
}

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,54 @@
11
package eatda.repository.cheer;
22

33
import eatda.domain.cheer.Cheer;
4+
import eatda.domain.cheer.CheerTagName;
45
import eatda.domain.member.Member;
6+
import eatda.domain.store.District;
57
import eatda.domain.store.Store;
8+
import eatda.domain.store.StoreCategory;
9+
import jakarta.persistence.criteria.JoinType;
610
import java.util.List;
711
import org.springframework.data.domain.PageRequest;
812
import org.springframework.data.domain.Pageable;
13+
import org.springframework.data.jpa.domain.Specification;
914
import org.springframework.data.jpa.repository.EntityGraph;
1015
import org.springframework.data.jpa.repository.JpaRepository;
16+
import org.springframework.lang.Nullable;
1117

1218
public interface CheerRepository extends JpaRepository<Cheer, Long> {
1319

14-
@EntityGraph(attributePaths = {"store", "member", "cheerTags.values", "images"})
15-
List<Cheer> findAllByOrderByCreatedAtDesc(Pageable pageable);
20+
@EntityGraph(attributePaths = {"member", "cheerTags.values"})
21+
List<Cheer> findAllByStoreOrderByCreatedAtDesc(Store store, PageRequest pageRequest);
1622

17-
List<Cheer> findAllByStoreOrderByCreatedAtDesc(Store store, PageRequest of);
23+
default List<Cheer> findAllByConditions(@Nullable StoreCategory category,
24+
List<CheerTagName> cheerTagNames,
25+
List<District> districts, Pageable pageable) {
26+
Specification<Cheer> spec = createSpecification(category, cheerTagNames, districts);
27+
return findAll(spec, pageable);
28+
}
29+
30+
private Specification<Cheer> createSpecification(@Nullable StoreCategory category,
31+
List<CheerTagName> cheerTagNames,
32+
List<District> districts) {
33+
Specification<Cheer> spec = Specification.allOf();
34+
if (category != null) {
35+
spec = spec.and((root, query, cb) -> cb.equal(root.get("store").get("category"), category));
36+
}
37+
if (!cheerTagNames.isEmpty()) {
38+
spec = spec.and(((root, query, cb) -> {
39+
query.distinct(true);
40+
return root.join("cheerTags").join("values", JoinType.LEFT)
41+
.get("name").in(cheerTagNames);
42+
}));
43+
}
44+
if (!districts.isEmpty()) {
45+
spec = spec.and((root, query, cb) -> root.get("store").get("district").in(districts));
46+
}
47+
return spec;
48+
}
49+
50+
@EntityGraph(attributePaths = {"store", "member", "cheerTags.values"})
51+
List<Cheer> findAll(Specification<Cheer> specification, Pageable pageable);
1852

1953
int countByMember(Member member);
2054

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ private Specification<Store> createSpecification(@Nullable StoreCategory categor
5353
}
5454
if (!cheerTagNames.isEmpty()) {
5555
spec = spec.and(((root, query, cb) ->
56-
root.join("cheers").get("cheerTags").get("values").get("name").in(cheerTagNames)));
56+
root.join("cheers").join("cheerTags").join("values").get("name").in(cheerTagNames)));
5757
}
5858
if (!districts.isEmpty()) {
5959
spec = spec.and((root, query, cb) -> root.get("district").in(districts));

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import eatda.controller.cheer.CheerPreviewResponse;
77
import eatda.controller.cheer.CheerRegisterRequest;
88
import eatda.controller.cheer.CheerResponse;
9+
import eatda.controller.cheer.CheerSearchParameters;
910
import eatda.controller.cheer.CheersInStoreResponse;
1011
import eatda.controller.cheer.CheersResponse;
1112
import eatda.domain.ImageDomain;
@@ -25,6 +26,8 @@
2526
import lombok.RequiredArgsConstructor;
2627
import org.springframework.beans.factory.annotation.Value;
2728
import org.springframework.data.domain.PageRequest;
29+
import org.springframework.data.domain.Sort;
30+
import org.springframework.data.domain.Sort.Direction;
2831
import org.springframework.stereotype.Service;
2932
import org.springframework.transaction.annotation.Transactional;
3033

@@ -111,8 +114,13 @@ private void saveCheerImages(Cheer cheer,
111114
}
112115

113116
@Transactional(readOnly = true)
114-
public CheersResponse getCheers(int page, int size) {
115-
List<Cheer> cheers = cheerRepository.findAllByOrderByCreatedAtDesc(PageRequest.of(page, size));
117+
public CheersResponse getCheers(CheerSearchParameters parameters) {
118+
List<Cheer> cheers = cheerRepository.findAllByConditions(
119+
parameters.getCategory(),
120+
parameters.getCheerTagNames(),
121+
parameters.getDistricts(),
122+
PageRequest.of(parameters.getPage(), parameters.getSize(), Sort.by(Direction.DESC, "createdAt"))
123+
);
116124
return toCheersResponse(cheers);
117125
}
118126

src/test/java/eatda/controller/BaseControllerTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import eatda.fixture.CheerGenerator;
1717
import eatda.fixture.CheerImageGenerator;
1818
import eatda.fixture.CheerTagGenerator;
19+
import eatda.fixture.CheerTagGenerator;
1920
import eatda.fixture.MemberGenerator;
2021
import eatda.fixture.StoreGenerator;
2122
import eatda.fixture.StoryGenerator;

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

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
import static org.junit.jupiter.api.Assertions.assertAll;
55

66
import eatda.controller.BaseControllerTest;
7+
import eatda.controller.store.SearchDistrict;
78
import eatda.domain.cheer.Cheer;
89
import eatda.domain.cheer.CheerTagName;
910
import eatda.domain.member.Member;
1011
import eatda.domain.store.District;
1112
import eatda.domain.store.Store;
13+
import eatda.domain.store.StoreCategory;
1214
import java.time.LocalDateTime;
1315
import java.util.ArrayList;
1416
import java.util.List;
@@ -24,7 +26,8 @@ class RegisterCheer {
2426
@Test
2527
void 응원을_등록한다() {
2628
Store store = storeGenerator.generate("123", "서울시 노원구 월계3동 123-45", District.NOWON);
27-
CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!", new ArrayList<>(), List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.CLEAN_RESTROOM));
29+
CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!",
30+
new ArrayList<>(), List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.CLEAN_RESTROOM));
2831

2932
CheerResponse response = given()
3033
.header(HttpHeaders.AUTHORIZATION, accessToken())
@@ -46,7 +49,8 @@ class RegisterCheer {
4649
@Test
4750
void 이미지가_비어있을_경우에도_응원을_등록한다() {
4851
Store store = storeGenerator.generate("123", "서울시 노원구 월계3동 123-45", District.NOWON);
49-
CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!", new ArrayList<>(), List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.CLEAN_RESTROOM));
52+
CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!",
53+
new ArrayList<>(), List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.CLEAN_RESTROOM));
5054

5155
CheerResponse response = given()
5256
.header(HttpHeaders.AUTHORIZATION, accessToken())
@@ -78,13 +82,11 @@ class GetCheers {
7882
Cheer cheer1 = cheerGenerator.generateAdmin(member, store1, startAt);
7983
Cheer cheer2 = cheerGenerator.generateAdmin(member, store1, startAt.plusHours(1));
8084
Cheer cheer3 = cheerGenerator.generateAdmin(member, store2, startAt.plusHours(2));
81-
int page = 0;
82-
int size = 2;
8385

8486
CheersResponse response = given()
8587
.when()
86-
.queryParam("page", page)
87-
.queryParam("size", size)
88+
.queryParam("page", 0)
89+
.queryParam("size", 2)
8890
.get("/api/cheer")
8991
.then()
9092
.statusCode(200)
@@ -101,6 +103,41 @@ class GetCheers {
101103
);
102104
}
103105

106+
@Test
107+
void 필터링을_통해_응원을_조회한다() {
108+
Member member = memberGenerator.generateRegisteredMember("nickname", "[email protected]", "123", "01011111111");
109+
Store store1 = storeGenerator.generate("111", "서울시 노원구 월계3동 123-45", District.NOWON,
110+
StoreCategory.KOREAN);
111+
Store store2 = storeGenerator.generate("222", "서울시 노원구 월계3동 123-46", District.NOWON,
112+
StoreCategory.KOREAN);
113+
LocalDateTime startAt = LocalDateTime.of(2025, 7, 26, 1, 0, 0);
114+
Cheer cheer1 = cheerGenerator.generateAdmin(member, store1, startAt);
115+
Cheer cheer2 = cheerGenerator.generateAdmin(member, store1, startAt.plusHours(1));
116+
Cheer cheer3 = cheerGenerator.generateAdmin(member, store2, startAt.plusHours(2));
117+
cheerTagGenerator.generate(cheer1, List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.CLEAN_RESTROOM));
118+
cheerTagGenerator.generate(cheer2, List.of(CheerTagName.INSTAGRAMMABLE));
119+
cheerTagGenerator.generate(cheer3, List.of(CheerTagName.CLEAN_RESTROOM));
120+
121+
CheersResponse response = given()
122+
.when()
123+
.queryParam("page", 0)
124+
.queryParam("size", 2)
125+
.queryParam("category", StoreCategory.KOREAN)
126+
.queryParam("tag", CheerTagName.INSTAGRAMMABLE)
127+
.queryParam("location", SearchDistrict.GEUMCHEON, SearchDistrict.MYEONGDONG)
128+
.get("/api/cheer")
129+
.then()
130+
.statusCode(200)
131+
.extract().as(CheersResponse.class);
132+
133+
CheerPreviewResponse firstResponse = response.cheers().get(0);
134+
assertAll(
135+
() -> assertThat(response.cheers()).hasSize(2),
136+
() -> assertThat(response.cheers().get(0).cheerId()).isEqualTo(cheer2.getId()),
137+
() -> assertThat(response.cheers().get(1).cheerId()).isEqualTo(cheer1.getId())
138+
);
139+
}
140+
104141
@Nested
105142
class GetCheersByStoreId {
106143

0 commit comments

Comments
 (0)