diff --git a/pom.xml b/pom.xml
index 8f89f11..1ede3bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,7 +117,6 @@
jjwt-jackson
${jjvt.version}
-
org.apache.logging.log4j
log4j-core
@@ -133,7 +132,21 @@
log4j-slf4j-impl
2.20.0
-
+
+ javax.mail
+ javax.mail-api
+ 1.6.2
+
+
+ javax.activation
+ javax.activation-api
+ 1.2.0
+
+
+ com.sun.mail
+ javax.mail
+ 1.6.2
+
diff --git a/src/main/java/spring/boot/bookstore/controller/OrderController.java b/src/main/java/spring/boot/bookstore/controller/OrderController.java
new file mode 100644
index 0000000..fe5b7bd
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/controller/OrderController.java
@@ -0,0 +1,81 @@
+package spring.boot.bookstore.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 java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+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;
+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;
+import spring.boot.bookstore.dto.order.OrderRequestDto;
+import spring.boot.bookstore.dto.order.OrderResponseDto;
+import spring.boot.bookstore.dto.order.OrderUpdateDto;
+import spring.boot.bookstore.dto.orderitem.OrderItemResponseDto;
+import spring.boot.bookstore.model.Order;
+import spring.boot.bookstore.model.User;
+import spring.boot.bookstore.service.order.OrderService;
+
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/orders")
+@Tag(name = "Order Controller management", description = "Endpoints for managing users orders")
+public class OrderController {
+ private static final Logger logger = LogManager.getLogger(OrderController.class);
+ private final OrderService orderService;
+
+ @PostMapping
+ @Operation(summary = "Place order", description = "place order")
+ public OrderResponseDto create(Authentication authentication,
+ @RequestBody
+ @Valid OrderRequestDto orderRequestDto) {
+ User user = (User) authentication.getPrincipal();
+ logger.info("Placing a new Order.");
+ return orderService.create(user.getId(), orderRequestDto);
+ }
+
+ @Operation(summary = "Get all users order history", description = "Get all users order history")
+ @GetMapping
+ public List findAll(Authentication authentication, Pageable pageable) {
+ User user = (User) authentication.getPrincipal();
+ logger.info("Find all Orders.");
+ return orderService.findAllOrders(user.getId(), pageable);
+ }
+
+ @Operation(summary = "update order", description = "update order status")
+ @PatchMapping("/{id}")
+ @PreAuthorize("hasRole('ROLE_ADMIN')")
+ public OrderResponseDto updateOrderStatus(@PathVariable Long id,
+ @RequestBody OrderUpdateDto orderUpdateDto) {
+ logger.info("updating Order Status by id." + id);
+ return orderService.updateOrderStatus(id, Order.Status
+ .valueOf(String.valueOf(orderUpdateDto.getStatus())));
+ }
+
+ @Operation(summary = "Find all order Items", description = "Find all order Items")
+ @GetMapping("/{orderId}/items")
+ @PreAuthorize("hasRole('USER')")
+ public Set findAllOrderItems(@PathVariable Long orderId) {
+ logger.info("find All Order Items using id" + orderId);
+ return orderService.findAllOrderItems(orderId);
+ }
+
+ @Operation(summary = "Find order item by ID ", description = "Find order item by ID ")
+ @GetMapping("/{orderId}/items/{itemId}")
+ @PreAuthorize("hasRole('USER')")
+ public OrderItemResponseDto findOrderItemById(@PathVariable Long orderId,
+ @PathVariable Long itemId) {
+ logger.info("find Order Item By Id" + orderId + "and here Item id : " + itemId);
+ return orderService.findOrderItemById(orderId, itemId);
+ }
+}
diff --git a/src/main/java/spring/boot/bookstore/dto/order/OrderRequestDto.java b/src/main/java/spring/boot/bookstore/dto/order/OrderRequestDto.java
new file mode 100644
index 0000000..e52d695
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/dto/order/OrderRequestDto.java
@@ -0,0 +1,8 @@
+package spring.boot.bookstore.dto.order;
+
+import lombok.Data;
+
+@Data
+public class OrderRequestDto {
+ private String shippingAddress;
+}
diff --git a/src/main/java/spring/boot/bookstore/dto/order/OrderResponseDto.java b/src/main/java/spring/boot/bookstore/dto/order/OrderResponseDto.java
new file mode 100644
index 0000000..aec7af2
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/dto/order/OrderResponseDto.java
@@ -0,0 +1,17 @@
+package spring.boot.bookstore.dto.order;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Set;
+import lombok.Data;
+import spring.boot.bookstore.dto.orderitem.OrderItemResponseDto;
+
+@Data
+public class OrderResponseDto {
+ private Long id;
+ private Long userId;
+ private Set orderItems;
+ private LocalDateTime orderTime;
+ private BigDecimal totalPrice;
+ private String status;
+}
diff --git a/src/main/java/spring/boot/bookstore/dto/order/OrderUpdateDto.java b/src/main/java/spring/boot/bookstore/dto/order/OrderUpdateDto.java
new file mode 100644
index 0000000..e4979d6
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/dto/order/OrderUpdateDto.java
@@ -0,0 +1,9 @@
+package spring.boot.bookstore.dto.order;
+
+import lombok.Data;
+import spring.boot.bookstore.model.Order;
+
+@Data
+public class OrderUpdateDto {
+ private Order.Status status;
+}
diff --git a/src/main/java/spring/boot/bookstore/dto/orderitem/OrderItemResponseDto.java b/src/main/java/spring/boot/bookstore/dto/orderitem/OrderItemResponseDto.java
new file mode 100644
index 0000000..bdcbd07
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/dto/orderitem/OrderItemResponseDto.java
@@ -0,0 +1,10 @@
+package spring.boot.bookstore.dto.orderitem;
+
+import lombok.Data;
+
+@Data
+public class OrderItemResponseDto {
+ private Long id;
+ private Long bookId;
+ private int quantity;
+}
diff --git a/src/main/java/spring/boot/bookstore/exception/MessageSenderException.java b/src/main/java/spring/boot/bookstore/exception/MessageSenderException.java
new file mode 100644
index 0000000..4808ea7
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/exception/MessageSenderException.java
@@ -0,0 +1,7 @@
+package spring.boot.bookstore.exception;
+
+public class MessageSenderException extends RuntimeException {
+ public MessageSenderException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/spring/boot/bookstore/mapper/OrderItemMapper.java b/src/main/java/spring/boot/bookstore/mapper/OrderItemMapper.java
new file mode 100644
index 0000000..54631b5
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/mapper/OrderItemMapper.java
@@ -0,0 +1,14 @@
+package spring.boot.bookstore.mapper;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import spring.boot.bookstore.config.MapperConfig;
+import spring.boot.bookstore.dto.orderitem.OrderItemResponseDto;
+import spring.boot.bookstore.model.OrderItem;
+
+@Mapper(config = MapperConfig.class)
+public interface OrderItemMapper {
+ @Mappings({@Mapping(target = "bookId", source = "book.id")})
+ OrderItemResponseDto toDto(OrderItem orderItem);
+}
diff --git a/src/main/java/spring/boot/bookstore/mapper/OrderMapper.java b/src/main/java/spring/boot/bookstore/mapper/OrderMapper.java
new file mode 100644
index 0000000..ec28cf0
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/mapper/OrderMapper.java
@@ -0,0 +1,16 @@
+package spring.boot.bookstore.mapper;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import spring.boot.bookstore.config.MapperConfig;
+import spring.boot.bookstore.dto.order.OrderResponseDto;
+import spring.boot.bookstore.model.Order;
+
+@Mapper(config = MapperConfig.class, uses = OrderItemMapper.class)
+public interface OrderMapper {
+ @Mappings({@Mapping(target = "userId", source = "user.id"),
+ @Mapping(source = "orderDate", target = "orderTime")})
+ @Mapping(source = "total", target = "totalPrice")
+ OrderResponseDto toDto(Order order);
+}
diff --git a/src/main/java/spring/boot/bookstore/model/Order.java b/src/main/java/spring/boot/bookstore/model/Order.java
new file mode 100644
index 0000000..e89286e
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/model/Order.java
@@ -0,0 +1,56 @@
+package spring.boot.bookstore.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.Where;
+
+@Entity
+@Getter
+@Setter
+@SQLDelete(sql = "UPDATE orders SET is_deleted = true WHERE id = ?")
+@Where(clause = "is_deleted = false")
+@Table(name = "orders")
+public class Order {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @ManyToOne
+ @JoinColumn(name = "users_id",nullable = false)
+ private User user;
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", nullable = false)
+ private Status status;
+ @Column(name = "total", nullable = false)
+ private BigDecimal total;
+ @Column(name = "order_date",nullable = false)
+ private LocalDateTime orderDate;
+ @Column(name = "shipping_address", nullable = false)
+ private String shippingAddress;
+ @OneToMany(mappedBy = "order",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ private Set orderItems;
+ @Column(name = "is_deleted", nullable = false)
+ private boolean isDeleted = false;
+
+ public enum Status {
+ COMPLETED,
+ PENDING,
+ DELIVERED
+ }
+}
diff --git a/src/main/java/spring/boot/bookstore/model/OrderItem.java b/src/main/java/spring/boot/bookstore/model/OrderItem.java
new file mode 100644
index 0000000..00e5981
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/model/OrderItem.java
@@ -0,0 +1,44 @@
+package spring.boot.bookstore.model;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+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;
+import org.hibernate.annotations.SQLDelete;
+import org.hibernate.annotations.Where;
+
+@Entity
+@Getter
+@Setter
+@SQLDelete(sql = "UPDATE order_items SET is_deleted = true WHERE id = ? ")
+@Where(clause = "is_deleted = false")
+@Table(name = "order_items")
+public class OrderItem {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @ManyToOne
+ @JoinColumn(name = "order_id", nullable = false)
+ private Order order;
+ @ManyToOne
+ @JoinColumn(name = "book_id", nullable = false)
+ private Book book;
+ @Column(name = "quantity", nullable = false)
+ private int quantity;
+ @Column(name = "price", nullable = false)
+ private BigDecimal price;
+ @Column(name = "is_deleted", nullable = false)
+ private boolean isDeleted = false;
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", nullable = false)
+ private Order.Status status;
+}
diff --git a/src/main/java/spring/boot/bookstore/model/ShoppingCart.java b/src/main/java/spring/boot/bookstore/model/ShoppingCart.java
index 7defd92..93fba93 100644
--- a/src/main/java/spring/boot/bookstore/model/ShoppingCart.java
+++ b/src/main/java/spring/boot/bookstore/model/ShoppingCart.java
@@ -2,6 +2,7 @@
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@@ -33,7 +34,7 @@ public class ShoppingCart {
@OneToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
- @ManyToMany
+ @ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "shopping_cart_items",
joinColumns = @JoinColumn(name = "shopping_cart_id"),
inverseJoinColumns = @JoinColumn(name = "cart_items_id"))
diff --git a/src/main/java/spring/boot/bookstore/repository/BookRepository.java b/src/main/java/spring/boot/bookstore/repository/BookRepository.java
index bbee66c..50aad73 100644
--- a/src/main/java/spring/boot/bookstore/repository/BookRepository.java
+++ b/src/main/java/spring/boot/bookstore/repository/BookRepository.java
@@ -18,5 +18,4 @@ List findBookByCategoriesId(@Param("categoryId") Long categoryId,
@Query("FROM Book b INNER JOIN FETCH b.categories")
List findAllWithCategories(Pageable pageable);
-
}
diff --git a/src/main/java/spring/boot/bookstore/repository/OrderItemRepository.java b/src/main/java/spring/boot/bookstore/repository/OrderItemRepository.java
new file mode 100644
index 0000000..efc0ac5
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/repository/OrderItemRepository.java
@@ -0,0 +1,9 @@
+package spring.boot.bookstore.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import spring.boot.bookstore.model.OrderItem;
+
+@Repository
+public interface OrderItemRepository extends JpaRepository {
+}
diff --git a/src/main/java/spring/boot/bookstore/repository/OrderRepository.java b/src/main/java/spring/boot/bookstore/repository/OrderRepository.java
new file mode 100644
index 0000000..eec5812
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/repository/OrderRepository.java
@@ -0,0 +1,19 @@
+package spring.boot.bookstore.repository;
+
+import java.util.List;
+import java.util.Optional;
+import org.springframework.data.jpa.repository.EntityGraph;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+import spring.boot.bookstore.model.Order;
+
+@Repository
+public interface OrderRepository extends JpaRepository {
+ @Query("SELECT o FROM Order o LEFT JOIN FETCH o.orderItems"
+ + " LEFT JOIN FETCH o.user u WHERE u.id = :userId")
+ List findAllOrders(long userId);
+
+ @EntityGraph(attributePaths = "orderItems")
+ Optional findById(Long id);
+}
diff --git a/src/main/java/spring/boot/bookstore/repository/shoppingcart/ShoppingCartRepository.java b/src/main/java/spring/boot/bookstore/repository/shoppingcart/ShoppingCartRepository.java
index cfada86..fe3de71 100644
--- a/src/main/java/spring/boot/bookstore/repository/shoppingcart/ShoppingCartRepository.java
+++ b/src/main/java/spring/boot/bookstore/repository/shoppingcart/ShoppingCartRepository.java
@@ -1,6 +1,7 @@
package spring.boot.bookstore.repository.shoppingcart;
import java.util.Optional;
+import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import spring.boot.bookstore.model.ShoppingCart;
@@ -8,4 +9,7 @@
@Repository
public interface ShoppingCartRepository extends JpaRepository {
Optional getUserById(Long id);
+
+ @EntityGraph(attributePaths = "cartItems")
+ Optional findById(Long id);
}
diff --git a/src/main/java/spring/boot/bookstore/service/emailsender/EmailService.java b/src/main/java/spring/boot/bookstore/service/emailsender/EmailService.java
new file mode 100644
index 0000000..3a214af
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/service/emailsender/EmailService.java
@@ -0,0 +1,7 @@
+package spring.boot.bookstore.service.emailsender;
+
+import spring.boot.bookstore.model.Order;
+
+public interface EmailService {
+ void sendStatusChangeEmail(String userEmail, Order.Status newStatus);
+}
diff --git a/src/main/java/spring/boot/bookstore/service/emailsender/EmailServiceImpl.java b/src/main/java/spring/boot/bookstore/service/emailsender/EmailServiceImpl.java
new file mode 100644
index 0000000..eb916b6
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/service/emailsender/EmailServiceImpl.java
@@ -0,0 +1,118 @@
+package spring.boot.bookstore.service.emailsender;
+
+import java.util.Properties;
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.activation.FileDataSource;
+import javax.mail.Authenticator;
+import javax.mail.BodyPart;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import spring.boot.bookstore.exception.MessageSenderException;
+import spring.boot.bookstore.model.Order;
+
+@Service
+public class EmailServiceImpl implements EmailService {
+ @Value("${mail.smtp.host}")
+ private String smtpHost;
+ @Value("${mail.smtp.port}")
+ private int smtpPort;
+ @Value("${mail.smtp.auth}")
+ private String auth;
+ @Value("${mail.smtp.starttls.enable}")
+ private String starttlsEnable;
+ @Value("${mail.username}")
+ private String username;
+ @Value("${mail.password}")
+ private String password;
+ @Value("${image.path.pending}")
+ private String imagePathPending;
+ @Value("${image.path.completed}")
+ private String imagePathCompleted;
+ @Value("${image.path.delivered}")
+ private String imagePathDelivered;
+
+ @Override
+ public void sendStatusChangeEmail(String userEmail, Order.Status newStatus) {
+ try {
+ Session session = prepareSession();
+ MimeMessage message = createMessage(session, userEmail, newStatus);
+ Transport.send(message);
+ } catch (MessagingException e) {
+ throw new MessageSenderException("Error sending email....", e);
+ }
+ }
+
+ private Session prepareSession() {
+ Properties properties = prepareProperties();
+ return Session.getInstance(properties, new Authenticator() {
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+
+ private Properties prepareProperties() {
+ Properties properties = new Properties();
+ properties.put("mail.smtp.host", smtpHost);
+ properties.put("mail.smtp.port", smtpPort);
+ properties.put("mail.smtp.auth", auth);
+ properties.put("mail.smtp.starttls.enable", starttlsEnable);
+ return properties;
+ }
+
+ private MimeMessage createMessage(Session session, String userEmail, Order.Status newStatus)
+ throws MessagingException {
+ MimeMessage message = new MimeMessage(session);
+ message.setFrom(new InternetAddress(username));
+ message.addRecipient(Message.RecipientType.TO, new InternetAddress(userEmail));
+ message.setSubject("Order Status Change ! ");
+ Multipart multipart = createMultipart(newStatus);
+ message.setContent(multipart);
+ return message;
+ }
+
+ private Multipart createMultipart(Order.Status newStatus)
+ throws MessagingException {
+ Multipart multipart = new MimeMultipart();
+ addTextPart(multipart, newStatus);
+ addImagePart(multipart, newStatus);
+ return multipart;
+ }
+
+ private void addTextPart(Multipart multipart, Order.Status newStatus)
+ throws MessagingException {
+ BodyPart textPart = new MimeBodyPart();
+ String emailText = "The status of your order has been changed to : " + newStatus;
+ textPart.setText(emailText);
+ multipart.addBodyPart(textPart);
+ }
+
+ private void addImagePart(Multipart multipart, Order.Status newStatus)
+ throws MessagingException {
+ BodyPart imagePart = new MimeBodyPart();
+ String imagePath = getImagePathForStatus(newStatus);
+ DataSource source = new FileDataSource(imagePath);
+ imagePart.setDataHandler(new DataHandler(source));
+ imagePart.setFileName("image.jpg");
+ multipart.addBodyPart(imagePart);
+ }
+
+ private String getImagePathForStatus(Order.Status status) {
+ return switch (status) {
+ case COMPLETED -> imagePathCompleted;
+ case DELIVERED -> imagePathDelivered;
+ default -> imagePathPending;
+ };
+ }
+}
diff --git a/src/main/java/spring/boot/bookstore/service/order/OrderService.java b/src/main/java/spring/boot/bookstore/service/order/OrderService.java
new file mode 100644
index 0000000..854bfdd
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/service/order/OrderService.java
@@ -0,0 +1,21 @@
+package spring.boot.bookstore.service.order;
+
+import java.util.List;
+import java.util.Set;
+import org.springframework.data.domain.Pageable;
+import spring.boot.bookstore.dto.order.OrderRequestDto;
+import spring.boot.bookstore.dto.order.OrderResponseDto;
+import spring.boot.bookstore.dto.orderitem.OrderItemResponseDto;
+import spring.boot.bookstore.model.Order;
+
+public interface OrderService {
+ OrderResponseDto create(Long id, OrderRequestDto orderRequestDto);
+
+ List findAllOrders(Long id, Pageable pageable);
+
+ OrderResponseDto updateOrderStatus(Long orderId, Order.Status status);
+
+ Set findAllOrderItems(Long orderId);
+
+ OrderItemResponseDto findOrderItemById(Long orderId, Long itemId);
+}
diff --git a/src/main/java/spring/boot/bookstore/service/order/OrderServiceImpl.java b/src/main/java/spring/boot/bookstore/service/order/OrderServiceImpl.java
new file mode 100644
index 0000000..31950ce
--- /dev/null
+++ b/src/main/java/spring/boot/bookstore/service/order/OrderServiceImpl.java
@@ -0,0 +1,124 @@
+package spring.boot.bookstore.service.order;
+
+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 org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import spring.boot.bookstore.dto.order.OrderRequestDto;
+import spring.boot.bookstore.dto.order.OrderResponseDto;
+import spring.boot.bookstore.dto.orderitem.OrderItemResponseDto;
+import spring.boot.bookstore.exception.EntityNotFoundException;
+import spring.boot.bookstore.mapper.OrderItemMapper;
+import spring.boot.bookstore.mapper.OrderMapper;
+import spring.boot.bookstore.model.Order;
+import spring.boot.bookstore.model.OrderItem;
+import spring.boot.bookstore.model.ShoppingCart;
+import spring.boot.bookstore.model.User;
+import spring.boot.bookstore.repository.OrderRepository;
+import spring.boot.bookstore.repository.shoppingcart.ShoppingCartRepository;
+import spring.boot.bookstore.service.emailsender.EmailServiceImpl;
+import spring.boot.bookstore.service.shoppingcart.impl.ShoppingCartManager;
+import spring.boot.bookstore.service.user.UserService;
+
+@Service
+@RequiredArgsConstructor
+public class OrderServiceImpl implements OrderService {
+ private final OrderItemMapper orderItemMapper;
+ private final OrderMapper orderMapper;
+ private final OrderRepository repository;
+ private final ShoppingCartRepository shoppingCartRepository;
+ private final UserService userService;
+ private final ShoppingCartManager registerNewCart;
+ private final EmailServiceImpl emailService;
+
+ @Override public OrderResponseDto create(Long id, OrderRequestDto orderRequestDto) {
+ User authUser = userService.getAuthenticated();
+ ShoppingCart shoppingCart = shoppingCartRepository.getUserById(authUser.getId())
+ .orElseGet(() -> registerNewCart.registerNewCart(authUser));
+ Order order = new Order();
+ order.setShippingAddress(orderRequestDto.getShippingAddress());
+ order.setUser(shoppingCart.getUser());
+ order.setTotal(BigDecimal.ZERO);
+ order.setStatus(Order.Status.PENDING);
+ Set orderItems = getOrderItemsFromShoppingCart(shoppingCart, order);
+ order.setOrderItems(orderItems);
+ order.setOrderDate(LocalDateTime.now());
+ BigDecimal totalPrice = orderItems
+ .stream()
+ .map(orderItem -> orderItem
+ .getBook()
+ .getPrice()
+ .multiply(new BigDecimal(orderItem.getQuantity())))
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ order.setTotal(totalPrice);
+ return orderMapper.toDto(repository.save(order));
+ }
+
+ @Override
+ public List findAllOrders(Long id, Pageable pageable) {
+ return repository.findAllOrders(id).stream().map(orderMapper::toDto).toList();
+ }
+
+ @Override
+ public OrderResponseDto updateOrderStatus(Long orderId, Order.Status status) {
+ Order order = getOrderById(orderId);
+ order.setStatus(status);
+ order = repository.save(order);
+ emailService.sendStatusChangeEmail(order.getUser().getEmail(), status);
+ return orderMapper.toDto(order);
+ }
+
+ private Order getOrderById(Long orderId) {
+ return repository.findById(orderId).orElseThrow(() ->
+ new EntityNotFoundException("can't find order by id: " + orderId));
+ }
+
+ @Override
+ @Transactional
+ public Set findAllOrderItems(Long orderId) {
+ Order order = repository.findById(orderId)
+ .orElseThrow(
+ () -> new EntityNotFoundException("cant find Order using provided ID : "
+ + orderId));
+ return order.getOrderItems().stream()
+ .map(orderItemMapper::toDto)
+ .collect(Collectors.toSet());
+ }
+
+ private Set getOrderItemsFromShoppingCart(ShoppingCart shoppingCart, Order order) {
+ return shoppingCart.getCartItems().stream()
+ .map(cartItem -> {
+ OrderItem orderItem = new OrderItem();
+ orderItem.setBook(cartItem.getBook());
+ orderItem.setQuantity(cartItem.getQuantity());
+ orderItem.setOrder(order);
+ orderItem.setPrice(cartItem.getBook().getPrice()
+ .multiply(new BigDecimal(cartItem.getQuantity())));
+ return orderItem;
+ })
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public OrderItemResponseDto findOrderItemById(Long orderId, Long itemId) {
+ Order order = repository.findById(orderId)
+ .orElseThrow(
+ () -> new EntityNotFoundException("cant find order by provided ID : "
+ + orderId));
+ return order.getOrderItems()
+ .stream()
+ .filter(o -> o.getId().equals(itemId))
+ .findFirst()
+ .map(orderItemMapper::toDto)
+ .orElseThrow(
+ () -> new EntityNotFoundException("cant find item using provided ID : "
+ + itemId
+ + ", with order id"
+ + orderId));
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index a262c59..c8213b6 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -11,3 +11,14 @@ spring.jpa.open-in-view=false
jwt.expiration=900000
jwt.secret=s3cr3t_str!ng*s0me_Rand0mVa!u?s3cr3t_str!ng*s0me_Rand0mVa!u
+
+image.path.pending=src/main/resources/imageForEmail/2023-10-04 12.20.42.jpg
+image.path.completed=src/main/resources/imageForEmail/2023-10-04 12.20.50.jpg
+image.path.delivered=src/main/resources/imageForEmail/2023-10-03 22.08.55.jpg
+
+mail.smtp.host=smtp.gmail.com
+mail.smtp.port=587
+mail.smtp.auth=true
+mail.smtp.starttls.enable=true
+mail.username=rospsix@gmail.com
+mail.password=boujgexfjbdhzkvn
diff --git a/src/main/resources/db/changelog/changes/19-create-order-table.yaml b/src/main/resources/db/changelog/changes/19-create-order-table.yaml
new file mode 100644
index 0000000..2391ef3
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/19-create-order-table.yaml
@@ -0,0 +1,49 @@
+databaseChangeLog:
+ - changeSet:
+ id: create-orders-table
+ author: Ros
+ changes:
+ - createTable:
+ tableName: orders
+ columns:
+ - column:
+ name: id
+ type: bigint
+ autoIncrement: true
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: users_id
+ type: bigint
+ constraints:
+ foreignKeyName: fk_orders_users
+ referencedTableName: users
+ referencedColumnNames: id
+ nullable: false
+ - column:
+ name: status
+ type: varchar(255)
+ constraints:
+ nullable: false
+ - column:
+ name: total
+ type: decimal(38,2)
+ constraints:
+ nullable: false
+ - column:
+ name: order_date
+ type: timestamp
+ constraints:
+ nullable: false
+ - column:
+ name: shipping_address
+ type: varchar(255)
+ constraints:
+ nullable: false
+ - column:
+ name: is_deleted
+ type: boolean
+ defaultValueBoolean: false
+ constraints:
+ nullable: false
diff --git a/src/main/resources/db/changelog/changes/20-create-order-items-table.yaml b/src/main/resources/db/changelog/changes/20-create-order-items-table.yaml
new file mode 100644
index 0000000..1617d26
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/20-create-order-items-table.yaml
@@ -0,0 +1,53 @@
+databaseChangeLog:
+ - changeSet:
+ id: create-order-items-table
+ author: Ros
+ 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_items_orders
+ referencedTableName: orders
+ referencedColumnNames: id
+ nullable: false
+ - column:
+ name: book_id
+ type: bigint
+ constraints:
+ foreignKeyName: fk_order_items_books
+ referencedTableName: books
+ referencedColumnNames: id
+ nullable: false
+ - column:
+ name: quantity
+ type: int
+ constraints:
+ nullable: false
+ - column:
+ name: price
+ type: decimal(38,2)
+ constraints:
+ nullable: false
+ - column:
+ name: is_deleted
+ type: boolean
+ defaultValueBoolean: false
+ constraints:
+ nullable: false
+ - column:
+ name: status
+ type: varchar(255)
+ defaultValue: "PENDING"
+ constraints:
+ nullable: true
diff --git a/src/main/resources/db/changelog/changes/21-fill-order-table-with-data.yaml b/src/main/resources/db/changelog/changes/21-fill-order-table-with-data.yaml
new file mode 100644
index 0000000..0f826b4
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/21-fill-order-table-with-data.yaml
@@ -0,0 +1,59 @@
+ databaseChangeLog:
+ - changeSet:
+ id: fill-orders-table-with-data
+ author: Ros
+ changes:
+ - insert:
+ tableName: orders
+ columns:
+ - column:
+ name: "users_id"
+ value: "1"
+ - column:
+ name: "status"
+ value: "COMPLETED"
+ - column:
+ name: "total"
+ value: "100.00"
+ - column:
+ name: "order_date"
+ value: "2023-01-15 10:00:00"
+ - column:
+ name: "shipping_address"
+ value: "123 Main St, City, Country"
+ - insert:
+ tableName: orders
+ columns:
+ - column:
+ name: "users_id"
+ value: "2"
+ - column:
+ name: "status"
+ value: "PENDING"
+ - column:
+ name: "total"
+ value: "75.50"
+ - column:
+ name: "order_date"
+ value: "2023-01-16 11:30:00"
+ - column:
+ name: "shipping_address"
+ value: "456 Elm St, City, Country"
+ - insert:
+ tableName: orders
+ columns:
+ - column:
+ name: "users_id"
+ value: "3"
+ - column:
+ name: "status"
+ value: "DELIVERED"
+ - column:
+ name: "total"
+ value: "150.25"
+ - column:
+ name: "order_date"
+ value: "2023-01-17 14:15:00"
+ - column:
+ name: "shipping_address"
+ value: "789 Oak St, City, Country"
diff --git a/src/main/resources/db/changelog/changes/22-fill-order-items-table-with-data.yaml b/src/main/resources/db/changelog/changes/22-fill-order-items-table-with-data.yaml
new file mode 100644
index 0000000..da81412
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/22-fill-order-items-table-with-data.yaml
@@ -0,0 +1,50 @@
+databaseChangeLog:
+ - changeSet:
+ id: fill-order-items-table-with-data
+ author: Ros
+ changes:
+ - insert:
+ tableName: order_items
+ columns:
+ - column:
+ name: "order_id"
+ value: "1"
+ - column:
+ name: "book_id"
+ value: "1"
+ - column:
+ name: "quantity"
+ value: "2"
+ - column:
+ name: "price"
+ value: "49.99"
+ - insert:
+ tableName: order_items
+ columns:
+ - column:
+ name: "order_id"
+ value: "1"
+ - column:
+ name: "book_id"
+ value: "3"
+ - column:
+ name: "quantity"
+ value: "1"
+ - column:
+ name: "price"
+ value: "29.99"
+ - insert:
+ tableName: order_items
+ columns:
+ - column:
+ name: "order_id"
+ value: "2"
+ - column:
+ name: "book_id"
+ value: "2"
+ - column:
+ name: "quantity"
+ value: "3"
+ - column:
+ name: "price"
+ value: "19.99"
diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml
index 43c2137..3d72a21 100644
--- a/src/main/resources/db/changelog/db.changelog-master.yaml
+++ b/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -35,8 +35,14 @@ databaseChangeLog:
file: db/changelog/changes/17-create-shopping-cart-item-table.yaml
- include:
file: db/changelog/changes/18-fill-shopping-cart-item-table-with-data.yaml
-
-
+ - include:
+ file: db/changelog/changes/19-create-order-table.yaml
+ - include:
+ file: db/changelog/changes/20-create-order-items-table.yaml
+ - include:
+ file: db/changelog/changes/21-fill-order-table-with-data.yaml
+ - include:
+ file: db/changelog/changes/22-fill-order-items-table-with-data.yaml
diff --git a/src/main/resources/imageForEmail/2023-10-03 22.08.55.jpg b/src/main/resources/imageForEmail/2023-10-03 22.08.55.jpg
new file mode 100644
index 0000000..2386e95
Binary files /dev/null and b/src/main/resources/imageForEmail/2023-10-03 22.08.55.jpg differ
diff --git a/src/main/resources/imageForEmail/2023-10-04 12.20.42.jpg b/src/main/resources/imageForEmail/2023-10-04 12.20.42.jpg
new file mode 100644
index 0000000..abc2c60
Binary files /dev/null and b/src/main/resources/imageForEmail/2023-10-04 12.20.42.jpg differ
diff --git a/src/main/resources/imageForEmail/2023-10-04 12.20.50.jpg b/src/main/resources/imageForEmail/2023-10-04 12.20.50.jpg
new file mode 100644
index 0000000..a8a26bf
Binary files /dev/null and b/src/main/resources/imageForEmail/2023-10-04 12.20.50.jpg differ