From b885e5a09de64099dfa97ba1f88177ca756af572 Mon Sep 17 00:00:00 2001 From: shinchaerin79 Date: Fri, 22 Aug 2025 22:51:41 +0900 Subject: [PATCH 1/3] =?UTF-8?q?:recycle:=20=EA=B0=80=EA=B2=8C=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=95=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreController.java | 6 +- .../domain/store/mapper/StoreMapper.java | 11 ++++ .../domain/store/service/StoreService.java | 58 ++++++++++--------- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/likelion/danchu/domain/store/controller/StoreController.java b/src/main/java/com/likelion/danchu/domain/store/controller/StoreController.java index 4c7c4e1..71c9504 100644 --- a/src/main/java/com/likelion/danchu/domain/store/controller/StoreController.java +++ b/src/main/java/com/likelion/danchu/domain/store/controller/StoreController.java @@ -23,6 +23,7 @@ import com.likelion.danchu.domain.store.dto.request.StoreRequest; import com.likelion.danchu.domain.store.dto.response.PageableResponse; import com.likelion.danchu.domain.store.dto.response.StoreDistanceResponse; +import com.likelion.danchu.domain.store.dto.response.StoreListItemResponse; import com.likelion.danchu.domain.store.dto.response.StoreResponse; import com.likelion.danchu.domain.store.exception.StoreErrorCode; import com.likelion.danchu.domain.store.service.StoreHashtagService; @@ -98,10 +99,11 @@ public ResponseEntity> createStore( - size : 페이지 당 보여줄 가게 수입니다. (기본값: 3) """) @GetMapping - public ResponseEntity>> getPaginatedStores( + public ResponseEntity>> getPaginatedStores( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "3") int size) { - PageableResponse storeResponses = storeService.getPaginatedStores(page, size); + PageableResponse storeResponses = + storeService.getPaginatedStores(page, size); return ResponseEntity.ok(BaseResponse.success("가게 페이징 조회에 성공했습니다.", storeResponses)); } diff --git a/src/main/java/com/likelion/danchu/domain/store/mapper/StoreMapper.java b/src/main/java/com/likelion/danchu/domain/store/mapper/StoreMapper.java index 9cbc3ec..faa5a2a 100644 --- a/src/main/java/com/likelion/danchu/domain/store/mapper/StoreMapper.java +++ b/src/main/java/com/likelion/danchu/domain/store/mapper/StoreMapper.java @@ -10,6 +10,7 @@ import com.likelion.danchu.domain.hashtag.dto.response.HashtagResponse; import com.likelion.danchu.domain.menu.dto.response.MenuResponse; import com.likelion.danchu.domain.store.dto.request.StoreRequest; +import com.likelion.danchu.domain.store.dto.response.StoreListItemResponse; import com.likelion.danchu.domain.store.dto.response.StoreResponse; import com.likelion.danchu.domain.store.entity.Store; @@ -72,4 +73,14 @@ public StoreResponse toResponse(Store store, List hashtags) { public StoreResponse toResponse(Store store) { return toResponse(store, List.of(), List.of()); } + + // StoreResponse를 store로 감싼 목록 아이템으로 변환 + public StoreListItemResponse toListItem( + Store store, List hashtags, List menus) { + return StoreListItemResponse.builder().store(toResponse(store, hashtags, menus)).build(); + } + + public StoreListItemResponse toListItem(Store store, List hashtags) { + return StoreListItemResponse.builder().store(toResponse(store, hashtags)).build(); + } } diff --git a/src/main/java/com/likelion/danchu/domain/store/service/StoreService.java b/src/main/java/com/likelion/danchu/domain/store/service/StoreService.java index bd0e8ec..c2b5b29 100644 --- a/src/main/java/com/likelion/danchu/domain/store/service/StoreService.java +++ b/src/main/java/com/likelion/danchu/domain/store/service/StoreService.java @@ -1,19 +1,5 @@ package com.likelion.danchu.domain.store.service; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import jakarta.transaction.Transactional; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - import com.likelion.danchu.domain.coupon.repository.CouponRepository; import com.likelion.danchu.domain.hashtag.dto.response.HashtagResponse; import com.likelion.danchu.domain.hashtag.mapper.HashtagMapper; @@ -26,6 +12,7 @@ import com.likelion.danchu.domain.store.dto.request.StoreRequest; import com.likelion.danchu.domain.store.dto.response.PageableResponse; import com.likelion.danchu.domain.store.dto.response.StoreDistanceResponse; +import com.likelion.danchu.domain.store.dto.response.StoreListItemResponse; import com.likelion.danchu.domain.store.dto.response.StoreResponse; import com.likelion.danchu.domain.store.entity.Store; import com.likelion.danchu.domain.store.entity.StoreHashtag; @@ -39,9 +26,19 @@ import com.likelion.danchu.infra.kakao.KakaoLocalClient; import com.likelion.danchu.infra.s3.entity.PathName; import com.likelion.danchu.infra.s3.service.S3Service; - import io.micrometer.common.lang.Nullable; +import jakarta.transaction.Transactional; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; @Service @RequiredArgsConstructor @@ -66,7 +63,7 @@ public class StoreService { * 새로운 가게를 생성합니다. * * @param storeRequest 가게 생성 요청 DTO - * @param imageFile 업로드할 메인 이미지 파일 + * @param imageFile 업로드할 메인 이미지 파일 * @return 생성된 가게 정보 * @throws CustomException 주소 또는 인증 코드가 중복되거나, 이미지 업로드/저장 실패 시 발생 */ @@ -128,13 +125,15 @@ public StoreResponse createStore(StoreRequest storeRequest, MultipartFile imageF * @param size 한 페이지에 포함될 가게 수 * @return 페이징된 가게 목록 응답 */ - public PageableResponse getPaginatedStores(int page, int size) { + public PageableResponse getPaginatedStores(int page, int size) { PageRequest pageRequest = PageRequest.of(page, size); // 페이지당 3개 Page storePage = storeRepository.findAll(pageRequest); List stores = storePage.getContent(); if (stores.isEmpty()) { - return PageableResponse.from(storePage.map(storeMapper::toResponse)); // 그대로 빈 페이지 반환 + // 빈 페이지라도 제네릭 타입은 StoreListItemResponse 이어야 함 + Page empty = storePage.map(s -> storeMapper.toListItem(s, List.of())); + return PageableResponse.from(empty); } // 현재 페이지의 가게 ID들 수집 @@ -154,21 +153,24 @@ public PageableResponse getPaginatedStores(int page, int size) { Collectors.toList()))); // 각 가게 별로 해시태그 포함하여 DTO 변환 - Page storeResponsePage = + Page wrappedPage = storePage.map( store -> - storeMapper.toResponse( - store, hashtagsByStoreId.getOrDefault(store.getId(), List.of()))); + storeMapper.toListItem( + store, + hashtagsByStoreId.getOrDefault(store.getId(), List.of()), + List.of() // 메뉴 미포함. 필요 시 주입 + )); - return PageableResponse.from(storeResponsePage); + return PageableResponse.from(wrappedPage); } /** * 검색어(keyword)가 가게 이름에 포함된 가게들을 페이징 조회합니다. 각 가게의 해시태그를 함께 반환합니다. * * @param keyword 검색 키워드 - * @param page 페이지 번호 (0부터 시작) - * @param size 한 페이지에 포함될 가게 수 + * @param page 페이지 번호 (0부터 시작) + * @param size 한 페이지에 포함될 가게 수 * @return 페이징된 가게 목록 응답 * @throws CustomException 검색어가 비어 있는 경우 */ @@ -274,10 +276,10 @@ public StoreResponse getStoreDetail(Long storeId) { /** * 현재 위치(:lat, :lng)를 기준으로 거리순으로 정렬된 가게 목록을 페이징 조회합니다. * - * @param lat 사용자 위도(WGS84) - * @param lng 사용자 경도(WGS84) - * @param page 0부터 시작하는 페이지 번호 - * @param size 페이지 크기(1 이상) + * @param lat 사용자 위도(WGS84) + * @param lng 사용자 경도(WGS84) + * @param page 0부터 시작하는 페이지 번호 + * @param size 페이지 크기(1 이상) * @param radiusMeters 검색 반경(미터). null이면 반경 제한 없음 * @return 거리 정보가 포함된 페이징 응답 */ From 0e90345256e3f41612d2e177bf785c42d4998d83 Mon Sep 17 00:00:00 2001 From: shinchaerin79 Date: Fri, 22 Aug 2025 22:53:01 +0900 Subject: [PATCH 2/3] =?UTF-8?q?:sparkles:=20content[i].store=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A5=BC=20=EB=A7=9E=EC=B6=94=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20StoreListItemResponse=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/StoreListItemResponse.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/com/likelion/danchu/domain/store/dto/response/StoreListItemResponse.java diff --git a/src/main/java/com/likelion/danchu/domain/store/dto/response/StoreListItemResponse.java b/src/main/java/com/likelion/danchu/domain/store/dto/response/StoreListItemResponse.java new file mode 100644 index 0000000..555798a --- /dev/null +++ b/src/main/java/com/likelion/danchu/domain/store/dto/response/StoreListItemResponse.java @@ -0,0 +1,18 @@ +package com.likelion.danchu.domain.store.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(title = "StoreListItemResponse", description = "목록 아이템(가게 정보를 store로 래핑)") +public class StoreListItemResponse { + + @Schema(description = "가게 정보", requiredMode = Schema.RequiredMode.REQUIRED) + private StoreResponse store; +} From 075065be57b0e36a11461ceeba731a8fd1e5e7aa Mon Sep 17 00:00:00 2001 From: shinchaerin79 Date: Fri, 22 Aug 2025 23:28:08 +0900 Subject: [PATCH 3/3] =?UTF-8?q?:art:=20spotless=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/store/service/StoreService.java | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/likelion/danchu/domain/store/service/StoreService.java b/src/main/java/com/likelion/danchu/domain/store/service/StoreService.java index 1fb2b18..274553e 100644 --- a/src/main/java/com/likelion/danchu/domain/store/service/StoreService.java +++ b/src/main/java/com/likelion/danchu/domain/store/service/StoreService.java @@ -1,5 +1,19 @@ package com.likelion.danchu.domain.store.service; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import jakarta.transaction.Transactional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + import com.likelion.danchu.domain.coupon.repository.CouponRepository; import com.likelion.danchu.domain.hashtag.dto.response.HashtagResponse; import com.likelion.danchu.domain.hashtag.mapper.HashtagMapper; @@ -26,19 +40,9 @@ import com.likelion.danchu.infra.kakao.KakaoLocalClient; import com.likelion.danchu.infra.s3.entity.PathName; import com.likelion.danchu.infra.s3.service.S3Service; + import io.micrometer.common.lang.Nullable; -import jakarta.transaction.Transactional; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; @Service @RequiredArgsConstructor @@ -63,7 +67,7 @@ public class StoreService { * 새로운 가게를 생성합니다. * * @param storeRequest 가게 생성 요청 DTO - * @param imageFile 업로드할 메인 이미지 파일 + * @param imageFile 업로드할 메인 이미지 파일 * @return 생성된 가게 정보 * @throws CustomException 주소 또는 인증 코드가 중복되거나, 이미지 업로드/저장 실패 시 발생 */ @@ -163,7 +167,7 @@ public PageableResponse getPaginatedStores(int page, int store, hashtagsByStoreId.getOrDefault(store.getId(), List.of()), List.of() // 메뉴 미포함. 필요 시 주입 - )); + )); return PageableResponse.from(wrappedPage); } @@ -172,8 +176,8 @@ public PageableResponse getPaginatedStores(int page, int * 검색어(keyword)가 가게 이름에 포함된 가게들을 페이징 조회합니다. 각 가게의 해시태그를 함께 반환합니다. * * @param keyword 검색 키워드 - * @param page 페이지 번호 (0부터 시작) - * @param size 한 페이지에 포함될 가게 수 + * @param page 페이지 번호 (0부터 시작) + * @param size 한 페이지에 포함될 가게 수 * @return 페이징된 가게 목록 응답 * @throws CustomException 검색어가 비어 있는 경우 */ @@ -287,10 +291,10 @@ public StoreResponse getStoreDetail(Long storeId) { /** * 현재 위치(:lat, :lng)를 기준으로 거리순으로 정렬된 가게 목록을 페이징 조회합니다. * - * @param lat 사용자 위도(WGS84) - * @param lng 사용자 경도(WGS84) - * @param page 0부터 시작하는 페이지 번호 - * @param size 페이지 크기(1 이상) + * @param lat 사용자 위도(WGS84) + * @param lng 사용자 경도(WGS84) + * @param page 0부터 시작하는 페이지 번호 + * @param size 페이지 크기(1 이상) * @param radiusMeters 검색 반경(미터). null이면 반경 제한 없음 * @return 거리 정보가 포함된 페이징 응답 */