Review Create Service
Building on our ReviewService interface from the previous lesson, we'll now implement the core logic for creating restaurant reviews, including handling photos and updating restaurant ratings.
Service Implementation
The ReviewServiceImpl class is responsible for handling the business logic of creating reviews while maintaining data consistency.
Let's now implement the createReview method on the ReviewServiceImpl class:
package com.devtiro.restaurant.services.impl;
import com.devtiro.restaurant.domain.ReviewCreateUpdateRequest;
import com.devtiro.restaurant.domain.entities.Photo;
import com.devtiro.restaurant.domain.entities.Restaurant;
import com.devtiro.restaurant.domain.entities.Review;
import com.devtiro.restaurant.domain.entities.User;
import com.devtiro.restaurant.exceptions.RestaurantNotFoundException;
import com.devtiro.restaurant.exceptions.ReviewNotAllowedException;
import com.devtiro.restaurant.repositories.RestaurantRepository;
import com.devtiro.restaurant.services.ReviewService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class ReviewServiceImpl implements ReviewService {
private final RestaurantRepository restaurantRepository;
@Override
public Review createReview(User author, String restaurantId, ReviewCreateUpdateRequest createReview) {
Restaurant restaurant = getRestaurantOrThrow(restaurantId);
// Check if user has already reviewed this restaurant
boolean hasExistingReview = restaurant.getReviews().stream()
.anyMatch(r -> r.getWrittenBy().getId().equals(author.getId()));
if (hasExistingReview) {
throw new ReviewNotAllowedException("User has already reviewed this restaurant");
}
LocalDateTime now = LocalDateTime.now();
// Create photos
List<Photo> photos = createReview.getPhotoIds().stream().map(url -> {
Photo photo = new Photo();
photo.setUrl(url);
photo.setUploadDate(now);
return photo;
}).collect(Collectors.toList());
// Create review
Review review = Review.builder()
.id(UUID.randomUUID().toString())
.content(createReview.getContent())
.rating(createReview.getRating())
.photos(photos)
.datePosted(now)
.lastEdited(now)
.writtenBy(author)
.build();
// Add review to restaurant
restaurant.getReviews().add(review);
// Update restaurant's average rating
updateRestaurantAverageRating(restaurant);
// Save restaurant with new review
Restaurant updatedRestaurant = restaurantRepository.save(restaurant);
// Return the newly created review
return updatedRestaurant.getReviews().stream()
.filter(r -> r.getDatePosted().equals(review.getDatePosted()))
.findFirst()
.orElseThrow(() -> new RuntimeException("Error retrieving created review"));
}
private Restaurant getRestaurantOrThrow(String restaurantId) {
return restaurantRepository.findById(restaurantId)
.orElseThrow(() -> new RestaurantNotFoundException("Restaurant not found with id: " + restaurantId));
}
private void updateRestaurantAverageRating(Restaurant restaurant) {
List<Review> reviews = restaurant.getReviews();
if (reviews.isEmpty()) {
restaurant.setAverageRating(0.0f);
} else {
float averageRating = (float) reviews.stream()
.mapToDouble(Review::getRating)
.average()
.orElse(0.0);
restaurant.setAverageRating(averageRating);
}
}
}Summary
- Implemented
ReviewServiceImplwith duplicate review prevention - Created helper methods for restaurant lookup and rating calculations
- Updated restaurant average ratings