1 | package edu.ucsb.cs156.dining.controllers; | |
2 | ||
3 | import edu.ucsb.cs156.dining.entities.MenuItem; | |
4 | import edu.ucsb.cs156.dining.entities.Review; | |
5 | import edu.ucsb.cs156.dining.entities.User; | |
6 | import edu.ucsb.cs156.dining.errors.EntityNotFoundException; | |
7 | import edu.ucsb.cs156.dining.models.CurrentUser; | |
8 | import edu.ucsb.cs156.dining.models.EditedReview; | |
9 | import edu.ucsb.cs156.dining.models.Entree; | |
10 | import edu.ucsb.cs156.dining.repositories.MenuItemRepository; | |
11 | import edu.ucsb.cs156.dining.repositories.ReviewRepository; | |
12 | import edu.ucsb.cs156.dining.statuses.ModerationStatus; | |
13 | import io.swagger.v3.oas.annotations.Operation; | |
14 | import io.swagger.v3.oas.annotations.Parameter; | |
15 | import io.swagger.v3.oas.annotations.tags.Tag; | |
16 | import lombok.extern.slf4j.Slf4j; | |
17 | import com.fasterxml.jackson.core.JsonProcessingException; | |
18 | ||
19 | import java.time.LocalDateTime; | |
20 | import java.util.HashMap; | |
21 | import java.util.Map; | |
22 | import java.util.List; | |
23 | import java.util.ArrayList; | |
24 | ||
25 | import org.springframework.beans.factory.annotation.Autowired; | |
26 | import org.springframework.format.annotation.DateTimeFormat; | |
27 | import org.springframework.http.HttpStatus; | |
28 | import org.springframework.http.ResponseEntity; | |
29 | import org.springframework.security.access.AccessDeniedException; | |
30 | import org.springframework.security.access.prepost.PreAuthorize; | |
31 | import org.springframework.web.bind.annotation.DeleteMapping; | |
32 | import org.springframework.web.bind.annotation.ExceptionHandler; | |
33 | import org.springframework.web.bind.annotation.GetMapping; | |
34 | import org.springframework.web.bind.annotation.PostMapping; | |
35 | import org.springframework.web.bind.annotation.PutMapping; | |
36 | import org.springframework.web.bind.annotation.RequestBody; | |
37 | import org.springframework.web.bind.annotation.RequestMapping; | |
38 | import org.springframework.web.bind.annotation.RequestParam; | |
39 | import org.springframework.web.bind.annotation.RestController; | |
40 | import org.springframework.web.server.ResponseStatusException; | |
41 | ||
42 | import com.nimbusds.openid.connect.sdk.claims.UserInfo; | |
43 | ||
44 | import jakarta.validation.Valid; | |
45 | ||
46 | import edu.ucsb.cs156.dining.models.MenuItemReviewAverageRating; | |
47 | /** | |
48 | * This is a REST controller for Reviews | |
49 | */ | |
50 | ||
51 | @Tag(name = "Review") | |
52 | @RequestMapping("/api/reviews") | |
53 | @RestController | |
54 | @Slf4j | |
55 | public class ReviewController extends ApiController { | |
56 | ||
57 | @ExceptionHandler(IllegalArgumentException.class) | |
58 | public ResponseEntity<Map<String, String>> handleValidationExceptions(IllegalArgumentException ex) { | |
59 | Map<String, String> errors = new HashMap<>(); | |
60 | errors.put("error", ex.getMessage()); | |
61 |
1
1. handleValidationExceptions : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::handleValidationExceptions → KILLED |
return ResponseEntity.badRequest().body(errors); |
62 | } | |
63 | ||
64 | @Autowired | |
65 | ReviewRepository reviewRepository; | |
66 | ||
67 | @Autowired | |
68 | MenuItemRepository menuItemRepository; | |
69 | ||
70 | /** | |
71 | * This method returns a list of all Reviews. | |
72 | * | |
73 | * @return a list of all Reviews | |
74 | */ | |
75 | @Operation(summary = "List all Reviews") | |
76 | @PreAuthorize("hasRole('ROLE_ADMIN')") | |
77 | @GetMapping("/all") | |
78 | public Iterable<Review> allReviews() { | |
79 | log.info("Attempting to log all reviews"); | |
80 | Iterable<Review> reviews = reviewRepository.findAll(); | |
81 | log.info("all reviews found, ", reviews); | |
82 |
1
1. allReviews : replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/ReviewController::allReviews → KILLED |
return reviews; |
83 | } | |
84 | ||
85 | /** | |
86 | * This method allows a user to submit a review | |
87 | * | |
88 | * @return message that says an review was added to the database | |
89 | * @param itemId id of the item | |
90 | * @param dateItemServed localDataTime | |
91 | * All others params must not be parameters and instead | |
92 | * derived from data sources that are dynamic (Date), or | |
93 | * set to be null or some other signifier | |
94 | */ | |
95 | @Operation(summary = "Create a new review") | |
96 | @PreAuthorize("hasRole('ROLE_USER')") | |
97 | @PostMapping("/post") | |
98 | public Review postReview( | |
99 | @Parameter(name = "itemId") @RequestParam long itemId, | |
100 | @Parameter(description = "Comments by the reviewer, can be blank") @RequestParam(required = false) String reviewerComments, | |
101 | @Parameter(name = "itemsStars") @RequestParam Long itemsStars, | |
102 | @Parameter(name = "dateItemServed", description = "date (in iso format, e.g. YYYY-mm-ddTHH:MM:SS; see https://en.wikipedia.org/wiki/ISO_8601)") @RequestParam("dateItemServed") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateItemServed) // For | |
103 | throws JsonProcessingException { | |
104 | LocalDateTime now = LocalDateTime.now(); | |
105 | Review review = new Review(); | |
106 |
1
1. postReview : removed call to edu/ucsb/cs156/dining/entities/Review::setDateItemServed → KILLED |
review.setDateItemServed(dateItemServed); |
107 | ||
108 | boolean isEmpty = false; | |
109 | String commentsToSet; | |
110 | ModerationStatus statusToSet; | |
111 |
2
1. postReview : negated conditional → KILLED 2. postReview : negated conditional → KILLED |
if ((reviewerComments == null || reviewerComments.trim().isEmpty())) { |
112 | isEmpty = true; | |
113 | } | |
114 |
1
1. postReview : negated conditional → KILLED |
if (isEmpty) { |
115 | commentsToSet = null; | |
116 | } else { | |
117 | commentsToSet = reviewerComments; | |
118 | } | |
119 |
1
1. postReview : negated conditional → KILLED |
if (isEmpty) { |
120 | statusToSet = ModerationStatus.APPROVED; | |
121 | } else { | |
122 | statusToSet = ModerationStatus.AWAITING_REVIEW; | |
123 | } | |
124 |
1
1. postReview : removed call to edu/ucsb/cs156/dining/entities/Review::setReviewerComments → KILLED |
review.setReviewerComments(commentsToSet); |
125 |
1
1. postReview : removed call to edu/ucsb/cs156/dining/entities/Review::setStatus → KILLED |
review.setStatus(statusToSet); |
126 | // Ensure user inputs rating 1-5 | |
127 |
4
1. postReview : changed conditional boundary → KILLED 2. postReview : negated conditional → KILLED 3. postReview : changed conditional boundary → KILLED 4. postReview : negated conditional → KILLED |
if (itemsStars < 1 || itemsStars > 5) { |
128 | throw new IllegalArgumentException("Items stars must be between 1 and 5."); | |
129 | } | |
130 | ||
131 |
1
1. postReview : removed call to edu/ucsb/cs156/dining/entities/Review::setItemsStars → KILLED |
review.setItemsStars(itemsStars); |
132 | | |
133 | MenuItem reviewedItem = menuItemRepository.findById(itemId).orElseThrow( | |
134 |
1
1. lambda$postReview$0 : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::lambda$postReview$0 → KILLED |
() -> new EntityNotFoundException(MenuItem.class, itemId) |
135 | ); | |
136 |
1
1. postReview : removed call to edu/ucsb/cs156/dining/entities/Review::setItem → KILLED |
review.setItem(reviewedItem); |
137 | CurrentUser user = getCurrentUser(); | |
138 |
1
1. postReview : removed call to edu/ucsb/cs156/dining/entities/Review::setReviewer → KILLED |
review.setReviewer(user.getUser()); |
139 | log.info("reviews={}", review); | |
140 | review = reviewRepository.save(review); | |
141 |
1
1. postReview : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::postReview → KILLED |
return review; |
142 | } | |
143 | ||
144 | /** | |
145 | * This method allows a user to get a list of reviews that they have previously made. | |
146 | * Only user can only get a list of their own reviews, and you cant request another persons reviews | |
147 | * @return a list of reviews sent by a given user | |
148 | */ | |
149 | @Operation(summary = "Get all reviews a user has sent: only callable by the user") | |
150 | @PreAuthorize("hasRole('ROLE_USER')") | |
151 | @GetMapping("/userReviews") | |
152 | public Iterable<Review> get_all_review_by_user_id(){ | |
153 | CurrentUser user = getCurrentUser(); | |
154 | Iterable<Review> reviews = reviewRepository.findByReviewer(user.getUser()); | |
155 |
1
1. get_all_review_by_user_id : replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/ReviewController::get_all_review_by_user_id → KILLED |
return reviews; |
156 | } | |
157 | ||
158 | @Operation(summary = "Edit a review") | |
159 | @PreAuthorize("hasRole('ROLE_USER')") | |
160 | @PutMapping("/reviewer") | |
161 | public Review editReview(@Parameter Long id, @RequestBody @Valid EditedReview incoming) { | |
162 | ||
163 | Review oldReview = reviewRepository.findById(id).orElseThrow( | |
164 |
1
1. lambda$editReview$1 : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::lambda$editReview$1 → KILLED |
() -> new EntityNotFoundException(Review.class, id) |
165 | ); | |
166 | User current = getCurrentUser().getUser(); | |
167 |
1
1. editReview : negated conditional → KILLED |
if(current.getId() != oldReview.getReviewer().getId()) { |
168 | throw new AccessDeniedException("No permission to edit review"); | |
169 | } | |
170 | ||
171 |
4
1. editReview : negated conditional → KILLED 2. editReview : changed conditional boundary → KILLED 3. editReview : negated conditional → KILLED 4. editReview : changed conditional boundary → KILLED |
if(incoming.getItemStars() < 1 || incoming.getItemStars() > 5) { |
172 | throw new IllegalArgumentException("Items stars must be between 1 and 5."); | |
173 | }else{ | |
174 |
1
1. editReview : removed call to edu/ucsb/cs156/dining/entities/Review::setItemsStars → KILLED |
oldReview.setItemsStars(incoming.getItemStars()); |
175 | } | |
176 | ||
177 |
2
1. editReview : negated conditional → KILLED 2. editReview : negated conditional → KILLED |
if (incoming.getReviewerComments() != null &&!incoming.getReviewerComments().trim().isEmpty()) { |
178 |
1
1. editReview : removed call to edu/ucsb/cs156/dining/entities/Review::setReviewerComments → KILLED |
oldReview.setReviewerComments(incoming.getReviewerComments()); |
179 |
1
1. editReview : removed call to edu/ucsb/cs156/dining/entities/Review::setStatus → KILLED |
oldReview.setStatus(ModerationStatus.AWAITING_REVIEW); |
180 | }else{ | |
181 |
1
1. editReview : removed call to edu/ucsb/cs156/dining/entities/Review::setReviewerComments → KILLED |
oldReview.setReviewerComments(null); |
182 |
1
1. editReview : removed call to edu/ucsb/cs156/dining/entities/Review::setStatus → KILLED |
oldReview.setStatus(ModerationStatus.APPROVED); |
183 | } | |
184 | ||
185 |
1
1. editReview : removed call to edu/ucsb/cs156/dining/entities/Review::setDateItemServed → KILLED |
oldReview.setDateItemServed(incoming.getDateItemServed()); |
186 | ||
187 |
1
1. editReview : removed call to edu/ucsb/cs156/dining/entities/Review::setModeratorComments → KILLED |
oldReview.setModeratorComments(null); |
188 | ||
189 | Review review = reviewRepository.save(oldReview); | |
190 | ||
191 |
1
1. editReview : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::editReview → KILLED |
return review; |
192 | } | |
193 | ||
194 | @Operation(summary = "Delete a review") | |
195 | @PreAuthorize("hasRole('ROLE_USER')") | |
196 | @DeleteMapping("/reviewer") | |
197 | public Object deleteReview(@Parameter Long id) { | |
198 | Review review = reviewRepository.findById(id).orElseThrow( | |
199 |
1
1. lambda$deleteReview$2 : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::lambda$deleteReview$2 → KILLED |
() -> new EntityNotFoundException(Review.class, id) |
200 | ); | |
201 | ||
202 | User current = getCurrentUser().getUser(); | |
203 |
2
1. deleteReview : negated conditional → KILLED 2. deleteReview : negated conditional → KILLED |
if(current.getId() != review.getReviewer().getId() && !current.getAdmin()) { |
204 | throw new AccessDeniedException("No permission to delete review"); | |
205 | } | |
206 | ||
207 |
1
1. deleteReview : removed call to edu/ucsb/cs156/dining/repositories/ReviewRepository::delete → KILLED |
reviewRepository.delete(review); |
208 |
1
1. deleteReview : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::deleteReview → KILLED |
return genericMessage("Review with id %s deleted".formatted(id)); |
209 | } | |
210 | ||
211 | @Operation(summary = "Moderate a review") | |
212 | @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_MODERATOR')") | |
213 | @PutMapping("/moderate") | |
214 | public Review moderateReview(@Parameter Long id, @Parameter ModerationStatus status, @Parameter String moderatorComments) { | |
215 | Review review = reviewRepository.findById(id).orElseThrow( | |
216 |
1
1. lambda$moderateReview$3 : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::lambda$moderateReview$3 → KILLED |
() -> new EntityNotFoundException(Review.class, id) |
217 | ); | |
218 | ||
219 |
1
1. moderateReview : removed call to edu/ucsb/cs156/dining/entities/Review::setModeratorComments → KILLED |
review.setModeratorComments(moderatorComments); |
220 |
1
1. moderateReview : removed call to edu/ucsb/cs156/dining/entities/Review::setStatus → KILLED |
review.setStatus(status); |
221 | ||
222 | review = reviewRepository.save(review); | |
223 |
1
1. moderateReview : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::moderateReview → KILLED |
return review; |
224 | } | |
225 | ||
226 | @Operation(summary = "See reviews that need moderation") | |
227 | @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_MODERATOR')") | |
228 | @GetMapping("/needsmoderation") | |
229 | public Iterable<Review> needsmoderation() { | |
230 | Iterable<Review> reviewsList = reviewRepository.findByStatus(ModerationStatus.AWAITING_REVIEW); | |
231 |
1
1. needsmoderation : replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/ReviewController::needsmoderation → KILLED |
return reviewsList; |
232 | } | |
233 | ||
234 | @Operation(summary = "Get a specific single review ID") | |
235 | @PreAuthorize("hasRole('ROLE_USER')") | |
236 | @GetMapping("/get") | |
237 | public Review getReviewByID(@Parameter Long id) { | |
238 | Review review = reviewRepository.findById(id).orElseThrow( | |
239 |
1
1. lambda$getReviewByID$4 : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::lambda$getReviewByID$4 → KILLED |
() -> new EntityNotFoundException(Review.class, id) |
240 | ); | |
241 | ||
242 | User current = getCurrentUser().getUser(); | |
243 |
2
1. getReviewByID : negated conditional → KILLED 2. getReviewByID : negated conditional → KILLED |
if(current.getId() != review.getReviewer().getId() && !current.getAdmin()) { |
244 | throw new AccessDeniedException("Only user who made this review or admin can get id"); | |
245 | } | |
246 |
1
1. getReviewByID : replaced return value with null for edu/ucsb/cs156/dining/controllers/ReviewController::getReviewByID → KILLED |
return review; |
247 | } | |
248 | | |
249 | @Operation(summary = "Get average rating for each menu item") | |
250 | @PreAuthorize("hasRole('ROLE_USER')") | |
251 | @GetMapping("/averageRatingPerMenuItem") | |
252 | public Iterable<MenuItemReviewAverageRating> getAverageRatingPerMenuItem() { | |
253 | Iterable<MenuItem> items = menuItemRepository.findAll(); | |
254 | List<MenuItemReviewAverageRating> result = new ArrayList<>(); | |
255 | ||
256 | for (MenuItem item : items) { | |
257 | Iterable<Review> reviews = reviewRepository.findByItemId(item.getId()); | |
258 | ||
259 | int count = 0; | |
260 | long sum = 0; | |
261 | ||
262 | for (Review review : reviews) { | |
263 |
1
1. getAverageRatingPerMenuItem : negated conditional → KILLED |
if (review.getItemsStars() != null) { |
264 |
1
1. getAverageRatingPerMenuItem : Replaced long addition with subtraction → KILLED |
sum += review.getItemsStars(); |
265 |
1
1. getAverageRatingPerMenuItem : Changed increment from 1 to -1 → KILLED |
count++; |
266 | } | |
267 | } | |
268 | ||
269 | Double avg; | |
270 | ||
271 |
1
1. getAverageRatingPerMenuItem : negated conditional → KILLED |
if (count == 0){ |
272 | avg = null; | |
273 | } | |
274 | else { | |
275 |
1
1. getAverageRatingPerMenuItem : Replaced double division with multiplication → KILLED |
avg = (sum / (double) count); |
276 | ||
277 | } | |
278 | | |
279 | MenuItemReviewAverageRating entry = new MenuItemReviewAverageRating(); | |
280 |
1
1. getAverageRatingPerMenuItem : removed call to edu/ucsb/cs156/dining/models/MenuItemReviewAverageRating::setId → KILLED |
entry.setId(item.getId()); |
281 |
1
1. getAverageRatingPerMenuItem : removed call to edu/ucsb/cs156/dining/models/MenuItemReviewAverageRating::setName → KILLED |
entry.setName(item.getName()); |
282 |
1
1. getAverageRatingPerMenuItem : removed call to edu/ucsb/cs156/dining/models/MenuItemReviewAverageRating::setStation → KILLED |
entry.setStation(item.getStation()); |
283 |
1
1. getAverageRatingPerMenuItem : removed call to edu/ucsb/cs156/dining/models/MenuItemReviewAverageRating::setDiningCommonsCode → KILLED |
entry.setDiningCommonsCode(item.getDiningCommonsCode()); |
284 |
1
1. getAverageRatingPerMenuItem : removed call to edu/ucsb/cs156/dining/models/MenuItemReviewAverageRating::setAverageRating → KILLED |
entry.setAverageRating(avg); |
285 | ||
286 | result.add(entry); | |
287 | } | |
288 | ||
289 |
1
1. getAverageRatingPerMenuItem : replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/ReviewController::getAverageRatingPerMenuItem → KILLED |
return result; |
290 | } | |
291 | } | |
292 | ||
293 | ||
Mutations | ||
61 |
1.1 |
|
82 |
1.1 |
|
106 |
1.1 |
|
111 |
1.1 2.2 |
|
114 |
1.1 |
|
119 |
1.1 |
|
124 |
1.1 |
|
125 |
1.1 |
|
127 |
1.1 2.2 3.3 4.4 |
|
131 |
1.1 |
|
134 |
1.1 |
|
136 |
1.1 |
|
138 |
1.1 |
|
141 |
1.1 |
|
155 |
1.1 |
|
164 |
1.1 |
|
167 |
1.1 |
|
171 |
1.1 2.2 3.3 4.4 |
|
174 |
1.1 |
|
177 |
1.1 2.2 |
|
178 |
1.1 |
|
179 |
1.1 |
|
181 |
1.1 |
|
182 |
1.1 |
|
185 |
1.1 |
|
187 |
1.1 |
|
191 |
1.1 |
|
199 |
1.1 |
|
203 |
1.1 2.2 |
|
207 |
1.1 |
|
208 |
1.1 |
|
216 |
1.1 |
|
219 |
1.1 |
|
220 |
1.1 |
|
223 |
1.1 |
|
231 |
1.1 |
|
239 |
1.1 |
|
243 |
1.1 2.2 |
|
246 |
1.1 |
|
263 |
1.1 |
|
264 |
1.1 |
|
265 |
1.1 |
|
271 |
1.1 |
|
275 |
1.1 |
|
280 |
1.1 |
|
281 |
1.1 |
|
282 |
1.1 |
|
283 |
1.1 |
|
284 |
1.1 |
|
289 |
1.1 |