Skip to content

Commit

Permalink
Merge pull request #19 from leung018/order-service-part-2
Browse files Browse the repository at this point in the history
Order-service-part-2
  • Loading branch information
leung018 authored Dec 10, 2024
2 parents f57bfa2 + e92407c commit d900698
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.leungcheng.spring_simple_backend.auth.UserAuthenticatedInfoToken;
import com.leungcheng.spring_simple_backend.domain.User;
import com.leungcheng.spring_simple_backend.domain.UserRepository;
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -18,5 +19,5 @@ public UserAccountInfo me(UserAuthenticatedInfoToken authToken) {
return new UserAccountInfo(user.getUsername(), user.getBalance());
}

public record UserAccountInfo(String username, double balance) {}
public record UserAccountInfo(String username, BigDecimal balance) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.leungcheng.spring_simple_backend.domain.Product;
import com.leungcheng.spring_simple_backend.domain.ProductRepository;
import jakarta.validation.Valid;
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
Expand Down Expand Up @@ -33,5 +34,5 @@ Product one(@PathVariable String id) {
return repository.findById(id).orElseThrow(() -> new ProductNotFoundException(id));
}

public record CreateProductRequest(String name, double price, int quantity) {}
public record CreateProductRequest(String name, BigDecimal price, int quantity) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,29 @@
import jakarta.persistence.Table;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import java.math.BigDecimal;

