From cafda8b5bbc15733e57b12fbd2a9e06a969967e1 Mon Sep 17 00:00:00 2001 From: Seungwan Yoo Date: Sun, 6 Oct 2024 16:04:40 +0200 Subject: [PATCH] =?UTF-8?q?=EC=A0=90=ED=8F=AC=20=EC=A1=B0=ED=9A=8C=203?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=20=EA=B5=AC=EB=B6=84=20=EB=B0=8F=20=EC=84=B8?= =?UTF-8?q?=EB=B6=80=20=EA=B5=AC=ED=98=84=20(#75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 점포 조회 3단계 구분 및 세부 구현 - 1. 지도상 자신 주변의 점포 조회 (북마크 된 점포는 별도 아이콘 표시) - 2. 지도상 점포 마커 클릭 시 간략 정보 조회 (/api/store/{id}/summary) - 3. 점포 세부 조회 * 간략 정보 조회 수정 * 점포 상세 조회 수정 - 최신 리뷰 3개만 조회하도록 수정 - 리뷰 작성자의 프로필 사진 응답 추가 - 방문 성공, 실패 횟수 응답 추가 - 쿼리 최적화 필요(중요...) --- .../controller/store/StoreController.java | 19 ++++--- .../dongyang/dongpo/domain/store/Store.java | 49 ++++++++++++++++--- .../dongpo/domain/store/StoreReview.java | 14 ++++-- .../dongyang/dongpo/dto/store/ReviewDto.java | 10 ++-- .../dongyang/dongpo/dto/store/StoreDto.java | 3 ++ .../dongpo/dto/store/StoreIndexDto.java | 8 +++ .../bookmark/BookmarkRepository.java | 3 ++ .../service/bookmark/BookmarkService.java | 4 ++ .../service/store/StoreReviewService.java | 11 +++++ .../dongpo/service/store/StoreService.java | 39 +++++++++++---- .../service/store/StoreServiceTest.java | 8 ++- 11 files changed, 138 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/dongyang/dongpo/controller/store/StoreController.java b/src/main/java/com/dongyang/dongpo/controller/store/StoreController.java index 5bc2003..97eccee 100644 --- a/src/main/java/com/dongyang/dongpo/controller/store/StoreController.java +++ b/src/main/java/com/dongyang/dongpo/controller/store/StoreController.java @@ -3,9 +3,7 @@ import com.dongyang.dongpo.apiresponse.ApiResponse; import com.dongyang.dongpo.domain.member.Member; import com.dongyang.dongpo.dto.location.LatLong; -import com.dongyang.dongpo.dto.store.StoreDto; -import com.dongyang.dongpo.dto.store.StoreRegisterDto; -import com.dongyang.dongpo.dto.store.StoreUpdateDto; +import com.dongyang.dongpo.dto.store.*; import com.dongyang.dongpo.service.store.StoreService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; @@ -30,14 +28,21 @@ public class StoreController { @GetMapping("") @Operation(summary = "현재 위치 기준 주변 점포 조회") - public ResponseEntity>> getStoresByCurrentLocation(@ModelAttribute LatLong latLong) { - return ResponseEntity.ok(new ApiResponse<>(storeService.findStoresByCurrentLocation(latLong))); + public ResponseEntity>> getStoresByCurrentLocation(@ModelAttribute LatLong latLong, + @AuthenticationPrincipal Member member) { + return ResponseEntity.ok(new ApiResponse<>(storeService.findStoresByCurrentLocation(latLong, member))); + } + + @GetMapping("/{id}/summary") + @Operation(summary = "점포 간략 정보 조회") + public ResponseEntity> getStoreSummary(@PathVariable Long id, @AuthenticationPrincipal Member member) { + return ResponseEntity.ok(new ApiResponse<>(storeService.getStoreSummary(id, member))); } @GetMapping("/{id}") @Operation(summary = "점포 상세 조회") - public ResponseEntity> detailStore(@PathVariable Long id) throws Exception { - return ResponseEntity.ok(new ApiResponse<>(storeService.detailStore(id))); + public ResponseEntity> detailStore(@PathVariable Long id, @AuthenticationPrincipal Member member) { + return ResponseEntity.ok(new ApiResponse<>(storeService.detailStore(id, member))); } @PostMapping("") diff --git a/src/main/java/com/dongyang/dongpo/domain/store/Store.java b/src/main/java/com/dongyang/dongpo/domain/store/Store.java index 5285371..8243067 100644 --- a/src/main/java/com/dongyang/dongpo/domain/store/Store.java +++ b/src/main/java/com/dongyang/dongpo/domain/store/Store.java @@ -1,17 +1,14 @@ package com.dongyang.dongpo.domain.store; import com.dongyang.dongpo.domain.member.Member; -import com.dongyang.dongpo.dto.store.OpenPossibility; -import com.dongyang.dongpo.dto.store.ReviewDto; -import com.dongyang.dongpo.dto.store.StoreDto; -import com.dongyang.dongpo.dto.store.StoreIndexDto; -import com.dongyang.dongpo.dto.store.StoreUpdateDto; +import com.dongyang.dongpo.dto.store.*; import jakarta.persistence.*; import lombok.*; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -70,6 +67,9 @@ public class Store { @Builder.Default private List reviews = new ArrayList<>(); + @OneToMany(mappedBy = "store", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private List storeVisitCerts = new ArrayList<>(); + public enum StoreStatus { ACTIVE, INACTIVE, HIDDEN, CLOSED } @@ -122,8 +122,32 @@ public StoreIndexDto toIndexResponse() { .registerDate(registerDate) .build(); } + + public StoreIndexDto toIndexResponse(OpenPossibility openPossibility, Boolean isBookmarked, List reviewPics) { + return StoreIndexDto.builder() + .id(id) + .name(name) + .address(address) + .status(status) + .openPossibility(openPossibility) + .isBookmarked(isBookmarked) + .reviewPics(reviewPics) + .build(); + } + + public StoreIndexDto toIndexResponse(Boolean isBookmarked, OpenPossibility openPossibility) { + return StoreIndexDto.builder() + .id(id) + .name(name) + .latitude(latitude) + .longitude(longitude) + .status(status) + .openPossibility(openPossibility) + .isBookmarked(isBookmarked) + .build(); + } - public StoreDto toResponse(OpenPossibility openPossibility) { + public StoreDto toResponse(OpenPossibility openPossibility, boolean isBookmarked) { List operatingDayValues = this.storeOperatingDays.stream() .map(StoreOperatingDay::getOperatingDay) .collect(Collectors.toList()); @@ -133,9 +157,19 @@ public StoreDto toResponse(OpenPossibility openPossibility) { .collect(Collectors.toList()); List reviewDtos = this.reviews.stream() + .sorted(Comparator.comparingLong(StoreReview::getId).reversed()) .map(StoreReview::toResponse) + .limit(3) .toList(); + Long visitSuccessfulCount = storeVisitCerts.stream() + .filter(StoreVisitCert::getIsVisitSuccessful) + .count(); + + Long visitFailCount = storeVisitCerts.stream() + .filter(cert -> !cert.getIsVisitSuccessful()) + .count(); + return StoreDto.builder() .id(id) .name(name) @@ -152,6 +186,9 @@ public StoreDto toResponse(OpenPossibility openPossibility) { .status(status) .reviews(reviewDtos) .openPossibility(openPossibility) + .isBookmarked(isBookmarked) + .visitSuccessfulCount(visitSuccessfulCount) + .visitFailCount(visitFailCount) .build(); } diff --git a/src/main/java/com/dongyang/dongpo/domain/store/StoreReview.java b/src/main/java/com/dongyang/dongpo/domain/store/StoreReview.java index 6a5a942..00aee80 100644 --- a/src/main/java/com/dongyang/dongpo/domain/store/StoreReview.java +++ b/src/main/java/com/dongyang/dongpo/domain/store/StoreReview.java @@ -6,6 +6,8 @@ import lombok.*; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Entity @Getter @@ -31,9 +33,6 @@ public class StoreReview { @Column(columnDefinition = "TEXT") private String text; - @Column(length = 128) - private String reviewPic; - @Column(columnDefinition = "DATETIME DEFAULT CURRENT_TIMESTAMP") @Builder.Default private LocalDateTime registerDate = LocalDateTime.now(); @@ -49,19 +48,26 @@ public class StoreReview { @Builder.Default private Integer reportCount = 0; + @OneToMany(mappedBy = "reviewId", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @Builder.Default + private List reviewPics = new ArrayList<>(); + public enum ReviewStatus { VISIBLE, HIDDEN, DELETED } public ReviewDto toResponse(){ + List reviewOnlyPic = reviewPics.stream().map(StoreReviewPic::getPicUrl).toList(); + return ReviewDto.builder() .id(id) .registerDate(registerDate) .reviewStar(reviewStar) .text(text) .memberId(member.getId()) + .memberProfilePic(member.getProfilePic()) .storeId(store.getId()) - .reviewPic(reviewPic) + .reviewPics(reviewOnlyPic) .build(); } diff --git a/src/main/java/com/dongyang/dongpo/dto/store/ReviewDto.java b/src/main/java/com/dongyang/dongpo/dto/store/ReviewDto.java index 6a3a4f7..1516669 100644 --- a/src/main/java/com/dongyang/dongpo/dto/store/ReviewDto.java +++ b/src/main/java/com/dongyang/dongpo/dto/store/ReviewDto.java @@ -3,6 +3,7 @@ import com.dongyang.dongpo.domain.member.Member; import com.dongyang.dongpo.domain.store.Store; import com.dongyang.dongpo.domain.store.StoreReview; +import com.dongyang.dongpo.domain.store.StoreReviewPic; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; @@ -10,6 +11,7 @@ import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.List; @Data @Builder @@ -20,9 +22,10 @@ public class ReviewDto { private Long id; private Long storeId; private Long memberId; + private String memberProfilePic; private Integer reviewStar; private String text; - private String reviewPic; + private List reviewPics; private LocalDateTime registerDate; private StoreReview.ReviewStatus status; private Integer reportCount; @@ -33,19 +36,20 @@ public StoreReview toEntity(Store store, Member member){ .member(member) .store(store) .text(text) - .reviewPic(reviewPic) .reviewStar(reviewStar) .build(); } public static ReviewDto toDto(StoreReview storeReview){ + List picUrlList = storeReview.getReviewPics().stream().map(StoreReviewPic::getPicUrl).toList(); + return ReviewDto.builder() .id(storeReview.getId()) .storeId(storeReview.getStore().getId()) .memberId(storeReview.getMember().getId()) .reviewStar(storeReview.getReviewStar()) .text(storeReview.getText()) - .reviewPic(storeReview.getReviewPic()) + .reviewPics(picUrlList) .registerDate(storeReview.getRegisterDate()) .status(storeReview.getStatus()) .reportCount(storeReview.getReportCount()) diff --git a/src/main/java/com/dongyang/dongpo/dto/store/StoreDto.java b/src/main/java/com/dongyang/dongpo/dto/store/StoreDto.java index 20bad66..6ae5f4d 100644 --- a/src/main/java/com/dongyang/dongpo/dto/store/StoreDto.java +++ b/src/main/java/com/dongyang/dongpo/dto/store/StoreDto.java @@ -31,4 +31,7 @@ public class StoreDto { private List payMethods; private List reviews; private OpenPossibility openPossibility; + private Boolean isBookmarked; + private Long visitSuccessfulCount; + private Long visitFailCount; } diff --git a/src/main/java/com/dongyang/dongpo/dto/store/StoreIndexDto.java b/src/main/java/com/dongyang/dongpo/dto/store/StoreIndexDto.java index e89bee0..689bf7b 100644 --- a/src/main/java/com/dongyang/dongpo/dto/store/StoreIndexDto.java +++ b/src/main/java/com/dongyang/dongpo/dto/store/StoreIndexDto.java @@ -1,5 +1,6 @@ package com.dongyang.dongpo.dto.store; +import com.dongyang.dongpo.domain.store.Store; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; @@ -7,6 +8,7 @@ import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.List; @Data @Builder @@ -16,6 +18,12 @@ public class StoreIndexDto { private Long id; private String name; + private Double latitude; + private Double longitude; private String address; private LocalDateTime registerDate; + private Store.StoreStatus status; + private OpenPossibility openPossibility; + private Boolean isBookmarked; + private List reviewPics; } diff --git a/src/main/java/com/dongyang/dongpo/repository/bookmark/BookmarkRepository.java b/src/main/java/com/dongyang/dongpo/repository/bookmark/BookmarkRepository.java index 1f2da8e..7c6f8ea 100644 --- a/src/main/java/com/dongyang/dongpo/repository/bookmark/BookmarkRepository.java +++ b/src/main/java/com/dongyang/dongpo/repository/bookmark/BookmarkRepository.java @@ -2,6 +2,7 @@ import com.dongyang.dongpo.domain.member.Member; +import com.dongyang.dongpo.domain.store.Store; import com.dongyang.dongpo.domain.store.StoreBookmark; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,4 +12,6 @@ public interface BookmarkRepository extends JpaRepository { List findByMemberId(Long id); List findByMember(Member member); + + boolean existsByStoreAndMember(Store store, Member member); } diff --git a/src/main/java/com/dongyang/dongpo/service/bookmark/BookmarkService.java b/src/main/java/com/dongyang/dongpo/service/bookmark/BookmarkService.java index d412248..befdce1 100644 --- a/src/main/java/com/dongyang/dongpo/service/bookmark/BookmarkService.java +++ b/src/main/java/com/dongyang/dongpo/service/bookmark/BookmarkService.java @@ -61,4 +61,8 @@ public void deleteBookmark(Long id, Member member) { bookmarkRepository.delete(bookmark); log.info("Member Id : {} is Delete Bookmark Id : {}", member.getId(), id); } + + public boolean isStoreBookmarkedByMember(Store store, Member member) { + return bookmarkRepository.existsByStoreAndMember(store, member); + } } diff --git a/src/main/java/com/dongyang/dongpo/service/store/StoreReviewService.java b/src/main/java/com/dongyang/dongpo/service/store/StoreReviewService.java index 964885f..7edbcd0 100644 --- a/src/main/java/com/dongyang/dongpo/service/store/StoreReviewService.java +++ b/src/main/java/com/dongyang/dongpo/service/store/StoreReviewService.java @@ -4,6 +4,7 @@ import com.dongyang.dongpo.domain.member.Title; import com.dongyang.dongpo.domain.store.Store; import com.dongyang.dongpo.domain.store.StoreReview; +import com.dongyang.dongpo.domain.store.StoreReviewPic; import com.dongyang.dongpo.dto.store.ReviewDto; import com.dongyang.dongpo.exception.CustomException; import com.dongyang.dongpo.exception.ErrorCode; @@ -17,6 +18,8 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; @Service @Transactional(readOnly = true) @@ -61,4 +64,12 @@ public ReviewDto findOne(Long id){ return review.toResponse(); } + + public List getReviewPicsByStoreId(Long id) { + return reviewRepository.findByStoreId(id).stream() + .flatMap(storeReview -> storeReview.getReviewPics().stream()) + .map(StoreReviewPic::getPicUrl) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/dongyang/dongpo/service/store/StoreService.java b/src/main/java/com/dongyang/dongpo/service/store/StoreService.java index c1486dc..437cd9d 100644 --- a/src/main/java/com/dongyang/dongpo/service/store/StoreService.java +++ b/src/main/java/com/dongyang/dongpo/service/store/StoreService.java @@ -5,6 +5,7 @@ import com.dongyang.dongpo.domain.store.Store; import com.dongyang.dongpo.domain.store.StoreOperatingDay; import com.dongyang.dongpo.domain.store.StorePayMethod; +import com.dongyang.dongpo.dto.bookmark.BookmarkDto; import com.dongyang.dongpo.dto.location.CoordinateRange; import com.dongyang.dongpo.dto.location.LatLong; import com.dongyang.dongpo.dto.store.*; @@ -13,6 +14,7 @@ import com.dongyang.dongpo.repository.store.StoreOperatingDayRepository; import com.dongyang.dongpo.repository.store.StorePayMethodRepository; import com.dongyang.dongpo.repository.store.StoreRepository; +import com.dongyang.dongpo.service.bookmark.BookmarkService; import com.dongyang.dongpo.service.location.LocationService; import com.dongyang.dongpo.service.open.OpenPossibilityService; import com.dongyang.dongpo.service.title.TitleService; @@ -24,6 +26,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -37,6 +40,8 @@ public class StoreService { private final LocationService locationService; private final TitleService titleService; private final OpenPossibilityService openPossibilityService; + private final BookmarkService bookmarkService; + private final StoreReviewService storeReviewService; @Transactional @@ -80,22 +85,38 @@ public List findAll() { return storeResponse; } - public List findStoresByCurrentLocation(LatLong latLong) { + public List findStoresByCurrentLocation(LatLong latLong, Member member) { CoordinateRange coordinateRange = locationService.calcCoordinateRangeByCurrentLocation(latLong); - List stores = new ArrayList<>(); - for (Store store : storeRepository.findStoresWithinRange(coordinateRange.getMinLat(), coordinateRange.getMaxLat(), - coordinateRange.getMinLong(), coordinateRange.getMaxLong()) - ) stores.add(store.toResponse()); - return stores; + List myBookmarks = bookmarkService.getMyBookmarks(member); + + return storeRepository.findStoresWithinRange(coordinateRange.getMinLat(), coordinateRange.getMaxLat(), + coordinateRange.getMinLong(), coordinateRange.getMaxLong()) + .stream() + .map(store -> { + boolean isBookmarked = myBookmarks.stream() + .anyMatch(bookmark -> store.getId().equals(bookmark.getStoreId())); + + return store.toIndexResponse(isBookmarked, openPossibilityService.getOpenPossibility(store)); + }) + .collect(Collectors.toList()); + } + + public StoreIndexDto getStoreSummary(Long id, Member member) { + Store store = storeRepository.findById(id) + .orElseThrow(() -> new CustomException(ErrorCode.STORE_NOT_FOUND)); + + return store.toIndexResponse(openPossibilityService.getOpenPossibility(store), + bookmarkService.isStoreBookmarkedByMember(store, member), + storeReviewService.getReviewPicsByStoreId(id)); } - public StoreDto detailStore(Long id) { + public StoreDto detailStore(Long id, Member member) { Store store = storeRepository.findById(id) .orElseThrow(() -> new CustomException(ErrorCode.STORE_NOT_FOUND)); - OpenPossibility openPossibility = openPossibilityService.getOpenPossibility(store); - return store.toResponse(openPossibility); + return store.toResponse(openPossibilityService.getOpenPossibility(store), + bookmarkService.isStoreBookmarkedByMember(store, member)); } @Transactional diff --git a/src/test/java/com/dongyang/dongpo/service/store/StoreServiceTest.java b/src/test/java/com/dongyang/dongpo/service/store/StoreServiceTest.java index 2e64634..1f03859 100644 --- a/src/test/java/com/dongyang/dongpo/service/store/StoreServiceTest.java +++ b/src/test/java/com/dongyang/dongpo/service/store/StoreServiceTest.java @@ -11,6 +11,7 @@ import com.dongyang.dongpo.repository.store.StoreOperatingDayRepository; import com.dongyang.dongpo.repository.store.StorePayMethodRepository; import com.dongyang.dongpo.repository.store.StoreRepository; +import com.dongyang.dongpo.service.bookmark.BookmarkService; import com.dongyang.dongpo.service.location.LocationService; import com.dongyang.dongpo.service.open.OpenPossibilityService; import org.junit.jupiter.api.DisplayName; @@ -49,6 +50,9 @@ class StoreServiceTest { @Mock private OpenPossibilityService openPossibilityService; + @Mock + private BookmarkService bookmarkService; + @Test @DisplayName("점포_등록") @@ -94,14 +98,16 @@ void findStoresByCurrentLocation() { void detailStore() { // given Store store = mock(Store.class); + Member member = mock(Member.class); Optional optionalStore = Optional.of(store); OpenPossibility openPossibility = mock(OpenPossibility.class); when(storeRepository.findById(any())).thenReturn(optionalStore); when(openPossibilityService.getOpenPossibility(any())).thenReturn(openPossibility); + when(bookmarkService.isStoreBookmarkedByMember(any(), any())).thenReturn(true); // when - storeService.detailStore(store.getId()); + storeService.detailStore(store.getId(), member); // then verify(storeRepository).findById(any());