From a85aa144db20d2bfc808150eca9ae2fb7c4d3c96 Mon Sep 17 00:00:00 2001 From: trokhim03 Date: Wed, 5 Mar 2025 16:59:39 +0200 Subject: [PATCH 1/2] added order model --- .../bookshop/controller/OrderController.java | 76 +++++++++++ .../bookshop/dto/order/OrderRequestDto.java | 10 ++ .../bookshop/dto/order/OrderResponseDto.java | 22 ++++ .../dto/order/OrderUpdateStatusDto.java | 6 + .../dto/orderitem/OrderItemResponseDto.java | 15 +++ .../bookshop/mapper/OrderItemMapper.java | 20 +++ .../academy/bookshop/mapper/OrderMapper.java | 27 ++++ .../mate/academy/bookshop/model/Order.java | 62 +++++++++ .../academy/bookshop/model/OrderItem.java | 38 ++++++ .../repository/OrderItemRepository.java | 14 ++ .../bookshop/repository/OrderRepository.java | 16 +++ .../bookshop/service/order/OrderService.java | 21 +++ .../service/order/OrderServiceImpl.java | 120 ++++++++++++++++++ .../shoppingcart/ShoppingCartServiceImpl.java | 2 +- src/main/resources/application.properties | 2 +- .../changes/13-create-orders-table.yaml | 43 +++++++ .../changes/14-create-order-items-table.yaml | 41 ++++++ .../db/changelog/db.changelog-master.yaml | 4 + 18 files changed, 537 insertions(+), 2 deletions(-) create mode 100644 src/main/java/mate/academy/bookshop/controller/OrderController.java create mode 100644 src/main/java/mate/academy/bookshop/dto/order/OrderRequestDto.java create mode 100644 src/main/java/mate/academy/bookshop/dto/order/OrderResponseDto.java create mode 100644 src/main/java/mate/academy/bookshop/dto/order/OrderUpdateStatusDto.java create mode 100644 src/main/java/mate/academy/bookshop/dto/orderitem/OrderItemResponseDto.java create mode 100644 src/main/java/mate/academy/bookshop/mapper/OrderItemMapper.java create mode 100644 src/main/java/mate/academy/bookshop/mapper/OrderMapper.java create mode 100644 src/main/java/mate/academy/bookshop/model/Order.java create mode 100644 src/main/java/mate/academy/bookshop/model/OrderItem.java create mode 100644 src/main/java/mate/academy/bookshop/repository/OrderItemRepository.java create mode 100644 src/main/java/mate/academy/bookshop/repository/OrderRepository.java create mode 100644 src/main/java/mate/academy/bookshop/service/order/OrderService.java create mode 100644 src/main/java/mate/academy/bookshop/service/order/OrderServiceImpl.java create mode 100644 src/main/resources/db/changelog/changes/13-create-orders-table.yaml create mode 100644 src/main/resources/db/changelog/changes/14-create-order-items-table.yaml diff --git a/src/main/java/mate/academy/bookshop/controller/OrderController.java b/src/main/java/mate/academy/bookshop/controller/OrderController.java new file mode 100644 index 0000000..0424fbb --- /dev/null +++ b/src/main/java/mate/academy/bookshop/controller/OrderController.java @@ -0,0 +1,76 @@ +package mate.academy.bookshop.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.bookshop.dto.order.OrderRequestDto; +import mate.academy.bookshop.dto.order.OrderResponseDto; +import mate.academy.bookshop.dto.order.OrderUpdateStatusDto; +import mate.academy.bookshop.dto.orderitem.OrderItemResponseDto; +import mate.academy.bookshop.model.User; +import mate.academy.bookshop.service.order.OrderService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@Tag(name = "Orders", description = "Operations for managing orders") +@RequestMapping("/orders") +public class OrderController { + private final OrderService orderService; + + @Operation(summary = "Get all orders for the authenticated user", + description = "Retrieves a list of orders for the currently authenticated user.") + @GetMapping + public List getOrders(Authentication authentication) { + return orderService.getOrderByUser((User) authentication.getPrincipal()); + } + + @Operation(summary = "Create a new order", + description = "Creates a new order for the authenticated user.") + @PostMapping + public OrderResponseDto createOrder(Authentication authentication, + @RequestBody @Valid OrderRequestDto orderRequestDto) { + return orderService.createOrderByUser((User) authentication.getPrincipal(), + orderRequestDto); + } + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @Operation(summary = "Update the status of an order", + description = "Updates the status of an order identified" + + " by orderId for the authenticated user.") + @PatchMapping("/{orderId}") + public OrderResponseDto updateStatus( + @PathVariable Long orderId, + @RequestBody @Valid OrderUpdateStatusDto orderUpdateStatusDto) { + return orderService.updateStatusByOrderId(orderId, orderUpdateStatusDto); + } + + @Operation(summary = "Get all items in a specific order", + description = "Retrieves the list of items for a specific" + + " order identified by orderId for the authenticated user.") + @GetMapping("/{orderId}/items") + public List getOrderItems(@PathVariable Long orderId, + Authentication authentication) { + User user = (User) authentication.getPrincipal(); + return orderService.getOrderItemsByOrderId(orderId, user.getId()); + } + + @Operation(summary = "Get a specific item from an order", + description = "Retrieves a specific item from an order " + + "by itemId, within the order identified by orderId.") + @GetMapping("/{orderId}/items/{itemId}") + public OrderItemResponseDto getOrderItem(@PathVariable Long orderId, + @PathVariable Long itemId) { + return orderService.getOrderItemFromOrderById(orderId, itemId); + } +} diff --git a/src/main/java/mate/academy/bookshop/dto/order/OrderRequestDto.java b/src/main/java/mate/academy/bookshop/dto/order/OrderRequestDto.java new file mode 100644 index 0000000..4387381 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/dto/order/OrderRequestDto.java @@ -0,0 +1,10 @@ +package mate.academy.bookshop.dto.order; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class OrderRequestDto { + @NotBlank + private String shippingAddress; +} diff --git a/src/main/java/mate/academy/bookshop/dto/order/OrderResponseDto.java b/src/main/java/mate/academy/bookshop/dto/order/OrderResponseDto.java new file mode 100644 index 0000000..39b2036 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/dto/order/OrderResponseDto.java @@ -0,0 +1,22 @@ +package mate.academy.bookshop.dto.order; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import lombok.Data; +import mate.academy.bookshop.dto.orderitem.OrderItemResponseDto; + +@Data +public class OrderResponseDto { + private Long id; + + private Long userId; + + private List orderItemsDto; + + private LocalDateTime orderDate; + + private BigDecimal total; + + private String status; +} diff --git a/src/main/java/mate/academy/bookshop/dto/order/OrderUpdateStatusDto.java b/src/main/java/mate/academy/bookshop/dto/order/OrderUpdateStatusDto.java new file mode 100644 index 0000000..800bf59 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/dto/order/OrderUpdateStatusDto.java @@ -0,0 +1,6 @@ +package mate.academy.bookshop.dto.order; + +import jakarta.validation.constraints.NotBlank; + +public record OrderUpdateStatusDto(@NotBlank String status) { +} diff --git a/src/main/java/mate/academy/bookshop/dto/orderitem/OrderItemResponseDto.java b/src/main/java/mate/academy/bookshop/dto/orderitem/OrderItemResponseDto.java new file mode 100644 index 0000000..75ddbd9 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/dto/orderitem/OrderItemResponseDto.java @@ -0,0 +1,15 @@ +package mate.academy.bookshop.dto.orderitem; + +import java.math.BigDecimal; +import lombok.Data; + +@Data +public class OrderItemResponseDto { + private Long id; + + private Long bookId; + + private int quantity; + + private BigDecimal price; +} diff --git a/src/main/java/mate/academy/bookshop/mapper/OrderItemMapper.java b/src/main/java/mate/academy/bookshop/mapper/OrderItemMapper.java new file mode 100644 index 0000000..004e361 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/mapper/OrderItemMapper.java @@ -0,0 +1,20 @@ +package mate.academy.bookshop.mapper; + +import java.util.List; +import mate.academy.bookshop.config.MapperConfig; +import mate.academy.bookshop.dto.orderitem.OrderItemResponseDto; +import mate.academy.bookshop.model.OrderItem; +import org.mapstruct.IterableMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper(config = MapperConfig.class) +public interface OrderItemMapper { + @Named("orderItemToDto") + @Mapping(target = "bookId", source = "book.id") + OrderItemResponseDto toDto(OrderItem orderItem); + + @IterableMapping(qualifiedByName = "orderItemToDto") + List toDto(List orderItems); +} diff --git a/src/main/java/mate/academy/bookshop/mapper/OrderMapper.java b/src/main/java/mate/academy/bookshop/mapper/OrderMapper.java new file mode 100644 index 0000000..2b81581 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/mapper/OrderMapper.java @@ -0,0 +1,27 @@ +package mate.academy.bookshop.mapper; + +import java.util.List; +import mate.academy.bookshop.config.MapperConfig; +import mate.academy.bookshop.dto.order.OrderRequestDto; +import mate.academy.bookshop.dto.order.OrderResponseDto; +import mate.academy.bookshop.dto.order.OrderUpdateStatusDto; +import mate.academy.bookshop.model.Order; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; + +@Mapper(config = MapperConfig.class, uses = OrderItemMapper.class) +public interface OrderMapper { + @Mapping(target = "userId", source = "user.id") + @Mapping(target = "orderItemsDto", source = "orderItems", qualifiedByName = "orderItemToDto") + @Mapping(target = "status", expression = "java(order.getStatus().name())") + @Mapping(target = "orderDate", source = "orderDate", dateFormat = "yyyy-MM-dd HH:mm:ss") + OrderResponseDto toDto(Order order); + + List toDto(List order); + + Order toEntity(OrderRequestDto orderRequestDto); + + void updateOrderStatusFromDto(OrderUpdateStatusDto orderUpdateStatusDto, + @MappingTarget Order order); +} diff --git a/src/main/java/mate/academy/bookshop/model/Order.java b/src/main/java/mate/academy/bookshop/model/Order.java new file mode 100644 index 0000000..02c4e24 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/model/Order.java @@ -0,0 +1,62 @@ +package mate.academy.bookshop.model; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Set; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; + +@Entity +@Getter +@Setter +@SQLDelete(sql = "UPDATE orders SET is_deleted = true WHERE id=?") +@SQLRestriction(value = "is_deleted = false") +@Table(name = "orders") +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Status status; + + @Column(nullable = false) + private BigDecimal total; + + @Column(nullable = false) + private LocalDateTime orderDate; + + @Column(nullable = false) + private String shippingAddress; + + @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true) + private Set orderItems; + + private boolean isDeleted = false; + + public enum Status { + PENDING, + CONFIRMED, + DELIVERED, + } +} diff --git a/src/main/java/mate/academy/bookshop/model/OrderItem.java b/src/main/java/mate/academy/bookshop/model/OrderItem.java new file mode 100644 index 0000000..6ae644d --- /dev/null +++ b/src/main/java/mate/academy/bookshop/model/OrderItem.java @@ -0,0 +1,38 @@ +package mate.academy.bookshop.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Table(name = "order_items") +public class OrderItem { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "order_id", nullable = false) + private Order order; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "book_id", nullable = false) + private Book book; + + @Column(nullable = false) + private int quantity; + + @Column(nullable = false) + private BigDecimal price; +} diff --git a/src/main/java/mate/academy/bookshop/repository/OrderItemRepository.java b/src/main/java/mate/academy/bookshop/repository/OrderItemRepository.java new file mode 100644 index 0000000..f7eb8f4 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/repository/OrderItemRepository.java @@ -0,0 +1,14 @@ +package mate.academy.bookshop.repository; + +import java.util.List; +import java.util.Optional; +import mate.academy.bookshop.model.Order; +import mate.academy.bookshop.model.OrderItem; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OrderItemRepository extends JpaRepository { + List findAllByOrder(Order order); + + Optional findByIdAndOrderId(Long orderItemId, Long orderId); + +} diff --git a/src/main/java/mate/academy/bookshop/repository/OrderRepository.java b/src/main/java/mate/academy/bookshop/repository/OrderRepository.java new file mode 100644 index 0000000..8b6a873 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/repository/OrderRepository.java @@ -0,0 +1,16 @@ +package mate.academy.bookshop.repository; + +import java.util.List; +import java.util.Optional; +import mate.academy.bookshop.model.Order; +import mate.academy.bookshop.model.User; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OrderRepository extends JpaRepository { + @EntityGraph(attributePaths = {"orderItems", "orderItems.book"}) + List findAllByUser(User user); + + @EntityGraph(attributePaths = {"orderItems", "orderItems.book"}) + Optional findByIdAndUserId(Long orderId, Long userId); +} diff --git a/src/main/java/mate/academy/bookshop/service/order/OrderService.java b/src/main/java/mate/academy/bookshop/service/order/OrderService.java new file mode 100644 index 0000000..e08e2d9 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/service/order/OrderService.java @@ -0,0 +1,21 @@ +package mate.academy.bookshop.service.order; + +import java.util.List; +import mate.academy.bookshop.dto.order.OrderRequestDto; +import mate.academy.bookshop.dto.order.OrderResponseDto; +import mate.academy.bookshop.dto.order.OrderUpdateStatusDto; +import mate.academy.bookshop.dto.orderitem.OrderItemResponseDto; +import mate.academy.bookshop.model.User; + +public interface OrderService { + OrderResponseDto createOrderByUser(User user, OrderRequestDto orderRequestDto); + + List getOrderByUser(User user); + + OrderResponseDto updateStatusByOrderId(Long orderId, + OrderUpdateStatusDto orderUpdateStatusDto); + + List getOrderItemsByOrderId(Long orderId, Long userId); + + OrderItemResponseDto getOrderItemFromOrderById(Long orderId, Long orderItemId); +} diff --git a/src/main/java/mate/academy/bookshop/service/order/OrderServiceImpl.java b/src/main/java/mate/academy/bookshop/service/order/OrderServiceImpl.java new file mode 100644 index 0000000..3885494 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/service/order/OrderServiceImpl.java @@ -0,0 +1,120 @@ +package mate.academy.bookshop.service.order; + +import jakarta.transaction.Transactional; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import mate.academy.bookshop.dto.order.OrderRequestDto; +import mate.academy.bookshop.dto.order.OrderResponseDto; +import mate.academy.bookshop.dto.order.OrderUpdateStatusDto; +import mate.academy.bookshop.dto.orderitem.OrderItemResponseDto; +import mate.academy.bookshop.exceptions.EntityNotFoundException; +import mate.academy.bookshop.mapper.OrderItemMapper; +import mate.academy.bookshop.mapper.OrderMapper; +import mate.academy.bookshop.model.CartItem; +import mate.academy.bookshop.model.Order; +import mate.academy.bookshop.model.OrderItem; +import mate.academy.bookshop.model.ShoppingCart; +import mate.academy.bookshop.model.User; +import mate.academy.bookshop.repository.OrderItemRepository; +import mate.academy.bookshop.repository.OrderRepository; +import mate.academy.bookshop.repository.ShoppingCartRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class OrderServiceImpl implements OrderService { + private final OrderItemMapper orderItemMapper; + private final OrderMapper orderMapper; + private final OrderRepository orderRepository; + private final OrderItemRepository orderItemRepository; + private final ShoppingCartRepository shoppingCartRepository; + + @Override + @Transactional + public OrderResponseDto createOrderByUser(User user, OrderRequestDto orderRequestDto) { + ShoppingCart shoppingCart = shoppingCartRepository + .findByUserId(user.getId()) + .orElseThrow(() -> new EntityNotFoundException("Shopping cart not found" + + " for user with id: " + user.getId() + )); + if (shoppingCart.getCartItems().isEmpty()) { + throw new EntityNotFoundException("Shopping cart is empty " + + "for user id: " + user.getId()); + } + Order order = orderMapper.toEntity(orderRequestDto); + order.setUser(user); + order.setStatus(Order.Status.PENDING); + order.setOrderDate(LocalDateTime.now()); + + Set orderItemSet = shoppingCart + .getCartItems() + .stream() + .map(cart -> mapToOrderItem(cart, order)) + .collect(Collectors.toSet()); + + order.setOrderItems(orderItemSet); + + BigDecimal totalPrice = orderItemSet + .stream() + .map(OrderItem::getPrice) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + order.setTotal(totalPrice); + orderRepository.save(order); + return orderMapper.toDto(order); + } + + @Override + public List getOrderByUser(User user) { + List ordersByUser = orderRepository.findAllByUser(user); + return orderMapper.toDto(ordersByUser); + } + + @Override + @Transactional + public OrderResponseDto updateStatusByOrderId(Long orderId, + OrderUpdateStatusDto orderUpdateStatusDto) { + Order order = orderRepository + .findById(orderId) + .orElseThrow(() -> new EntityNotFoundException("Order not found with id: " + + orderId + )); + orderMapper.updateOrderStatusFromDto(orderUpdateStatusDto, order); + orderRepository.save(order); + return orderMapper.toDto(order); + } + + @Override + @Transactional + public List getOrderItemsByOrderId(Long orderId, Long userId) { + Order order = orderRepository + .findByIdAndUserId(orderId, userId) + .orElseThrow(() -> new EntityNotFoundException("Order items not found " + + "for order id: " + orderId + " and user id: " + userId + )); + return orderItemMapper.toDto(orderItemRepository.findAllByOrder(order)); + } + + @Override + public OrderItemResponseDto getOrderItemFromOrderById(Long orderId, Long orderItemId) { + OrderItem orderItem = orderItemRepository + .findByIdAndOrderId(orderItemId, orderId) + .orElseThrow(() -> new EntityNotFoundException("Can't find order item by order id: " + + orderId + " and order item id: " + orderItemId)); + + return orderItemMapper.toDto(orderItem); + } + + private OrderItem mapToOrderItem(CartItem cart, Order order) { + OrderItem orderItem = new OrderItem(); + orderItem.setOrder(order); + orderItem.setBook(cart.getBook()); + orderItem.setQuantity(cart.getQuantity()); + orderItem.setPrice(cart.getBook().getPrice().multiply(new BigDecimal(cart.getQuantity()))); + return orderItem; + } +} diff --git a/src/main/java/mate/academy/bookshop/service/shoppingcart/ShoppingCartServiceImpl.java b/src/main/java/mate/academy/bookshop/service/shoppingcart/ShoppingCartServiceImpl.java index 660abb7..5ee360b 100644 --- a/src/main/java/mate/academy/bookshop/service/shoppingcart/ShoppingCartServiceImpl.java +++ b/src/main/java/mate/academy/bookshop/service/shoppingcart/ShoppingCartServiceImpl.java @@ -52,7 +52,7 @@ public ShoppingCartResponseDto addCartItem(CartItemRequestDto cartItemUpdateDto, .findFirst(); if (existingCartItem.isPresent()) { - CartItem cartItem = existingCartItem.get(); + CartItem cartItem = existingCartItem.orElseThrow(); cartItem.setQuantity(cartItem.getQuantity() + cartItemUpdateDto.getQuantity()); } else { CartItem cartItem = cartItemMapper.toEntity(cartItemUpdateDto); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 43b5889..a4f6529 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ spring.datasource.username=root spring.datasource.password=QWERTY123 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.jpa.hibernate.ddl-auto=validate +spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect diff --git a/src/main/resources/db/changelog/changes/13-create-orders-table.yaml b/src/main/resources/db/changelog/changes/13-create-orders-table.yaml new file mode 100644 index 0000000..4a2f96b --- /dev/null +++ b/src/main/resources/db/changelog/changes/13-create-orders-table.yaml @@ -0,0 +1,43 @@ +databaseChangeLog: + - changeSet: + id: create-orders-table + author: trokhim + changes: + - createTable: + tableName: orders + columns: + - column: + name: id + type: bigint + autoIncrement: true + constraints: + primaryKey: true + nullable: false + - column: + name: user_id + type: bigint + constraints: + foreignKeyName: fk_order_user + referencedTableName: users + referencedColumnNames: id + nullable: false + - column: + name: status + type: varchar(255) + constraints: + nullable: false + - column: + name: total + type: decimal(19,2) + constraints: + nullable: false + - column: + name: order_date + type: datetime + constraints: + nullable: false + - column: + name: shipping_address + type: varchar(255) + constraints: + nullable: false diff --git a/src/main/resources/db/changelog/changes/14-create-order-items-table.yaml b/src/main/resources/db/changelog/changes/14-create-order-items-table.yaml new file mode 100644 index 0000000..5cf505d --- /dev/null +++ b/src/main/resources/db/changelog/changes/14-create-order-items-table.yaml @@ -0,0 +1,41 @@ +databaseChangeLog: + - changeSet: + id: create-order-items-table + author: trokhim + changes: + - createTable: + tableName: order_items + columns: + - column: + name: id + type: bigint + autoIncrement: true + constraints: + primaryKey: true + nullable: false + - column: + name: order_id + type: bigint + constraints: + foreignKeyName: fk_order_item_order + referencedTableName: orders + referencedColumnNames: id + nullable: false + - column: + name: book_id + type: bigint + constraints: + foreignKeyName: fk_order_item_book + referencedTableName: books + referencedColumnNames: id + nullable: false + - column: + name: quantity + type: int + constraints: + nullable: false + - column: + name: price + type: decimal(19,2) + constraints: + nullable: false diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 21b6003..aa5b67c 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -21,3 +21,7 @@ databaseChangeLog: file: db/changelog/changes/11-create-cart-items-table.yaml - include: file: db/changelog/changes/12-insert-shopping-carts.yaml + - include: + file: db/changelog/changes/13-create-orders-table.yaml + - include: + file: db/changelog/changes/14-create-order-items-table.yaml From 1c3378ec4d70e1a2d4fe60b504d1f9a2ba394e3f Mon Sep 17 00:00:00 2001 From: trokhim03 Date: Thu, 6 Mar 2025 12:24:37 +0200 Subject: [PATCH 2/2] improved the code --- .../bookshop/controller/OrderController.java | 23 ++++++++---- .../exceptions/OrderProcessingException.java | 8 ++++ .../bookshop/mapper/OrderItemMapper.java | 2 +- .../repository/OrderItemRepository.java | 5 ++- .../bookshop/repository/OrderRepository.java | 4 +- .../bookshop/service/order/OrderService.java | 10 +++-- .../service/order/OrderServiceImpl.java | 37 +++++++++++-------- src/main/resources/application.properties | 2 +- .../changes/13-create-orders-table.yaml | 6 +++ 9 files changed, 64 insertions(+), 33 deletions(-) create mode 100644 src/main/java/mate/academy/bookshop/exceptions/OrderProcessingException.java diff --git a/src/main/java/mate/academy/bookshop/controller/OrderController.java b/src/main/java/mate/academy/bookshop/controller/OrderController.java index 0424fbb..dee84a9 100644 --- a/src/main/java/mate/academy/bookshop/controller/OrderController.java +++ b/src/main/java/mate/academy/bookshop/controller/OrderController.java @@ -11,6 +11,7 @@ import mate.academy.bookshop.dto.orderitem.OrderItemResponseDto; import mate.academy.bookshop.model.User; import mate.academy.bookshop.service.order.OrderService; +import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; @@ -31,8 +32,9 @@ public class OrderController { @Operation(summary = "Get all orders for the authenticated user", description = "Retrieves a list of orders for the currently authenticated user.") @GetMapping - public List getOrders(Authentication authentication) { - return orderService.getOrderByUser((User) authentication.getPrincipal()); + public List getOrders(Pageable pageable, Authentication authentication) { + Long authenticatedUserId = getAuthenticatedUserId(authentication); + return orderService.getOrderByUserId(pageable, authenticatedUserId); } @Operation(summary = "Create a new order", @@ -40,7 +42,8 @@ public List getOrders(Authentication authentication) { @PostMapping public OrderResponseDto createOrder(Authentication authentication, @RequestBody @Valid OrderRequestDto orderRequestDto) { - return orderService.createOrderByUser((User) authentication.getPrincipal(), + Long authenticatedUserId = getAuthenticatedUserId(authentication); + return orderService.createOrderByUserId(authenticatedUserId, orderRequestDto); } @@ -61,8 +64,8 @@ public OrderResponseDto updateStatus( @GetMapping("/{orderId}/items") public List getOrderItems(@PathVariable Long orderId, Authentication authentication) { - User user = (User) authentication.getPrincipal(); - return orderService.getOrderItemsByOrderId(orderId, user.getId()); + Long authenticatedUserId = getAuthenticatedUserId(authentication); + return orderService.getOrderItemsByOrderId(orderId, authenticatedUserId); } @Operation(summary = "Get a specific item from an order", @@ -70,7 +73,13 @@ public List getOrderItems(@PathVariable Long orderId, + "by itemId, within the order identified by orderId.") @GetMapping("/{orderId}/items/{itemId}") public OrderItemResponseDto getOrderItem(@PathVariable Long orderId, - @PathVariable Long itemId) { - return orderService.getOrderItemFromOrderById(orderId, itemId); + @PathVariable Long itemId, + Authentication authentication) { + Long authenticatedUserId = getAuthenticatedUserId(authentication); + return orderService.getOrderItemFromOrderById(orderId, itemId, authenticatedUserId); + } + + private Long getAuthenticatedUserId(Authentication authentication) { + return ((User) authentication.getPrincipal()).getId(); } } diff --git a/src/main/java/mate/academy/bookshop/exceptions/OrderProcessingException.java b/src/main/java/mate/academy/bookshop/exceptions/OrderProcessingException.java new file mode 100644 index 0000000..da88ef4 --- /dev/null +++ b/src/main/java/mate/academy/bookshop/exceptions/OrderProcessingException.java @@ -0,0 +1,8 @@ +package mate.academy.bookshop.exceptions; + +public class OrderProcessingException extends RuntimeException { + public OrderProcessingException(String message) { + super(message); + } +} + diff --git a/src/main/java/mate/academy/bookshop/mapper/OrderItemMapper.java b/src/main/java/mate/academy/bookshop/mapper/OrderItemMapper.java index 004e361..9e80369 100644 --- a/src/main/java/mate/academy/bookshop/mapper/OrderItemMapper.java +++ b/src/main/java/mate/academy/bookshop/mapper/OrderItemMapper.java @@ -16,5 +16,5 @@ public interface OrderItemMapper { OrderItemResponseDto toDto(OrderItem orderItem); @IterableMapping(qualifiedByName = "orderItemToDto") - List toDto(List orderItems); + List toOrderItemDtoList(List orderItems); } diff --git a/src/main/java/mate/academy/bookshop/repository/OrderItemRepository.java b/src/main/java/mate/academy/bookshop/repository/OrderItemRepository.java index f7eb8f4..4a465b0 100644 --- a/src/main/java/mate/academy/bookshop/repository/OrderItemRepository.java +++ b/src/main/java/mate/academy/bookshop/repository/OrderItemRepository.java @@ -9,6 +9,7 @@ public interface OrderItemRepository extends JpaRepository { List findAllByOrder(Order order); - Optional findByIdAndOrderId(Long orderItemId, Long orderId); - + Optional findByIdAndOrderIdAndOrderUserId(Long orderItemId, + Long orderId, + Long userId); } diff --git a/src/main/java/mate/academy/bookshop/repository/OrderRepository.java b/src/main/java/mate/academy/bookshop/repository/OrderRepository.java index 8b6a873..fefe783 100644 --- a/src/main/java/mate/academy/bookshop/repository/OrderRepository.java +++ b/src/main/java/mate/academy/bookshop/repository/OrderRepository.java @@ -3,13 +3,13 @@ import java.util.List; import java.util.Optional; import mate.academy.bookshop.model.Order; -import mate.academy.bookshop.model.User; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; public interface OrderRepository extends JpaRepository { @EntityGraph(attributePaths = {"orderItems", "orderItems.book"}) - List findAllByUser(User user); + List findAllByUserId(Pageable pageable, Long userId); @EntityGraph(attributePaths = {"orderItems", "orderItems.book"}) Optional findByIdAndUserId(Long orderId, Long userId); diff --git a/src/main/java/mate/academy/bookshop/service/order/OrderService.java b/src/main/java/mate/academy/bookshop/service/order/OrderService.java index e08e2d9..96c0fbb 100644 --- a/src/main/java/mate/academy/bookshop/service/order/OrderService.java +++ b/src/main/java/mate/academy/bookshop/service/order/OrderService.java @@ -5,17 +5,19 @@ import mate.academy.bookshop.dto.order.OrderResponseDto; import mate.academy.bookshop.dto.order.OrderUpdateStatusDto; import mate.academy.bookshop.dto.orderitem.OrderItemResponseDto; -import mate.academy.bookshop.model.User; +import org.springframework.data.domain.Pageable; public interface OrderService { - OrderResponseDto createOrderByUser(User user, OrderRequestDto orderRequestDto); + OrderResponseDto createOrderByUserId(Long userId, OrderRequestDto orderRequestDto); - List getOrderByUser(User user); + List getOrderByUserId(Pageable pageable, Long userId); OrderResponseDto updateStatusByOrderId(Long orderId, OrderUpdateStatusDto orderUpdateStatusDto); List getOrderItemsByOrderId(Long orderId, Long userId); - OrderItemResponseDto getOrderItemFromOrderById(Long orderId, Long orderItemId); + OrderItemResponseDto getOrderItemFromOrderById(Long orderId, + Long orderItemId, + Long userId); } diff --git a/src/main/java/mate/academy/bookshop/service/order/OrderServiceImpl.java b/src/main/java/mate/academy/bookshop/service/order/OrderServiceImpl.java index 3885494..98839e7 100644 --- a/src/main/java/mate/academy/bookshop/service/order/OrderServiceImpl.java +++ b/src/main/java/mate/academy/bookshop/service/order/OrderServiceImpl.java @@ -12,19 +12,22 @@ import mate.academy.bookshop.dto.order.OrderUpdateStatusDto; import mate.academy.bookshop.dto.orderitem.OrderItemResponseDto; import mate.academy.bookshop.exceptions.EntityNotFoundException; +import mate.academy.bookshop.exceptions.OrderProcessingException; import mate.academy.bookshop.mapper.OrderItemMapper; import mate.academy.bookshop.mapper.OrderMapper; import mate.academy.bookshop.model.CartItem; import mate.academy.bookshop.model.Order; import mate.academy.bookshop.model.OrderItem; import mate.academy.bookshop.model.ShoppingCart; -import mate.academy.bookshop.model.User; import mate.academy.bookshop.repository.OrderItemRepository; import mate.academy.bookshop.repository.OrderRepository; import mate.academy.bookshop.repository.ShoppingCartRepository; +import mate.academy.bookshop.repository.UserRepository; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @Service +@Transactional @RequiredArgsConstructor public class OrderServiceImpl implements OrderService { private final OrderItemMapper orderItemMapper; @@ -32,21 +35,23 @@ public class OrderServiceImpl implements OrderService { private final OrderRepository orderRepository; private final OrderItemRepository orderItemRepository; private final ShoppingCartRepository shoppingCartRepository; + private final UserRepository userRepository; @Override - @Transactional - public OrderResponseDto createOrderByUser(User user, OrderRequestDto orderRequestDto) { + public OrderResponseDto createOrderByUserId(Long userId, OrderRequestDto orderRequestDto) { ShoppingCart shoppingCart = shoppingCartRepository - .findByUserId(user.getId()) + .findByUserId(userId) .orElseThrow(() -> new EntityNotFoundException("Shopping cart not found" - + " for user with id: " + user.getId() + + " for user with id: " + userId )); if (shoppingCart.getCartItems().isEmpty()) { - throw new EntityNotFoundException("Shopping cart is empty " - + "for user id: " + user.getId()); + throw new OrderProcessingException("Shopping cart is empty " + + "for user id: " + userId); } Order order = orderMapper.toEntity(orderRequestDto); - order.setUser(user); + order.setUser(userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException("User with id " + + userId + " not found"))); order.setStatus(Order.Status.PENDING); order.setOrderDate(LocalDateTime.now()); @@ -69,13 +74,12 @@ public OrderResponseDto createOrderByUser(User user, OrderRequestDto orderReques } @Override - public List getOrderByUser(User user) { - List ordersByUser = orderRepository.findAllByUser(user); + public List getOrderByUserId(Pageable pageable, Long userId) { + List ordersByUser = orderRepository.findAllByUserId(pageable, userId); return orderMapper.toDto(ordersByUser); } @Override - @Transactional public OrderResponseDto updateStatusByOrderId(Long orderId, OrderUpdateStatusDto orderUpdateStatusDto) { Order order = orderRepository @@ -89,20 +93,21 @@ public OrderResponseDto updateStatusByOrderId(Long orderId, } @Override - @Transactional public List getOrderItemsByOrderId(Long orderId, Long userId) { Order order = orderRepository .findByIdAndUserId(orderId, userId) - .orElseThrow(() -> new EntityNotFoundException("Order items not found " + .orElseThrow(() -> new EntityNotFoundException("Order not found " + "for order id: " + orderId + " and user id: " + userId )); - return orderItemMapper.toDto(orderItemRepository.findAllByOrder(order)); + return orderItemMapper.toOrderItemDtoList(orderItemRepository.findAllByOrder(order)); } @Override - public OrderItemResponseDto getOrderItemFromOrderById(Long orderId, Long orderItemId) { + public OrderItemResponseDto getOrderItemFromOrderById(Long orderId, + Long orderItemId, + Long userId) { OrderItem orderItem = orderItemRepository - .findByIdAndOrderId(orderItemId, orderId) + .findByIdAndOrderIdAndOrderUserId(orderItemId, orderId, userId) .orElseThrow(() -> new EntityNotFoundException("Can't find order item by order id: " + orderId + " and order item id: " + orderItemId)); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a4f6529..43b5889 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ spring.datasource.username=root spring.datasource.password=QWERTY123 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.ddl-auto=validate spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect diff --git a/src/main/resources/db/changelog/changes/13-create-orders-table.yaml b/src/main/resources/db/changelog/changes/13-create-orders-table.yaml index 4a2f96b..f64ab41 100644 --- a/src/main/resources/db/changelog/changes/13-create-orders-table.yaml +++ b/src/main/resources/db/changelog/changes/13-create-orders-table.yaml @@ -41,3 +41,9 @@ databaseChangeLog: type: varchar(255) constraints: nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false