Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e77edb0e74 | |||
| be8bdde6cf | |||
| cb9096dd1b | |||
| 837f5f595a | |||
| dd3c09865f | |||
| 5a7b70145c | |||
| bcbda39ed4 | |||
| 554e8767d8 | |||
| 6938b764fc | |||
| 4ae4fa6f2e | |||
| d9646ab9d1 | |||
| 0fa8e37c88 | |||
| bfd12e32f9 | |||
| 3fe3540e7f | |||
| 6931ef71bc | |||
| afda64befb | |||
| 7faca4e681 | |||
| 2bfd7a1364 | |||
| 1c5bc62d11 | |||
| dd51dd5f5a | |||
| 76e4df8384 | |||
| 910ef30c12 | |||
| 3af40b3725 | |||
| 0eaf52b727 | |||
| 11aab29fe7 | |||
| 44457d0510 | |||
| 7736761a11 | |||
| 0793e1ceab | |||
| 20f669f3bc | |||
| 232faa6897 | |||
| 689a153d7f | |||
| 2af438c308 |
@@ -1,18 +1,44 @@
|
||||
FROM alpine:3.22
|
||||
FROM ubuntu:26.04
|
||||
|
||||
RUN apk update && apk add --no-cache \
|
||||
# Tránh các câu hỏi tương tác trong quá trình cài đặt
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates && apt-get clean
|
||||
|
||||
RUN sed -i 's|http://archive.ubuntu.com|https://apt.demonkernel.io.vn|g' /etc/apt/sources.list.d/ubuntu.sources && \
|
||||
sed -i 's|http://security.ubuntu.com|https://apt.demonkernel.io.vn|g' /etc/apt/sources.list.d/ubuntu.sources
|
||||
|
||||
# 1. Cài đặt các công cụ cơ bản
|
||||
RUN apt-get update --fix-missing && apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
curl \
|
||||
bash \
|
||||
openjdk21-jdk \
|
||||
maven \
|
||||
build-essential \
|
||||
docker-buildx \
|
||||
git \
|
||||
ca-certificates \
|
||||
pnpm
|
||||
|
||||
# git trust all directories
|
||||
docker.io \
|
||||
maven \
|
||||
libz-dev
|
||||
|
||||
# 5. Cấu hình Git
|
||||
RUN git config --global --add safe.directory '*'
|
||||
|
||||
# Install quarkus
|
||||
# 2. Cài đặt Node.js & PNPM (thay thế cho apk pnpm)
|
||||
RUN curl -fsSL https://get.pnpm.io/install.sh | bash -
|
||||
|
||||
# 3. Cài đặt Kubectl
|
||||
RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
|
||||
&& install -m 0755 kubectl /usr/local/bin/kubectl
|
||||
|
||||
# 4. Cài đặt Mandrel 25
|
||||
# Lưu ý: Dùng bản linux-amd64 (glibc) tiêu chuẩn cho Ubuntu
|
||||
ENV JAVA_HOME=/usr/lib/jvm/mandrel
|
||||
ENV GRAALVM_HOME=/usr/lib/jvm/mandrel
|
||||
ENV PATH="${JAVA_HOME}/bin:${PATH}"
|
||||
|
||||
RUN mkdir -p ${JAVA_HOME} && \
|
||||
curl -L https://github.com/graalvm/mandrel/releases/download/mandrel-25.0.2.0-Final/mandrel-java25-linux-amd64-25.0.2.0-Final.tar.gz | tar -xz -C ${JAVA_HOME} --strip-components=1
|
||||
|
||||
# 6. Cài đặt JBang & Quarkus CLI
|
||||
ENV JBANG_DIR="/root/.jbang"
|
||||
ENV PATH="${JBANG_DIR}/bin:${PATH}"
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
{
|
||||
"dockerComposeFile": "docker-compose.main.yaml",
|
||||
"mounts": [
|
||||
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
|
||||
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind",
|
||||
"source=${localEnv:USERPROFILE}/.kube,target=/root/.kube,type=bind"
|
||||
],
|
||||
"remoteUser": "root",
|
||||
"service": "devcontainer",
|
||||
@@ -21,6 +22,8 @@
|
||||
}
|
||||
},
|
||||
"containerEnv": {
|
||||
"TESTCONTAINERS_RYUK_DISABLED": "true"
|
||||
}
|
||||
"TESTCONTAINERS_RYUK_DISABLED": "true",
|
||||
"QUARKUS_DEBUG_HOST": "0.0.0.0"
|
||||
},
|
||||
"forwardPorts": [5005, 8080]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
include:
|
||||
- ./docker-compose.kafka.yaml
|
||||
- ./docker-compose.postgres.yaml
|
||||
- ./docker-compose.redis.yaml
|
||||
|
||||
services:
|
||||
|
||||
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev-*
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -12,9 +11,9 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: gerlero/apt-install@v1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
packages: docker.io
|
||||
|
||||
- name: Setup SSH Key
|
||||
run: |
|
||||
@@ -23,11 +22,18 @@ jobs:
|
||||
echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
|
||||
ssh-keyscan -t ed25519 vps.demonkernel.io.vn >> ~/.ssh/known_hosts
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: "temurin"
|
||||
distribution: "graalvm"
|
||||
java-version: "25"
|
||||
cache: "maven"
|
||||
|
||||
- name: Log in to Gitea Container Registry
|
||||
uses: docker/login-action@v3
|
||||
@@ -36,35 +42,11 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.ACCESS_TOKEN }}
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: latest
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/package.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
cache: true
|
||||
|
||||
- name: Run Semantic Release
|
||||
env:
|
||||
@@ -77,14 +59,17 @@ jobs:
|
||||
|
||||
- name: Commit & Push Changes
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
if [ -n "$(git status --porcelain)" ]&& [ "${{ github.ref_name }}" = "main" ]; then
|
||||
git config --global user.name "gitea-actions"
|
||||
git config --global user.email "actions-user@noreply.git.demonkernel.io.vn"
|
||||
|
||||
git remote set-url origin git@git.demonkernel.io.vn:${{ github.repository }}.git
|
||||
REPO_NAME="${{ gitea.repository }}"
|
||||
|
||||
git remote set-url origin ssh://git@vps.demonkernel.io.vn/${REPO_NAME}.git
|
||||
|
||||
git rm -r --cached .
|
||||
git add .
|
||||
git commit -m "chore: release [ci skip]"
|
||||
|
||||
git push origin HEAD:${{ github.ref_name }}
|
||||
git push
|
||||
fi
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
name: Java CI with Maven
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
@@ -12,22 +18,25 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: shogo82148/actions-setup-redis@v1
|
||||
with:
|
||||
redis-version: "7.x"
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: "temurin"
|
||||
distribution: "graalvm"
|
||||
java-version: "25"
|
||||
cache: "maven"
|
||||
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v4
|
||||
- uses: gerlero/apt-install@v1
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
packages: build-essential zlib1g-dev docker.io
|
||||
|
||||
- name: Build and Test all modules
|
||||
run: ./mvnw -B clean verify -Dquarkus.container-image.build=false -Dquarkus.container-image.push=false
|
||||
run: |
|
||||
./mvnw -B install \
|
||||
-Dquarkus.container-image.push=false \
|
||||
|
||||
- name: Publish Test Report
|
||||
uses: scacap/action-surefire-report@v1
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
wrapperVersion=3.3.4
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
|
||||
distributionSha256Sum=305773a68d6ddfd413df58c82b3f8050e89778e777f3a745c8e5b8cbea4018ef
|
||||
distributionSha256Sum=
|
||||
|
||||
+10
-3
@@ -1,5 +1,12 @@
|
||||
{
|
||||
"branches": ["main", { "name": "dev-*", "prerelease": "dev" }],
|
||||
"branches": [
|
||||
"main",
|
||||
{
|
||||
"name": "dev-*",
|
||||
"prerelease": "${name.replace('dev-', '')}",
|
||||
"channel": "${name.replace('dev-', '')}"
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
@@ -7,8 +14,8 @@
|
||||
[
|
||||
"@semantic-release/exec",
|
||||
{
|
||||
"prepareCmd": "./mvnw versions:set -DnewVersion=${nextRelease.version}",
|
||||
"publishCmd": "if [ \"${branch.name}\" = \"main\" ]; then ./mvnw clean package -Dquarkus.container-image.tag=${nextRelease.version} -DskipTests; fi"
|
||||
"prepareCmd": "./mvnw versions:set -DnewVersion=${nextRelease.version} && sed -i \"/-service:/ s|:[^:]*$|:${nextRelease.version}|\" k8s.yaml",
|
||||
"publishCmd": "if [ \"${branch.name}\" = \"main\" ]; then ./mvnw package -Dquarkus.container-image.tag=${nextRelease.version} -DskipTests; fi"
|
||||
}
|
||||
],
|
||||
[
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
<properties>
|
||||
<compiler-plugin.version>3.15.0</compiler-plugin.version>
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<maven.compiler.release>25</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding
|
||||
>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.drinkool.models;
|
||||
|
||||
import com.drinkool.entities.BaseEntity;
|
||||
import com.drinkool.utils.PhoneValidator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.password4j.Password;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.transaction.*;
|
||||
@@ -20,6 +21,7 @@ public abstract class UserModel extends BaseEntity {
|
||||
public String phone;
|
||||
|
||||
@Column(nullable = false, length = 1024)
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
public String hashedPassword;
|
||||
|
||||
@Transactional
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.drinkool.services;
|
||||
|
||||
import com.drinkool.Jwt;
|
||||
import com.drinkool.Role;
|
||||
import com.drinkool.Url;
|
||||
import com.drinkool.dtos.*;
|
||||
import com.drinkool.entities.CustomerEntity;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -7,54 +10,96 @@ import jakarta.transaction.Transactional;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.*;
|
||||
|
||||
@Path("/api")
|
||||
@Path("")
|
||||
public class AuthenticationService {
|
||||
|
||||
@Inject
|
||||
OtpService otpService;
|
||||
|
||||
@POST
|
||||
@Path("/signup")
|
||||
@Path(Url.Signup)
|
||||
@Transactional
|
||||
public Response signup(Signup input) {
|
||||
CustomerEntity newCustomer = new CustomerEntity();
|
||||
|
||||
newCustomer.signup(input.name, input.phone, input.password);
|
||||
|
||||
return Response.status(Response.Status.CREATED).build();
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.header(Jwt.userId, newCustomer.id.toString())
|
||||
.header(Jwt.role, Role.Customer)
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/quickSignup")
|
||||
@Path(Url.QuickSignup)
|
||||
@Transactional
|
||||
public Response quickSignup(QuickSignup input) {
|
||||
CustomerEntity newCustomer = new CustomerEntity();
|
||||
|
||||
newCustomer.signup(input.phone);
|
||||
|
||||
return Response.status(Response.Status.CREATED).build();
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.header(Jwt.userId, newCustomer.id.toString())
|
||||
.header(Jwt.role, Role.Customer)
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/login")
|
||||
@Path(Url.Login)
|
||||
public Response login(Login input) {
|
||||
CustomerEntity customer = CustomerEntity.find(
|
||||
"phone",
|
||||
input.phone
|
||||
).firstResult();
|
||||
|
||||
if (customer == null) throw new BadRequestException("InvalidPhoneNumber");
|
||||
if (customer == null) return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("InvalidPhoneNumber")
|
||||
.build();
|
||||
|
||||
if (!customer.login(input.password)) throw new BadRequestException(
|
||||
"InvalidPassword"
|
||||
);
|
||||
if (!customer.login(input.password)) return Response.status(
|
||||
Response.Status.BAD_REQUEST
|
||||
)
|
||||
.entity("InvalidPassword")
|
||||
.build();
|
||||
|
||||
return Response.accepted().build();
|
||||
return Response.accepted()
|
||||
.header(Jwt.userId, customer.id.toString())
|
||||
.header(Jwt.role, Role.Customer)
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/sms_otp")
|
||||
@Path(Url.QuickLogin)
|
||||
public Response quickLogin(QuickLogin input) {
|
||||
if (!otpService.verifyOtp(input.phone, input.otp)) {
|
||||
return Response.status(Response.Status.UNAUTHORIZED)
|
||||
.entity("InvalidOTP")
|
||||
.build();
|
||||
}
|
||||
|
||||
CustomerEntity customer = CustomerEntity.find(
|
||||
"phone",
|
||||
input.phone
|
||||
).firstResult();
|
||||
|
||||
return Response.accepted()
|
||||
.header(Jwt.userId, customer.id.toString())
|
||||
.header(Jwt.role, Role.Customer)
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path(Url.SmsOtp)
|
||||
public Response smsOtp(SmsOtp input) {
|
||||
CustomerEntity customer = CustomerEntity.find(
|
||||
"phone",
|
||||
input.phone
|
||||
).firstResult();
|
||||
|
||||
if (customer == null) return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("InvalidPhoneNumber")
|
||||
.build();
|
||||
|
||||
String otp = otpService.generateAndSaveOtp(input.phone);
|
||||
|
||||
// Ở đây bạn sẽ gọi Kafka để gửi SMS thực tế
|
||||
@@ -62,15 +107,4 @@ public class AuthenticationService {
|
||||
|
||||
return Response.accepted().build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/quicklogin")
|
||||
public Response quickLogin(QuickLogin input) {
|
||||
if (otpService.verifyOtp(input.phone, input.otp)) {
|
||||
return Response.accepted().build();
|
||||
}
|
||||
return Response.status(Response.Status.UNAUTHORIZED)
|
||||
.entity("InvalidOTP")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.drinkool.services;
|
||||
|
||||
import com.drinkool.Jwt;
|
||||
import com.drinkool.Url;
|
||||
import com.drinkool.models.UserModel;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.UUID;
|
||||
|
||||
@Path("")
|
||||
public class UserService {
|
||||
|
||||
@POST
|
||||
@Path(Url.Me)
|
||||
@Transactional
|
||||
public Response me(@HeaderParam(Jwt.userId) UUID id) {
|
||||
if (id == null) return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity("InvalidUserId")
|
||||
.build();
|
||||
|
||||
return Response.ok(UserModel.find("id", id).firstResult()).build();
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,13 @@ quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/postgres
|
||||
quarkus.hibernate-orm.database.generation=update
|
||||
|
||||
# Docker
|
||||
## URL của Gitea Registry (thường là gitea.yourdomain.com)
|
||||
quarkus.container-image.registry=git.demonkernel.io.vn
|
||||
quarkus.container-image.group=foodsurf
|
||||
quarkus.container-image.name=account-service
|
||||
quarkus.container-image.push=true
|
||||
quarkus.container-image.build=true
|
||||
quarkus.container-image.builder=docker
|
||||
quarkus.container-image.additional-tags=latest
|
||||
|
||||
# Redis
|
||||
quarkus.redis.hosts=redis://localhost:6379
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.drinkool.entities;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
public class CustomerEntityTest {
|
||||
|
||||
@Inject
|
||||
EntityManager entityManager;
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testCustomerPersistence() {
|
||||
// 1. Khởi tạo đúng Entity (CustomerEntity chứ không phải CustomerEntityTest)
|
||||
CustomerEntity customer = new CustomerEntity();
|
||||
|
||||
// Gán đúng biến name, phone và hashedPassword (các trường kế thừa từ UserModel)
|
||||
customer.name = "Nguyen Van A";
|
||||
customer.phone = "+84987654321";
|
||||
customer.hashedPassword = "dummy_hashed_password";
|
||||
|
||||
// 2. Lưu vào DB
|
||||
entityManager.persist(customer);
|
||||
entityManager.flush();
|
||||
entityManager.clear();
|
||||
|
||||
// 3. Truy vấn lại để kiểm tra (Sửa lại class map của query thành CustomerEntity)
|
||||
var query = entityManager.createQuery(
|
||||
"SELECT c FROM CustomerEntity c WHERE c.phone = :phone",
|
||||
CustomerEntity.class
|
||||
);
|
||||
query.setParameter("phone", "+84987654321");
|
||||
|
||||
// Nhận kết quả về đúng kiểu CustomerEntity
|
||||
CustomerEntity savedCustomer = query.getSingleResult();
|
||||
|
||||
// 4. Assert (Kiểm chứng)
|
||||
Assertions.assertNotNull(savedCustomer, "Customer không được null");
|
||||
Assertions.assertEquals(
|
||||
"Nguyen Van A",
|
||||
savedCustomer.name,
|
||||
"Tên không khớp"
|
||||
);
|
||||
Assertions.assertEquals(
|
||||
"+84987654321",
|
||||
savedCustomer.phone,
|
||||
"Số điện thoại không khớp"
|
||||
);
|
||||
Assertions.assertNotNull(savedCustomer.id, "ID chưa được sinh ra");
|
||||
}
|
||||
}
|
||||
@@ -3,39 +3,148 @@ package com.drinkool.models;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.*;
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import java.util.Random;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
@Table(name = "test_users")
|
||||
class TestUserEntity extends UserModel {}
|
||||
|
||||
@QuarkusTest
|
||||
public class UserModelTest {
|
||||
|
||||
// Helper method để tạo số điện thoại ngẫu nhiên hợp lệ
|
||||
private String generateValidPhone() {
|
||||
return "+8477" + String.format("%07d", (new Random()).nextInt(10000000));
|
||||
}
|
||||
|
||||
// 1. Test case: Đăng ký thành công (đầy đủ tham số)
|
||||
@Test
|
||||
@Transactional
|
||||
public void testSignupSuccess() {
|
||||
//dang nhap thanh cong
|
||||
TestUserEntity user = new TestUserEntity();
|
||||
String phone =
|
||||
"+8477" + String.format("%07d", (new Random()).nextInt(10000000));
|
||||
String phone = generateValidPhone();
|
||||
String password = "02092006Danh";
|
||||
|
||||
user.signup("Tran Huu Danh", phone, password);
|
||||
|
||||
assertNotNull(user.id, "ID not null after persist");
|
||||
assertEquals("Tran Huu Danh", user.name);
|
||||
assertEquals(phone, user.phone);
|
||||
assertNotNull(user.hashedPassword, "password phai hash");
|
||||
assertNotNull(user.hashedPassword, "Password phải được hash");
|
||||
|
||||
TestUserEntity savedUser = TestUserEntity.find(
|
||||
"phone",
|
||||
phone
|
||||
).firstResult();
|
||||
assertNotNull(savedUser);
|
||||
assertNotNull(savedUser, "Phải tìm thấy user trong database");
|
||||
}
|
||||
|
||||
// 2. Test case: Đăng ký thành công (chỉ dùng số điện thoại)
|
||||
@Test
|
||||
@Transactional
|
||||
public void testSignupOneParamSuccess() {
|
||||
TestUserEntity user = new TestUserEntity();
|
||||
String phone = generateValidPhone();
|
||||
|
||||
user.signup(phone);
|
||||
|
||||
assertNotNull(user.id, "ID not null after persist");
|
||||
assertEquals(phone, user.name, "Tên mặc định phải là số điện thoại");
|
||||
assertEquals(phone, user.phone);
|
||||
assertNotNull(
|
||||
user.hashedPassword,
|
||||
"Password tự động phải được tạo và hash"
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Test case: Đăng ký thất bại do số điện thoại đã tồn tại
|
||||
@Test
|
||||
@Transactional
|
||||
public void testSignupFailExistedUser() {
|
||||
String phone = generateValidPhone();
|
||||
String password = "tungtungtungshahua";
|
||||
|
||||
// Tạo user đầu tiên
|
||||
TestUserEntity existUser = new TestUserEntity();
|
||||
existUser.signup("Nguyen Thanh Quy", phone, password);
|
||||
|
||||
// Tạo user thứ hai với cùng số điện thoại
|
||||
TestUserEntity newUser = new TestUserEntity();
|
||||
BadRequestException exception = assertThrows(
|
||||
BadRequestException.class,
|
||||
() -> {
|
||||
newUser.signup("Nguyen Viet Anh", phone, "02092006Anh");
|
||||
}
|
||||
);
|
||||
|
||||
assertEquals("ExistedUser", exception.getMessage());
|
||||
}
|
||||
|
||||
// 4. Test case: Đăng ký thất bại do số điện thoại sai định dạng
|
||||
@Test
|
||||
@Transactional
|
||||
public void testSignupFailInvalidPhoneNumber() {
|
||||
TestUserEntity user = new TestUserEntity();
|
||||
String invalidPhone = "12345";
|
||||
|
||||
BadRequestException exception = assertThrows(
|
||||
BadRequestException.class,
|
||||
() -> {
|
||||
user.signup("Lam Vi Hoang", invalidPhone, "Pass123");
|
||||
}
|
||||
);
|
||||
|
||||
assertEquals("InvalidPhoneNumber", exception.getMessage());
|
||||
}
|
||||
|
||||
// 5. Test case: Đăng nhập thành công
|
||||
@Test
|
||||
@Transactional
|
||||
public void testLoginSuccess() {
|
||||
TestUserEntity user = new TestUserEntity();
|
||||
String phone = generateValidPhone();
|
||||
String password = "02092006Danh";
|
||||
|
||||
user.signup("Tran Huu Danh", phone, password);
|
||||
|
||||
boolean loginResult = user.login(password);
|
||||
|
||||
assertTrue(loginResult, "Đăng nhập phải thành công với mật khẩu đúng");
|
||||
}
|
||||
|
||||
// 6. Test case: Đăng nhập thất bại do sai mật khẩu
|
||||
@Test
|
||||
@Transactional
|
||||
public void testLoginFailWrongPassword() {
|
||||
TestUserEntity user = new TestUserEntity();
|
||||
String phone = generateValidPhone();
|
||||
String truePassword = "02092006Danh";
|
||||
String wrongPassword = "02092006danhh";
|
||||
|
||||
user.signup("Tran Huu Danh", phone, truePassword);
|
||||
|
||||
boolean loginResult = user.login(wrongPassword);
|
||||
|
||||
assertFalse(loginResult, "Đăng nhập phải thất bại với mật khẩu sai");
|
||||
}
|
||||
|
||||
// 7. Test case: Đăng nhập thất bại do hashedPassword bị null
|
||||
@Test
|
||||
@Transactional
|
||||
public void testLoginFailNullHashedPassword() {
|
||||
TestUserEntity user = new TestUserEntity();
|
||||
user.hashedPassword = null; // Cố tình set null để test lỗi
|
||||
|
||||
boolean loginResult = user.login("anhdomixi");
|
||||
|
||||
assertFalse(
|
||||
loginResult,
|
||||
"Đăng nhập phải thất bại nếu hashedPassword là null"
|
||||
);
|
||||
}
|
||||
// 2. Test case: Đăng ký thất bại do số điện thoại đã tồn tại
|
||||
// 3. Test case: Đăng ký thất bại do số điện thoại sai định dạng
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package com.drinkool.services;
|
||||
|
||||
import static io.restassured.RestAssured.*;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import com.drinkool.Jwt;
|
||||
import com.drinkool.Role;
|
||||
import com.drinkool.Url;
|
||||
import com.drinkool.dtos.*;
|
||||
import com.drinkool.entities.CustomerEntity;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.http.ContentType;
|
||||
import java.util.Random;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
public class AuthenticationServiceTest {
|
||||
|
||||
public static Random random = new Random();
|
||||
|
||||
public static String generateRandomPhone() {
|
||||
return "+8477" + String.format("%07d", random.nextInt(10000000));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSignup() {
|
||||
String phone = generateRandomPhone();
|
||||
Signup input = new Signup();
|
||||
input.name = "Test User";
|
||||
input.phone = phone;
|
||||
input.password = "password123";
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(input)
|
||||
.when()
|
||||
.post(Url.Signup)
|
||||
.then()
|
||||
.statusCode(201);
|
||||
|
||||
// Kiểm tra database: Phải tìm thấy 1 user với số điện thoại này
|
||||
assertEquals(1, CustomerEntity.find("phone", phone).count());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQuickSignup() {
|
||||
String phone = generateRandomPhone();
|
||||
QuickSignup input = new QuickSignup();
|
||||
input.phone = phone;
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(input)
|
||||
.when()
|
||||
.post(Url.QuickSignup)
|
||||
.then()
|
||||
.header(Jwt.userId, notNullValue())
|
||||
.header(Jwt.role, is(Role.Customer))
|
||||
.statusCode(201);
|
||||
|
||||
assertEquals(1, CustomerEntity.find("phone", phone).count());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoginSuccess() {
|
||||
// BƯỚC 1: Tạo user trước bằng signup
|
||||
String phone = generateRandomPhone();
|
||||
Signup signupData = new Signup();
|
||||
signupData.name = "Login User";
|
||||
signupData.phone = phone;
|
||||
signupData.password = "secure_pass";
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(signupData)
|
||||
.post(Url.Signup)
|
||||
.then()
|
||||
.statusCode(201);
|
||||
|
||||
// BƯỚC 2: Thử đăng nhập
|
||||
Login loginInput = new Login();
|
||||
loginInput.phone = phone;
|
||||
loginInput.password = "secure_pass";
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(loginInput)
|
||||
.when()
|
||||
.post(Url.Login)
|
||||
.then()
|
||||
.statusCode(202);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoginFailWrongPassword() {
|
||||
String phone = generateRandomPhone();
|
||||
Signup signupData = new Signup();
|
||||
signupData.name = "Wrong Pass User";
|
||||
signupData.phone = phone;
|
||||
signupData.password = "correct_password";
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(signupData)
|
||||
.post(Url.Signup)
|
||||
.then()
|
||||
.statusCode(201);
|
||||
|
||||
Login loginInput = new Login();
|
||||
loginInput.phone = phone;
|
||||
loginInput.password = "wrong_password";
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(loginInput)
|
||||
.when()
|
||||
.post(Url.Login)
|
||||
.then()
|
||||
.statusCode(400); // BadRequestException
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSmsOtp() {
|
||||
String phone = generateRandomPhone();
|
||||
SmsOtp input = new SmsOtp();
|
||||
input.phone = phone;
|
||||
|
||||
Signup signup = new Signup();
|
||||
signup.phone = phone;
|
||||
signup.name = "sms user";
|
||||
signup.password = "password123";
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(signup)
|
||||
.when()
|
||||
.post(Url.Signup)
|
||||
.then();
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(input)
|
||||
.when()
|
||||
.post(Url.SmsOtp)
|
||||
.then()
|
||||
.statusCode(202);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQuickLoginFailInvalidOtp() {
|
||||
String phone = generateRandomPhone();
|
||||
QuickLogin input = new QuickLogin();
|
||||
input.phone = phone;
|
||||
input.otp = "000000"; // Giả sử mã này luôn sai vì chưa được tạo
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(input)
|
||||
.when()
|
||||
.post(Url.QuickLogin)
|
||||
.then()
|
||||
.statusCode(401);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.drinkool.services;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Random;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
public class OtpServiceTest {
|
||||
|
||||
@Inject
|
||||
OtpService otpService;
|
||||
|
||||
// 1. Test case: Tạo và lưu OTP thành công
|
||||
@Test
|
||||
public void testGenerateAndSaveOtp() {
|
||||
String phone =
|
||||
"+8477" + String.format("%07d", new Random().nextInt(10000000));
|
||||
String otp = otpService.generateAndSaveOtp(phone);
|
||||
|
||||
assertNotNull(otp, "OTP sinh ra không được null");
|
||||
assertEquals(6, otp.length(), "OTP phải có độ dài đúng 6 ký tự");
|
||||
assertTrue(otp.matches("\\d{6}"), "OTP chỉ được chứa các chữ số");
|
||||
}
|
||||
|
||||
// 2. Test case: Xác thực OTP thành công
|
||||
@Test
|
||||
public void testVerifyOtpSuccess() {
|
||||
String phone =
|
||||
"+8477" + String.format("%07d", new Random().nextInt(10000000));
|
||||
String generatedOtp = otpService.generateAndSaveOtp(phone);
|
||||
|
||||
// Xác thực với OTP vừa tạo ra
|
||||
boolean verifyResult = otpService.verifyOtp(phone, generatedOtp);
|
||||
|
||||
assertTrue(verifyResult, "Xác thực phải thành công với OTP đúng");
|
||||
}
|
||||
|
||||
// 3. Test case: Xác thực thất bại do sai mã OTP
|
||||
@Test
|
||||
public void testVerifyOtpFailWrongOtp() {
|
||||
String phone =
|
||||
"+8477" + String.format("%07d", new Random().nextInt(10000000));
|
||||
otpService.generateAndSaveOtp(phone); // Tạo OTP nhưng không dùng
|
||||
|
||||
String wrongOtp = "999999"; // Giả sử mã sai
|
||||
boolean verifyResult = otpService.verifyOtp(phone, wrongOtp);
|
||||
|
||||
assertFalse(verifyResult, "Xác thực phải thất bại khi truyền sai mã OTP");
|
||||
}
|
||||
|
||||
// 4. Test case: Xác thực thất bại do OTP không tồn tại (chưa tạo hoặc đã hết hạn)
|
||||
@Test
|
||||
public void testVerifyOtpFailNotExists() {
|
||||
String unverifiedPhone =
|
||||
"+8499" + String.format("%07d", new Random().nextInt(10000000));
|
||||
|
||||
// Cố tình xác thực cho một số điện thoại chưa từng gọi generateAndSaveOtp
|
||||
boolean verifyResult = otpService.verifyOtp(unverifiedPhone, "123456");
|
||||
|
||||
assertFalse(
|
||||
verifyResult,
|
||||
"Xác thực phải thất bại với SĐT không có OTP trong Redis"
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Test case: Đảm bảo OTP chỉ được sử dụng 1 lần (đã bị xóa sau khi verify thành công bằng lệnh getdel)
|
||||
@Test
|
||||
public void testVerifyOtpConsumedAfterSuccess() {
|
||||
String phone =
|
||||
"+8477" + String.format("%07d", new Random().nextInt(10000000));
|
||||
String generatedOtp = otpService.generateAndSaveOtp(phone);
|
||||
|
||||
// Lần 1: Xác thực thành công
|
||||
assertTrue(otpService.verifyOtp(phone, generatedOtp));
|
||||
|
||||
// Lần 2: Xác thực lại cùng mã đó sẽ thất bại vì key đã bị getdel xóa khỏi Redis
|
||||
boolean secondVerifyResult = otpService.verifyOtp(phone, generatedOtp);
|
||||
assertFalse(
|
||||
secondVerifyResult,
|
||||
"OTP không được phép tái sử dụng sau khi đã verify thành công"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.drinkool.services;
|
||||
|
||||
import static io.restassured.RestAssured.*;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
|
||||
import com.drinkool.Jwt;
|
||||
import com.drinkool.Url;
|
||||
import com.drinkool.dtos.QuickSignup;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.http.ContentType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
public class UserServiceTest {
|
||||
|
||||
@Test
|
||||
public void testMeEndpointWithHeader() {
|
||||
String phone = AuthenticationServiceTest.generateRandomPhone();
|
||||
QuickSignup input = new QuickSignup();
|
||||
input.phone = phone;
|
||||
|
||||
String testUserId = given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(input)
|
||||
.when()
|
||||
.post(Url.QuickSignup)
|
||||
.then()
|
||||
.extract()
|
||||
.header(Jwt.userId);
|
||||
|
||||
given()
|
||||
.header(Jwt.userId, testUserId)
|
||||
.contentType(ContentType.JSON)
|
||||
.when()
|
||||
.post(Url.Me)
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("id", is(testUserId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMeEndpointWithoutHeader() {
|
||||
given()
|
||||
.when()
|
||||
.post(Url.Me)
|
||||
.then()
|
||||
.statusCode(400);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.drinkool.utils;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class PhoneValidatorTest {
|
||||
|
||||
@Test
|
||||
public void testValidVietnamesePhoneNumbers() {
|
||||
// Số điện thoại VN hợp lệ (có mã vùng hoặc số 0 ở đầu đều được)
|
||||
Assertions.assertTrue(
|
||||
PhoneValidator.isValidInternationalPhone("0987654321")
|
||||
);
|
||||
Assertions.assertTrue(
|
||||
PhoneValidator.isValidInternationalPhone("+84987654321")
|
||||
);
|
||||
Assertions.assertTrue(
|
||||
PhoneValidator.isValidInternationalPhone("84987654321")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidInternationalPhoneNumbers() {
|
||||
// Số điện thoại quốc tế hợp lệ (Ví dụ: Mỹ +1)
|
||||
Assertions.assertTrue(
|
||||
PhoneValidator.isValidInternationalPhone("+14155552671")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidPhoneNumbers() {
|
||||
// Chuỗi không phải số điện thoại
|
||||
Assertions.assertFalse(
|
||||
PhoneValidator.isValidInternationalPhone("not-a-phone-number")
|
||||
);
|
||||
|
||||
// Số điện thoại quá ngắn hoặc sai định dạng
|
||||
Assertions.assertFalse(PhoneValidator.isValidInternationalPhone("123"));
|
||||
Assertions.assertFalse(
|
||||
PhoneValidator.isValidInternationalPhone("099999999999999")
|
||||
);
|
||||
|
||||
// Chuỗi rỗng
|
||||
Assertions.assertFalse(PhoneValidator.isValidInternationalPhone(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullPhoneNumber() {
|
||||
// Cố tình truyền null để xem hàm xử lý thế nào
|
||||
// Lưu ý: Đọc thêm phần "Mẹo nhỏ" bên dưới để pass được test này
|
||||
Assertions.assertFalse(PhoneValidator.isValidInternationalPhone(null));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
# Quarkus
|
||||
quarkus.http.host=0.0.0.0
|
||||
|
||||
# Database
|
||||
## Datasource
|
||||
%test.quarkus.datasource.db-kind=h2
|
||||
%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:test_db;DB_CLOSE_DELAY=-1
|
||||
|
||||
## Orm
|
||||
%test.quarkus.hibernate-orm.database.generation=drop-and-create
|
||||
|
||||
# Docker
|
||||
%test.quarkus.container-image.build=false
|
||||
%test.quarkus.container-image.push=false
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
<properties>
|
||||
<compiler-plugin.version>3.15.0</compiler-plugin.version>
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<maven.compiler.release>25</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding
|
||||
>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.drinkool.entities;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
public class EateryInventoryItemEntity extends BaseEntity {
|
||||
|
||||
public double quantity;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "inventory_item_id")
|
||||
public InventoryItemEntity inventoryItem;
|
||||
|
||||
private EateryInventoryItemEntity() {}
|
||||
|
||||
// Create
|
||||
@Transactional
|
||||
public static EateryInventoryItemEntity create(
|
||||
String name,
|
||||
double quantity,
|
||||
String unit
|
||||
) {
|
||||
String itemName = name.toLowerCase();
|
||||
|
||||
InventoryItemEntity item = InventoryItemEntity.find(
|
||||
"name",
|
||||
itemName
|
||||
).firstResult();
|
||||
|
||||
if (item == null || item.unit != unit) {
|
||||
if (unit != null || unit == "") {
|
||||
item = InventoryItemEntity.create(itemName, unit);
|
||||
} else {
|
||||
item = InventoryItemEntity.create(itemName);
|
||||
}
|
||||
}
|
||||
|
||||
EateryInventoryItemEntity eateryInventoryItem =
|
||||
new EateryInventoryItemEntity();
|
||||
|
||||
eateryInventoryItem.quantity = quantity;
|
||||
eateryInventoryItem.inventoryItem = item;
|
||||
|
||||
eateryInventoryItem.persist();
|
||||
|
||||
return eateryInventoryItem;
|
||||
}
|
||||
}
|
||||
@@ -9,21 +9,25 @@ import jakarta.transaction.Transactional;
|
||||
public class InventoryItemEntity extends BaseEntity {
|
||||
|
||||
public String name;
|
||||
public int quantity;
|
||||
public String unit;
|
||||
|
||||
private InventoryItemEntity() {}
|
||||
|
||||
// Create
|
||||
@Transactional
|
||||
public void add(String name, int quantity, String unit) {
|
||||
this.name = name;
|
||||
this.quantity = quantity;
|
||||
this.unit = unit;
|
||||
public static InventoryItemEntity create(String name, String unit) {
|
||||
InventoryItemEntity item = new InventoryItemEntity();
|
||||
|
||||
this.persist();
|
||||
item.name = name;
|
||||
item.unit = unit;
|
||||
|
||||
item.persist();
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void add(String name, int quantity) {
|
||||
add(name, quantity, "unit");
|
||||
public static InventoryItemEntity create(String name) {
|
||||
return InventoryItemEntity.create(name, "unit");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.drinkool.entities;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.*;
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
public class MenuItemEntity extends BaseEntity {
|
||||
|
||||
public String name;
|
||||
public double price;
|
||||
|
||||
// Danh sách các nguyên liệu cấu thành món ăn này
|
||||
@OneToMany(
|
||||
mappedBy = "menuItem",
|
||||
cascade = CascadeType.ALL,
|
||||
orphanRemoval = true
|
||||
)
|
||||
public List<MenuItemIngredientEntity> ingredients = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Thêm nguyên liệu vào món ăn với định lượng cụ thể
|
||||
*/
|
||||
@Transactional
|
||||
public void addIngredient(
|
||||
EateryInventoryItemEntity inventoryItem,
|
||||
double requiredQty
|
||||
) {
|
||||
MenuItemIngredientEntity ingredient = MenuItemIngredientEntity.create(
|
||||
this,
|
||||
inventoryItem,
|
||||
requiredQty
|
||||
);
|
||||
|
||||
this.ingredients.add(ingredient);
|
||||
|
||||
this.persist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Phương thức tiện ích để tạo nhanh Menu Item từ tên
|
||||
*/
|
||||
@Transactional
|
||||
public static MenuItemEntity create(String name, double price) {
|
||||
MenuItemEntity menu = new MenuItemEntity();
|
||||
|
||||
menu.name = name;
|
||||
menu.price = price;
|
||||
|
||||
menu.persist();
|
||||
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.drinkool.entities;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "menu_item_ingredients")
|
||||
public class MenuItemIngredientEntity extends BaseEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "menu_item_id")
|
||||
public MenuItemEntity menuItem;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "inventory_item_id")
|
||||
public EateryInventoryItemEntity inventoryItem;
|
||||
|
||||
public double requiredQuantity;
|
||||
|
||||
private MenuItemIngredientEntity() {}
|
||||
|
||||
public static MenuItemIngredientEntity create(
|
||||
MenuItemEntity menuItem,
|
||||
EateryInventoryItemEntity inventoryItem,
|
||||
double requiredQuantity
|
||||
) {
|
||||
MenuItemIngredientEntity ingredient = new MenuItemIngredientEntity();
|
||||
|
||||
ingredient.menuItem = menuItem;
|
||||
ingredient.inventoryItem = inventoryItem;
|
||||
ingredient.requiredQuantity = requiredQuantity;
|
||||
|
||||
ingredient.persist();
|
||||
|
||||
return ingredient;
|
||||
}
|
||||
}
|
||||
@@ -13,16 +13,23 @@ public class ReviewEntity extends ReviewModel {
|
||||
|
||||
public UUID eateryId;
|
||||
|
||||
private ReviewEntity(ReviewModel input) {
|
||||
super(input);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void receive(UUID reviewerId, UUID eateryId, ReviewModel review) {
|
||||
this.reviewerId = reviewerId;
|
||||
public static ReviewEntity create(
|
||||
UUID reviewerId,
|
||||
UUID eateryId,
|
||||
ReviewModel reviewInput
|
||||
) {
|
||||
ReviewEntity review = new ReviewEntity(reviewInput);
|
||||
|
||||
this.eateryId = eateryId;
|
||||
review.reviewerId = reviewerId;
|
||||
review.eateryId = eateryId;
|
||||
|
||||
this.rating = review.rating;
|
||||
review.persist();
|
||||
|
||||
this.comment = review.comment;
|
||||
|
||||
this.persist();
|
||||
return review;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,10 @@ import jakarta.ws.rs.core.Response;
|
||||
public class InventoryService {
|
||||
|
||||
@POST
|
||||
@Path("/add")
|
||||
@Path("/create")
|
||||
@Transactional
|
||||
public Response add(AddInventory input) {
|
||||
InventoryItemEntity newItem = new InventoryItemEntity();
|
||||
|
||||
newItem.add(input.name, input.quantity, input.unit);
|
||||
public Response create(AddInventory input) {
|
||||
InventoryItemEntity.create(input.name, input.unit);
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@@ -6,16 +6,14 @@ import jakarta.transaction.Transactional;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
@Path("/api")
|
||||
@Path("/api/review")
|
||||
public class ReviewService {
|
||||
|
||||
@POST
|
||||
@Path("/leaveReview")
|
||||
@Path("/create")
|
||||
@Transactional
|
||||
public Response leaveReview(SendReview input) {
|
||||
ReviewEntity newReview = new ReviewEntity();
|
||||
|
||||
newReview.receive(input.reviewerId, input.orderId, input.review);
|
||||
public Response create(SendReview input) {
|
||||
ReviewEntity.create(input.reviewerId, input.orderId, input.review);
|
||||
|
||||
return Response.status(201).build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.drinkool.entities;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
public class EateryInventoryItemEntityTest {
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void testAdd_WhenItemExists_ShouldUseExistingItem() {
|
||||
String itemName = "Coca Cola";
|
||||
int quantity = 10;
|
||||
|
||||
InventoryItemEntity.create(itemName);
|
||||
|
||||
EateryInventoryItemEntity eateryItem = EateryInventoryItemEntity.create(
|
||||
itemName,
|
||||
quantity,
|
||||
""
|
||||
);
|
||||
|
||||
EateryInventoryItemEntity savedEntity = EateryInventoryItemEntity.find(
|
||||
"id",
|
||||
eateryItem.id
|
||||
).firstResult();
|
||||
assertNotNull(savedEntity);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void testAdd_WhenItemDoesNotExist_ShouldCreateNewItem() {
|
||||
String itemName = "Pepsi";
|
||||
int quantity = 5;
|
||||
String unit = "Bottle";
|
||||
|
||||
EateryInventoryItemEntity eateryItem = EateryInventoryItemEntity.create(
|
||||
itemName,
|
||||
quantity,
|
||||
unit
|
||||
);
|
||||
|
||||
EateryInventoryItemEntity savedEntity = EateryInventoryItemEntity.find(
|
||||
"id",
|
||||
eateryItem.id
|
||||
).firstResult();
|
||||
assertNotNull(savedEntity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.drinkool.entities;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
public class InventoryItemEntityTest {
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testCreateInventoryItem() {
|
||||
// 1. Chuẩn bị dữ liệu (Lưu ý: Không còn biến quantity nữa)
|
||||
String name = "Trà sữa truyền thống";
|
||||
String unit = "Ly";
|
||||
|
||||
// 2. Gọi hàm static create (Không dùng new hay hàm add nữa)
|
||||
// Hàm này vừa tạo object, vừa gán data, vừa gọi persist() luôn
|
||||
InventoryItemEntity item = InventoryItemEntity.create(name, unit);
|
||||
|
||||
// 3. Kiểm chứng
|
||||
Assertions.assertNotNull(item, "Entity tạo ra không được null");
|
||||
Assertions.assertEquals(name, item.name, "Tên không khớp");
|
||||
Assertions.assertEquals(unit, item.unit, "Đơn vị không khớp");
|
||||
Assertions.assertNotNull(
|
||||
item.id,
|
||||
"ID phải được sinh ra tự động sau khi persist"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testCreateWithDefaultUnit() {
|
||||
// 1. Chuẩn bị dữ liệu
|
||||
String name = "Trân châu đen";
|
||||
|
||||
// 2. Gọi hàm static create rút gọn (chỉ truyền name, unit sẽ tự là "unit")
|
||||
InventoryItemEntity item = InventoryItemEntity.create(name);
|
||||
|
||||
// 3. Kiểm chứng
|
||||
Assertions.assertNotNull(item, "Entity tạo ra không được null");
|
||||
Assertions.assertEquals(name, item.name, "Tên không khớp");
|
||||
Assertions.assertEquals("unit", item.unit, "Unit mặc định phải là 'unit'");
|
||||
Assertions.assertNotNull(
|
||||
item.id,
|
||||
"ID phải được sinh ra tự động sau khi persist"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.drinkool.entities;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
public class MenuItemEntityTest {
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testCreateMenuItemSuccessfully() {
|
||||
// 1. Chuẩn bị dữ liệu (Arrange)
|
||||
String name = "Cà phê sữa đá";
|
||||
double price = 29000.0;
|
||||
|
||||
// 2. Thực thi (Act) - Gọi hàm tĩnh create
|
||||
MenuItemEntity menuItem = MenuItemEntity.create(name, price);
|
||||
|
||||
// 3. Kiểm chứng (Assert)
|
||||
assertNotNull(menuItem, "Thực thể MenuItem không được null");
|
||||
assertEquals(name, menuItem.name, "Tên món ăn không khớp");
|
||||
assertEquals(price, menuItem.price, "Giá món ăn không khớp");
|
||||
assertNotNull(menuItem.id, "ID phải được tự động sinh ra sau khi persist");
|
||||
assertTrue(
|
||||
menuItem.ingredients.isEmpty(),
|
||||
"Danh sách nguyên liệu ban đầu phải rỗng"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testAddIngredientSuccessfully() {
|
||||
// 1. Chuẩn bị dữ liệu (Arrange)
|
||||
// Tạo món ăn
|
||||
MenuItemEntity menuItem = MenuItemEntity.create(
|
||||
"Trà sữa trân châu",
|
||||
45000.0
|
||||
);
|
||||
|
||||
// Tạo nguyên liệu trong kho (Dùng đúng hàm add() của EateryInventoryItemEntity như đã fix)
|
||||
EateryInventoryItemEntity inventoryItem = EateryInventoryItemEntity.create(
|
||||
"Trân châu đen",
|
||||
50,
|
||||
"kg"
|
||||
);
|
||||
|
||||
double requiredQty = 0.2; // Cần 0.2 kg trân châu cho 1 ly
|
||||
|
||||
// 2. Thực thi (Act) - Thêm nguyên liệu vào món ăn
|
||||
menuItem.addIngredient(inventoryItem, requiredQty);
|
||||
|
||||
// 3. Kiểm chứng (Assert)
|
||||
// Kiểm tra danh sách nguyên liệu của món ăn
|
||||
assertFalse(
|
||||
menuItem.ingredients.isEmpty(),
|
||||
"Danh sách nguyên liệu không được rỗng sau khi thêm"
|
||||
);
|
||||
assertEquals(
|
||||
1,
|
||||
menuItem.ingredients.size(),
|
||||
"Phải có đúng 1 nguyên liệu trong danh sách"
|
||||
);
|
||||
|
||||
// Lấy phần tử nguyên liệu đầu tiên ra để kiểm tra chi tiết các liên kết
|
||||
MenuItemIngredientEntity addedIngredient = menuItem.ingredients.get(0);
|
||||
|
||||
assertNotNull(
|
||||
addedIngredient.id,
|
||||
"ID của Entity trung gian phải được sinh ra"
|
||||
);
|
||||
assertEquals(
|
||||
menuItem.id,
|
||||
addedIngredient.menuItem.id,
|
||||
"Khóa ngoại MenuItem bị sai"
|
||||
);
|
||||
assertEquals(
|
||||
inventoryItem.id,
|
||||
addedIngredient.inventoryItem.id,
|
||||
"Khóa ngoại InventoryItem bị sai"
|
||||
);
|
||||
assertEquals(
|
||||
requiredQty,
|
||||
addedIngredient.requiredQuantity,
|
||||
"Định lượng (requiredQty) không khớp"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.drinkool.entities;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
public class MenuItemIngredientEntityTest {
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void testCreate_ShouldPersistIngredientSuccessfully() {
|
||||
// 1. Chuẩn bị dữ liệu giả định
|
||||
|
||||
String menuItemName = "Milk Tea";
|
||||
double menuItemPrice = 40;
|
||||
|
||||
MenuItemEntity menuItem = MenuItemEntity.create(
|
||||
menuItemName,
|
||||
menuItemPrice
|
||||
);
|
||||
|
||||
String inventoryItemName = "Water";
|
||||
int inventoryItemQuantity = 10; // Đổi thành int cho khớp với kiểu của EateryInventoryItemEntity
|
||||
String inventoryItemUnit = "l";
|
||||
|
||||
// SỬA Ở ĐÂY: Khởi tạo bằng new và dùng hàm add() đúng như cấu trúc class của bạn
|
||||
EateryInventoryItemEntity inventoryItem = EateryInventoryItemEntity.create(
|
||||
inventoryItemName,
|
||||
inventoryItemQuantity,
|
||||
inventoryItemUnit
|
||||
);
|
||||
// Lưu ý: Hàm add() của bạn đã có sẵn lệnh this.persist() bên trong nên không cần gọi lại nữa!
|
||||
|
||||
double requiredQuantity = 0.5;
|
||||
|
||||
// 2. Gọi hàm static create cần test
|
||||
MenuItemIngredientEntity createdIngredient =
|
||||
MenuItemIngredientEntity.create(
|
||||
menuItem,
|
||||
inventoryItem,
|
||||
requiredQuantity
|
||||
);
|
||||
|
||||
// 3. Kiểm tra kết quả
|
||||
assertNotNull(createdIngredient.id, "ID không được null sau khi persist");
|
||||
|
||||
// Truy vấn lại từ Database để đảm bảo dữ liệu đã được lưu đúng
|
||||
MenuItemIngredientEntity savedEntity = MenuItemIngredientEntity.findById(
|
||||
createdIngredient.id
|
||||
);
|
||||
|
||||
assertNotNull(savedEntity);
|
||||
assertEquals(menuItem.id, savedEntity.menuItem.id);
|
||||
assertEquals(inventoryItem.id, savedEntity.inventoryItem.id);
|
||||
assertEquals(requiredQuantity, savedEntity.requiredQuantity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.drinkool.entities;
|
||||
|
||||
import com.drinkool.models.ReviewModel;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
public class ReviewEntityTest {
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testCreateReview() {
|
||||
// 1. Chuẩn bị dữ liệu (Arrange)
|
||||
UUID testReviewerId = UUID.randomUUID();
|
||||
UUID testEateryId = UUID.randomUUID();
|
||||
|
||||
// Truyền trực tiếp rating và comment vào constructor của ReviewModel
|
||||
ReviewModel inputModel = new ReviewModel(5, "Món ăn tuyệt vời!");
|
||||
|
||||
// 2. Thực thi (Act)
|
||||
ReviewEntity savedReview = ReviewEntity.create(
|
||||
testReviewerId,
|
||||
testEateryId,
|
||||
inputModel
|
||||
);
|
||||
|
||||
// 3. Kiểm chứng (Assert)
|
||||
Assertions.assertNotNull(
|
||||
savedReview,
|
||||
"Thực thể Review sau khi tạo không được null"
|
||||
);
|
||||
Assertions.assertEquals(
|
||||
testReviewerId,
|
||||
savedReview.reviewerId,
|
||||
"Reviewer ID không khớp"
|
||||
);
|
||||
Assertions.assertEquals(
|
||||
testEateryId,
|
||||
savedReview.eateryId,
|
||||
"Eatery ID không khớp"
|
||||
);
|
||||
Assertions.assertEquals(5, savedReview.rating, "Rating không khớp");
|
||||
Assertions.assertEquals(
|
||||
"Món ăn tuyệt vời!",
|
||||
savedReview.comment,
|
||||
"Comment không khớp"
|
||||
);
|
||||
|
||||
Assertions.assertNotNull(
|
||||
savedReview.id,
|
||||
"ID phải được tự động sinh ra sau khi persist"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import com.drinkool.dtos.AddInventory;
|
||||
import com.drinkool.entities.InventoryItemEntity;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.http.ContentType;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -16,19 +15,16 @@ public class InventoryServiceTest {
|
||||
|
||||
@Test
|
||||
void add() {
|
||||
Random random = new Random();
|
||||
|
||||
String name = UUID.randomUUID().toString();
|
||||
|
||||
AddInventory input = new AddInventory();
|
||||
input.name = name;
|
||||
input.quantity = random.nextInt(100);
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(input)
|
||||
.when()
|
||||
.post("/api/inventory/add")
|
||||
.post("/api/inventory/create")
|
||||
.then();
|
||||
|
||||
assertEquals(1, InventoryItemEntity.find("name", name).count());
|
||||
|
||||
@@ -27,17 +27,16 @@ public class ReviewServiceTest {
|
||||
SendReview input = new SendReview();
|
||||
input.reviewerId = reviewerId;
|
||||
input.orderId = orderId;
|
||||
ReviewModel reviewModel = new ReviewModel();
|
||||
reviewModel.comment = comment;
|
||||
reviewModel.rating = rating;
|
||||
ReviewModel reviewModel = new ReviewModel(rating, comment);
|
||||
input.review = reviewModel;
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(input)
|
||||
.when()
|
||||
.post("/api/leaveReview")
|
||||
.then();
|
||||
.post("/api/review/create")
|
||||
.then()
|
||||
.statusCode(201);
|
||||
|
||||
assertEquals(1, ReviewEntity.find("reviewerId", reviewerId).count());
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
quarkus.http.host=0.0.0.0
|
||||
|
||||
# Database
|
||||
# Database
|
||||
## Datasource
|
||||
%test.quarkus.datasource.db-kind=h2
|
||||
%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:test_db;DB_CLOSE_DELAY=-1
|
||||
|
||||
%test.quarkus.hibernate-orm.database.generation=drop-and-create
|
||||
## Orm
|
||||
%test.quarkus.hibernate-orm.database.generation=drop-and-create
|
||||
+17
-34
@@ -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>
|
||||
|
||||
@@ -17,14 +17,15 @@
|
||||
<packaging>quarkus</packaging>
|
||||
|
||||
<properties>
|
||||
<quarkus.package.jar.enabled>false</quarkus.package.jar.enabled>
|
||||
<skipITs>false</skipITs>
|
||||
<quarkus.native.enabled>true</quarkus.native.enabled>
|
||||
<compiler-plugin.version>3.15.0</compiler-plugin.version>
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<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.32.4</quarkus.platform.version>
|
||||
<skipITs>true</skipITs>
|
||||
<surefire-plugin.version>3.5.4</surefire-plugin.version>
|
||||
@@ -48,10 +49,6 @@
|
||||
<artifactId>lib</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-redis-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-jackson</artifactId>
|
||||
@@ -64,6 +61,10 @@
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-container-image-docker</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-client-jackson</artifactId>
|
||||
@@ -101,8 +102,7 @@
|
||||
<configuration>
|
||||
<argLine>@{argLine}</argLine>
|
||||
<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>
|
||||
@@ -123,28 +123,11 @@
|
||||
<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>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>native</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>native</name>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<quarkus.package.jar.enabled>false</quarkus.package.jar.enabled>
|
||||
<skipITs>false</skipITs>
|
||||
<quarkus.native.enabled>true</quarkus.native.enabled>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
</project>
|
||||
@@ -2,7 +2,6 @@ package com.drinkool;
|
||||
|
||||
import com.drinkool.dtos.*;
|
||||
import com.drinkool.services.AccountService;
|
||||
import io.quarkus.redis.datasource.RedisDataSource;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
@@ -16,28 +15,14 @@ public class CustomerGatewayResource {
|
||||
@RestClient
|
||||
AccountService accountService;
|
||||
|
||||
@Inject
|
||||
RedisDataSource redisDataSource;
|
||||
|
||||
@POST
|
||||
@Path("/signup")
|
||||
@Path(Url.Signup)
|
||||
public Uni<Response> proxySignup(Signup input) {
|
||||
String otpVerifiedKey = "otp:verified:" + input.phone;
|
||||
var valueCmd = redisDataSource.value(String.class);
|
||||
|
||||
if (valueCmd.get(otpVerifiedKey) == null) {
|
||||
return Uni.createFrom().item(
|
||||
Response.status(Response.Status.FORBIDDEN)
|
||||
.entity("Phone not verified")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
return accountService.signup(input);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/login")
|
||||
@Path(Url.Login)
|
||||
public Uni<Response> proxyLogin(Login input) {
|
||||
return accountService.login(input);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.drinkool.services;
|
||||
|
||||
import com.drinkool.Url;
|
||||
import com.drinkool.dtos.*;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.ws.rs.*;
|
||||
@@ -7,17 +8,25 @@ import jakarta.ws.rs.core.Response;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
@RegisterRestClient(configKey = "account")
|
||||
@Path("/api")
|
||||
@Path("")
|
||||
public interface AccountService {
|
||||
@POST
|
||||
@Path("/signup")
|
||||
@Path(Url.Signup)
|
||||
Uni<Response> signup(Signup input);
|
||||
|
||||
@POST
|
||||
@Path("/quickSignup")
|
||||
@Path(Url.QuickSignup)
|
||||
Uni<Response> quickSignup(QuickSignup input);
|
||||
|
||||
@POST
|
||||
@Path("/login")
|
||||
@Path(Url.Login)
|
||||
Uni<Response> login(Login input);
|
||||
|
||||
@POST
|
||||
@Path(Url.QuickLogin)
|
||||
Uni<Response> quickLogin(QuickSignup input);
|
||||
|
||||
@POST
|
||||
@Path(Url.SmsOtp)
|
||||
Uni<Response> smsOtp(SmsOtp input);
|
||||
}
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
quarkus.rest-client.account.url=http://localhost:502
|
||||
# Docker
|
||||
quarkus.container-image.registry=git.demonkernel.io.vn
|
||||
quarkus.container-image.group=foodsurf
|
||||
quarkus.container-image.name=gateway-service
|
||||
quarkus.container-image.push=true
|
||||
quarkus.container-image.build=true
|
||||
quarkus.container-image.builder=docker
|
||||
quarkus.container-image.additional-tags=latest
|
||||
|
||||
# Build
|
||||
quarkus.native.container-build=false
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
# 1. ACCOUNT SERVICE
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: account-deploy
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: account
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: account
|
||||
spec:
|
||||
containers:
|
||||
- name: account-pod
|
||||
image: git.demonkernel.io.vn/foodsurf/account-service:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: QUARKUS_REDIS_HOST
|
||||
value: "redis-service"
|
||||
- name: QUARKUS_DATASOURCE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-credentials
|
||||
key: password
|
||||
- name: QUARKUS_DATASOURCE_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-credentials
|
||||
key: username
|
||||
- name: QUARKUS_DATASOURCE_JDBC_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-credentials
|
||||
key: url
|
||||
resources:
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "512Mi"
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "256Mi"
|
||||
volumeMounts:
|
||||
- name: keys-volume
|
||||
mountPath: "/etc/keys"
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: keys-volume
|
||||
secret:
|
||||
secretName: jwt-keys
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: account-service
|
||||
spec:
|
||||
selector:
|
||||
app: account
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
---
|
||||
# 2. GATEWAY SERVICE
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gateway-deploy
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: gateway
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: gateway
|
||||
spec:
|
||||
containers:
|
||||
- name: gateway-pod
|
||||
image: git.demonkernel.io.vn/foodsurf/gateway-service:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: QUARKUS_REST_CLIENT_ACCOUNT_URL
|
||||
value: "http://account-service"
|
||||
resources:
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "512Mi"
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "256Mi"
|
||||
volumeMounts:
|
||||
- name: keys-volume
|
||||
mountPath: "/etc/keys"
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: keys-volume
|
||||
secret:
|
||||
secretName: jwt-keys
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gateway-service
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: gateway
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
nodePort: 32080
|
||||
---
|
||||
# 3. REDIS
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis-deploy
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
containers:
|
||||
- name: redis-pod
|
||||
image: redis:alpine
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
resources:
|
||||
limits:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
requests:
|
||||
cpu: "20m"
|
||||
memory: "64Mi"
|
||||
command:
|
||||
[
|
||||
"redis-server",
|
||||
"--maxmemory",
|
||||
"96mb",
|
||||
"--maxmemory-policy",
|
||||
"allkeys-lru",
|
||||
]
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-service
|
||||
spec:
|
||||
selector:
|
||||
app: redis
|
||||
ports:
|
||||
- port: 6379
|
||||
+2
-2
@@ -15,8 +15,8 @@
|
||||
|
||||
<artifactId>lib</artifactId>
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<maven.compiler.source>25</maven.compiler.source>
|
||||
<maven.compiler.target>25</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.drinkool;
|
||||
|
||||
public class Jwt {
|
||||
public static final String headerId = "X-Internal-";
|
||||
public static final String userId = headerId + "UserId";
|
||||
public static final String role = headerId + "Role";
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.drinkool;
|
||||
|
||||
public class Role {
|
||||
public static final String Customer = "customer";
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.drinkool;
|
||||
|
||||
public class Url {
|
||||
|
||||
public static final String Signup = "/signup";
|
||||
public static final String QuickSignup = "/quick_signup";
|
||||
public static final String Login = "/login";
|
||||
public static final String QuickLogin = "/quick_login";
|
||||
public static final String SmsOtp = "/sms_otp";
|
||||
public static final String Me = "/me";
|
||||
}
|
||||
@@ -3,6 +3,5 @@ package com.drinkool.dtos;
|
||||
public class AddInventory {
|
||||
|
||||
public String name;
|
||||
public int quantity;
|
||||
public String unit;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,18 @@ public class ReviewModel extends BaseEntity {
|
||||
@Column
|
||||
public String comment;
|
||||
|
||||
public ReviewModel(int rating, String comment) {
|
||||
this.rating = rating;
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public ReviewModel() {}
|
||||
|
||||
public ReviewModel(ReviewModel input) {
|
||||
this(input.rating, input.comment);
|
||||
}
|
||||
|
||||
@org.hibernate.annotations.CreationTimestamp
|
||||
@Column(name = "review_date", updatable = false)
|
||||
protected LocalDateTime reviewDate;
|
||||
public LocalDateTime reviewDate;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<version>1.1.0-dev.1</version>
|
||||
<packaging>pom</packaging>
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.source>25</maven.compiler.source>
|
||||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
||||
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
||||
<quarkus.platform.version>3.32.3</quarkus.platform.version>
|
||||
|
||||
Reference in New Issue
Block a user