Skip to content

Commit

Permalink
Added Order entity with all related dto, service, mapper and controll…
Browse files Browse the repository at this point in the history
…er (#14)

* Add Order, OrderItem entities, and Status enum.

* Added repository and dto layers, added mappers for related Order & OrderItem entities.

* Added liquibase change files to create orders and order-items tables in DB. Fixed checkstyle issues.

* Added service and controller layers. Code refactored. Fixed issue in ShoppingCartRepository with findByUser query.

* Resolved issues:
 - Refactored order saving method in OrderServiceImpl.
 - Fixed issues with lazy initialization.
 - Added authorized user in the getOrderItem method in the controller so that any user cannot get another user's data.
 - Added equals & hashcode, and toString in OrderItem entity.
 - Added equals and hashcode to all entities.
 - Resolved issue when anyone can search for any order items.

* Resolved issue with shopping cart: now when an order is placed, the shopping cart is removed. Removed soft delete in ShoppingCart

* Fixed the issue where categories were not being added to saved books.

* Added exclude constraint for EqualsAndHashcode and ToString annotations. 
* Added @CreationTimestamp for orderDate in Order entity.
  • Loading branch information
nklimovych authored May 27, 2024
1 parent 56913c9 commit 6340daa
Show file tree
Hide file tree
Showing 35 changed files with 641 additions and 45 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ build/
!**/src/test/**/build/

### VS Code ###
.vscode/
.vscode/

### Docker ###
/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package mate.academy.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 lombok.RequiredArgsConstructor;
import mate.academy.bookstore.dto.order.OrderItemResponseDto;
import mate.academy.bookstore.dto.order.OrderRequestDto;
import mate.academy.bookstore.dto.order.OrderResponseDto;
import mate.academy.bookstore.dto.order.OrderStatusDto;
import mate.academy.bookstore.model.User;
import mate.academy.bookstore.service.OrderService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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;

@Tag(name = "Order Management", description = "Endpoints for managing the order")
@RestController
@RequestMapping("/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;

@PostMapping
@PreAuthorize("hasAnyRole('ROLE_USER','ROLE_ADMIN')")
@Operation(summary = "Create an order",
description = "Creates a new order with the items in the current user cart")
public void createOrder(
@Valid @RequestBody OrderRequestDto orderDto,
@AuthenticationPrincipal User user) {
orderService.createOrder(orderDto, user);
}

@GetMapping
@PreAuthorize("hasAnyRole('ROLE_USER','ROLE_ADMIN')")
@Operation(summary = "Get orders history",
description = "Retrieves the order history for the current user")
public List<OrderResponseDto> getOrdersHistory(
@AuthenticationPrincipal User currentUser) {
return orderService.getAllOrders(currentUser);
}

@PatchMapping("/{orderId}")
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
@Operation(summary = "Update an order status",
description = "Updates the status of an existing order identified by its id")
public void updateOrderStatus(
@Valid @RequestBody OrderStatusDto statusDto,
@PathVariable Long orderId,
@AuthenticationPrincipal User user) {
orderService.updateStatus(statusDto, orderId, user.getId());
}

@GetMapping("/{orderId}/items")
@PreAuthorize("hasAnyRole('ROLE_USER','ROLE_ADMIN')")
@Operation(summary = "Get all order items from order",
description = "Retrieves all items associated for authenticated user")
public List<OrderItemResponseDto> getAllOrderItems(
@PathVariable Long orderId,
@AuthenticationPrincipal User user) {
return orderService.getAllOrderItems(orderId, user);
}

@GetMapping("/{orderId}/items/{itemId}")
@PreAuthorize("hasAnyRole('ROLE_USER','ROLE_ADMIN')")
@Operation(summary = "Get item from order",
description = "Retrieves a specific item from an order identified by its id")
public OrderItemResponseDto getOrderItem(
@PathVariable Long orderId,
@PathVariable Long itemId,
@AuthenticationPrincipal User user) {
return orderService.getOrderItem(orderId, itemId, user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Set;
import lombok.Data;
import org.hibernate.validator.constraints.ISBN;

Expand All @@ -16,6 +18,8 @@ public class CreateBookRequestDto {
@NotNull
@ISBN(type = ISBN.Type.ANY)
private String isbn;
@NotEmpty
private Set<Long> categoryIds;
@NotNull
@Min(0)
private BigDecimal price;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;

public record CategoryDto(
Long id,
@NotBlank
@Size(min = 4, max = 24, message = "Category name must be 4 to 24 characters long")
String name,
String description
) {
@Data
public class CategoryDto {
private Long id;
@NotBlank
@Size(min = 4, max = 24, message = "length should be 4 to 24 characters long")
private String name;
private String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package mate.academy.bookstore.dto.order;

import lombok.Data;

@Data
public class OrderItemResponseDto {
private Long id;
private Long bookId;
private int quantity;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package mate.academy.bookstore.dto.order;

import jakarta.validation.constraints.NotBlank;
import lombok.Data;

@Data
public class OrderRequestDto {
@NotBlank(message = "Shipping address can not be empty")
private String shippingAddress;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package mate.academy.bookstore.dto.order;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Set;
import lombok.Data;

@Data
public class OrderResponseDto {
private Long id;
private Long userId;
private Set<OrderItemResponseDto> orderItems;
private LocalDateTime orderDate;
private BigDecimal total;
private String status;
}
11 changes: 11 additions & 0 deletions src/main/java/mate/academy/bookstore/dto/order/OrderStatusDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package mate.academy.bookstore.dto.order;

import jakarta.validation.constraints.NotNull;
import lombok.Data;
import mate.academy.bookstore.model.order.Status;

@Data
public class OrderStatusDto {
@NotNull
private Status status;
}
25 changes: 22 additions & 3 deletions src/main/java/mate/academy/bookstore/mapper/BookMapper.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mate.academy.bookstore.mapper;

import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import mate.academy.bookstore.config.MapperConfig;
import mate.academy.bookstore.dto.book.BookDto;
Expand All @@ -20,6 +21,7 @@ public interface BookMapper {

@Mapping(target = "id", ignore = true)
@Mapping(target = "deleted", ignore = true)
@Mapping(target = "categories", ignore = true)
Book toEntity(CreateBookRequestDto requestDto);

BookDtoWithoutCategoryIds toDtoWithoutCategories(Book book);
Expand All @@ -28,9 +30,26 @@ public interface BookMapper {
default void setCategoryIds(@MappingTarget BookDto bookDto, Book book) {
if (book.getCategories() == null) {
bookDto.setCategories(Collections.emptySet());
} else {
bookDto.setCategories(book.getCategories().stream()
.map(Category::getId)
.collect(Collectors.toSet()));
}
}

@AfterMapping
default void setCategories(@MappingTarget Book book, CreateBookRequestDto requestDto) {
if (requestDto.getCategoryIds() == null) {
book.setCategories(Collections.emptySet());
} else {
Set<Category> categories = requestDto.getCategoryIds().stream()
.map(id -> {
Category category = new Category();
category.setId(id);
return category;
})
.collect(Collectors.toSet());
book.setCategories(categories);
}
bookDto.setCategories(book.getCategories().stream()
.map(Category::getId)
.collect(Collectors.toSet()));
}
}
13 changes: 13 additions & 0 deletions src/main/java/mate/academy/bookstore/mapper/OrderItemMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.bookstore.mapper;

import mate.academy.bookstore.config.MapperConfig;
import mate.academy.bookstore.dto.order.OrderItemResponseDto;
import mate.academy.bookstore.model.order.OrderItem;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(config = MapperConfig.class)
public interface OrderItemMapper {
@Mapping(target = "bookId", source = "book.id")
OrderItemResponseDto toDto(OrderItem orderItem);
}
13 changes: 13 additions & 0 deletions src/main/java/mate/academy/bookstore/mapper/OrderMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.bookstore.mapper;

import mate.academy.bookstore.config.MapperConfig;
import mate.academy.bookstore.dto.order.OrderResponseDto;
import mate.academy.bookstore.model.order.Order;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(config = MapperConfig.class, uses = OrderItemMapper.class)
public interface OrderMapper {
@Mapping(source = "user.id", target = "userId")
OrderResponseDto toDto(Order order);
}
4 changes: 3 additions & 1 deletion src/main/java/mate/academy/bookstore/model/Book.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;

@Getter
@Setter
@EqualsAndHashCode(of = {"id", "title", "author", "isbn", "categories"})
@EqualsAndHashCode(exclude = {"categories"})
@ToString(exclude = {"categories"})
@Entity
@SQLDelete(sql = "UPDATE books SET is_deleted = true WHERE id=?")
@SQLRestriction(value = "is_deleted=false")
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/mate/academy/bookstore/model/CartItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@EqualsAndHashCode(of = {"id", "shoppingCart", "book"})
@EqualsAndHashCode(exclude = {"shoppingCart", "book"})
@ToString(exclude = {"shoppingCart", "book"})
@Entity
@Table(name = "cart_items")
public class CartItem {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/mate/academy/bookstore/model/Category.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.SoftDelete;

@Getter
@Setter
@EqualsAndHashCode
@ToString
@Entity
@SoftDelete
@Table(name = "categories")
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/mate/academy/bookstore/model/Role.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;

@Getter
@Setter
@EqualsAndHashCode
@ToString
@Entity
@Table(name = "roles")
public class Role implements GrantedAuthority {
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/mate/academy/bookstore/model/ShoppingCart.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@
import jakarta.persistence.Table;
import java.util.HashSet;
import java.util.Set;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.SoftDelete;
import lombok.ToString;

@Getter
@Setter
@Entity
@SoftDelete
@EqualsAndHashCode(exclude = {"user", "cartItems"})
@ToString(exclude = {"user", "cartItems"})
@NoArgsConstructor
@Table(name = "shopping_carts")
public class ShoppingCart {
Expand All @@ -32,7 +34,7 @@ public class ShoppingCart {
@JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)
private User user;

@OneToMany(mappedBy = "shoppingCart", cascade = CascadeType.REMOVE)
@OneToMany(mappedBy = "shoppingCart", cascade = CascadeType.ALL)
private Set<CartItem> cartItems = new HashSet<>();

public ShoppingCart(User user) {
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/mate/academy/bookstore/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Getter
@Setter
@EqualsAndHashCode(of = {"id", "email", "firstName", "lastName", "shippingAddress"})
@EqualsAndHashCode(exclude = {"roles"})
@ToString(exclude = {"roles"})
@Entity
@SQLDelete(sql = "UPDATE users SET is_deleted=true WHERE id=?")
@SQLRestriction(value = "is_deleted=false")
Expand Down
Loading

0 comments on commit 6340daa

Please sign in to comment.