| 1 | package edu.ucsb.cs156.dining.controllers; | |
| 2 | ||
| 3 | import edu.ucsb.cs156.dining.repositories.ReviewRepository; | |
| 4 | import edu.ucsb.cs156.dining.repositories.projections.CommonsRatingProjection; | |
| 5 | import edu.ucsb.cs156.dining.repositories.projections.ItemRatingProjection; | |
| 6 | import edu.ucsb.cs156.dining.statuses.ModerationStatus; | |
| 7 | import edu.ucsb.cs156.dining.util.StatsWindow; | |
| 8 | import io.swagger.v3.oas.annotations.Operation; | |
| 9 | import io.swagger.v3.oas.annotations.tags.Tag; | |
| 10 | import java.time.LocalDateTime; | |
| 11 | import java.util.List; | |
| 12 | import lombok.extern.slf4j.Slf4j; | |
| 13 | import org.springframework.beans.factory.annotation.Autowired; | |
| 14 | import org.springframework.data.domain.PageRequest; | |
| 15 | import org.springframework.security.access.prepost.PreAuthorize; | |
| 16 | import org.springframework.web.bind.annotation.GetMapping; | |
| 17 | import org.springframework.web.bind.annotation.RequestMapping; | |
| 18 | import org.springframework.web.bind.annotation.RequestParam; | |
| 19 | import org.springframework.web.bind.annotation.RestController; | |
| 20 | ||
| 21 | @Tag(name = "Statistics") | |
| 22 | @RequestMapping("/api/statistics") | |
| 23 | @RestController | |
| 24 | @Slf4j | |
| 25 | public class StatisticsController extends ApiController { | |
| 26 | ||
| 27 | private static final int DEFAULT_LIMIT = 10; | |
| 28 | private static final int MAX_LIMIT = 50; | |
| 29 | private static final long DEFAULT_MIN_REVIEWS = 3; | |
| 30 | ||
| 31 | @Autowired ReviewRepository reviewRepository; | |
| 32 | ||
| 33 | public record StatisticsSummary( | |
| 34 | long totalApprovedReviews, | |
| 35 | long totalMenuItemsReviewed, | |
| 36 | long totalCommonsCovered, | |
| 37 | LocalDateTime lastUpdated) {} | |
| 38 | ||
| 39 | public record RatedItem( | |
| 40 | Long itemId, | |
| 41 | String name, | |
| 42 | String diningCommonsCode, | |
| 43 | String mealCode, | |
| 44 | Double avgStars, | |
| 45 | Long reviewCount) {} | |
| 46 | ||
| 47 | public record CommonsAverage(String diningCommonsCode, Double avgStars, Long reviewCount) {} | |
| 48 | ||
| 49 | @Operation(summary = "Get a summary of review statistics") | |
| 50 | @PreAuthorize("hasRole('ROLE_USER')") | |
| 51 | @GetMapping(value = "", produces = "application/json") | |
| 52 | public StatisticsSummary getSummary() { | |
| 53 |
1
1. getSummary : replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::getSummary → KILLED |
return new StatisticsSummary( |
| 54 | reviewRepository.countByStatus(ModerationStatus.APPROVED), | |
| 55 | reviewRepository.countDistinctItemsByStatus(ModerationStatus.APPROVED), | |
| 56 | reviewRepository.countDistinctCommonsByStatus(ModerationStatus.APPROVED), | |
| 57 | reviewRepository.findMaxDateEditedByStatus(ModerationStatus.APPROVED)); | |
| 58 | } | |
| 59 | ||
| 60 | LocalDateTime sinceFor(StatsWindow window) { | |
| 61 |
1
1. sinceFor : replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::sinceFor → KILLED |
return window.since(LocalDateTime.now()); |
| 62 | } | |
| 63 | ||
| 64 | private static int clampLimit(int limit) { | |
| 65 |
1
1. clampLimit : replaced int return with 0 for edu/ucsb/cs156/dining/controllers/StatisticsController::clampLimit → KILLED |
return Math.min(Math.max(limit, 1), MAX_LIMIT); |
| 66 | } | |
| 67 | ||
| 68 | private static long clampMinReviews(long minReviews) { | |
| 69 |
1
1. clampMinReviews : replaced long return with 0 for edu/ucsb/cs156/dining/controllers/StatisticsController::clampMinReviews → KILLED |
return Math.max(minReviews, 1L); |
| 70 | } | |
| 71 | ||
| 72 | private RatedItem toRatedItem(ItemRatingProjection p) { | |
| 73 |
1
1. toRatedItem : replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::toRatedItem → KILLED |
return new RatedItem( |
| 74 | p.getItemId(), | |
| 75 | p.getName(), | |
| 76 | p.getDiningCommonsCode(), | |
| 77 | p.getMealCode(), | |
| 78 | p.getAvgStars(), | |
| 79 | p.getReviewCount()); | |
| 80 | } | |
| 81 | ||
| 82 | private CommonsAverage toCommonsAverage(CommonsRatingProjection p) { | |
| 83 |
1
1. toCommonsAverage : replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::toCommonsAverage → KILLED |
return new CommonsAverage(p.getDiningCommonsCode(), p.getAvgStars(), p.getReviewCount()); |
| 84 | } | |
| 85 | ||
| 86 | @Operation(summary = "Get best rated menu items by average stars") | |
| 87 | @PreAuthorize("hasRole('ROLE_USER')") | |
| 88 | @GetMapping(value = "/items/best", produces = "application/json") | |
| 89 | public List<RatedItem> bestRatedItems( | |
| 90 | @RequestParam(defaultValue = "ALL") StatsWindow window, | |
| 91 | @RequestParam(defaultValue = "" + DEFAULT_LIMIT) int limit, | |
| 92 | @RequestParam(name = "minReviews", defaultValue = "" + DEFAULT_MIN_REVIEWS) long minReviews) { | |
| 93 | int clampedLimit = clampLimit(limit); | |
| 94 | long clampedMinReviews = clampMinReviews(minReviews); | |
| 95 | ||
| 96 | LocalDateTime since = sinceFor(window); | |
| 97 | List<ItemRatingProjection> projections = | |
| 98 | reviewRepository.findTopRatedItems( | |
| 99 | ModerationStatus.APPROVED, since, clampedMinReviews, PageRequest.of(0, clampedLimit)); | |
| 100 | ||
| 101 |
1
1. bestRatedItems : replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/StatisticsController::bestRatedItems → KILLED |
return projections.stream().map(this::toRatedItem).toList(); |
| 102 | } | |
| 103 | ||
| 104 | @Operation(summary = "Get worst rated menu items by average stars") | |
| 105 | @PreAuthorize("hasRole('ROLE_USER')") | |
| 106 | @GetMapping(value = "/items/worst", produces = "application/json") | |
| 107 | public List<RatedItem> worstRatedItems( | |
| 108 | @RequestParam(defaultValue = "ALL") StatsWindow window, | |
| 109 | @RequestParam(defaultValue = "" + DEFAULT_LIMIT) int limit, | |
| 110 | @RequestParam(name = "minReviews", defaultValue = "" + DEFAULT_MIN_REVIEWS) long minReviews) { | |
| 111 | int clampedLimit = clampLimit(limit); | |
| 112 | long clampedMinReviews = clampMinReviews(minReviews); | |
| 113 | ||
| 114 | LocalDateTime since = sinceFor(window); | |
| 115 | List<ItemRatingProjection> projections = | |
| 116 | reviewRepository.findBottomRatedItems( | |
| 117 | ModerationStatus.APPROVED, since, clampedMinReviews, PageRequest.of(0, clampedLimit)); | |
| 118 | ||
| 119 |
1
1. worstRatedItems : replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/StatisticsController::worstRatedItems → KILLED |
return projections.stream().map(this::toRatedItem).toList(); |
| 120 | } | |
| 121 | ||
| 122 | @Operation(summary = "Get average star ratings grouped by dining commons") | |
| 123 | @PreAuthorize("hasRole('ROLE_USER')") | |
| 124 | @GetMapping(value = "/commons/averages", produces = "application/json") | |
| 125 | public List<CommonsAverage> commonsAverages( | |
| 126 | @RequestParam(defaultValue = "ALL") StatsWindow window) { | |
| 127 | LocalDateTime since = sinceFor(window); | |
| 128 | List<CommonsRatingProjection> projections = | |
| 129 | reviewRepository.findCommonsAverages(ModerationStatus.APPROVED, since); | |
| 130 | ||
| 131 |
1
1. commonsAverages : replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/StatisticsController::commonsAverages → KILLED |
return projections.stream().map(this::toCommonsAverage).toList(); |
| 132 | } | |
| 133 | } | |
Mutations | ||
| 53 |
1.1 |
|
| 61 |
1.1 |
|
| 65 |
1.1 |
|
| 69 |
1.1 |
|
| 73 |
1.1 |
|
| 83 |
1.1 |
|
| 101 |
1.1 |
|
| 119 |
1.1 |
|
| 131 |
1.1 |