diff --git a/src/main/java/team7/inplace/search/application/SearchService.java b/src/main/java/team7/inplace/search/application/SearchService.java index 18ae87af..a62db78f 100644 --- a/src/main/java/team7/inplace/search/application/SearchService.java +++ b/src/main/java/team7/inplace/search/application/SearchService.java @@ -5,10 +5,14 @@ import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import team7.inplace.favoriteInfluencer.persistent.FavoriteInfluencerRepository; import team7.inplace.influencer.application.dto.InfluencerInfo; +import team7.inplace.influencer.domain.Influencer; import team7.inplace.likedPlace.persistence.LikedPlaceRepository; import team7.inplace.search.application.dto.AutoCompletionInfo; import team7.inplace.search.application.dto.PlaceSearchInfo; @@ -16,6 +20,7 @@ import team7.inplace.search.persistence.InfluencerSearchRepository; import team7.inplace.search.persistence.PlaceSearchRepository; import team7.inplace.search.persistence.VideoSearchRepository; +import team7.inplace.search.persistence.dto.SearchResult; import team7.inplace.security.util.AuthorizationUtil; import team7.inplace.video.application.dto.VideoInfo; @@ -80,6 +85,38 @@ public List searchInfluencer(String keyword) { .toList(); } + public Page searchInfluencer(String keyword, Pageable pageable) { + Page> influencerInfos = influencerSearchRepository.searchEntityByKeywords(keyword, + pageable); + Long userId = AuthorizationUtil.getUserId(); + + if (userId == null) { + return new PageImpl<>( + influencerInfos.getContent().stream() + .map(influencer -> InfluencerInfo.from(influencer.searchResult(), false)) + .toList(), + pageable, + influencerInfos.getTotalElements() + ); + } + + var likedInfluencerIds = favoriteInfluencerRepository.findLikedInfluencerIdsByUserId(userId); + + List sortedContent = influencerInfos.getContent().stream() + .map(influencerInfo -> { + boolean isLiked = likedInfluencerIds.contains(influencerInfo.searchResult().getId()); + return InfluencerInfo.from(influencerInfo.searchResult(), isLiked); + }) + .sorted((a, b) -> Boolean.compare(b.likes(), a.likes())) + .toList(); + + return new PageImpl<>( + sortedContent, + pageable, + influencerInfos.getTotalElements() + ); + } + public List searchPlace(String keyword) { var placeInfos = placeSearchRepository.searchEntityByKeywords(keyword); Long userId = AuthorizationUtil.getUserId(); diff --git a/src/main/java/team7/inplace/search/persistence/InfluencerSearchRepository.java b/src/main/java/team7/inplace/search/persistence/InfluencerSearchRepository.java index a76ad3e9..94eb9555 100644 --- a/src/main/java/team7/inplace/search/persistence/InfluencerSearchRepository.java +++ b/src/main/java/team7/inplace/search/persistence/InfluencerSearchRepository.java @@ -5,6 +5,9 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; import team7.inplace.influencer.domain.Influencer; import team7.inplace.influencer.domain.QInfluencer; @@ -40,4 +43,40 @@ public List> searchEntityByKeywords(String keyword) { tuple.get(1, Double.class) )).toList(); } + + public Page> searchEntityByKeywords(String keyword, Pageable pageable) { + NumberTemplate matchScore = Expressions.numberTemplate( + Double.class, + "function('match_against', {0}, {1})", + QInfluencer.influencer.name, + keyword + ); + + // 결과 조회 + List> content = queryFactory + .select( + QInfluencer.influencer, + matchScore.as("score") + ) + .from(QInfluencer.influencer) + .where(matchScore.gt(0)) + .orderBy(matchScore.desc()) + .offset(pageable.getOffset()) // 페이지 오프셋 + .limit(pageable.getPageSize()) // 페이지 사이즈 + .fetch() + .stream() + .map(tuple -> new SearchResult<>( + tuple.get(0, Influencer.class), + tuple.get(1, Double.class) + )).toList(); + + // 전체 카운트 조회 + long total = queryFactory + .select(QInfluencer.influencer.count()) + .from(QInfluencer.influencer) + .where(matchScore.gt(0)) + .fetchOne(); + + return new PageImpl<>(content, pageable, total); + } } diff --git a/src/main/java/team7/inplace/search/presentation/SearchController.java b/src/main/java/team7/inplace/search/presentation/SearchController.java index 6ce45281..b075b8d8 100644 --- a/src/main/java/team7/inplace/search/presentation/SearchController.java +++ b/src/main/java/team7/inplace/search/presentation/SearchController.java @@ -2,12 +2,16 @@ import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import team7.inplace.influencer.application.dto.InfluencerInfo; import team7.inplace.influencer.presentation.dto.InfluencerResponse; import team7.inplace.search.application.SearchService; import team7.inplace.search.application.dto.AutoCompletionInfo; @@ -50,11 +54,24 @@ public ResponseEntity> searchInfluencer(@RequestParam S return new ResponseEntity<>(response, HttpStatus.OK); } + @Override + @GetMapping("/page/influencer") + public ResponseEntity> searchInfluencer( + @RequestParam String value, + @PageableDefault(size = 10) Pageable pageable + ) { + Page influencers = searchService.searchInfluencer(value, pageable); + + Page response = influencers.map(InfluencerResponse::from); + + return new ResponseEntity<>(response, HttpStatus.OK); + } + @Override @GetMapping("/place") public ResponseEntity> searchPlace(@RequestParam String value) { var places = searchService.searchPlace(value); - + return new ResponseEntity<>(places, HttpStatus.OK); } } diff --git a/src/main/java/team7/inplace/search/presentation/SearchControllerApiSpec.java b/src/main/java/team7/inplace/search/presentation/SearchControllerApiSpec.java index 7ce9e0f1..47bb55b3 100644 --- a/src/main/java/team7/inplace/search/presentation/SearchControllerApiSpec.java +++ b/src/main/java/team7/inplace/search/presentation/SearchControllerApiSpec.java @@ -4,6 +4,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import team7.inplace.influencer.presentation.dto.InfluencerResponse; import team7.inplace.search.application.dto.AutoCompletionInfo; @@ -28,4 +31,8 @@ public interface SearchControllerApiSpec { @Operation(summary = "장소를 검색합니다.") @ApiResponse(responseCode = "200", description = "장소 검색 성공") ResponseEntity> searchPlace(String value); + + @Operation(summary = "인플루언서를 페이지로 조회합니다.") + @ApiResponse(responseCode = "200", description = "인플루언서 페이지 조회 성공") + ResponseEntity> searchInfluencer(String value, @PageableDefault Pageable pageable); }