Compare commits

...

4 Commits

Author SHA1 Message Date
TakahashiNg 32f5ac9f17 chore: update 2026-05-11 10:00:38 +00:00
TakahashiNg 3f5ef961df chore: update 2026-05-11 09:36:47 +00:00
TakahashiNg 66664d002b chore: update 2026-05-11 09:35:39 +00:00
TakahashiNg b7fa9ca176 chore: update 2026-05-11 09:30:07 +00:00
8 changed files with 123 additions and 177 deletions
+10 -18
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
@@ -20,11 +20,9 @@
<compiler-plugin.version>3.15.0</compiler-plugin.version>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding
>UTF-8</project.reporting.outputEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id
>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.34.6</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.5.4</surefire-plugin.version>
@@ -66,11 +64,7 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client</artifactId>
<artifactId>quarkus-mongodb-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
@@ -132,8 +126,7 @@
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager
>org.jboss.logmanager.LogManager</java.util.logging.manager>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
@@ -153,8 +146,7 @@
<systemPropertyVariables>
<native.image.path>
${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager
>org.jboss.logmanager.LogManager</java.util.logging.manager>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
@@ -177,4 +169,4 @@
</properties>
</profile>
</profiles>
</project>
</project>
@@ -1,18 +0,0 @@
package com.drinkool.dtos;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Cart {
public UUID Id;
public UUID userId;
public UUID eateryId;
public List<CartItem> items;
}
@@ -0,0 +1,44 @@
package com.drinkool.entities;
import io.quarkus.mongodb.panache.PanacheMongoEntity;
import io.quarkus.mongodb.panache.common.MongoEntity;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@MongoEntity(collection = "thiss")
public class Cart extends PanacheMongoEntity {
public String userId;
public UUID eateryId;
public List<CartItem> items;
public Instant createdAt = Instant.now();
public void updateOrAddItem(UUID menuItemId, Integer quantity) {
if (this.items == null) this.items = new ArrayList<>();
Optional<CartItem> existingItem = this.items.stream()
.filter(item -> item.productId.equals(menuItemId))
.findFirst();
if (existingItem.isPresent()) {
CartItem item = existingItem.get();
item.quantity += quantity;
if (item.quantity <= 0) this.items.remove(item);
} else if (quantity > 0) {
this.items.add(new CartItem(menuItemId, quantity));
}
this.update();
}
}
@@ -1,5 +1,6 @@
package com.drinkool.dtos;
package com.drinkool.entities;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -9,6 +10,6 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
public class CartItem {
public String productId;
public UUID productId;
public Integer quantity;
}
@@ -2,21 +2,14 @@ package com.drinkool.services;
import com.drinkool.InternalValue;
import com.drinkool.controllers.EateryController;
import com.drinkool.dtos.Cart;
import com.drinkool.dtos.CartItem;
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.hash.HashCommands;
import io.quarkus.redis.datasource.value.ValueCommands;
import com.drinkool.entities.Cart;
import io.smallrye.mutiny.Uni;
import io.vertx.core.http.HttpServerRequest;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.bson.types.ObjectId;
import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.GraphQLException;
import org.eclipse.microprofile.graphql.Mutation;
@@ -35,15 +28,6 @@ public class CartService {
@RestClient
EateryController eateryController;
private final HashCommands<String, String, String> hashCommands;
private final ValueCommands<String, String> valueCommands;
@Inject
public CartService(RedisDataSource ds) {
this.hashCommands = ds.hash(String.class);
this.valueCommands = ds.value(String.class);
}
@Mutation("createCart")
public Uni<String> createCart(@Name("eateryId") UUID eateryId) {
String userId = request.getHeader(InternalValue.userId);
@@ -51,91 +35,53 @@ public class CartService {
return eateryController
.getEateryById(eateryId)
.onItem()
.transform(response -> {
.<String>transform(response -> {
if (response.getStatus() != Response.Status.OK.getStatusCode()) {
throw new RuntimeException("NotFoundEatery");
}
String cartId = UUID.randomUUID().toString();
String redisKey = "cart:" + cartId;
Cart cart = new Cart();
cart.eateryId = eateryId;
cart.userId = userId != null ? userId : "GUEST";
hashCommands.hset(
redisKey,
Map.of(
"_metadata_owner",
userId != null ? userId : "GUEST",
"_metadata_eatery",
eateryId.toString()
)
);
cart.persist();
valueCommands
.getDataSource()
.key()
.expire(redisKey, Duration.ofDays(30));
return cartId;
return cart.id.toString();
})
.onFailure()
.transform(f ->
new GraphQLException(
"Lỗi kết nối đến dịch vụ nhà hàng: " + f.getMessage()
"Lỗi kết nối đến dịch vụ nhà hàng hoặc DB: " + f.getMessage()
)
);
}
@Query("getCart")
public Cart getCart(@Name("cartId") UUID cartId) throws GraphQLException {
String key = "cart:" + cartId;
Map<String, String> data = hashCommands.hgetall(key);
if (data.isEmpty()) {
throw new GraphQLException("NotFoundCart");
}
List<CartItem> items = new ArrayList<>();
data.forEach((k, v) -> {
if (!k.startsWith("_")) {
items.add(new CartItem(k, Integer.parseInt(v)));
}
});
return new Cart(
cartId,
UUID.fromString(data.get("_metadata_owner")),
UUID.fromString(data.get("_metadata_eatery")),
items
);
public Cart getCart(@Name("cartId") ObjectId cartId) throws GraphQLException {
return Cart.findById(cartId);
}
@Mutation("addItem")
public Uni<Cart> addItem(
@Name("cartId") UUID cartId,
@Name("cartId") ObjectId cartId,
@Name("menuItemId") UUID menuItemId,
@Name("quantity") long quantity
@Name("quantity") Integer quantity
) {
String key = "cart:" + cartId;
Cart cart = Cart.findById(cartId);
UUID eateryId = UUID.fromString(hashCommands.hget(key, "_metadata_eatery"));
if (cart == null) throw new RuntimeException("NotFoundCart");
return eateryController
.checkMenuItemExists(eateryId, menuItemId)
.checkMenuItemExists(cart.eateryId, menuItemId)
.onItem()
.transform(response -> {
if (response.getStatus() != Response.Status.OK.getStatusCode()) {
throw new RuntimeException("NotFoundEatery");
throw new RuntimeException("NotFoundMenuItemInEatery");
}
hashCommands.hincrby(key, menuItemId.toString(), quantity);
hashCommands.getDataSource().key().expire(key, Duration.ofDays(30));
cart.updateOrAddItem(menuItemId, quantity);
try {
return getCart(cartId);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
return cart;
});
}
}
@@ -16,8 +16,9 @@ quarkus.native.additional-build-args=--future-defaults=all
quarkus.index-dependency.lib.group-id=com.drinkool
quarkus.index-dependency.lib.artifact-id=lib
# Redis
quarkus.redis.hosts=redis://${REDIS_SERVICE_SERVICE_HOST:localhost}:${REDIS_SERVICE_SERVICE_PORT:6379}
# MongoDB
quarkus.mongodb.connection-string=mongodb://${MONGO_SERVICE_SERVICE_HOST:host.docker.internal}:27017
quarkus.mongodb.database=drinkool_db
# Rest Client
quarkus.rest-client.eatery.url=http://eatery-service
@@ -1,16 +1,11 @@
package com.drinkool.services;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import com.drinkool.controllers.EateryController;
import com.drinkool.dtos.Cart;
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.hash.HashCommands;
import io.quarkus.redis.datasource.value.ValueCommands;
import com.drinkool.entities.Cart;
import com.drinkool.entities.CartItem;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.smallrye.mutiny.Uni;
@@ -19,9 +14,9 @@ import io.vertx.core.http.HttpServerRequest;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.UUID;
import org.bson.types.ObjectId;
import org.eclipse.microprofile.graphql.GraphQLException;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.BeforeEach;
@@ -40,18 +35,9 @@ public class CartServiceTest {
@RestClient
EateryController eateryController;
@Inject
RedisDataSource redisDataSource;
HashCommands<String, String, String> hashCommands;
ValueCommands<String, String> valueCommands;
@BeforeEach
void setup() {
redisDataSource.flushall();
hashCommands = redisDataSource.hash(String.class);
valueCommands = redisDataSource.value(String.class);
Cart.deleteAll();
}
@Test
@@ -62,54 +48,52 @@ public class CartServiceTest {
Uni.createFrom().item(Response.ok().build())
);
String cartId = cartService
String cartIdStr = cartService
.createCart(eateryId)
.subscribe()
.withSubscriber(UniAssertSubscriber.create())
.awaitItem()
.getItem();
assertNotNull(cartId);
assertNotNull(cartIdStr);
// Kiểm tra xem dữ liệu có thực sự nằm trong MongoDB không
assertNotNull(Cart.findById((cartIdStr)));
}
@Test
void testGetCart_Success() throws Exception {
UUID cartId = UUID.randomUUID();
UUID ownerId = UUID.randomUUID();
UUID eateryId = UUID.randomUUID();
String key = "cart:" + cartId;
Map<String, String> mockData = new HashMap<>();
mockData.put("_metadata_owner", ownerId.toString());
mockData.put("_metadata_eatery", eateryId.toString());
mockData.put("product-1", "2");
mockData.put("product-2", "5");
UUID productId = UUID.randomUUID();
hashCommands.hset(key, mockData);
Cart cart = new Cart();
cart.userId = ownerId.toString();
cart.eateryId = eateryId;
cart.items = new ArrayList<>();
cart.items.add(new CartItem(productId, 2));
cart.items.add(new CartItem(UUID.randomUUID(), 5));
cart.persist();
ObjectId cartId = cart.id;
Cart result = cartService.getCart(cartId);
assertNotNull(result);
assertEquals(cartId, result.getId());
assertEquals(ownerId, result.getUserId());
assertEquals(eateryId, result.getEateryId());
assertEquals(cartId, result.id);
assertEquals(ownerId.toString(), result.userId);
assertEquals(2, result.items.size());
assertEquals(2, result.getItems().size());
boolean hasProduct1 = result
.getItems()
boolean hasProduct1 = result.items
.stream()
.anyMatch(
item ->
item.getProductId().equals("product-1") && item.getQuantity() == 2
);
assertTrue(hasProduct1, "Phải chứa product-1 với số lượng 2");
.anyMatch(item -> item.productId.equals(productId) && item.quantity == 2);
assertTrue(hasProduct1);
}
@Test
void testGetCart_NotFound() {
UUID nonExistentId = UUID.randomUUID();
ObjectId nonExistentId = ObjectId.get();
GraphQLException exception = assertThrows(GraphQLException.class, () -> {
cartService.getCart(nonExistentId);
@@ -120,37 +104,35 @@ public class CartServiceTest {
@Test
void testAddItem_Success() {
UUID cartId = UUID.randomUUID();
UUID eateryId = UUID.randomUUID();
UUID menuItemId = UUID.randomUUID();
String key = "cart:" + cartId;
hashCommands.hset(key, "_metadata_eatery", eateryId.toString());
hashCommands.hset(key, "_metadata_owner", UUID.randomUUID().toString());
Cart cart = new Cart();
cart.eateryId = eateryId;
cart.userId = UUID.randomUUID().toString();
cart.items = new ArrayList<>();
cart.persist();
ObjectId cartId = cart.id;
// 2. Mock API check món ăn
when(eateryController.checkMenuItemExists(eateryId, menuItemId)).thenReturn(
Uni.createFrom().item(Response.ok().build())
);
// 3. Thực thi (Lưu ý: addItem trả về Uni nên cần await)
Cart result = cartService
.addItem(cartId, menuItemId, 2L)
.addItem(cartId, menuItemId, 2)
.await()
.atMost(Duration.ofSeconds(5));
// 4. Kiểm tra kết quả trả về
assertNotNull(result);
assertEquals(cartId, result.getId());
assertEquals(1, result.items.size());
assertEquals(menuItemId, result.items.get(0).productId);
assertEquals(2, result.items.get(0).quantity);
boolean hasItem = result
.getItems()
.stream()
.anyMatch(
item ->
item.getProductId().equals(menuItemId.toString()) &&
item.getQuantity() == 2
);
assertTrue(hasItem, "Sản phẩm phải có trong giỏ hàng sau khi thêm");
String quantityInRedis = hashCommands.hget(key, menuItemId.toString());
assertEquals("2", quantityInRedis);
Cart dbCart = Cart.findById(cartId);
assertEquals(2, dbCart.items.get(0).quantity);
}
}
@@ -1,2 +0,0 @@
# Redis
quarkus.redis.hosts=redis://${REDIS_SERVICE_SERVICE_HOST:localhost}:${REDIS_SERVICE_SERVICE_PORT:6379}