diff --git a/00-backend/datasource/database.sqlite b/00-backend/datasource/database.sqlite index 50edaa2..47cafb1 100644 Binary files a/00-backend/datasource/database.sqlite and b/00-backend/datasource/database.sqlite differ diff --git a/00-backend/src/main/java/de/htwsaar/webshop/config/ControllerPathConfig.java b/00-backend/src/main/java/de/htwsaar/webshop/config/ControllerPathConfig.java index 8b06f3e..c536e5d 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/config/ControllerPathConfig.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/config/ControllerPathConfig.java @@ -41,4 +41,10 @@ public class ControllerPathConfig { //ReviewController public static final String REVIEW_BASE = "/review"; public static final String REVIEW_GET_ALL = REVIEW_BASE + "/all"; + + //StatisticsController + private static final String STATISTICS_BASE = "/statistics"; + public static final String STATISTICS_VOLUME = STATISTICS_BASE + "/volume"; + public static final String STATISTICS_REVENUE = STATISTICS_BASE + "/revenue"; + } \ No newline at end of file diff --git a/00-backend/src/main/java/de/htwsaar/webshop/config/ParameterConfig.java b/00-backend/src/main/java/de/htwsaar/webshop/config/ParameterConfig.java index 2191a76..85dd7e8 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/config/ParameterConfig.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/config/ParameterConfig.java @@ -13,4 +13,5 @@ public class ParameterConfig { public static final String PARAM_IMAGE = "image"; public static final String PARAM_STATUS = "status"; public static final String PARAM_STANDARD = "standard"; + public static final String PARAM_SESSION = "session"; } diff --git a/00-backend/src/main/java/de/htwsaar/webshop/controller/OrderController.java b/00-backend/src/main/java/de/htwsaar/webshop/controller/OrderController.java index 127dc05..c073219 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/controller/OrderController.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/controller/OrderController.java @@ -63,7 +63,8 @@ public class OrderController { log.warn("[{}] failed Validation, sending bad request", request.getRequestURI()); return ResponseEntity.badRequest().body(false); } - + order.setId(null); + order.getOrderItems().forEach(orderItem -> orderItem.setId(null)); Order saved = orderService.save(order); return ResponseEntity.ok(saved != null); } diff --git a/00-backend/src/main/java/de/htwsaar/webshop/controller/StatisticsController.java b/00-backend/src/main/java/de/htwsaar/webshop/controller/StatisticsController.java new file mode 100644 index 0000000..8dfddd1 --- /dev/null +++ b/00-backend/src/main/java/de/htwsaar/webshop/controller/StatisticsController.java @@ -0,0 +1,58 @@ +package de.htwsaar.webshop.controller; + +import de.htwsaar.webshop.model.MonthlyCatModel; +import de.htwsaar.webshop.service.SessionService; +import de.htwsaar.webshop.service.StatisticsService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; + +import static de.htwsaar.webshop.config.ControllerPathConfig.STATISTICS_REVENUE; +import static de.htwsaar.webshop.config.ControllerPathConfig.STATISTICS_VOLUME; +import static de.htwsaar.webshop.config.ParameterConfig.PARAM_EMAIL; +import static de.htwsaar.webshop.config.ParameterConfig.PARAM_SESSION; +import static de.htwsaar.webshop.util.LoggerUtil.logRequest; + +@RestController +@Slf4j +public class StatisticsController { + + private final StatisticsService statisticsService; + private final SessionService sessionService; + + public StatisticsController(StatisticsService statisticsService, SessionService sessionService) { + this.statisticsService = statisticsService; + this.sessionService = sessionService; + } + + @RequestMapping(value = STATISTICS_VOLUME, method = RequestMethod.GET, produces = "application/json") + public ResponseEntity> getMonthlySalesVolume(HttpServletRequest request, + @RequestParam(value = PARAM_SESSION) UUID session, + @RequestParam(value = PARAM_EMAIL) String email) { + logRequest(request); + if (!sessionService.isAdmin(session, email)) { + log.warn("Invalid session requesting Admin {}", session); + return ResponseEntity.status(403).build(); + } + return ResponseEntity.ok(statisticsService.getSalesVolume()); + } + + @RequestMapping(value = STATISTICS_REVENUE, method = RequestMethod.GET, produces = "application/json") + public ResponseEntity> getMonthlyRevenue(HttpServletRequest request, + @RequestParam(value = PARAM_SESSION) UUID token, + @RequestParam(value = PARAM_EMAIL) String email) { + logRequest(request); + if (!sessionService.isAdmin(token, email)) { + log.warn("Invalid session requesting Admin {}", token); + return ResponseEntity.status(403).build(); + } + return ResponseEntity.ok(statisticsService.getSalesRevenue()); + } + +} diff --git a/00-backend/src/main/java/de/htwsaar/webshop/model/MonthlyCatModel.java b/00-backend/src/main/java/de/htwsaar/webshop/model/MonthlyCatModel.java new file mode 100644 index 0000000..1ada3cf --- /dev/null +++ b/00-backend/src/main/java/de/htwsaar/webshop/model/MonthlyCatModel.java @@ -0,0 +1,15 @@ +package de.htwsaar.webshop.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +@Getter +@Setter +@AllArgsConstructor +public class MonthlyCatModel { + Map> monthCategoryMap; + +} diff --git a/00-backend/src/main/java/de/htwsaar/webshop/repository/OrderRepository.java b/00-backend/src/main/java/de/htwsaar/webshop/repository/OrderRepository.java index a37d19a..f1456c8 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/repository/OrderRepository.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/repository/OrderRepository.java @@ -9,4 +9,6 @@ import java.util.List; @Repository public interface OrderRepository extends JpaRepository { List getOrdersByCustomer_Id(Long customerId); + + List getOrdersByTimeBetween(Long timeAfter, Long timeBefore); } diff --git a/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/FarmImage.java b/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/FarmImage.java index 6af5a75..65f772e 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/FarmImage.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/FarmImage.java @@ -11,7 +11,7 @@ import org.hibernate.type.NumericBooleanConverter; @AllArgsConstructor @ToString @Entity -@Table(name = "FarmImages") +@Table(name = "Farm_Images") public class FarmImage { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/Order.java b/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/Order.java index 10bbc72..01d632e 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/Order.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/Order.java @@ -31,10 +31,9 @@ public class Order { @Column(name = "status", nullable = false) private OrderStatus status; - @OneToMany(mappedBy = "order", orphanRemoval = true, fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = OrderItem.class) + @OneToMany(mappedBy = "order", orphanRemoval = true, fetch = FetchType.EAGER, cascade = CascadeType.ALL, targetEntity = OrderItem.class) private List orderItems; - public OrderModel toModel() { return new OrderModel(id, customer.getId(), time, status, orderItems.stream().map(OrderItem::toModel).toList()); } diff --git a/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/OrderItem.java b/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/OrderItem.java index 5f4fd88..b4e5968 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/OrderItem.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/repository/entities/OrderItem.java @@ -14,7 +14,7 @@ import org.hibernate.annotations.OnDelete; @AllArgsConstructor @NoArgsConstructor @Entity -@Table(name = "OrderItems") +@Table(name = "Order_Items") public class OrderItem { @Id @@ -25,8 +25,8 @@ public class OrderItem { @Min(value = 1, message = "Amount must be at least 1") private Integer amount; - @ManyToOne(fetch = FetchType.LAZY) - @MapsId("orderId") + @ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL, targetEntity = Order.class) + @MapsId("order_id") @JoinColumn(name = "order_id", referencedColumnName = "id", nullable = false) @OnDelete(action = org.hibernate.annotations.OnDeleteAction.CASCADE) private Order order; diff --git a/00-backend/src/main/java/de/htwsaar/webshop/service/OrderService.java b/00-backend/src/main/java/de/htwsaar/webshop/service/OrderService.java index 802dc7e..b1d0df4 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/service/OrderService.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/service/OrderService.java @@ -4,6 +4,7 @@ import de.htwsaar.webshop.model.OrderModel; import de.htwsaar.webshop.repository.entities.Order; import java.util.List; +import java.util.Map; public interface OrderService { Order save(Order order); @@ -15,4 +16,6 @@ public interface OrderService { Order getOrderById(Long orderId); List getAllOrders(Long customerId); + + Map> getTimeSortedOrders(long fromMilli, long months); } diff --git a/00-backend/src/main/java/de/htwsaar/webshop/service/SessionService.java b/00-backend/src/main/java/de/htwsaar/webshop/service/SessionService.java index f75221a..71fe2ae 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/service/SessionService.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/service/SessionService.java @@ -18,5 +18,5 @@ public interface SessionService { boolean isValid(UUID token, String email); - boolean isAdmin(Session session, String email); + boolean isAdmin(UUID token, String email); } diff --git a/00-backend/src/main/java/de/htwsaar/webshop/service/StatisticsService.java b/00-backend/src/main/java/de/htwsaar/webshop/service/StatisticsService.java new file mode 100644 index 0000000..50ee146 --- /dev/null +++ b/00-backend/src/main/java/de/htwsaar/webshop/service/StatisticsService.java @@ -0,0 +1,10 @@ +package de.htwsaar.webshop.service; + +import de.htwsaar.webshop.model.MonthlyCatModel; + +public interface StatisticsService { + MonthlyCatModel getSalesVolume(); + + MonthlyCatModel getSalesRevenue(); + +} diff --git a/00-backend/src/main/java/de/htwsaar/webshop/service/impl/OrderServiceImpl.java b/00-backend/src/main/java/de/htwsaar/webshop/service/impl/OrderServiceImpl.java index d1d081c..58a239c 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/service/impl/OrderServiceImpl.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/service/impl/OrderServiceImpl.java @@ -8,11 +8,14 @@ import de.htwsaar.webshop.repository.entities.OrderItem; import de.htwsaar.webshop.service.ArticleService; import de.htwsaar.webshop.service.CustomerService; import de.htwsaar.webshop.service.OrderService; +import de.htwsaar.webshop.util.TimeUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Service @Slf4j @@ -69,4 +72,19 @@ public class OrderServiceImpl implements OrderService { public List getAllOrders(Long customerId) { return orderRepository.getOrdersByCustomer_Id(customerId); } + + @Override + public Map> getTimeSortedOrders(long fromMilli, long months) { + Map> orders = new HashMap<>(); + long lower = fromMilli; + long upper; + while (months > 0) { + upper = fromMilli + TimeUtil.monthLength(fromMilli); + log.info("Getting Orders from {} to {}", lower, upper); + orders.put(fromMilli, orderRepository.getOrdersByTimeBetween(lower, upper)); + lower = upper; + months--; + } + return orders; + } } diff --git a/00-backend/src/main/java/de/htwsaar/webshop/service/impl/SessionServiceImpl.java b/00-backend/src/main/java/de/htwsaar/webshop/service/impl/SessionServiceImpl.java index 5c4a35a..2f6a6a9 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/service/impl/SessionServiceImpl.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/service/impl/SessionServiceImpl.java @@ -72,11 +72,11 @@ public class SessionServiceImpl implements SessionService { } @Override - public boolean isAdmin(Session session, String email) { - if(session == null || email == null) { + public boolean isAdmin(UUID token, String email) { + if(token == null || email == null) { return false; } - if(!isValid(session, email)) { + if(!isValid(token, email)) { log.warn("Invalid Session with email {} trying to access Admin Services", email); return false; } diff --git a/00-backend/src/main/java/de/htwsaar/webshop/service/impl/StatisticsServiceImpl.java b/00-backend/src/main/java/de/htwsaar/webshop/service/impl/StatisticsServiceImpl.java new file mode 100644 index 0000000..197f47b --- /dev/null +++ b/00-backend/src/main/java/de/htwsaar/webshop/service/impl/StatisticsServiceImpl.java @@ -0,0 +1,59 @@ +package de.htwsaar.webshop.service.impl; + +import de.htwsaar.webshop.model.MonthlyCatModel; +import de.htwsaar.webshop.repository.entities.Order; +import de.htwsaar.webshop.repository.entities.OrderItem; +import de.htwsaar.webshop.service.OrderService; +import de.htwsaar.webshop.service.StatisticsService; +import de.htwsaar.webshop.util.TimeUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Map; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class StatisticsServiceImpl implements StatisticsService { + private final OrderService orderService; + + @Autowired + public StatisticsServiceImpl(OrderService orderService) { + this.orderService = orderService; + } + + //don't ask pls + //returns Map> + private MonthlyCatModel getMonthCategoryMap(Function mappingFunction, + BinaryOperator reduceFunction) { + return new MonthlyCatModel<>( + orderService.getTimeSortedOrders(TimeUtil.nowMonthsAgo(12), 12).entrySet().stream() + .map(entry -> Map.entry(entry.getKey(), + entry.getValue().stream() + .map(Order::getOrderItems) + .reduce(new ArrayList<>(), (l, r) -> { + l.addAll(r); + return l; + }) + .stream() + .map(orderItem -> Map.entry(orderItem.getArticle().getCategory(), mappingFunction.apply(orderItem))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, reduceFunction)) + )) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) + ); + } + + @Override + public MonthlyCatModel getSalesVolume() { + return getMonthCategoryMap(OrderItem::getAmount, Integer::sum); + } + + @Override + public MonthlyCatModel getSalesRevenue() { + return getMonthCategoryMap(item -> item.getArticle().getPrice100(), Integer::sum); + } +} diff --git a/00-backend/src/main/java/de/htwsaar/webshop/util/TimeUtil.java b/00-backend/src/main/java/de/htwsaar/webshop/util/TimeUtil.java index 5e75a5f..f9fd439 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/util/TimeUtil.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/util/TimeUtil.java @@ -1,5 +1,9 @@ package de.htwsaar.webshop.util; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; + /** * Helper Class for Unix-Millis related Things */ @@ -41,13 +45,11 @@ public class TimeUtil { return System.currentTimeMillis() + (days * MILLIS_TO_DAY); } - /** - * Check whether the Unix-Millis are valid - * - * @param time the Millis to Check - * @return whether the Time can be valid - */ - public static boolean isValidTime(long time) { - return time >= VALID_MIN_MILLIS_TIMESTAMP; + public static long nowMonthsAgo(long months) { + return LocalDateTime.now().minusMonths(months).atZone(ZoneId.systemDefault()).toEpochSecond(); + } + + public static long monthLength(long epoch) { + return LocalDateTime.ofEpochSecond(epoch, 0, ZoneOffset.UTC).getMonth().length(false) * MILLIS_TO_DAY; } } diff --git a/00-backend/src/main/resources/db/initdb.sql b/00-backend/src/main/resources/db/initdb.sql index c756f24..c18bfc5 100644 --- a/00-backend/src/main/resources/db/initdb.sql +++ b/00-backend/src/main/resources/db/initdb.sql @@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS Images FOREIGN KEY (article_id) REFERENCES Articles (id) ); -CREATE TABLE IF NOT EXISTS FarmImages +CREATE TABLE IF NOT EXISTS Farm_Images ( id INTEGER PRIMARY KEY NOT NULL, article_id INTEGER NOT NULL, @@ -82,7 +82,7 @@ CREATE TABLE IF NOT EXISTS Orders ON UPDATE CASCADE ); -CREATE TABLE IF NOT EXISTS OrderItems +CREATE TABLE IF NOT EXISTS Order_Items ( id INTEGER NOT NULL PRIMARY KEY, order_id INTEGER NOT NULL,