ReviewsController.java

package edu.ucsb.cs156.dining.controllers;

import edu.ucsb.cs156.dining.entities.Review;
import edu.ucsb.cs156.dining.errors.EntityNotFoundException;
import edu.ucsb.cs156.dining.repositories.ReviewRepository;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;

import com.fasterxml.jackson.core.JsonProcessingException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.http.HttpStatus;

import jakarta.validation.Valid;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

import java.util.Arrays;
import java.util.ArrayList;

/**
 * This is a REST controller for Reviews
 */

@Tag(name = "Reviews")
@RequestMapping("/api/reviews")
@RestController
@Slf4j
public class ReviewsController extends ApiController {

    @Autowired
    ReviewRepository reviewsRepository;

    
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public void handleIllegalArgumentException() {

    }

    /**
     * List all reviews
     * 
     * @return an iterable of Review
     */
    @Operation(summary= "List all reviews")
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/all")
    public Iterable<Review> allReviews() {
        Iterable<Review> reviews = reviewsRepository.findAll();
        return reviews;
    }

    /**
     * List all reviews created by a specific user
     * 
     * @return an iterable of Review
     */
    @Operation(summary= "List all reviews created by a specific user")
    @PreAuthorize("hasRole('ROLE_USER')")
    @GetMapping("")
    public Iterable<Review> getReviewsByUser() {
        Iterable<Review> reviews = reviewsRepository.findAllByReviewerId(getCurrentUser().getUser().getId());
        return reviews;
    }

    /**
     * List all reviews needing moderation
     * 
     * @return an iterable of Review
     */
    @Operation(summary= "List all reviews needing moderation")
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/needsmoderation")
    public Iterable<Review> getReviewsNeedingModeration() {
        Iterable<Review> reviews = reviewsRepository.findAllByStatus("Awaiting Moderation");
        return reviews;
    }

    /**
     * Create a new review
     * 
     * @param itemId            id of item in DiningCommonsMenuItem table
     * @param dateServed        date item was served
     * @param stars             rating from 0-5 inclusive
     * @param reviewText        reviewer comments
     * @return the saved review
     */
    @Operation(summary= "Create a new review")
    @PreAuthorize("hasRole('ROLE_USER')")
    @PostMapping("/post")
    public Review postReview(
            @Parameter(name="itemId") @RequestParam long itemId,
            @Parameter(name="dateServed", description="date (in iso format, e.g. YYYY-mm-ddTHH:MM:SS") @RequestParam("dateServed") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateServed,
            @Parameter(name="stars") @RequestParam long stars,
            @Parameter(name="reviewText") @RequestParam String reviewText)
            throws JsonProcessingException {

        // For an explanation of @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
        // See: https://www.baeldung.com/spring-date-parameters

        LocalDateTime createdDate = LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS);

        log.info("createdDate={}", createdDate.format(DateTimeFormatter.ISO_DATE_TIME));

        Review reviews = new Review();
        reviews.setReviewerId(getCurrentUser().getUser().getId());
        reviews.setItemId(itemId);
        reviews.setDateServed(dateServed);
        reviews.setStars(stars);
        reviews.setReviewText(reviewText);
        reviews.setStatus("Awaiting Moderation");
        reviews.setCreatedDate(createdDate);
        reviews.setLastEditedDate(createdDate);

        Review savedReview = reviewsRepository.save(reviews);

        return savedReview;
    }
    
    /**
     * Update a single review
     * 
     * @param id        id of the review to update
     * @param incoming  the new review
     * @return the updated review object
     */
    @Operation(summary= "Update a single review")
    @PreAuthorize("hasRole('ROLE_USER')")
    @PutMapping("/reviewer")
    public Review updateReview(
            @Parameter(name="id") @RequestParam Long id,
            @RequestBody @Valid Review incoming) {
            
        Review review = reviewsRepository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException(Review.class, id));

        review.setStars(incoming.getStars());
        review.setReviewText(incoming.getReviewText());
        review.setStatus("Awaiting Moderation");
        review.setModId(null);
        review.setModComments(null);
        review.setLastEditedDate(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));

        reviewsRepository.save(review);

        return review;
    }

    /**
     * Moderate a single review
     * 
     * @param id        id of the review to moderate
     * @param incoming  the new review
     * @return the updated review object
     */
    @Operation(summary= "Moderate a single review")
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @PutMapping("/moderator")
    public Review moderateReview(
            @Parameter(name="id") @RequestParam Long id,
            @RequestBody @Valid Review incoming) {


        Review review = reviewsRepository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException(Review.class, id));
        
        ArrayList<String> validStatus = new ArrayList<>(Arrays.asList("Approved", "Awaiting Moderation", "Rejected"));

        if( !validStatus.contains(incoming.getStatus()) ) {
            throw new IllegalArgumentException("Status must be one of: " + validStatus);
        } 

        review.setStatus(incoming.getStatus());
        review.setModComments(incoming.getModComments());
        review.setModId(getCurrentUser().getUser().getId());
        review.setLastEditedDate(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));

        reviewsRepository.save(review);

        return review;
    }

}