diff --git a/src/main/java/org/kakaoshare/backend/domain/funding/controller/FundingController.java b/src/main/java/org/kakaoshare/backend/domain/funding/controller/FundingController.java index 60b3eee6..12439882 100644 --- a/src/main/java/org/kakaoshare/backend/domain/funding/controller/FundingController.java +++ b/src/main/java/org/kakaoshare/backend/domain/funding/controller/FundingController.java @@ -1,16 +1,14 @@ package org.kakaoshare.backend.domain.funding.controller; -import java.util.List; import lombok.RequiredArgsConstructor; import org.kakaoshare.backend.common.dto.PageResponse; - -import org.kakaoshare.backend.domain.funding.dto.FundingCheckRequest; -import org.kakaoshare.backend.domain.funding.dto.inquiry.request.FriendFundingInquiryRequest; import org.kakaoshare.backend.domain.funding.dto.FriendFundingItemRequest; +import org.kakaoshare.backend.domain.funding.dto.FundingCheckRequest; import org.kakaoshare.backend.domain.funding.dto.FundingResponse; import org.kakaoshare.backend.domain.funding.dto.ProgressResponse; import org.kakaoshare.backend.domain.funding.dto.RegisterRequest; import org.kakaoshare.backend.domain.funding.dto.RegisterResponse; +import org.kakaoshare.backend.domain.funding.dto.inquiry.request.FriendFundingInquiryRequest; import org.kakaoshare.backend.domain.funding.dto.preview.request.FundingPreviewRequest; import org.kakaoshare.backend.domain.funding.dto.preview.response.FundingPreviewResponse; import org.kakaoshare.backend.domain.funding.entity.FundingStatus; @@ -24,18 +22,21 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RequiredArgsConstructor @RestController @RequestMapping("/api/v1") public class FundingController { + private static final int DEFAULT_FUNDING_SIZE = 20; + private static final int DEFAULT_TOP_CONTRIBUTORS_SIZE = 5; + private final FundingService fundingService; private final FundingDetailService fundingDetailService; - private static final int FUNDING_DEFAULT_SIZE = 20; @PostMapping("/funding/{productId}") public ResponseEntity registerFunding(@PathVariable("productId") Long productId, @@ -74,7 +75,7 @@ public ResponseEntity getFriendsActiveFundingItems(@LoggedInMember String pro @GetMapping("/members/funding/products") public ResponseEntity getMyAllFundingProducts(@LoggedInMember String providerId, @RequestParam(name = "status", required = false) FundingStatus status, - @PageableDefault(size = FUNDING_DEFAULT_SIZE) final Pageable pageable) { + @PageableDefault(size = DEFAULT_FUNDING_SIZE) final Pageable pageable) { PageResponse response = fundingService.getMyFilteredFundingProducts(providerId, status, pageable); return ResponseEntity.ok(response); } @@ -85,14 +86,10 @@ public ResponseEntity preview(@RequestBody final FundingPreviewRequest fundin return ResponseEntity.ok(fundingPreviewResponse); } - @GetMapping("/{fundingId}/contributors") - public ResponseEntity getTopContributors(@PathVariable Long fundingId, - @PageableDefault(size = 5) Pageable pageable, - @RequestHeader("Authorization") String accessToken) { - - accessToken = accessToken.substring("Bearer ".length()); - PageResponse contributors = fundingDetailService.getTopContributors(fundingId, pageable, - accessToken); - return ResponseEntity.ok(contributors); + @GetMapping("/funding/{fundingId}/contributors") + public ResponseEntity getTopContributors(@PathVariable final Long fundingId, + @PageableDefault(size = DEFAULT_TOP_CONTRIBUTORS_SIZE) final Pageable pageable) { + final PageResponse response = fundingDetailService.getTopContributors(fundingId, pageable); + return ResponseEntity.ok(response); } } diff --git a/src/main/java/org/kakaoshare/backend/domain/funding/dto/rank/response/TopContributorResponse.java b/src/main/java/org/kakaoshare/backend/domain/funding/dto/rank/response/TopContributorResponse.java new file mode 100644 index 00000000..bf7b63b6 --- /dev/null +++ b/src/main/java/org/kakaoshare/backend/domain/funding/dto/rank/response/TopContributorResponse.java @@ -0,0 +1,8 @@ +package org.kakaoshare.backend.domain.funding.dto.rank.response; + +public record TopContributorResponse( + String profileUrl, + String name, + Double rate +) { +} diff --git a/src/main/java/org/kakaoshare/backend/domain/funding/repository/FundingDetailRepository.java b/src/main/java/org/kakaoshare/backend/domain/funding/repository/FundingDetailRepository.java index 858571ad..4ca24faa 100644 --- a/src/main/java/org/kakaoshare/backend/domain/funding/repository/FundingDetailRepository.java +++ b/src/main/java/org/kakaoshare/backend/domain/funding/repository/FundingDetailRepository.java @@ -1,5 +1,6 @@ package org.kakaoshare.backend.domain.funding.repository; +import org.kakaoshare.backend.domain.funding.dto.rank.response.TopContributorResponse; import org.kakaoshare.backend.domain.funding.entity.Funding; import org.kakaoshare.backend.domain.funding.entity.FundingDetail; import org.kakaoshare.backend.domain.funding.repository.query.FundingDetailRepositoryCustom; @@ -21,7 +22,9 @@ public interface FundingDetailRepository extends JpaRepository findAllByFundingId(@Param("fundingId") final Long fundingId); - @Query("SELECT fd FROM FundingDetail fd WHERE fd.funding.fundingId = :fundingId ORDER BY fd.amount DESC") - Page findTopContributorsByFundingId(@Param("fundingId") Long fundingId, Pageable pageable); - + @Query("SELECT NEW org.kakaoshare.backend.domain.funding.dto.rank.response.TopContributorResponse(fd.member.profileImageUrl, fd.member.name, fd.rate) " + + "FROM FundingDetail fd " + + "LEFT JOIN fd.member m ON m.memberId = fd.member.memberId " + + "WHERE fd.funding.fundingId = :fundingId") + Page findTopContributorsByFundingId(@Param("fundingId") Long fundingId, Pageable pageable); } diff --git a/src/main/java/org/kakaoshare/backend/domain/funding/service/FundingDetailService.java b/src/main/java/org/kakaoshare/backend/domain/funding/service/FundingDetailService.java index 011daea5..0e38bd1a 100644 --- a/src/main/java/org/kakaoshare/backend/domain/funding/service/FundingDetailService.java +++ b/src/main/java/org/kakaoshare/backend/domain/funding/service/FundingDetailService.java @@ -3,29 +3,22 @@ import com.querydsl.core.util.StringUtils; import lombok.RequiredArgsConstructor; import org.kakaoshare.backend.common.dto.PageResponse; -import org.kakaoshare.backend.domain.friend.service.KakaoFriendService; import org.kakaoshare.backend.domain.funding.dto.inquiry.ContributedFundingHistoryDto; import org.kakaoshare.backend.domain.funding.dto.inquiry.request.ContributedFundingHistoryRequest; import org.kakaoshare.backend.domain.funding.dto.inquiry.response.ContributedFundingHistoryResponse; -import org.kakaoshare.backend.domain.funding.dto.inquiry.response.FundingContributorResponse; -import org.kakaoshare.backend.domain.funding.entity.FundingDetail; +import org.kakaoshare.backend.domain.funding.dto.rank.response.TopContributorResponse; import org.kakaoshare.backend.domain.funding.repository.FundingDetailRepository; import org.kakaoshare.backend.domain.funding.vo.FundingHistoryDate; -import org.kakaoshare.backend.domain.member.dto.oauth.profile.detail.KakaoFriendListDto; 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 java.util.List; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class FundingDetailService { private final FundingDetailRepository fundingDetailRepository; - private final KakaoFriendService kakaoFriendService; public PageResponse lookUp(final String providerId, final ContributedFundingHistoryRequest contributedFundingHistoryRequest, @@ -45,36 +38,8 @@ private Page getFundingDetailHistoryDto(final Stri return fundingDetailRepository.findHistoryByCondition(providerId, date, status, pageable); } - public PageResponse getTopContributors(Long fundingId, Pageable pageable, String accessToken) { - Page fundingDetails = fundingDetailRepository.findTopContributorsByFundingId(fundingId, pageable); - - List friendsList = kakaoFriendService.getFriendsList(accessToken); - - List responses = fundingDetails.stream() - .map(detail -> { - KakaoFriendListDto friendProfile = findFriendProfile(detail.getMember().getProviderId(), friendsList); - return FundingContributorResponse.of( - friendProfile.getProfileThumbnailImage(), - friendProfile.getProfileNickname(), - detail.getAmount(), - calculateContributionPercentage(detail) - ); - }).toList(); - - Page contributorResponses = new PageImpl<>(responses, pageable, fundingDetails.getTotalElements()); - return PageResponse.from(contributorResponses); - } - - private KakaoFriendListDto findFriendProfile(String providerId, List friendsList) { - return friendsList.stream() - .filter(friend -> friend.getId().equals(providerId)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("No matching friend found for providerId: " + providerId)); - } - - - - private double calculateContributionPercentage(FundingDetail detail) { - return 100.0 * detail.getAmount() / detail.getFunding().getGoalAmount(); + public PageResponse getTopContributors(final Long fundingId, final Pageable pageable) { + final Page page = fundingDetailRepository.findTopContributorsByFundingId(fundingId, pageable); + return PageResponse.from(page); } } diff --git a/src/main/resources/testdata/initial_5_payment.sql b/src/main/resources/testdata/initial_5_payment.sql new file mode 100644 index 00000000..9925c99d --- /dev/null +++ b/src/main/resources/testdata/initial_5_payment.sql @@ -0,0 +1,4 @@ +INSERT INTO payment (created_at, delivery_price, payment_id, purchase_price, total_price, payment_number, method) +VALUES (NOW(), 0, 1, 3000, 3000, '1', 'KAKAO_PAY'), + (NOW(), 0, 2, 2000, 2000, '2', 'KAKAO_PAY'), + (NOW(), 0, 3, 1000, 1000, '3', 'KAKAO_PAY'); \ No newline at end of file diff --git a/src/main/resources/testdata/initial_6_funding_detail.sql b/src/main/resources/testdata/initial_6_funding_detail.sql new file mode 100644 index 00000000..8505d37f --- /dev/null +++ b/src/main/resources/testdata/initial_6_funding_detail.sql @@ -0,0 +1,4 @@ +INSERT INTO funding_detail (created_at, amount, rate, funding_id, payment_id, member_id, status) +VALUES (NOW(), 3000, 30.0, 1, 1, 1, 'PROGRESS'), + (NOW(), 2000, 20.0, 1, 1, 2, 'PROGRESS'), + (NOW(), 1000, 10.0, 1, 1, 3, 'PROGRESS'); \ No newline at end of file diff --git a/src/test/java/org/kakaoshare/backend/domain/funding/repository/FundingDetailRepositoryTest.java b/src/test/java/org/kakaoshare/backend/domain/funding/repository/FundingDetailRepositoryTest.java new file mode 100644 index 00000000..cbc6050f --- /dev/null +++ b/src/test/java/org/kakaoshare/backend/domain/funding/repository/FundingDetailRepositoryTest.java @@ -0,0 +1,32 @@ +package org.kakaoshare.backend.domain.funding.repository; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.kakaoshare.backend.common.RepositoryTest; +import org.kakaoshare.backend.domain.funding.dto.rank.response.TopContributorResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.util.List; + +@RepositoryTest +class FundingDetailRepositoryTest { + @Autowired + FundingDetailRepository fundingDetailRepository; + + @Test + @DisplayName("최대 기여자 조회") + public void findTopContributorsByFundingId() throws Exception { + final Long fundingId = 1L; + final Pageable pageable = PageRequest.of(0, 5, Sort.by(Sort.Direction.DESC, "rate")); + final Page page = fundingDetailRepository.findTopContributorsByFundingId(fundingId, pageable); + final List content = page.getContent(); + Assertions.assertThat(content.get(0).rate()).isEqualTo(30.0); + Assertions.assertThat(content.get(1).rate()).isEqualTo(20.0); + Assertions.assertThat(content.get(2).rate()).isEqualTo(10.0); + } +} \ No newline at end of file diff --git a/src/test/java/org/kakaoshare/backend/domain/funding/service/FundingDetailServiceTest.java b/src/test/java/org/kakaoshare/backend/domain/funding/service/FundingDetailServiceTest.java index dcc3c2b7..d6d37e4e 100644 --- a/src/test/java/org/kakaoshare/backend/domain/funding/service/FundingDetailServiceTest.java +++ b/src/test/java/org/kakaoshare/backend/domain/funding/service/FundingDetailServiceTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EmptySource; @@ -12,6 +13,7 @@ import org.kakaoshare.backend.domain.funding.dto.inquiry.ContributedFundingHistoryDto; import org.kakaoshare.backend.domain.funding.dto.inquiry.request.ContributedFundingHistoryRequest; import org.kakaoshare.backend.domain.funding.dto.inquiry.response.ContributedFundingHistoryResponse; +import org.kakaoshare.backend.domain.funding.dto.rank.response.TopContributorResponse; import org.kakaoshare.backend.domain.funding.repository.FundingDetailRepository; import org.kakaoshare.backend.domain.funding.vo.FundingHistoryDate; import org.kakaoshare.backend.domain.member.entity.Member; @@ -156,6 +158,25 @@ public void lookUpWhenInvalidDateRange(final String status) throws Exception { .isInstanceOf(DateException.class); } + + @Test + @DisplayName("최대 기여자 조회") + public void getTopContributors() throws Exception { + final Long fundingId = 1L; + final List content = List.of( + new TopContributorResponse("profileUrl1", "테스터1", 30.0), + new TopContributorResponse("profileUrl1", "테스터2", 20.0), + new TopContributorResponse("profileUrl1", "테스터3", 10.0) + ); + + final Page page = new PageImpl<>(content, pageable, content.size()); + doReturn(page).when(fundingDetailRepository).findTopContributorsByFundingId(fundingId, pageable); + + final PageResponse expect = PageResponse.from(page); + final PageResponse actual = fundingDetailService.getTopContributors(fundingId, pageable); + assertThat(actual).usingRecursiveComparison().isEqualTo(expect); + } + private ProductDto getProductDto(final Product product) { return new ProductDto( product.getProductId(),