리뷰 관리 | 리뷰 작성 API 구현

Faithful Dev·2025년 3월 16일

매장 예약 서비스

목록 보기
12/15

구현한 기능

  • 리뷰 작성 기능 구현 (예약 완료 상태인 경우에만 가능)
  • 리뷰 조회 기능 구현 (매장별, 사용자별, 상세 조회)
  • 리뷰 수정 기능 구현 (작성자만 수정 가능)
  • 리뷰 삭제 기능 구현 (작성자 및 매장 관리자만 삭제 가능)
  • 매장 평점 통계 기능 구현 (평균 평점, 리뷰 수 조회)

코드 스냅샷

리뷰 엔티티

@Entity
@Table(name = "reviews")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Review {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "store_id", nullable = false)
    private Store store;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "reservation_id", nullable = false)
    private Reservation reservation;

    @Column(nullable = false)
    private Integer rating;

    @Column(nullable = false, columnDefinition = "TEXT")
    private String content;

    @CreationTimestamp
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;

    @Column(nullable = false)
    private boolean active;
}

리뷰 생성 메서드

@Transactional
public ReviewDto.Response createReview(Long userId, ReviewDto.CreateRequest request) {
    Reservation reservation = reservationRepository.findById(request.getReservationId())
            .orElseThrow(() -> new CustomException(ErrorCode.RESERVATION_NOT_FOUND));

    if (reservation.getStatus() != ReservationStatus.COMPLETED) {
        throw new CustomException(ErrorCode.INVALID_REQUEST, "완료된 예약에 대해서만 리뷰를 작성할 수 있습니다.");
    }

    if (!reservation.getUser().getId().equals(userId)) {
        throw new CustomException(ErrorCode.FORBIDDEN, "본인의 예약에 대해서만 리뷰를 작성할 수 있습니다.");
    }

    if (reviewRepository.findByReservationId(reservation.getId()).isPresent()) {
        throw new CustomException(ErrorCode.INVALID_REQUEST, "이미 리뷰가 작성된 예약입니다.");
    }

    User user = reservation.getUser();
    Store store = reservation.getStore();

    Review review = Review.builder()
            .user(user)
            .store(store)
            .reservation(reservation)
            .rating(request.getRating())
            .content(request.getContent())
            .active(true)
            .build();

    Review savedReview = reviewRepository.save(review);

    return convertToResponseDto(savedReview);
}

리뷰 삭제 메서드

@Transactional
public boolean deleteReview(Long reviewId, Long userId, boolean isPartner) {
    Review review = reviewRepository.findById(reviewId)
            .orElseThrow(() -> new CustomException(ErrorCode.REVIEW_NOT_FOUND));

    if (!review.isActive()) {
        throw new CustomException(ErrorCode.REVIEW_NOT_FOUND);
    }

    boolean isReviewOwner = review.getUser().getId().equals(userId);
    boolean isStorePartner = isPartner && review.getStore().getPartner().getId().equals(userId);

    if (!isReviewOwner && !isStorePartner) {
        throw new CustomException(ErrorCode.INVALID_REVIEW_PERMISSION);
    }

    review.setActive(false);
    reviewRepository.save(review);

    return true;
}

매장 평균 평점 조회

@Query("SELECT AVG(r.rating) FROM Review r WHERE r.store.id = :storeId AND r.active = true")
Double getAverageRatingByStoreId(@Param("storeId") Long storeId);

구현 예정

  • 매장 목록 조회 API (정렬: 별점순)
  • 예약 승인 API (파트너 전용)
  • 예약 거절 API (파트너 전용)
  • 예약 상태 알림 API (사용자에게 승인/거절 알림)
profile
Turning Vision into Reality.

0개의 댓글