Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b35b4be62e | |||
| 188c7e24c3 | |||
| fdcecf8368 | |||
| 9bf7233436 | |||
| 00cc6a0f1f | |||
| c632504b4f | |||
| e77edb0e74 | |||
| be8bdde6cf | |||
| cb9096dd1b | |||
| 837f5f595a | |||
| dd3c09865f | |||
| 5a7b70145c |
@@ -1,18 +1,45 @@
|
|||||||
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 \
|
curl \
|
||||||
bash \
|
bash \
|
||||||
openjdk21-jdk \
|
build-essential \
|
||||||
maven \
|
docker-buildx \
|
||||||
git \
|
git \
|
||||||
ca-certificates \
|
docker.io \
|
||||||
pnpm
|
nodejs \
|
||||||
|
maven \
|
||||||
# git trust all directories
|
libz-dev
|
||||||
|
|
||||||
|
# 5. Cấu hình Git
|
||||||
RUN git config --global --add safe.directory '*'
|
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 JBANG_DIR="/root/.jbang"
|
||||||
ENV PATH="${JBANG_DIR}/bin:${PATH}"
|
ENV PATH="${JBANG_DIR}/bin:${PATH}"
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
{
|
{
|
||||||
"dockerComposeFile": "docker-compose.main.yaml",
|
"dockerComposeFile": "docker-compose.main.yaml",
|
||||||
"mounts": [
|
"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",
|
"remoteUser": "root",
|
||||||
"service": "devcontainer",
|
"service": "devcontainer",
|
||||||
@@ -16,7 +17,8 @@
|
|||||||
"redhat.vscode-xml",
|
"redhat.vscode-xml",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"PeterSchmalfeldt.explorer-exclude",
|
"PeterSchmalfeldt.explorer-exclude",
|
||||||
"redhat.java"
|
"redhat.java",
|
||||||
|
"ryanluker.vscode-coverage-gutters"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -24,5 +26,5 @@
|
|||||||
"TESTCONTAINERS_RYUK_DISABLED": "true",
|
"TESTCONTAINERS_RYUK_DISABLED": "true",
|
||||||
"QUARKUS_DEBUG_HOST": "0.0.0.0"
|
"QUARKUS_DEBUG_HOST": "0.0.0.0"
|
||||||
},
|
},
|
||||||
"forwardPorts": [5005]
|
"forwardPorts": [5005, 8080]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,9 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Calculate and Export Target IP
|
- uses: gerlero/apt-install@v1
|
||||||
run: |
|
with:
|
||||||
# 1. Lấy IP hiện tại và tính toán IP đích (đổi octet cuối thành .1)
|
packages: docker.io
|
||||||
RUNNER_IP=$(hostname -I | awk '{print $1}')
|
|
||||||
CALCULATED_IP=$(echo $RUNNER_IP | cut -d'.' -f1-3).1
|
|
||||||
|
|
||||||
# 2. Đưa vào biến môi trường toàn cục của Job
|
|
||||||
echo "TARGET_IP=${CALCULATED_IP}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Setup SSH Key
|
- name: Setup SSH Key
|
||||||
run: |
|
run: |
|
||||||
@@ -27,7 +22,7 @@ jobs:
|
|||||||
echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519
|
echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519
|
||||||
chmod 600 ~/.ssh/id_ed25519
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
|
||||||
ssh-keyscan -p 222 -t ed25519 $TARGET_IP >> ~/.ssh/known_hosts
|
ssh-keyscan -t ed25519 vps.demonkernel.io.vn >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
@@ -36,8 +31,9 @@ jobs:
|
|||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: "temurin"
|
distribution: "graalvm"
|
||||||
java-version: "25"
|
java-version: "25"
|
||||||
|
cache: "maven"
|
||||||
|
|
||||||
- name: Log in to Gitea Container Registry
|
- name: Log in to Gitea Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -46,35 +42,11 @@ jobs:
|
|||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.ACCESS_TOKEN }}
|
password: ${{ secrets.ACCESS_TOKEN }}
|
||||||
|
|
||||||
- uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: latest
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
run_install: false
|
run_install: false
|
||||||
|
cache: true
|
||||||
- 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-
|
|
||||||
|
|
||||||
- name: Run Semantic Release
|
- name: Run Semantic Release
|
||||||
env:
|
env:
|
||||||
@@ -93,7 +65,7 @@ jobs:
|
|||||||
|
|
||||||
REPO_NAME="${{ gitea.repository }}"
|
REPO_NAME="${{ gitea.repository }}"
|
||||||
|
|
||||||
git remote set-url origin ssh://git@$TARGET_IP:222/${REPO_NAME}.git
|
git remote set-url origin ssh://git@vps.demonkernel.io.vn/${REPO_NAME}.git
|
||||||
|
|
||||||
git rm -r --cached .
|
git rm -r --cached .
|
||||||
git add .
|
git add .
|
||||||
|
|||||||
+18
-14
@@ -6,7 +6,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -25,22 +25,26 @@ jobs:
|
|||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: "temurin"
|
distribution: "graalvm"
|
||||||
java-version: "21"
|
java-version: "25"
|
||||||
|
cache: "maven"
|
||||||
|
|
||||||
- name: Cache Maven packages
|
- uses: gerlero/apt-install@v1
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
with:
|
||||||
path: ~/.m2/repository
|
packages: build-essential zlib1g-dev docker.io
|
||||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-maven-
|
|
||||||
|
|
||||||
- name: Build and Test all modules
|
- name: Build and Test all modules
|
||||||
run: ./mvnw -B install -Dquarkus.container-image.build=false -Dquarkus.container-image.push=false
|
run: |
|
||||||
|
./mvnw -B install \
|
||||||
|
-Dquarkus.container-image.push=false \
|
||||||
|
|
||||||
- name: Publish Test Report
|
- name: Jacoco Report to PR
|
||||||
uses: scacap/action-surefire-report@v1
|
uses: madrapps/jacoco-report@v1.7.2
|
||||||
if: failure()
|
if: always()
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.ACCESS_TOKEN }}
|
paths: |
|
||||||
|
./**/jacoco.xml,
|
||||||
|
token: ${{ secrets.ACCESS_TOKEN }}
|
||||||
|
min-coverage-overall: 40
|
||||||
|
min-coverage-changed-files: 60
|
||||||
|
title: "📊 Báo cáo Độ bao phủ Kiểm thử (Test Coverage)"
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
wrapperVersion=3.3.4
|
wrapperVersion=3.3.4
|
||||||
distributionType=only-script
|
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
|
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=
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
[
|
[
|
||||||
"@semantic-release/exec",
|
"@semantic-release/exec",
|
||||||
{
|
{
|
||||||
"prepareCmd": "./mvnw versions:set -DnewVersion=${nextRelease.version} && sed -i \"s|\\(image: git.demonkernel.io.vn/foodsurf/[^:]*\\):.*|\\1:${nextRelease.version}|g\" k8s.yaml",
|
"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"
|
"publishCmd": "if [ \"${branch.name}\" = \"main\" ]; then ./mvnw package -Dquarkus.container-image.tag=${nextRelease.version} -DskipTests; fi"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,3 +1,27 @@
|
|||||||
|
# [1.5.0](https://git.demonkernel.io.vn/FoodSurf/backend/compare/v1.4.0...v1.5.0) (2026-04-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* better url ([#20](https://git.demonkernel.io.vn/FoodSurf/backend/issues/20)) ([fdcecf8](https://git.demonkernel.io.vn/FoodSurf/backend/commit/fdcecf836876048af2227313165ec2c52d9ede48))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add jwt authorization ([#19](https://git.demonkernel.io.vn/FoodSurf/backend/issues/19)) ([00cc6a0](https://git.demonkernel.io.vn/FoodSurf/backend/commit/00cc6a0f1f44eab9e081fb54fff36743b5790313))
|
||||||
|
|
||||||
|
# [1.4.0](https://git.demonkernel.io.vn/FoodSurf/backend/compare/v1.3.0...v1.4.0) (2026-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* update ci script ([#18](https://git.demonkernel.io.vn/FoodSurf/backend/issues/18)) ([e77edb0](https://git.demonkernel.io.vn/FoodSurf/backend/commit/e77edb0e7402afaca494221270d2e289dac3448b))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* init k8s and customer service ([#15](https://git.demonkernel.io.vn/FoodSurf/backend/issues/15)) ([837f5f5](https://git.demonkernel.io.vn/FoodSurf/backend/commit/837f5f595a3b1c5ea5e261f60f265834081071ea))
|
||||||
|
|
||||||
# [1.1.0-dev.1](https://git.demonkernel.io.vn/FoodSurf/backend/compare/v1.0.0...v1.1.0-dev.1) (2026-03-24)
|
# [1.1.0-dev.1](https://git.demonkernel.io.vn/FoodSurf/backend/compare/v1.0.0...v1.1.0-dev.1) (2026-03-24)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+10
-18
@@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.drinkool</groupId>
|
<groupId>com.drinkool</groupId>
|
||||||
<artifactId>drinkool</artifactId>
|
<artifactId>drinkool</artifactId>
|
||||||
<version>1.1.0-dev.1</version>
|
<version>1.5.0</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@@ -17,8 +17,11 @@
|
|||||||
<packaging>quarkus</packaging>
|
<packaging>quarkus</packaging>
|
||||||
|
|
||||||
<properties>
|
<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>
|
<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.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding
|
<project.reporting.outputEncoding
|
||||||
>UTF-8</project.reporting.outputEncoding>
|
>UTF-8</project.reporting.outputEncoding>
|
||||||
@@ -43,6 +46,11 @@
|
|||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-jacoco</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.drinkool</groupId>
|
<groupId>com.drinkool</groupId>
|
||||||
<artifactId>lib</artifactId>
|
<artifactId>lib</artifactId>
|
||||||
@@ -158,20 +166,4 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</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>
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
package com.drinkool.entities;
|
package com.drinkool.entities;
|
||||||
|
|
||||||
|
import com.drinkool.Role;
|
||||||
import com.drinkool.models.UserModel;
|
import com.drinkool.models.UserModel;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "customer")
|
@Table(name = Role.Customer)
|
||||||
public class CustomerEntity extends UserModel {}
|
public class CustomerEntity extends UserModel {
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void signup(String phone) {
|
||||||
|
this.signup(phone, phone, UUID.randomUUID().toString().substring(0, 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.drinkool.entities;
|
||||||
|
|
||||||
|
import com.drinkool.Role;
|
||||||
|
import com.drinkool.models.UserModel;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = Role.Manager)
|
||||||
|
public class ManagerEntity extends UserModel {
|
||||||
|
|
||||||
|
public String eateryName;
|
||||||
|
|
||||||
|
public boolean isVerified;
|
||||||
|
|
||||||
|
public void signup(
|
||||||
|
String name,
|
||||||
|
String phone,
|
||||||
|
String rawPassword,
|
||||||
|
String eateryName
|
||||||
|
) {
|
||||||
|
this.eateryName = eateryName;
|
||||||
|
|
||||||
|
this.isVerified = false;
|
||||||
|
|
||||||
|
this.signup(name, phone, rawPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,11 @@ package com.drinkool.models;
|
|||||||
|
|
||||||
import com.drinkool.entities.BaseEntity;
|
import com.drinkool.entities.BaseEntity;
|
||||||
import com.drinkool.utils.PhoneValidator;
|
import com.drinkool.utils.PhoneValidator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.password4j.Password;
|
import com.password4j.Password;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import jakarta.transaction.*;
|
import jakarta.transaction.*;
|
||||||
import jakarta.ws.rs.BadRequestException;
|
import jakarta.ws.rs.BadRequestException;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Inheritance(strategy = InheritanceType.JOINED)
|
@Inheritance(strategy = InheritanceType.JOINED)
|
||||||
@@ -20,21 +20,15 @@ public abstract class UserModel extends BaseEntity {
|
|||||||
public String phone;
|
public String phone;
|
||||||
|
|
||||||
@Column(nullable = false, length = 1024)
|
@Column(nullable = false, length = 1024)
|
||||||
|
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||||
public String hashedPassword;
|
public String hashedPassword;
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public void signup(String phone) {
|
|
||||||
this.signup(phone, phone, UUID.randomUUID().toString().substring(0, 32));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void signup(String name, String phone, String rawPassword) {
|
public void signup(String name, String phone, String rawPassword) {
|
||||||
// Check existed user
|
UserModel existedUser = UserModel.find("phone", phone).firstResult();
|
||||||
UserModel existedUser = find("phone", phone).firstResult();
|
|
||||||
|
|
||||||
if (existedUser != null) throw new BadRequestException("ExistedUser");
|
if (existedUser != null) throw new BadRequestException("ExistedUser");
|
||||||
|
|
||||||
// Set user
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
if (!PhoneValidator.isValidInternationalPhone(phone)) {
|
if (!PhoneValidator.isValidInternationalPhone(phone)) {
|
||||||
@@ -45,7 +39,6 @@ public abstract class UserModel extends BaseEntity {
|
|||||||
|
|
||||||
this.hashedPassword = Password.hash(rawPassword).withArgon2().getResult();
|
this.hashedPassword = Password.hash(rawPassword).withArgon2().getResult();
|
||||||
|
|
||||||
// Save user
|
|
||||||
this.persist();
|
this.persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.drinkool.services;
|
||||||
|
|
||||||
|
import com.drinkool.Url;
|
||||||
|
import com.drinkool.dtos.SmsOtp;
|
||||||
|
import com.drinkool.models.UserModel;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.ws.rs.*;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
|
@Path("")
|
||||||
|
public class AccountService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
OtpService otpService;
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path(Url.SmsOtp)
|
||||||
|
public Response smsOtp(SmsOtp input) {
|
||||||
|
UserModel customer = UserModel.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ế
|
||||||
|
System.out.println("OTP cho " + input.phone + " là: " + otp);
|
||||||
|
|
||||||
|
return Response.accepted().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package com.drinkool.services;
|
|
||||||
|
|
||||||
import com.drinkool.Url;
|
|
||||||
import com.drinkool.dtos.*;
|
|
||||||
import com.drinkool.entities.CustomerEntity;
|
|
||||||
import jakarta.inject.Inject;
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
import jakarta.ws.rs.*;
|
|
||||||
import jakarta.ws.rs.core.*;
|
|
||||||
|
|
||||||
@Path("")
|
|
||||||
public class AuthenticationService {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
OtpService otpService;
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@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();
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path(Url.QuickSignup)
|
|
||||||
@Transactional
|
|
||||||
public Response quickSignup(QuickSignup input) {
|
|
||||||
CustomerEntity newCustomer = new CustomerEntity();
|
|
||||||
|
|
||||||
newCustomer.signup(input.phone);
|
|
||||||
|
|
||||||
return Response.status(Response.Status.CREATED).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@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.login(input.password)) throw new BadRequestException(
|
|
||||||
"InvalidPassword"
|
|
||||||
);
|
|
||||||
|
|
||||||
return Response.accepted().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path(Url.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();
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path(Url.SmsOtp)
|
|
||||||
public Response smsOtp(SmsOtp input) {
|
|
||||||
String otp = otpService.generateAndSaveOtp(input.phone);
|
|
||||||
|
|
||||||
// Ở đây bạn sẽ gọi Kafka để gửi SMS thực tế
|
|
||||||
System.out.println("OTP cho " + input.phone + " là: " + otp);
|
|
||||||
|
|
||||||
return Response.accepted().build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.drinkool.services;
|
||||||
|
|
||||||
|
import com.drinkool.InternalValue;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public abstract class BaseService {
|
||||||
|
|
||||||
|
private UserModel model;
|
||||||
|
|
||||||
|
public BaseService(UserModel model) {
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("static-access")
|
||||||
|
@POST
|
||||||
|
@Path(Url.Me)
|
||||||
|
@Transactional
|
||||||
|
public Response me(@HeaderParam(InternalValue.userId) UUID id) {
|
||||||
|
if (id == null) return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity("InvalidUserId")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return Response.ok(model.find("id", id).firstResult()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.drinkool.services;
|
||||||
|
|
||||||
|
import com.drinkool.*;
|
||||||
|
import com.drinkool.dtos.*;
|
||||||
|
import com.drinkool.entities.CustomerEntity;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import jakarta.ws.rs.*;
|
||||||
|
import jakarta.ws.rs.core.*;
|
||||||
|
|
||||||
|
@Path(Role.Customer)
|
||||||
|
public class CustomerService extends BaseService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
OtpService otpService;
|
||||||
|
|
||||||
|
public CustomerService() {
|
||||||
|
super(new CustomerEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@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)
|
||||||
|
.header(InternalValue.userId, newCustomer.id.toString())
|
||||||
|
.header(InternalValue.role, Role.Customer)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path(Url.QuickSignup)
|
||||||
|
@Transactional
|
||||||
|
public Response quickSignup(QuickSignup input) {
|
||||||
|
CustomerEntity newCustomer = new CustomerEntity();
|
||||||
|
|
||||||
|
newCustomer.signup(input.phone);
|
||||||
|
|
||||||
|
return Response.status(Response.Status.CREATED)
|
||||||
|
.header(InternalValue.userId, newCustomer.id.toString())
|
||||||
|
.header(InternalValue.role, Role.Customer)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path(Url.Login)
|
||||||
|
public Response login(Login input) {
|
||||||
|
CustomerEntity customer = CustomerEntity.find(
|
||||||
|
"phone",
|
||||||
|
input.phone
|
||||||
|
).firstResult();
|
||||||
|
|
||||||
|
if (customer == null) return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity("InvalidPhoneNumber")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if (!customer.login(input.password)) return Response.status(
|
||||||
|
Response.Status.BAD_REQUEST
|
||||||
|
)
|
||||||
|
.entity("InvalidPassword")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return Response.accepted()
|
||||||
|
.header(InternalValue.userId, customer.id.toString())
|
||||||
|
.header(InternalValue.role, Role.Customer)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@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(InternalValue.userId, customer.id.toString())
|
||||||
|
.header(InternalValue.role, Role.Customer)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.drinkool.services;
|
||||||
|
|
||||||
|
import com.drinkool.*;
|
||||||
|
import com.drinkool.dtos.*;
|
||||||
|
import com.drinkool.entities.ManagerEntity;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import jakarta.ws.rs.*;
|
||||||
|
import jakarta.ws.rs.core.*;
|
||||||
|
|
||||||
|
@Path(Role.Manager)
|
||||||
|
public class ManagerService {
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path(Url.Signup)
|
||||||
|
@Transactional
|
||||||
|
public Response signup(ManagerSignup input) {
|
||||||
|
ManagerEntity newManager = new ManagerEntity();
|
||||||
|
|
||||||
|
newManager.signup(
|
||||||
|
input.name,
|
||||||
|
input.phone,
|
||||||
|
input.password,
|
||||||
|
input.eateryName
|
||||||
|
);
|
||||||
|
|
||||||
|
return Response.status(Response.Status.CREATED)
|
||||||
|
.header(InternalValue.userId, newManager.id.toString())
|
||||||
|
.header(InternalValue.role, Role.Manager)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path(Url.Login)
|
||||||
|
public Response login(Login input) {
|
||||||
|
ManagerEntity customer = ManagerEntity.find(
|
||||||
|
"phone",
|
||||||
|
input.phone
|
||||||
|
).firstResult();
|
||||||
|
|
||||||
|
if (customer == null) return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity("InvalidPhoneNumber")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if (!customer.login(input.password)) return Response.status(
|
||||||
|
Response.Status.BAD_REQUEST
|
||||||
|
)
|
||||||
|
.entity("InvalidPassword")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return Response.accepted()
|
||||||
|
.header(InternalValue.userId, customer.id.toString())
|
||||||
|
.header(InternalValue.role, Role.Manager)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ quarkus.container-image.name=account-service
|
|||||||
quarkus.container-image.push=true
|
quarkus.container-image.push=true
|
||||||
quarkus.container-image.build=true
|
quarkus.container-image.build=true
|
||||||
quarkus.container-image.builder=docker
|
quarkus.container-image.builder=docker
|
||||||
|
quarkus.container-image.additional-tags=latest
|
||||||
|
|
||||||
# Redis
|
# Redis
|
||||||
quarkus.redis.hosts=redis://localhost:6379
|
quarkus.redis.hosts=redis://localhost:6379
|
||||||
@@ -20,4 +21,9 @@ quarkus.redis.hosts=redis://localhost:6379
|
|||||||
|
|
||||||
# Dependency
|
# Dependency
|
||||||
quarkus.index-dependency.lib.group-id=com.drinkool
|
quarkus.index-dependency.lib.group-id=com.drinkool
|
||||||
quarkus.index-dependency.lib.artifact-id=lib
|
quarkus.index-dependency.lib.artifact-id=lib
|
||||||
|
|
||||||
|
# Build
|
||||||
|
quarkus.native.container-build=false
|
||||||
|
quarkus.native.additional-build-args=--initialize-at-run-time=com.password4j.AlgorithmFinder,--future-defaults=all
|
||||||
|
quarkus.native.resources.includes=com/google/i18n/phonenumbers/data/.*
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.drinkool;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
public static Random random = new Random();
|
||||||
|
|
||||||
|
public static String generateRandomPhone() {
|
||||||
|
return "+8477" + String.format("%07d", random.nextInt(10000000));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.drinkool.entities;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import com.drinkool.*;
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
public class CustomerEntityTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Transactional
|
||||||
|
public void testSignupOneParamSuccess() {
|
||||||
|
CustomerEntity user = new CustomerEntity();
|
||||||
|
String phone = Utils.generateRandomPhone();
|
||||||
|
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,120 +2,131 @@ package com.drinkool.models;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
import com.drinkool.entities.CustomerEntity;
|
|
||||||
import io.quarkus.test.TestTransaction;
|
|
||||||
import io.quarkus.test.junit.QuarkusTest;
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
import jakarta.ws.rs.BadRequestException;
|
import jakarta.ws.rs.BadRequestException;
|
||||||
import java.util.*;
|
import java.util.Random;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table
|
@Table(name = "test_users")
|
||||||
class TestUserEntity extends UserModel {}
|
class TestUserEntity extends UserModel {}
|
||||||
|
|
||||||
@QuarkusTest
|
@QuarkusTest
|
||||||
public class UserModelTest {
|
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
|
@Test
|
||||||
@Transactional
|
@Transactional
|
||||||
public void testSignupSuccess() {
|
public void testSignupSuccess() {
|
||||||
//dang nhap thanh cong
|
|
||||||
TestUserEntity user = new TestUserEntity();
|
TestUserEntity user = new TestUserEntity();
|
||||||
String phone =
|
String phone = generateValidPhone();
|
||||||
"+8477" + String.format("%07d", (new Random()).nextInt(10000000));
|
|
||||||
String password = "02092006Danh";
|
String password = "02092006Danh";
|
||||||
|
|
||||||
user.signup("Tran Huu Danh", phone, password);
|
user.signup("Tran Huu Danh", phone, password);
|
||||||
|
|
||||||
assertNotNull(user.id, "ID not null after persist");
|
assertNotNull(user.id, "ID not null after persist");
|
||||||
assertEquals("Tran Huu Danh", user.name);
|
assertEquals("Tran Huu Danh", user.name);
|
||||||
assertEquals(phone, user.phone);
|
assertEquals(phone, user.phone);
|
||||||
// assertNotNull(user.hashedPassword, "password phai hash");
|
assertNotNull(user.hashedPassword, "Password phải được hash");
|
||||||
|
|
||||||
TestUserEntity savedUser = TestUserEntity.find(
|
TestUserEntity savedUser = TestUserEntity.find(
|
||||||
"phone",
|
"phone",
|
||||||
phone
|
phone
|
||||||
).firstResult();
|
).firstResult();
|
||||||
assertNotNull(savedUser);
|
assertNotNull(savedUser, "Phải tìm thấy user trong database");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 đã tồn tại
|
||||||
@Test
|
@Test
|
||||||
@Transactional
|
@Transactional
|
||||||
public void testSignupFailExistedUser() {
|
public void testSignupFailExistedUser() {
|
||||||
TestUserEntity existUser = new TestUserEntity();
|
String phone = generateValidPhone();
|
||||||
String phone =
|
|
||||||
"+8477" + String.format("%07d", (new Random()).nextInt(10000000));
|
|
||||||
String password = "tungtungtungshahua";
|
String password = "tungtungtungshahua";
|
||||||
|
|
||||||
|
// Tạo user đầu tiên
|
||||||
|
TestUserEntity existUser = new TestUserEntity();
|
||||||
existUser.signup("Nguyen Thanh Quy", phone, password);
|
existUser.signup("Nguyen Thanh Quy", phone, password);
|
||||||
|
|
||||||
CustomerEntity newCustomer = new CustomerEntity();
|
// Tạo user thứ hai với cùng số điện thoại
|
||||||
|
TestUserEntity newUser = new TestUserEntity();
|
||||||
BadRequestException exception = assertThrows(
|
BadRequestException exception = assertThrows(
|
||||||
BadRequestException.class,
|
BadRequestException.class,
|
||||||
() -> {
|
() -> {
|
||||||
newCustomer.signup("Nguyen Viet Anh", phone, "02092006Anh");
|
newUser.signup("Nguyen Viet Anh", phone, "02092006Anh");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
assertEquals("ExistedUser", exception.getMessage());
|
assertEquals("ExistedUser", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Test case: Đăng ký thất bại do số điện thoại sai định dạng
|
// 4. Test case: Đăng ký thất bại do số điện thoại sai định dạng
|
||||||
@Test
|
@Test
|
||||||
@TestTransaction
|
@Transactional
|
||||||
public void testSignupFailInvalidPhoneNumber() {
|
public void testSignupFailInvalidPhoneNumber() {
|
||||||
TestUserEntity customer = new TestUserEntity();
|
TestUserEntity user = new TestUserEntity();
|
||||||
String invalidPhone = "12345";
|
String invalidPhone = "12345";
|
||||||
|
|
||||||
BadRequestException exception = assertThrows(
|
BadRequestException exception = assertThrows(
|
||||||
BadRequestException.class,
|
BadRequestException.class,
|
||||||
() -> {
|
() -> {
|
||||||
customer.signup("Lam Vi Hoang", invalidPhone, "Pass123");
|
user.signup("Lam Vi Hoang", invalidPhone, "Pass123");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
assertEquals("InvalidPhoneNumber", exception.getMessage());
|
assertEquals("InvalidPhoneNumber", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Test case: Đăng nhập thành công
|
||||||
@Test
|
@Test
|
||||||
@Transactional
|
@Transactional
|
||||||
public void testLoginSuccess() {
|
public void testLoginSuccess() {
|
||||||
//dang nhap thanh cong
|
|
||||||
TestUserEntity user = new TestUserEntity();
|
TestUserEntity user = new TestUserEntity();
|
||||||
String phone =
|
String phone = generateValidPhone();
|
||||||
"+8477" + String.format("%07d", (new Random()).nextInt(10000000));
|
|
||||||
String password = "02092006Danh";
|
String password = "02092006Danh";
|
||||||
|
|
||||||
user.signup("Tran Huu Danh", phone, password);
|
user.signup("Tran Huu Danh", phone, password);
|
||||||
|
|
||||||
Boolean loginResult = user.login(password);
|
boolean loginResult = user.login(password);
|
||||||
|
|
||||||
assertTrue(loginResult);
|
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
|
@Test
|
||||||
@Transactional
|
@Transactional
|
||||||
public void testLoginFailWrongPassword() {
|
public void testLoginFailWrongPassword() {
|
||||||
//dang nhap that bai
|
|
||||||
TestUserEntity user = new TestUserEntity();
|
TestUserEntity user = new TestUserEntity();
|
||||||
String phone =
|
String phone = generateValidPhone();
|
||||||
"+8477" + String.format("%07d", (new Random()).nextInt(10000000));
|
|
||||||
String truePassword = "02092006Danh";
|
String truePassword = "02092006Danh";
|
||||||
String wrongPassword = "02092006danhh";
|
String wrongPassword = "02092006danhh";
|
||||||
|
|
||||||
user.signup("Tran Huu Danh", phone, truePassword);
|
user.signup("Tran Huu Danh", phone, truePassword);
|
||||||
Boolean wrongTest = user.login(wrongPassword);
|
|
||||||
|
|
||||||
assertFalse(wrongTest);
|
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
|
@Test
|
||||||
@Transactional
|
@Transactional
|
||||||
public void testLoginFailNullHashedPassword() {
|
public void testLoginFailNullHashedPassword() {
|
||||||
TestUserEntity user = new TestUserEntity();
|
TestUserEntity user = new TestUserEntity();
|
||||||
user.hashedPassword = null;
|
user.hashedPassword = null; // Cố tình set null để test lỗi
|
||||||
|
|
||||||
boolean loginResult = user.login("anhdomixi");
|
boolean loginResult = user.login("anhdomixi");
|
||||||
assertFalse(loginResult);
|
|
||||||
|
assertFalse(
|
||||||
|
loginResult,
|
||||||
|
"Đăng nhập phải thất bại nếu hashedPassword là null"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.drinkool.services;
|
||||||
|
|
||||||
|
import static io.restassured.RestAssured.*;
|
||||||
|
|
||||||
|
import com.drinkool.*;
|
||||||
|
import com.drinkool.dtos.*;
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
public class AccountServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSmsOtp() {
|
||||||
|
String phone = Utils.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(Role.Customer + Url.Signup)
|
||||||
|
.then();
|
||||||
|
|
||||||
|
given()
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(input)
|
||||||
|
.when()
|
||||||
|
.post(Url.SmsOtp)
|
||||||
|
.then()
|
||||||
|
.statusCode(202);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.drinkool.services;
|
||||||
|
|
||||||
|
import static io.restassured.RestAssured.*;
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
|
||||||
|
import com.drinkool.*;
|
||||||
|
import com.drinkool.dtos.*;
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
public class BaseServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMeEndpointWithHeader() {
|
||||||
|
String phone = Utils.generateRandomPhone();
|
||||||
|
QuickSignup input = new QuickSignup();
|
||||||
|
input.phone = phone;
|
||||||
|
|
||||||
|
String testUserId = given()
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(input)
|
||||||
|
.when()
|
||||||
|
.post(Role.Customer + Url.QuickSignup)
|
||||||
|
.then()
|
||||||
|
.extract()
|
||||||
|
.header(InternalValue.userId);
|
||||||
|
|
||||||
|
given()
|
||||||
|
.header(InternalValue.userId, testUserId)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.when()
|
||||||
|
.post(Role.Customer + Url.Me)
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("id", is(testUserId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMeEndpointWithoutHeader() {
|
||||||
|
given().when().post(Role.Customer + Url.Me).then().statusCode(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
-28
@@ -1,28 +1,26 @@
|
|||||||
package com.drinkool.services;
|
package com.drinkool.services;
|
||||||
|
|
||||||
import static io.restassured.RestAssured.given;
|
import static io.restassured.RestAssured.*;
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
import com.drinkool.Url;
|
import com.drinkool.*;
|
||||||
import com.drinkool.dtos.*;
|
import com.drinkool.dtos.*;
|
||||||
import com.drinkool.entities.CustomerEntity;
|
import com.drinkool.entities.CustomerEntity;
|
||||||
import io.quarkus.test.junit.QuarkusTest;
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
import io.restassured.http.ContentType;
|
import io.restassured.http.ContentType;
|
||||||
import java.util.Random;
|
import jakarta.inject.Inject;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@QuarkusTest
|
@QuarkusTest
|
||||||
public class AuthenticationServiceTest {
|
public class CustomerServiceTest {
|
||||||
|
|
||||||
private Random random = new Random();
|
@Inject
|
||||||
|
OtpService otpService;
|
||||||
private String generateRandomPhone() {
|
|
||||||
return "+8477" + String.format("%07d", random.nextInt(10000000));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSignup() {
|
void testSignup() {
|
||||||
String phone = generateRandomPhone();
|
String phone = Utils.generateRandomPhone();
|
||||||
Signup input = new Signup();
|
Signup input = new Signup();
|
||||||
input.name = "Test User";
|
input.name = "Test User";
|
||||||
input.phone = phone;
|
input.phone = phone;
|
||||||
@@ -32,7 +30,7 @@ public class AuthenticationServiceTest {
|
|||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body(input)
|
.body(input)
|
||||||
.when()
|
.when()
|
||||||
.post(Url.Signup)
|
.post(Role.Customer + Url.Signup)
|
||||||
.then()
|
.then()
|
||||||
.statusCode(201);
|
.statusCode(201);
|
||||||
|
|
||||||
@@ -42,7 +40,7 @@ public class AuthenticationServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testQuickSignup() {
|
void testQuickSignup() {
|
||||||
String phone = generateRandomPhone();
|
String phone = Utils.generateRandomPhone();
|
||||||
QuickSignup input = new QuickSignup();
|
QuickSignup input = new QuickSignup();
|
||||||
input.phone = phone;
|
input.phone = phone;
|
||||||
|
|
||||||
@@ -50,8 +48,10 @@ public class AuthenticationServiceTest {
|
|||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body(input)
|
.body(input)
|
||||||
.when()
|
.when()
|
||||||
.post(Url.QuickSignup)
|
.post(Role.Customer + Url.QuickSignup)
|
||||||
.then()
|
.then()
|
||||||
|
.header(InternalValue.userId, notNullValue())
|
||||||
|
.header(InternalValue.role, is(Role.Customer))
|
||||||
.statusCode(201);
|
.statusCode(201);
|
||||||
|
|
||||||
assertEquals(1, CustomerEntity.find("phone", phone).count());
|
assertEquals(1, CustomerEntity.find("phone", phone).count());
|
||||||
@@ -60,7 +60,7 @@ public class AuthenticationServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void testLoginSuccess() {
|
void testLoginSuccess() {
|
||||||
// BƯỚC 1: Tạo user trước bằng signup
|
// BƯỚC 1: Tạo user trước bằng signup
|
||||||
String phone = generateRandomPhone();
|
String phone = Utils.generateRandomPhone();
|
||||||
Signup signupData = new Signup();
|
Signup signupData = new Signup();
|
||||||
signupData.name = "Login User";
|
signupData.name = "Login User";
|
||||||
signupData.phone = phone;
|
signupData.phone = phone;
|
||||||
@@ -69,7 +69,7 @@ public class AuthenticationServiceTest {
|
|||||||
given()
|
given()
|
||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body(signupData)
|
.body(signupData)
|
||||||
.post(Url.Signup)
|
.post(Role.Customer + Url.Signup)
|
||||||
.then()
|
.then()
|
||||||
.statusCode(201);
|
.statusCode(201);
|
||||||
|
|
||||||
@@ -82,14 +82,14 @@ public class AuthenticationServiceTest {
|
|||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body(loginInput)
|
.body(loginInput)
|
||||||
.when()
|
.when()
|
||||||
.post(Url.Login)
|
.post(Role.Customer + Url.Login)
|
||||||
.then()
|
.then()
|
||||||
.statusCode(202);
|
.statusCode(202);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLoginFailWrongPassword() {
|
void testLoginFailWrongPassword() {
|
||||||
String phone = generateRandomPhone();
|
String phone = Utils.generateRandomPhone();
|
||||||
Signup signupData = new Signup();
|
Signup signupData = new Signup();
|
||||||
signupData.name = "Wrong Pass User";
|
signupData.name = "Wrong Pass User";
|
||||||
signupData.phone = phone;
|
signupData.phone = phone;
|
||||||
@@ -98,7 +98,7 @@ public class AuthenticationServiceTest {
|
|||||||
given()
|
given()
|
||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body(signupData)
|
.body(signupData)
|
||||||
.post(Url.Signup)
|
.post(Role.Customer + Url.Signup)
|
||||||
.then()
|
.then()
|
||||||
.statusCode(201);
|
.statusCode(201);
|
||||||
|
|
||||||
@@ -110,32 +110,41 @@ public class AuthenticationServiceTest {
|
|||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body(loginInput)
|
.body(loginInput)
|
||||||
.when()
|
.when()
|
||||||
.post(Url.Login)
|
.post(Role.Customer + Url.Login)
|
||||||
.then()
|
.then()
|
||||||
.statusCode(400); // BadRequestException
|
.statusCode(400); // BadRequestException
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSmsOtp() {
|
public void testQuickLogin_Success() {
|
||||||
String phone = generateRandomPhone();
|
String phone = Utils.generateRandomPhone();
|
||||||
SmsOtp input = new SmsOtp();
|
|
||||||
|
QuickSignup input = new QuickSignup();
|
||||||
input.phone = phone;
|
input.phone = phone;
|
||||||
|
|
||||||
given()
|
given()
|
||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body(input)
|
.body(input)
|
||||||
.when()
|
.when()
|
||||||
.post(Url.SmsOtp)
|
.post(Role.Customer + Url.QuickSignup)
|
||||||
|
.then();
|
||||||
|
|
||||||
|
QuickLogin payload = new QuickLogin();
|
||||||
|
payload.phone = phone;
|
||||||
|
payload.otp = otpService.generateAndSaveOtp(phone);
|
||||||
|
|
||||||
|
given()
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body(payload)
|
||||||
|
.when()
|
||||||
|
.post(Role.Customer + Url.QuickLogin)
|
||||||
.then()
|
.then()
|
||||||
.statusCode(202);
|
.statusCode(202);
|
||||||
|
|
||||||
// Lưu ý: Vì không dùng Mock, code sẽ chạy qua OtpService thật.
|
|
||||||
// Nếu OtpService lưu vào DB, bạn có thể bổ sung Assert tại đây để check OtpEntity.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testQuickLoginFailInvalidOtp() {
|
void testQuickLoginFailInvalidOtp() {
|
||||||
String phone = generateRandomPhone();
|
String phone = Utils.generateRandomPhone();
|
||||||
QuickLogin input = new QuickLogin();
|
QuickLogin input = new QuickLogin();
|
||||||
input.phone = phone;
|
input.phone = phone;
|
||||||
input.otp = "000000"; // Giả sử mã này luôn sai vì chưa được tạo
|
input.otp = "000000"; // Giả sử mã này luôn sai vì chưa được tạo
|
||||||
@@ -144,7 +153,7 @@ public class AuthenticationServiceTest {
|
|||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body(input)
|
.body(input)
|
||||||
.when()
|
.when()
|
||||||
.post(Url.QuickLogin)
|
.post(Role.Customer + Url.QuickLogin)
|
||||||
.then()
|
.then()
|
||||||
.statusCode(401);
|
.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,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));
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-22
@@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.drinkool</groupId>
|
<groupId>com.drinkool</groupId>
|
||||||
<artifactId>drinkool</artifactId>
|
<artifactId>drinkool</artifactId>
|
||||||
<version>1.1.0-dev.1</version>
|
<version>1.5.0</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@@ -17,8 +17,11 @@
|
|||||||
<packaging>quarkus</packaging>
|
<packaging>quarkus</packaging>
|
||||||
|
|
||||||
<properties>
|
<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>
|
<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.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding
|
<project.reporting.outputEncoding
|
||||||
>UTF-8</project.reporting.outputEncoding>
|
>UTF-8</project.reporting.outputEncoding>
|
||||||
@@ -43,6 +46,11 @@
|
|||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-jacoco</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.drinkool</groupId>
|
<groupId>com.drinkool</groupId>
|
||||||
<artifactId>lib</artifactId>
|
<artifactId>lib</artifactId>
|
||||||
@@ -66,10 +74,6 @@
|
|||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-jackson</artifactId>
|
<artifactId>quarkus-jackson</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>io.quarkus</groupId>
|
|
||||||
<artifactId>quarkus-messaging-kafka</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-jdbc-postgresql</artifactId>
|
<artifactId>quarkus-jdbc-postgresql</artifactId>
|
||||||
@@ -86,6 +90,10 @@
|
|||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-rest-jackson</artifactId>
|
<artifactId>quarkus-rest-jackson</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-container-image-docker</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-junit</artifactId>
|
<artifactId>quarkus-junit</artifactId>
|
||||||
@@ -144,20 +152,4 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</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>
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
# Kafka
|
|
||||||
kafka.bootstrap.servers=localhost:9092
|
|
||||||
|
|
||||||
# Postgress
|
# Postgress
|
||||||
## Cấu hình Database
|
## Cấu hình Database
|
||||||
quarkus.datasource.db-kind=postgresql
|
quarkus.datasource.db-kind=postgresql
|
||||||
@@ -11,6 +8,19 @@ quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/eatery
|
|||||||
## Tự động tạo bảng từ Entity (chỉ dùng cho môi trường dev)
|
## Tự động tạo bảng từ Entity (chỉ dùng cho môi trường dev)
|
||||||
quarkus.hibernate-orm.database.generation=update
|
quarkus.hibernate-orm.database.generation=update
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
quarkus.container-image.registry=git.demonkernel.io.vn
|
||||||
|
quarkus.container-image.group=foodsurf
|
||||||
|
quarkus.container-image.name=eatery-service
|
||||||
|
quarkus.container-image.push=true
|
||||||
|
quarkus.container-image.build=true
|
||||||
|
quarkus.container-image.builder=docker
|
||||||
|
quarkus.container-image.additional-tags=latest
|
||||||
|
|
||||||
# Dependency
|
# Dependency
|
||||||
quarkus.index-dependency.lib.group-id=com.drinkool
|
quarkus.index-dependency.lib.group-id=com.drinkool
|
||||||
quarkus.index-dependency.lib.artifact-id=lib
|
quarkus.index-dependency.lib.artifact-id=lib
|
||||||
|
|
||||||
|
# Build
|
||||||
|
quarkus.native.container-build=false
|
||||||
|
quarkus.native.additional-build-args=--future-defaults=all
|
||||||
@@ -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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -23,14 +23,16 @@ public class MenuItemIngredientEntityTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
String inventoryItemName = "Water";
|
String inventoryItemName = "Water";
|
||||||
double inventoryItemQuantity = 10;
|
int inventoryItemQuantity = 10; // Đổi thành int cho khớp với kiểu của EateryInventoryItemEntity
|
||||||
String inventoryItemUnit = "l";
|
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(
|
EateryInventoryItemEntity inventoryItem = EateryInventoryItemEntity.create(
|
||||||
inventoryItemName,
|
inventoryItemName,
|
||||||
inventoryItemQuantity,
|
inventoryItemQuantity,
|
||||||
inventoryItemUnit
|
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;
|
double requiredQuantity = 0.5;
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,8 @@ public class ReviewServiceTest {
|
|||||||
.body(input)
|
.body(input)
|
||||||
.when()
|
.when()
|
||||||
.post("/api/review/create")
|
.post("/api/review/create")
|
||||||
.then();
|
.then()
|
||||||
|
.statusCode(201);
|
||||||
|
|
||||||
assertEquals(1, ReviewEntity.find("reviewerId", reviewerId).count());
|
assertEquals(1, ReviewEntity.find("reviewerId", reviewerId).count());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ quarkus.http.host=0.0.0.0
|
|||||||
%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:test_db;DB_CLOSE_DELAY=-1
|
%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:test_db;DB_CLOSE_DELAY=-1
|
||||||
|
|
||||||
## Orm
|
## Orm
|
||||||
%test.quarkus.hibernate-orm.database.generation=drop-and-create
|
%test.quarkus.hibernate-orm.database.generation=drop-and-create
|
||||||
+15
-18
@@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.drinkool</groupId>
|
<groupId>com.drinkool</groupId>
|
||||||
<artifactId>drinkool</artifactId>
|
<artifactId>drinkool</artifactId>
|
||||||
<version>1.1.0-dev.1</version>
|
<version>1.5.0</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@@ -17,8 +17,11 @@
|
|||||||
<packaging>quarkus</packaging>
|
<packaging>quarkus</packaging>
|
||||||
|
|
||||||
<properties>
|
<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>
|
<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.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding
|
<project.reporting.outputEncoding
|
||||||
>UTF-8</project.reporting.outputEncoding>
|
>UTF-8</project.reporting.outputEncoding>
|
||||||
@@ -43,6 +46,16 @@
|
|||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-jacoco</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bitbucket.b_c</groupId>
|
||||||
|
<artifactId>jose4j</artifactId>
|
||||||
|
<version>0.9.6</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.drinkool</groupId>
|
<groupId>com.drinkool</groupId>
|
||||||
<artifactId>lib</artifactId>
|
<artifactId>lib</artifactId>
|
||||||
@@ -131,20 +144,4 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</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>
|
||||||
|
|||||||
+7
-2
@@ -8,8 +8,8 @@ import jakarta.ws.rs.*;
|
|||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||||
|
|
||||||
@Path("/api/v1/customers")
|
@Path("/api/" + Role.Customer)
|
||||||
public class CustomerGatewayResource {
|
class CustomerGatewayResource {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@RestClient
|
@RestClient
|
||||||
@@ -26,4 +26,9 @@ public class CustomerGatewayResource {
|
|||||||
public Uni<Response> proxyLogin(Login input) {
|
public Uni<Response> proxyLogin(Login input) {
|
||||||
return accountService.login(input);
|
return accountService.login(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
public Uni<Response> me() {
|
||||||
|
return accountService.me();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package com.drinkool.filters;
|
||||||
|
|
||||||
|
import com.drinkool.InternalValue;
|
||||||
|
import io.smallrye.mutiny.Uni;
|
||||||
|
import jakarta.ws.rs.Priorities;
|
||||||
|
import jakarta.ws.rs.container.*;
|
||||||
|
import jakarta.ws.rs.core.*;
|
||||||
|
import java.util.*;
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
import org.jboss.resteasy.reactive.server.ServerRequestFilter;
|
||||||
|
import org.jboss.resteasy.reactive.server.ServerResponseFilter;
|
||||||
|
import org.jose4j.jws.AlgorithmIdentifiers;
|
||||||
|
import org.jose4j.jws.JsonWebSignature;
|
||||||
|
import org.jose4j.jwt.JwtClaims;
|
||||||
|
import org.jose4j.jwt.consumer.JwtConsumer;
|
||||||
|
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
|
||||||
|
import org.jose4j.keys.HmacKey;
|
||||||
|
|
||||||
|
public class InternalHeaderGatewayFilter {
|
||||||
|
|
||||||
|
@ConfigProperty(name = "gateway.jwt.secret")
|
||||||
|
String SECRET_KEY;
|
||||||
|
|
||||||
|
private boolean isSystemClaim(String key) {
|
||||||
|
return List.of("iss", "sub", "aud", "exp", "nbf", "iat", "jti").contains(
|
||||||
|
key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServerRequestFilter(preMatching = true)
|
||||||
|
public Uni<Response> handleRequestHeaders(
|
||||||
|
ContainerRequestContext requestContext
|
||||||
|
) {
|
||||||
|
requestContext
|
||||||
|
.getHeaders()
|
||||||
|
.keySet()
|
||||||
|
.removeIf(
|
||||||
|
key ->
|
||||||
|
key.equalsIgnoreCase(InternalValue.headerId) ||
|
||||||
|
key.toLowerCase().startsWith(InternalValue.headerId.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, Cookie> cookies = requestContext.getCookies();
|
||||||
|
Cookie authCookie = cookies.get(InternalValue.cookieName);
|
||||||
|
|
||||||
|
if (authCookie == null) return Uni.createFrom().nullItem();
|
||||||
|
|
||||||
|
return Uni.createFrom().item(() -> {
|
||||||
|
try {
|
||||||
|
String token = authCookie.getValue();
|
||||||
|
|
||||||
|
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
|
||||||
|
.setRequireExpirationTime()
|
||||||
|
.setVerificationKey(new HmacKey(SECRET_KEY.getBytes()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JwtClaims claims = jwtConsumer.processToClaims(token);
|
||||||
|
|
||||||
|
Map<String, Object> allClaims = claims.getClaimsMap();
|
||||||
|
|
||||||
|
allClaims.forEach((key, value) -> {
|
||||||
|
if (!isSystemClaim(key) && value != null) {
|
||||||
|
String headerName = InternalValue.headerId + (key);
|
||||||
|
|
||||||
|
requestContext.getHeaders().add(headerName, value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Response.status(Response.Status.UNAUTHORIZED)
|
||||||
|
.entity("InvalidToken")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServerResponseFilter(priority = Priorities.USER + 100)
|
||||||
|
public void handleResponseHeaders(ContainerResponseContext responseContext) {
|
||||||
|
JwtClaims claims = new JwtClaims();
|
||||||
|
boolean hasInternalData = false;
|
||||||
|
|
||||||
|
MultivaluedMap<String, Object> headers = responseContext.getHeaders();
|
||||||
|
|
||||||
|
List<String> keysToRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String key : headers.keySet()) {
|
||||||
|
if (key.toLowerCase().startsWith(InternalValue.headerId.toLowerCase())) {
|
||||||
|
Object value = headers.getFirst(key);
|
||||||
|
if (value != null) {
|
||||||
|
String claimKey = key.substring(InternalValue.headerId.length());
|
||||||
|
claims.setClaim(claimKey, value.toString());
|
||||||
|
hasInternalData = true;
|
||||||
|
}
|
||||||
|
keysToRemove.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String key : keysToRemove) {
|
||||||
|
headers.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasInternalData) {
|
||||||
|
try {
|
||||||
|
claims.setExpirationTimeMinutesInTheFuture(60);
|
||||||
|
claims.setGeneratedJwtId();
|
||||||
|
claims.setIssuedAtToNow();
|
||||||
|
|
||||||
|
JsonWebSignature jws = new JsonWebSignature();
|
||||||
|
jws.setPayload(claims.toJson());
|
||||||
|
jws.setKey(new HmacKey(SECRET_KEY.getBytes()));
|
||||||
|
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
|
||||||
|
|
||||||
|
String jwt = jws.getCompactSerialization();
|
||||||
|
|
||||||
|
NewCookie jwtCookie = new NewCookie.Builder(InternalValue.cookieName)
|
||||||
|
.value(jwt)
|
||||||
|
.path("/")
|
||||||
|
.httpOnly(true)
|
||||||
|
.secure(true)
|
||||||
|
.sameSite(NewCookie.SameSite.STRICT)
|
||||||
|
.maxAge(3600)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
headers.add(HttpHeaders.SET_COOKIE, jwtCookie);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.drinkool.services;
|
package com.drinkool.services;
|
||||||
|
|
||||||
|
import com.drinkool.Role;
|
||||||
import com.drinkool.Url;
|
import com.drinkool.Url;
|
||||||
import com.drinkool.dtos.*;
|
import com.drinkool.dtos.*;
|
||||||
import io.smallrye.mutiny.Uni;
|
import io.smallrye.mutiny.Uni;
|
||||||
@@ -8,7 +9,7 @@ import jakarta.ws.rs.core.Response;
|
|||||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||||
|
|
||||||
@RegisterRestClient(configKey = "account")
|
@RegisterRestClient(configKey = "account")
|
||||||
@Path("")
|
@Path(Role.Customer)
|
||||||
public interface AccountService {
|
public interface AccountService {
|
||||||
@POST
|
@POST
|
||||||
@Path(Url.Signup)
|
@Path(Url.Signup)
|
||||||
@@ -29,4 +30,7 @@ public interface AccountService {
|
|||||||
@POST
|
@POST
|
||||||
@Path(Url.SmsOtp)
|
@Path(Url.SmsOtp)
|
||||||
Uni<Response> smsOtp(SmsOtp input);
|
Uni<Response> smsOtp(SmsOtp input);
|
||||||
|
|
||||||
|
@GET
|
||||||
|
Uni<Response> me();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,8 @@ quarkus.container-image.name=gateway-service
|
|||||||
quarkus.container-image.push=true
|
quarkus.container-image.push=true
|
||||||
quarkus.container-image.build=true
|
quarkus.container-image.build=true
|
||||||
quarkus.container-image.builder=docker
|
quarkus.container-image.builder=docker
|
||||||
|
quarkus.container-image.additional-tags=latest
|
||||||
|
|
||||||
|
# Build
|
||||||
|
quarkus.native.container-build=false
|
||||||
|
quarkus.native.additional-build-args=--future-defaults=all
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
package com.drinkool.filters;
|
||||||
|
|
||||||
|
import static io.restassured.RestAssured.*;
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import com.drinkool.InternalValue;
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import io.restassured.response.Response;
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
import org.jose4j.jws.AlgorithmIdentifiers;
|
||||||
|
import org.jose4j.jws.JsonWebSignature;
|
||||||
|
import org.jose4j.jwt.JwtClaims;
|
||||||
|
import org.jose4j.keys.HmacKey;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
public class InternalHeaderGatewayFilterTest {
|
||||||
|
|
||||||
|
@ConfigProperty(name = "gateway.jwt.secret")
|
||||||
|
String TEST_SECRET;
|
||||||
|
|
||||||
|
private String createTestToken(String userId) throws Exception {
|
||||||
|
JwtClaims claims = new JwtClaims();
|
||||||
|
claims.setExpirationTimeMinutesInTheFuture(10);
|
||||||
|
claims.setClaim("UserId", userId);
|
||||||
|
claims.setClaim("Role", "user");
|
||||||
|
|
||||||
|
JsonWebSignature jws = new JsonWebSignature();
|
||||||
|
jws.setPayload(claims.toJson());
|
||||||
|
jws.setKey(new HmacKey(TEST_SECRET.getBytes()));
|
||||||
|
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
|
||||||
|
return jws.getCompactSerialization();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Nên xóa header X-Internal nếu client tự ý gửi lên")
|
||||||
|
public void testShouldRemoveClentInternalHeaders() {
|
||||||
|
given()
|
||||||
|
.header("X-Internal-userId", "hacker-id")
|
||||||
|
.when()
|
||||||
|
.get("/test-gateway")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body(is("not-found")); // Filter phải xóa sạch trước khi vào Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Nên trích xuất JWT từ Cookie và gắn vào Header nội bộ")
|
||||||
|
public void testValidJwtInCookie() throws Exception {
|
||||||
|
String token = createTestToken("user123");
|
||||||
|
|
||||||
|
given()
|
||||||
|
.cookie(InternalValue.cookieName, token)
|
||||||
|
.when()
|
||||||
|
.get("/test-gateway")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body(is("user123")); // Resource nhận được userId từ Header do Filter gắn vào
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Nên trả về 401 nếu JWT sai chữ ký")
|
||||||
|
public void testInvalidSignature() {
|
||||||
|
given()
|
||||||
|
.cookie(InternalValue.cookieName, "invalid.token.string")
|
||||||
|
.when()
|
||||||
|
.get("/test-gateway")
|
||||||
|
.then()
|
||||||
|
.statusCode(401)
|
||||||
|
.body(containsString("InvalidToken"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName(
|
||||||
|
"Nên đóng gói X-Internal headers từ service thành JWT gửi về client"
|
||||||
|
)
|
||||||
|
public void testResponseFilterConvertsHeadersToJwt() throws Exception {
|
||||||
|
String token = createTestToken("user123");
|
||||||
|
|
||||||
|
Response response = given()
|
||||||
|
.cookie(InternalValue.cookieName, token)
|
||||||
|
.when()
|
||||||
|
.post("/test-gateway/update")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.cookie(InternalValue.cookieName) // Kiểm tra có Set-Cookie trả về không
|
||||||
|
.header("X-Internal-newStatus", nullValue()) // Header nội bộ phải bị xóa khỏi response
|
||||||
|
.extract()
|
||||||
|
.response();
|
||||||
|
|
||||||
|
String setCookie = response.getHeader("Set-Cookie");
|
||||||
|
assertTrue(setCookie.contains("HttpOnly"));
|
||||||
|
assertTrue(setCookie.contains("SameSite=Strict"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.drinkool.utils;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.*;
|
||||||
|
import jakarta.ws.rs.core.Context;
|
||||||
|
import jakarta.ws.rs.core.HttpHeaders;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
|
@Path("/test-gateway")
|
||||||
|
public class TestResource {
|
||||||
|
|
||||||
|
@GET
|
||||||
|
public Response getHeaders(@Context HttpHeaders headers) {
|
||||||
|
// Trả về header nội bộ để chúng ta kiểm tra xem Filter có gắn đúng không
|
||||||
|
String userId = headers.getHeaderString("X-Internal-userId");
|
||||||
|
return Response.ok().entity(userId != null ? userId : "not-found").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/update")
|
||||||
|
public Response updateInternal(
|
||||||
|
@HeaderParam("X-Internal-userId") String userId
|
||||||
|
) {
|
||||||
|
// Giả lập service trả về header nội bộ để Gateway đóng gói vào JWT
|
||||||
|
return Response.ok("updated")
|
||||||
|
.header("X-Internal-newStatus", "active")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Quarkus
|
||||||
|
quarkus.http.host=0.0.0.0
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
gateway.jwt.secret=1uZk07Hqu1316z9YqrcSGPTTJDhMWU1ZhFSLBrDQvmU=
|
||||||
@@ -2,25 +2,48 @@
|
|||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: account-service
|
name: account-deploy
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: account-service
|
app: account
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: account-service
|
app: account
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: account-service
|
- name: account-pod
|
||||||
image: git.demonkernel.io.vn/foodsurf/account-service:latest
|
image: git.demonkernel.io.vn/foodsurf/account-service:1.5.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
env:
|
env:
|
||||||
- name: QUARKUS_REDIS_HOST
|
- name: QUARKUS_REDIS_HOST
|
||||||
value: "redis-service"
|
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:
|
||||||
|
requests:
|
||||||
|
memory: "32Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "250m"
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
@@ -28,12 +51,68 @@ metadata:
|
|||||||
name: account-service
|
name: account-service
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
app: account-service
|
app: account
|
||||||
ports:
|
ports:
|
||||||
- port: 80
|
- port: 80
|
||||||
targetPort: 8080
|
targetPort: 8080
|
||||||
---
|
---
|
||||||
# 2. GATEWAY SERVICE
|
# 2. EATERY SERVICE
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: eatery-deploy
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: eatery
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: eatery
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: eatery-pod
|
||||||
|
image: git.demonkernel.io.vn/foodsurf/eatery-service:1.5.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
env:
|
||||||
|
- 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:
|
||||||
|
requests:
|
||||||
|
memory: "32Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: eatery-service
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: eatery
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
---
|
||||||
|
# 3. GATEWAY SERVICE
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -49,13 +128,26 @@ spec:
|
|||||||
app: gateway
|
app: gateway
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: gateway
|
- name: gateway-pod
|
||||||
image: git.demonkernel.io.vn/foodsurf/gateway-service:latest
|
image: git.demonkernel.io.vn/foodsurf/gateway-service:1.5.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
env:
|
env:
|
||||||
- name: QUARKUS_REST_CLIENT_ACCOUNT_URL
|
- name: QUARKUS_REST_CLIENT_ACCOUNT_URL
|
||||||
value: "http://account-service"
|
value: "http://account-service"
|
||||||
|
- name: GATEWAY_JWT_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: jwt-secret
|
||||||
|
key: secret
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "32Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "250m"
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
@@ -68,9 +160,9 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- port: 80
|
- port: 80
|
||||||
targetPort: 8080
|
targetPort: 8080
|
||||||
nodePort: 39080
|
nodePort: 32080
|
||||||
---
|
---
|
||||||
# 3. REDIS
|
# 4. REDIS
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -85,25 +177,25 @@ spec:
|
|||||||
app: redis
|
app: redis
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: redis
|
- name: redis-pod
|
||||||
image: redis:alpine
|
image: redis:alpine
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 6379
|
- containerPort: 6379
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpu: "100m"
|
cpu: "100m"
|
||||||
memory: "128Mi"
|
memory: "128Mi"
|
||||||
requests:
|
requests:
|
||||||
cpu: "20m"
|
cpu: "20m"
|
||||||
memory: "64Mi"
|
memory: "64Mi"
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"redis-server",
|
"redis-server",
|
||||||
"--maxmemory",
|
"--maxmemory",
|
||||||
"96mb",
|
"96mb",
|
||||||
"--maxmemory-policy",
|
"--maxmemory-policy",
|
||||||
"allkeys-lru",
|
"allkeys-lru",
|
||||||
]
|
]
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
|||||||
+3
-3
@@ -9,14 +9,14 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.drinkool</groupId>
|
<groupId>com.drinkool</groupId>
|
||||||
<artifactId>drinkool</artifactId>
|
<artifactId>drinkool</artifactId>
|
||||||
<version>1.1.0-dev.1</version>
|
<version>1.5.0</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>lib</artifactId>
|
<artifactId>lib</artifactId>
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>25</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>25</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.drinkool;
|
||||||
|
|
||||||
|
public class InternalValue {
|
||||||
|
|
||||||
|
public static final String cookieName = "auth";
|
||||||
|
public static final String headerId = "X-Internal-";
|
||||||
|
public static final String userId = headerId + "UserId";
|
||||||
|
public static final String role = headerId + "Role";
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.drinkool;
|
||||||
|
|
||||||
|
public class Role {
|
||||||
|
|
||||||
|
public static final String Customer = "customer";
|
||||||
|
public static final String Manager = "manager";
|
||||||
|
}
|
||||||
@@ -7,4 +7,5 @@ public class Url {
|
|||||||
public static final String Login = "/login";
|
public static final String Login = "/login";
|
||||||
public static final String QuickLogin = "/quick_login";
|
public static final String QuickLogin = "/quick_login";
|
||||||
public static final String SmsOtp = "/sms_otp";
|
public static final String SmsOtp = "/sms_otp";
|
||||||
|
public static final String Me = "/me";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.drinkool.dtos;
|
||||||
|
|
||||||
|
public class ManagerSignup extends Signup {
|
||||||
|
|
||||||
|
public String eateryName;
|
||||||
|
}
|
||||||
@@ -13,13 +13,13 @@ public class ReviewModel extends BaseEntity {
|
|||||||
@Column
|
@Column
|
||||||
public String comment;
|
public String comment;
|
||||||
|
|
||||||
public ReviewModel() {}
|
|
||||||
|
|
||||||
public ReviewModel(int rating, String comment) {
|
public ReviewModel(int rating, String comment) {
|
||||||
this.rating = rating;
|
this.rating = rating;
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReviewModel() {}
|
||||||
|
|
||||||
public ReviewModel(ReviewModel input) {
|
public ReviewModel(ReviewModel input) {
|
||||||
this(input.rating, input.comment);
|
this(input.rating, input.comment);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.drinkool</groupId>
|
<groupId>com.drinkool</groupId>
|
||||||
<artifactId>drinkool</artifactId>
|
<artifactId>drinkool</artifactId>
|
||||||
<version>1.1.0-dev.1</version>
|
<version>1.5.0</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>25</maven.compiler.source>
|
||||||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
<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.3</quarkus.platform.version>
|
<quarkus.platform.version>3.32.3</quarkus.platform.version>
|
||||||
@@ -30,6 +30,29 @@
|
|||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-jacoco</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
<version>${quarkus.platform.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<quarkus.jacoco.data-file
|
||||||
|
>${maven.multiModuleProjectDirectory}/target/jacoco.exec</quarkus.jacoco.data-file>
|
||||||
|
<quarkus.jacoco.reuse-data-file
|
||||||
|
>true</quarkus.jacoco.reuse-data-file>
|
||||||
|
<quarkus.jacoco.report-location
|
||||||
|
>${maven.multiModuleProjectDirectory}/target/coverage</quarkus.jacoco.report-location>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
Reference in New Issue
Block a user