diff --git a/src/main/java/ita/tinybite/domain/party/controller/PartyController.java b/src/main/java/ita/tinybite/domain/party/controller/PartyController.java index 1dd3af4..33e798e 100644 --- a/src/main/java/ita/tinybite/domain/party/controller/PartyController.java +++ b/src/main/java/ita/tinybite/domain/party/controller/PartyController.java @@ -490,10 +490,13 @@ public APIResponse getParty( schema = @Schema(allowableValues = {"ALL", "DELIVERY", "GROCERY", "HOUSEHOLD"}) ) @RequestParam(defaultValue = "ALL") PartyCategory category, + @RequestParam(required = false, name = "lat") Double userLat, + @RequestParam(required = false, name = "lon") Double userLon, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size ) { - return APIResponse.success(partySearchService.searchParty(q, category, page, size)); + + return APIResponse.success(partySearchService.searchParty(q, category, page, size, userLat, userLon)); } @Operation( @@ -511,7 +514,7 @@ public APIResponse> getRecentLog() { @Operation( summary = "특정 최근 검색어 삭제", description = """ - 최근 검색어에서 특정 검색어를 삭제합니다.
+ 최근 검색어에서 특정 검색어를 삭제합니다.
이때 검색어에 대한 Id값은 없고, 최근 검색어 자체를 keyword에 넣어주시면 됩니다. """ ) diff --git a/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java b/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java index 2050a2a..fc92205 100644 --- a/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java +++ b/src/main/java/ita/tinybite/domain/party/repository/PartyRepository.java @@ -1,17 +1,14 @@ package ita.tinybite.domain.party.repository; -import io.lettuce.core.dynamic.annotation.Param; -import ita.tinybite.domain.chat.enums.ChatRoomType; import ita.tinybite.domain.party.entity.Party; import ita.tinybite.domain.party.enums.PartyCategory; import java.util.List; import java.util.Optional; import ita.tinybite.domain.party.enums.PartyStatus; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository @@ -25,8 +22,4 @@ public interface PartyRepository extends JpaRepository { List findByPickupLocation_PlaceAndCategory(String place, PartyCategory category); List findByHostUserIdAndStatus(Long userId, PartyStatus partyStatus); - - Page findByTitleContaining(String title, Pageable pageable); - - Page findByTitleContainingAndCategory(String title, PartyCategory category, Pageable pageable); } diff --git a/src/main/java/ita/tinybite/domain/party/repository/PartySearchRepository.java b/src/main/java/ita/tinybite/domain/party/repository/PartySearchRepository.java new file mode 100644 index 0000000..76c462d --- /dev/null +++ b/src/main/java/ita/tinybite/domain/party/repository/PartySearchRepository.java @@ -0,0 +1,48 @@ +package ita.tinybite.domain.party.repository; + +import ita.tinybite.domain.party.entity.Party; +import ita.tinybite.domain.party.enums.PartyCategory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface PartySearchRepository extends JpaRepository { + + Page findByTitleContaining(String title, Pageable pageable); + + Page findByTitleContainingAndCategory(String title, PartyCategory category, Pageable pageable); + + @Query(value = """ + SELECT p.* + FROM party p + WHERE p.title LIKE CONCAT('%', :title, '%') + ORDER BY (6371000 * acos( + cos(radians(:lat)) * cos(radians(p.pickup_latitude)) + * cos(radians(p.pickup_longitude) - radians(:lon)) + + sin(radians(:lat)) * sin(radians(p.pickup_latitude)))) + """, countQuery = """ + SELECT COUNT(*) + FROM party p + WHERE p.title LIKE CONCAT('%', :title, '%') + """, nativeQuery = true) + Page findByTitleContainingWithDistance(String title, @Param("lat") Double lat, @Param("lon") Double lon, Pageable pageable); + + @Query(value = """ + SELECT p.* + FROM party p + WHERE p. + ORDER BY (6371000 * acos( + cos(radians(:lat)) * cos(radians(p.pickup_latitude)) + * cos(radians(p.pickup_longitude) - radians(:lon)) + + sin(radians(:lat)) * sin(radians(p.pickup_latitude)))) + """, countQuery = """ + SELECT COUNT(*) + FROM party p + WHERE p.title LIKE CONCAT('%', :title, '%') + """, nativeQuery = true) + Page findByTitleContainingAndCategoryWithDistance(String q, Double lat, Double lon, PartyCategory category, Pageable pageable); +} diff --git a/src/main/java/ita/tinybite/domain/party/service/PartySearchService.java b/src/main/java/ita/tinybite/domain/party/service/PartySearchService.java index 0cf87c2..ce76ecb 100644 --- a/src/main/java/ita/tinybite/domain/party/service/PartySearchService.java +++ b/src/main/java/ita/tinybite/domain/party/service/PartySearchService.java @@ -7,7 +7,8 @@ import ita.tinybite.domain.party.enums.ParticipantStatus; import ita.tinybite.domain.party.enums.PartyCategory; import ita.tinybite.domain.party.repository.PartyParticipantRepository; -import ita.tinybite.domain.party.repository.PartyRepository; +import ita.tinybite.domain.party.repository.PartySearchRepository; +import ita.tinybite.global.util.DistanceCalculator; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -23,7 +24,7 @@ @RequiredArgsConstructor public class PartySearchService { - private final PartyRepository partyRepository; + private final PartySearchRepository partySearchRepository; private final PartyParticipantRepository participantRepository; private final StringRedisTemplate redisTemplate; private final SecurityProvider securityProvider; @@ -35,7 +36,7 @@ private String key(Long userId) { } // 파티 검색 조회 - public PartyQueryListResponse searchParty(String q, PartyCategory category, int page, int size) { + public PartyQueryListResponse searchParty(String q, PartyCategory category, int page, int size, Double lat, Double lon) { Long userId = securityProvider.getCurrentUser().getUserId(); // recent_search:{userId} @@ -45,24 +46,49 @@ public PartyQueryListResponse searchParty(String q, PartyCategory category, int redisTemplate.opsForZSet().add(key, q, System.currentTimeMillis()); Pageable pageable = PageRequest.of(page, size); - - // category가 없을 시에는 ALL로 처리 - Page result = (category == null || category == PartyCategory.ALL) - ? partyRepository.findByTitleContaining(q, pageable) - : partyRepository.findByTitleContainingAndCategory(q, category, pageable); - - List partyCardResponseList = result.stream() - .map(party -> { - int currentParticipants = participantRepository - .countByPartyIdAndStatus(party.getId(), ParticipantStatus.APPROVED); - return PartyCardResponse.from(party, currentParticipants); - }) - .toList(); - - return PartyQueryListResponse.builder() - .parties(partyCardResponseList) - .hasNext(result.hasNext()) - .build(); + List partyCardResponseList; + + // 거리 정보 X + if(lat == null || lon == null) { + // category가 없을 시에는 ALL로 처리 + Page queryResults = (category == null || category == PartyCategory.ALL) + ? partySearchRepository.findByTitleContaining(q, pageable) + : partySearchRepository.findByTitleContainingAndCategory(q, category, pageable); + + partyCardResponseList = queryResults.stream() + .map(party -> { + int currentParticipants = participantRepository + .countByPartyIdAndStatus(party.getId(), ParticipantStatus.APPROVED); + return PartyCardResponse.from(party, currentParticipants); + }) + .toList(); + + return PartyQueryListResponse.builder() + .parties(partyCardResponseList) + .hasNext(queryResults.hasNext()) + .build(); + } else { + // 거리 정보 O (lat, lon) + Page queryResults = (category == null || category == PartyCategory.ALL) + ? partySearchRepository.findByTitleContainingWithDistance(q, lat, lon, pageable) + : partySearchRepository.findByTitleContainingAndCategoryWithDistance(q, lat, lon, category, pageable); + + partyCardResponseList = queryResults.stream() + .map(party -> { + int currentParticipants = participantRepository + .countByPartyIdAndStatus(party.getId(), ParticipantStatus.APPROVED); + PartyCardResponse res = PartyCardResponse.from(party, currentParticipants); + Double distance = DistanceCalculator.calculateDistance(lat, lon, party.getPickupLocation().getPickupLatitude(), party.getPickupLocation().getPickupLongitude()); + res.addDistanceKm(distance); + return res; + }) + .toList(); + + return PartyQueryListResponse.builder() + .parties(partyCardResponseList) + .hasNext(queryResults.hasNext()) + .build(); + } }