@Entity
@Table(name = "products")
public class Product {
public static class Builder {
private String name;
private double price;
private BigDecimal price;
private int quantity;
private String userId;
private String id = java.util.UUID.randomUUID().toString();

private Builder id(String id) {
this.id = id;
return this;
}

public Builder name(String name) {
this.name = name;
return this;
}

public Builder price(double price) {
public Builder price(BigDecimal price) {
this.price = price;
return this;
}
Expand All @@ -39,6 +46,7 @@ public Builder userId(String userId) {

public Product build() {
Product product = new Product();
product.id = id;
product.name = name;
product.price = price;
product.quantity = quantity;
Expand All @@ -51,16 +59,20 @@ public Product build() {

private Product() {}

public Builder toBuilder() {
return new Builder().name(name).price(price).quantity(quantity).userId(userId).id(id);
}

@Id
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String id = java.util.UUID.randomUUID().toString();
private String id;

private String userId;

@NotBlank private String name;

@Min(0)
private double price;
private BigDecimal price;

@Min(0)
private int quantity;
Expand All @@ -77,7 +89,7 @@ public String getName() {
return name;
}

public double getPrice() {
public BigDecimal getPrice() {
return price;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import jakarta.persistence.Table;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
Expand All @@ -16,12 +17,18 @@
@Entity
@Table(name = "users")
public class User implements UserDetails {
public static final double INITIAL_BALANCE = 100;
public static final BigDecimal INITIAL_BALANCE = new BigDecimal(100);

public static class Builder {
private String username;
private String password;
private double balance;
private BigDecimal balance;
private String id = java.util.UUID.randomUUID().toString();

private Builder id(String id) {
this.id = id;
return this;
}

public Builder username(String username) {
this.username = username;
Expand All @@ -33,13 +40,15 @@ public Builder password(String password) {
return this;
}

public Builder balance(double balance) {
public Builder balance(BigDecimal balance) {
this.balance = balance;
return this;
}

public User build() {
User user = new User();

user.id = id;
user.username = username;
user.password = password;
user.balance = balance;
Expand All @@ -51,9 +60,13 @@ public User build() {

private User() {}

public User.Builder toBuilder() {
return new User.Builder().username(username).password(password).balance(balance).id(id);
}

@Id
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String id = java.util.UUID.randomUUID().toString();
private String id;

@Column(unique = true)
@NotBlank
Expand All @@ -62,7 +75,7 @@ private User() {}
@NotBlank private String password;

@Min(0)
private double balance;
private BigDecimal balance;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Expand All @@ -83,7 +96,7 @@ public String getUsername() {
return username;
}

public double getBalance() {
public BigDecimal getBalance() {
return balance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@
@Table(name = "orders")
public class Order {
@Id private final String id = java.util.UUID.randomUUID().toString();
private String userId;
private String buyerUserId;
private PurchaseItems purchaseItems;

private Order() {}

Order(String buyerUserId, PurchaseItems purchaseItems) {
this.buyerUserId = buyerUserId;
this.purchaseItems = purchaseItems;
}

public String getId() {
return id;
}

public String getUserId() {
return userId;
public String getBuyerUserId() {
return buyerUserId;
}

public PurchaseItems getPurchaseItems() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package com.leungcheng.spring_simple_backend.domain.order;

import com.google.common.collect.ImmutableMap;
import com.leungcheng.spring_simple_backend.domain.Product;
import com.leungcheng.spring_simple_backend.domain.ProductRepository;
import com.leungcheng.spring_simple_backend.domain.User;
import com.leungcheng.spring_simple_backend.domain.UserRepository;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Optional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {
Expand All @@ -19,7 +27,77 @@ public OrderService(
this.orderRepository = orderRepository;
}

public Order createOrder(String userId, PurchaseItems purchaseItems) {
throw new UnsupportedOperationException("Not implemented");
@Transactional(isolation = Isolation.SERIALIZABLE)
public Order createOrder(String buyerUserId, PurchaseItems purchaseItems) {
User buyer =
getUser(buyerUserId)
.orElseThrow(() -> new IllegalArgumentException("Buyer does not exist"));

BigDecimal totalCost = processPurchaseItems(purchaseItems);

if (buyer.getBalance().compareTo(totalCost) < 0) {
throw new IllegalArgumentException("Insufficient balance");
}
saveNewBalance(buyer, buyer.getBalance().subtract(totalCost));

return addNewOrder(buyerUserId, purchaseItems);
}

private Optional<User> getUser(String userId) {
return userRepository.findById(userId);
}

private void saveNewBalance(User buyer, BigDecimal newBalance) {
User updatedBuyer = buyer.toBuilder().balance(newBalance).build();
userRepository.save(updatedBuyer);
}

private Order addNewOrder(String buyerUserId, PurchaseItems purchaseItems) {
Order order = new Order(buyerUserId, purchaseItems);
return orderRepository.save(order);
}

private BigDecimal processPurchaseItems(PurchaseItems purchaseItems) {
ImmutableMap<String, Integer> productIdToQuantity = purchaseItems.getProductIdToQuantity();
if (productIdToQuantity.isEmpty()) {
throw new IllegalArgumentException("Purchase items cannot be empty");
}

BigDecimal totalCost = BigDecimal.ZERO;
for (Map.Entry<String, Integer> entry : productIdToQuantity.entrySet()) {
String productId = entry.getKey();
int purchaseQuantity = entry.getValue();
Product product = getProduct(productId);

reduceProductStock(product, purchaseQuantity);
addProfitToSeller(product, purchaseQuantity);

BigDecimal itemCost = product.getPrice().multiply(BigDecimal.valueOf(purchaseQuantity));
totalCost = totalCost.add(itemCost);
}
return totalCost;
}

private void reduceProductStock(Product product, int purchaseQuantity) {
if (purchaseQuantity > product.getQuantity()) {
throw new IllegalArgumentException("Insufficient stock for product: " + product.getId());
}
int newQuantity = product.getQuantity() - purchaseQuantity;
Product updatedProduct = product.toBuilder().quantity(newQuantity).build();
productRepository.save(updatedProduct);
}

private void addProfitToSeller(Product product, int purchaseQuantity) {
User seller = getUser(product.getUserId()).orElseThrow();
BigDecimal profit = product.getPrice().multiply(BigDecimal.valueOf(purchaseQuantity));
BigDecimal newBalance = seller.getBalance().add(profit);
saveNewBalance(seller, newBalance);
}

private Product getProduct(String productId) {
return productRepository
.findById(productId)
.orElseThrow(
() -> new IllegalArgumentException("Product: " + productId + " does not exist"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.leungcheng.spring_simple_backend.domain.User;
import com.leungcheng.spring_simple_backend.domain.UserRepository;
import com.leungcheng.spring_simple_backend.validation.ObjectValidator.ObjectValidationException;
import java.math.BigDecimal;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -63,19 +64,20 @@ void shouldCreateAndGetProduct() throws Exception {
useNewUserAccessToken();

CreateProductParams params = CreateProductParams.sample();
params.price = "19.2";
MvcResult mvcResult =
createProduct(params)
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value(params.name))
.andExpect(jsonPath("$.price").value(params.price))
.andExpect(jsonPath("$.price").value("19.2"))
.andExpect(jsonPath("$.quantity").value(params.quantity))
.andReturn();
String productId = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.id");

getProduct(productId)
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value(params.name))
.andExpect(jsonPath("$.price").value(params.price))
.andExpect(jsonPath("$.price").value("19.2"))
.andExpect(jsonPath("$.quantity").value(params.quantity));
}

Expand All @@ -100,12 +102,16 @@ void shouldHandleObjectValidationException_AndIncludeTheProperInfoInTheResponse(
useNewUserAccessToken();

CreateProductParams params = CreateProductParams.sample();
params.price = -1;
params.price = "-1";

// Set up the expected exception that will be thrown when building this product
ObjectValidationException expectedException = null;
try {
new Product.Builder().name(params.name).price(params.price).quantity(params.quantity).build();
new Product.Builder()
.name(params.name)
.price(new BigDecimal(params.price))
.quantity(params.quantity)
.build();
} catch (ObjectValidationException ex) {
expectedException = ex;
}
Expand Down Expand Up @@ -231,20 +237,24 @@ void shouldGetAccountInfo() throws Exception {

getAccountInfo()
.andExpect(status().isOk())
.andExpect(jsonPath("$.balance").value(User.INITIAL_BALANCE))
.andExpect(
jsonPath("$.balance")
.value(
"100.0")) // FIXME: not hardcoding it. Perhaps move INITIAL_BALANCE to sth that
// can be accessed by tests
.andExpect(jsonPath("$.username").value(userCredentials.username));
}

private static class CreateProductParams {
String name;
double price;
String price;
int quantity;

private static CreateProductParams sample() {
return new CreateProductParams("Product 1", 1.0, 50);
return new CreateProductParams("Product 1", "10", 50);
}

private CreateProductParams(String name, double price, int quantity) {
private CreateProductParams(String name, String price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
Expand Down
Loading

0 comments on commit d900698

Please sign in to comment.