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