Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.server.running_handai.domain.review.controller;

import com.server.running_handai.domain.review.dto.MyReviewInfoDto;
import com.server.running_handai.domain.review.dto.ReviewCreateResponseDto;
import com.server.running_handai.domain.review.dto.ReviewInfoListDto;
import com.server.running_handai.domain.review.dto.ReviewCreateRequestDto;
Expand All @@ -14,9 +15,9 @@
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand Down Expand Up @@ -104,4 +105,23 @@ public ResponseEntity<CommonResponse<?>> deleteReview(
return ResponseEntity.ok(CommonResponse.success(ResponseCode.SUCCESS, null));
}

@Operation(summary = "내 리뷰 조회", description = "회원이 작성한 리뷰를 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "200", description = "성공 (리뷰 없음)")
})
@GetMapping("/api/members/me/reviews")
public ResponseEntity<CommonResponse<List<MyReviewInfoDto>>> getMyReviews(
@AuthenticationPrincipal CustomOAuth2User customOAuth2User
) {
Long memberId = customOAuth2User.getMember().getId();
List<MyReviewInfoDto> responseData = reviewService.getMyReviews(memberId);

if (responseData.isEmpty()) {
return ResponseEntity.ok(CommonResponse.success(ResponseCode.SUCCESS_EMPTY_REVIEWS, responseData));
}

return ResponseEntity.ok(CommonResponse.success(ResponseCode.SUCCESS, responseData));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.server.running_handai.domain.review.dto;

import com.server.running_handai.domain.course.entity.Course;
import com.server.running_handai.domain.review.entity.Review;
import java.time.format.DateTimeFormatter;

public record MyReviewInfoDto(
long reviewId,
long courseId,
String courseName,
String thumbnailUrl,
String area,
double distance,
int duration,
int maxElevation,
double stars,
String contents,
String createdAt
) {
public static MyReviewInfoDto from(Review review) {
Course course = review.getCourse();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedCreatedAt = review.getCreatedAt().format(formatter);

return new MyReviewInfoDto(
review.getId(),
course.getId(),
course.getName(),
(course.getCourseImage() != null) ? course.getCourseImage().getImgUrl() : null,
course.getArea().name(),
course.getDistance(),
course.getDuration(),
(int) course.getMaxElevation().doubleValue(),
review.getStars(),
review.getContents(),
formattedCreatedAt
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,14 @@ public interface ReviewRepository extends JpaRepository<Review, Long> {
*/
@Query("SELECT AVG(r.stars) FROM Review r WHERE r.course.id = :courseId")
Double findAverageStarsByCourseId(@Param("courseId") Long courseId);

/**
* 특정 회원이 작성한 리뷰 조회 (연관 엔티티 동시 조회)
*/
@Query("SELECT r FROM Review r " +
"LEFT JOIN FETCH r.course c " +
"LEFT JOIN FETCH c.courseImage ci " +
"WHERE r.writer.id = :memberId " +
"ORDER BY r.createdAt DESC")
List<Review> findReviewsWithDetailsByMemberId(@Param("memberId") Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.server.running_handai.domain.course.repository.CourseRepository;
import com.server.running_handai.domain.member.entity.Member;
import com.server.running_handai.domain.member.repository.MemberRepository;
import com.server.running_handai.domain.review.dto.MyReviewInfoDto;
import com.server.running_handai.domain.review.dto.ReviewCreateResponseDto;
import com.server.running_handai.domain.review.dto.ReviewInfoDto;
import com.server.running_handai.domain.review.dto.ReviewInfoListDto;
Expand All @@ -14,8 +15,10 @@
import com.server.running_handai.domain.review.repository.ReviewRepository;
import com.server.running_handai.global.response.ResponseCode;
import com.server.running_handai.global.response.exception.BusinessException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -163,4 +166,22 @@ public void deleteReview(Long reviewId, Long memberId) {
reviewRepository.delete(review);
}

/**
* 회원이 작성한 리뷰를 조회합니다.
*
* @param memberId 요청한 회원의 ID
* @return 내 리뷰 조회용 DTO
*/
public List<MyReviewInfoDto> getMyReviews(Long memberId) {
List<Review> reviews = reviewRepository.findReviewsWithDetailsByMemberId(memberId);

if (reviews.isEmpty()) {
return Collections.emptyList();
}

return reviews.stream()
.map(MyReviewInfoDto::from)
.collect(Collectors.toList());
}

}
4 changes: 4 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ external:
durunubi:
base-url: http://apis.data.go.kr/B551011/Durunubi
service-key: ${DURUNUBI_SERVICE_KEY}
spot:
base-url: http://apis.data.go.kr/B551011/KorService2
service-key: ${SPOT_SERVICE_KEY}
radius: 50000 # [국문 관광정보] 위치기반 관광정보 조회 API 거리 반경 (50000m = 5km)

springdoc:
default-produces-media-type: application/json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@

import com.server.running_handai.domain.course.entity.Area;
import com.server.running_handai.domain.course.entity.Course;
import com.server.running_handai.domain.course.entity.CourseImage;
import com.server.running_handai.domain.course.entity.CourseLevel;
import com.server.running_handai.domain.course.repository.CourseRepository;
import com.server.running_handai.domain.member.entity.Member;
import com.server.running_handai.domain.member.entity.Provider;
import com.server.running_handai.domain.member.entity.Role;
import com.server.running_handai.domain.member.repository.MemberRepository;
import com.server.running_handai.domain.review.dto.MyReviewInfoDto;
import com.server.running_handai.domain.review.dto.ReviewCreateResponseDto;
import com.server.running_handai.domain.review.dto.ReviewInfoListDto;
import com.server.running_handai.domain.review.dto.ReviewCreateRequestDto;
Expand All @@ -28,6 +30,7 @@
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -91,7 +94,7 @@ void setUp() throws ParseException {

course = Course.builder()
.name("courseName1")
.distance(10)
.distance(10.5)
.duration(120)
.level(CourseLevel.MEDIUM)
.area(Area.HAEUN_GWANGAN)
Expand All @@ -106,10 +109,14 @@ void setUp() throws ParseException {
.contents(VALID_REVIEW_CONTENTS)
.build();

CourseImage courseImage = CourseImage.builder().imgUrl("img_url").build();

// 테스트 리뷰에 연관관계 설정
review.setWriter(member);
review.setCourse(course);
course.updateCourseImage(courseImage);

ReflectionTestUtils.setField(course, "id", 10L);
ReflectionTestUtils.setField(review, "id", 100L);
ReflectionTestUtils.setField(member, "id", 50L);
ReflectionTestUtils.setField(review, "createdAt", LocalDateTime.now());
Expand Down Expand Up @@ -547,4 +554,53 @@ void deleteReview_fail_accessDenied() {
}

}

@Nested
@DisplayName("내 리뷰 조회 테스트")
class GetMyReviewTest {

@Test
@DisplayName("내 리뷰 조회 성공 - 리뷰가 있을 때")
void getMyReview_success_hasReview() {
// given
Long memberId = member.getId();
List<Review> myReviews = List.of(review);

given(reviewRepository.findReviewsWithDetailsByMemberId(memberId)).willReturn(myReviews);

// when
List<MyReviewInfoDto> myReviewInfoDtos = reviewService.getMyReviews(memberId);

// then
MyReviewInfoDto firstDto = myReviewInfoDtos.getFirst();
assertThat(myReviewInfoDtos.size()).isEqualTo(1);
assertThat(firstDto.reviewId()).isEqualTo(review.getId());
assertThat(firstDto.courseId()).isEqualTo(course.getId());
assertThat(firstDto.courseName()).isEqualTo(course.getName());
assertThat(firstDto.thumbnailUrl()).isEqualTo(course.getCourseImage().getImgUrl());
assertThat(firstDto.area()).isEqualTo(course.getArea().name());
assertThat(firstDto.distance()).isEqualTo(course.getDistance());
assertThat(firstDto.duration()).isEqualTo(course.getDuration());
assertThat(firstDto.maxElevation()).isEqualTo((int) course.getMaxElevation().doubleValue());

verify(reviewRepository).findReviewsWithDetailsByMemberId(memberId);
}

@Test
@DisplayName("내 리뷰 조회 성공 - 리뷰가 없을 때")
void getMyReview_success_HasNoReview() {
// given
Long memberId = member.getId();

given(reviewRepository.findReviewsWithDetailsByMemberId(memberId)).willReturn(Collections.emptyList());

// when
List<MyReviewInfoDto> myReviewInfoDtos = reviewService.getMyReviews(memberId);

// then
assertThat(myReviewInfoDtos).isEmpty();

verify(reviewRepository).findReviewsWithDetailsByMemberId(memberId);
}
}
}