From ff555a3a6789dee40151ef338e03e1ed3cf76e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=82=99=ED=97=8C?= <95845037+nak-honest@users.noreply.github.com> Date: Sun, 20 Oct 2024 23:31:23 +0900 Subject: [PATCH] =?UTF-8?q?[Feature]=20-=20=EC=9E=90=EC=8B=A0=EC=9D=B4=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=EB=A5=BC=20=EB=88=84=EB=A5=B8=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=EC=9D=84=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#540)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 내가 좋아요 한 여행기 조회 기능 구현 * test: 내가 좋아요 한 여행기 조회 테스트 코드 추가 * refactor: 다른 dto 와 형식 통일 --- .../member/controller/MyPageController.java | 25 +++++++++++++++ .../response/MyLikeTravelogueResponse.java | 31 +++++++++++++++++++ .../member/service/MyPageFacadeService.java | 13 ++++++++ .../repository/TravelogueLikeRepository.java | 4 +++ .../service/TravelogueLikeService.java | 7 +++++ .../controller/MyPageControllerTest.java | 19 ++++++++++++ .../service/TravelogueLikeServiceTest.java | 17 ++++++++++ 7 files changed, 116 insertions(+) create mode 100644 backend/src/main/java/kr/touroot/member/dto/response/MyLikeTravelogueResponse.java diff --git a/backend/src/main/java/kr/touroot/member/controller/MyPageController.java b/backend/src/main/java/kr/touroot/member/controller/MyPageController.java index d1ff6b98..6ea08a83 100644 --- a/backend/src/main/java/kr/touroot/member/controller/MyPageController.java +++ b/backend/src/main/java/kr/touroot/member/controller/MyPageController.java @@ -12,6 +12,7 @@ import kr.touroot.global.auth.dto.MemberAuth; import kr.touroot.global.exception.dto.ExceptionResponse; import kr.touroot.member.dto.request.ProfileUpdateRequest; +import kr.touroot.member.dto.response.MyLikeTravelogueResponse; import kr.touroot.member.dto.response.MyTravelogueResponse; import kr.touroot.member.dto.response.ProfileResponse; import kr.touroot.member.service.MyPageFacadeService; @@ -103,6 +104,30 @@ public ResponseEntity> readTravelPlans( return ResponseEntity.ok(data); } + @Operation(summary = "내가 좋아요 한 여행기 조회") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "내가 좋아요 한 여행기 조회에 성공했을 때" + ), + @ApiResponse( + responseCode = "401", + description = "로그인하지 않은 사용자가 조회를 시도할 때", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + @PageableAsQueryParam + @GetMapping("/likes") + public ResponseEntity> readLikes( + @NotNull MemberAuth memberAuth, + @Parameter(hidden = true) + @PageableDefault(size = 5, sort = "id", direction = Sort.Direction.DESC) + Pageable pageable + ) { + Page data = myPageFacadeService.readLikes(memberAuth, pageable); + return ResponseEntity.ok(data); + } + @Operation(summary = "나의 프로필 정보 수정") @ApiResponses(value = { @ApiResponse( diff --git a/backend/src/main/java/kr/touroot/member/dto/response/MyLikeTravelogueResponse.java b/backend/src/main/java/kr/touroot/member/dto/response/MyLikeTravelogueResponse.java new file mode 100644 index 00000000..a3840e0e --- /dev/null +++ b/backend/src/main/java/kr/touroot/member/dto/response/MyLikeTravelogueResponse.java @@ -0,0 +1,31 @@ +package kr.touroot.member.dto.response; + +import java.time.format.DateTimeFormatter; +import kr.touroot.travelogue.domain.Travelogue; +import lombok.Builder; + +@Builder +public record MyLikeTravelogueResponse( + long id, + String title, + String thumbnailUrl, + String createdAt, + String authorName, + String authorProfileImageUrl +) { + + public static MyLikeTravelogueResponse from(Travelogue travelogue) { + String createdAt = travelogue.getCreatedAt() + .toLocalDate() + .format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); + + return MyLikeTravelogueResponse.builder() + .id(travelogue.getId()) + .title(travelogue.getTitle()) + .thumbnailUrl(travelogue.getThumbnail()) + .createdAt(createdAt) + .authorName(travelogue.getAuthorNickname()) + .authorProfileImageUrl(travelogue.getAuthorProfileImageUrl()) + .build(); + } +} diff --git a/backend/src/main/java/kr/touroot/member/service/MyPageFacadeService.java b/backend/src/main/java/kr/touroot/member/service/MyPageFacadeService.java index f495395f..5644377c 100644 --- a/backend/src/main/java/kr/touroot/member/service/MyPageFacadeService.java +++ b/backend/src/main/java/kr/touroot/member/service/MyPageFacadeService.java @@ -3,9 +3,12 @@ import kr.touroot.global.auth.dto.MemberAuth; import kr.touroot.member.domain.Member; import kr.touroot.member.dto.request.ProfileUpdateRequest; +import kr.touroot.member.dto.response.MyLikeTravelogueResponse; import kr.touroot.member.dto.response.MyTravelogueResponse; import kr.touroot.member.dto.response.ProfileResponse; import kr.touroot.travelogue.domain.Travelogue; +import kr.touroot.travelogue.domain.TravelogueLike; +import kr.touroot.travelogue.service.TravelogueLikeService; import kr.touroot.travelogue.service.TravelogueService; import kr.touroot.travelplan.domain.TravelPlan; import kr.touroot.travelplan.dto.response.PlanResponse; @@ -23,6 +26,7 @@ public class MyPageFacadeService { private final MemberService memberService; private final TravelogueService travelogueService; private final TravelPlanService travelPlanService; + private final TravelogueLikeService travelogueLikeService; @Transactional(readOnly = true) public ProfileResponse readProfile(MemberAuth memberAuth) { @@ -46,6 +50,15 @@ public Page readTravelPlans(MemberAuth memberAuth, Pageable pageab return travelPlans.map(PlanResponse::from); } + @Transactional(readOnly = true) + public Page readLikes(MemberAuth memberAuth, Pageable pageable) { + Member member = memberService.getMemberById(memberAuth.memberId()); + + return travelogueLikeService.findByLiker(member, pageable) + .map(TravelogueLike::getTravelogue) + .map(MyLikeTravelogueResponse::from); + } + @Transactional public ProfileResponse updateProfile(ProfileUpdateRequest request, MemberAuth memberAuth) { return memberService.updateProfile(request, memberAuth); diff --git a/backend/src/main/java/kr/touroot/travelogue/repository/TravelogueLikeRepository.java b/backend/src/main/java/kr/touroot/travelogue/repository/TravelogueLikeRepository.java index 52df6652..31aa2f1a 100644 --- a/backend/src/main/java/kr/touroot/travelogue/repository/TravelogueLikeRepository.java +++ b/backend/src/main/java/kr/touroot/travelogue/repository/TravelogueLikeRepository.java @@ -3,10 +3,14 @@ import kr.touroot.member.domain.Member; import kr.touroot.travelogue.domain.Travelogue; import kr.touroot.travelogue.domain.TravelogueLike; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface TravelogueLikeRepository extends JpaRepository { + Page findAllByLiker(Member liker, Pageable pageable); + boolean existsByTravelogueAndLiker(Travelogue travelogue, Member liker); void deleteAllByTravelogue(Travelogue travelogue); diff --git a/backend/src/main/java/kr/touroot/travelogue/service/TravelogueLikeService.java b/backend/src/main/java/kr/touroot/travelogue/service/TravelogueLikeService.java index a5f6eea8..82b193c8 100644 --- a/backend/src/main/java/kr/touroot/travelogue/service/TravelogueLikeService.java +++ b/backend/src/main/java/kr/touroot/travelogue/service/TravelogueLikeService.java @@ -5,6 +5,8 @@ import kr.touroot.travelogue.domain.TravelogueLike; import kr.touroot.travelogue.repository.TravelogueLikeRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -14,6 +16,11 @@ public class TravelogueLikeService { private final TravelogueLikeRepository travelogueLikeRepository; + @Transactional(readOnly = true) + public Page findByLiker(Member liker, Pageable pageable) { + return travelogueLikeRepository.findAllByLiker(liker, pageable); + } + @Transactional(readOnly = true) public boolean existByTravelogueAndMember(Travelogue travelogue, Member liker) { return travelogueLikeRepository.existsByTravelogueAndLiker(travelogue, liker); diff --git a/backend/src/test/java/kr/touroot/member/controller/MyPageControllerTest.java b/backend/src/test/java/kr/touroot/member/controller/MyPageControllerTest.java index 93051ab4..3676ead7 100644 --- a/backend/src/test/java/kr/touroot/member/controller/MyPageControllerTest.java +++ b/backend/src/test/java/kr/touroot/member/controller/MyPageControllerTest.java @@ -101,6 +101,25 @@ void readTravelPlans() { .body("content.size()", is(2)); } + @DisplayName("마이 페이지 컨트롤러는 내가 좋아요 한 여행기 조회 시 요청이 들어오면 로그인한 사용자의 여행 계획을 조회한다.") + @Test + void readLikeTravelogues() { + // given + travelogueTestHelper.initTravelogueTestDataWithLike(member); + travelogueTestHelper.initTravelogueTestDataWithLike(member); + travelogueTestHelper.initTravelogueTestData(); + + // when & then + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .when().log().all() + .get("/api/v1/member/me/likes") + .then().log().all() + .statusCode(200) + .body("content.size()", is(2)); + } + @DisplayName("마이 페이지 컨트롤러는 내 프로필 수정 요청이 들어오면 로그인한 사용자의 프로필을 수정한다.") @Test void updateProfile() { diff --git a/backend/src/test/java/kr/touroot/travelogue/service/TravelogueLikeServiceTest.java b/backend/src/test/java/kr/touroot/travelogue/service/TravelogueLikeServiceTest.java index f1dd162b..50908264 100644 --- a/backend/src/test/java/kr/touroot/travelogue/service/TravelogueLikeServiceTest.java +++ b/backend/src/test/java/kr/touroot/travelogue/service/TravelogueLikeServiceTest.java @@ -12,12 +12,15 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Import; +import org.springframework.data.domain.Pageable; @DisplayName("여행기 좋아요 서비스") @Import(value = {TravelogueLikeService.class, TravelogueTestHelper.class}) @ServiceTest class TravelogueLikeServiceTest { + public static final int BASIC_PAGE_SIZE = 5; + private final TravelogueLikeService travelogueLikeService; private final DatabaseCleaner databaseCleaner; private final TravelogueTestHelper testHelper; @@ -38,6 +41,20 @@ void setUp() { databaseCleaner.executeTruncate(); } + @DisplayName("특정 멤버가 좋아요 한 여행기를 조회할 수 있다.") + @Test + void findByLiker() { + // given + Member liker = testHelper.initKakaoMemberTestData(); + testHelper.initTravelogueTestDataWithLike(liker); + testHelper.initTravelogueTestDataWithLike(liker); + testHelper.initTravelogueTestData(); + + // when & then + assertThat(travelogueLikeService.findByLiker(liker, Pageable.ofSize(BASIC_PAGE_SIZE))) + .hasSize(2); + } + @DisplayName("특정 여행기에 특정 멤버가 좋아요 했는지 알 수 있다") @Test void existByTravelogueAndMember() {