구현한 기능
- 리뷰 작성 기능 구현 (예약 완료 상태인 경우에만 가능)
- 리뷰 조회 기능 구현 (매장별, 사용자별, 상세 조회)
- 리뷰 수정 기능 구현 (작성자만 수정 가능)
- 리뷰 삭제 기능 구현 (작성자 및 매장 관리자만 삭제 가능)
- 매장 평점 통계 기능 구현 (평균 평점, 리뷰 수 조회)
코드 스냅샷
리뷰 엔티티
@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 (사용자에게 승인/거절 알림)