From 837f5f595a3b1c5ea5e261f60f265834081071ea Mon Sep 17 00:00:00 2001 From: TakahashiNguyen Date: Wed, 8 Apr 2026 08:24:00 +0000 Subject: [PATCH] feat: init k8s and customer service (#15) Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com> Reviewed-on: https://git.demonkernel.io.vn/FoodSurf/backend/pulls/15 --- .devcontainer/Dockerfile | 44 +++++++-- .devcontainer/devcontainer.json | 5 +- .gitea/workflows/test.yaml | 15 ++- .mvn/wrapper/maven-wrapper.properties | 2 +- .releaserc.json | 2 +- account-service/pom.xml | 2 +- .../java/com/drinkool/models/UserModel.java | 2 + .../services/AuthenticationService.java | 55 ++++++++--- .../com/drinkool/services/UserService.java | 24 +++++ .../src/main/resources/application.properties | 1 + .../services/AuthenticationServiceTest.java | 26 ++++-- .../drinkool/services/UserServiceTest.java | 50 ++++++++++ eatery-service/pom.xml | 2 +- gateway-service/pom.xml | 43 +++------ .../src/main/resources/application.properties | 4 + k8s.yaml | 93 ++++++++++++++----- lib/pom.xml | 4 +- lib/src/main/java/com/drinkool/Jwt.java | 7 ++ lib/src/main/java/com/drinkool/Role.java | 5 + lib/src/main/java/com/drinkool/Url.java | 1 + pom.xml | 2 +- 21 files changed, 297 insertions(+), 92 deletions(-) create mode 100644 account-service/src/main/java/com/drinkool/services/UserService.java create mode 100644 account-service/src/test/java/com/drinkool/services/UserServiceTest.java create mode 100644 lib/src/main/java/com/drinkool/Jwt.java create mode 100644 lib/src/main/java/com/drinkool/Role.java diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 8ab1008..2cc40b7 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,18 +1,44 @@ -FROM alpine:3.22 +FROM ubuntu:26.04 -RUN apk update && apk add --no-cache \ +# Tránh các câu hỏi tương tác trong quá trình cài đặt +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y ca-certificates && apt-get clean + +RUN sed -i 's|http://archive.ubuntu.com|https://apt.demonkernel.io.vn|g' /etc/apt/sources.list.d/ubuntu.sources && \ + sed -i 's|http://security.ubuntu.com|https://apt.demonkernel.io.vn|g' /etc/apt/sources.list.d/ubuntu.sources + +# 1. Cài đặt các công cụ cơ bản +RUN apt-get update --fix-missing && apt-get install --no-install-recommends --no-install-suggests -y \ curl \ bash \ - openjdk21-jdk \ - maven \ + build-essential \ + docker-buildx \ git \ - ca-certificates \ - pnpm - -# git trust all directories + docker.io \ + maven \ + libz-dev + +# 5. Cấu hình Git RUN git config --global --add safe.directory '*' -# Install quarkus +# 2. Cài đặt Node.js & PNPM (thay thế cho apk pnpm) +RUN curl -fsSL https://get.pnpm.io/install.sh | bash - + +# 3. Cài đặt Kubectl +RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \ + && install -m 0755 kubectl /usr/local/bin/kubectl + +# 4. Cài đặt Mandrel 25 +# Lưu ý: Dùng bản linux-amd64 (glibc) tiêu chuẩn cho Ubuntu +ENV JAVA_HOME=/usr/lib/jvm/mandrel +ENV GRAALVM_HOME=/usr/lib/jvm/mandrel +ENV PATH="${JAVA_HOME}/bin:${PATH}" + +RUN mkdir -p ${JAVA_HOME} && \ + curl -L https://github.com/graalvm/mandrel/releases/download/mandrel-25.0.2.0-Final/mandrel-java25-linux-amd64-25.0.2.0-Final.tar.gz | tar -xz -C ${JAVA_HOME} --strip-components=1 + +# 6. Cài đặt JBang & Quarkus CLI ENV JBANG_DIR="/root/.jbang" ENV PATH="${JBANG_DIR}/bin:${PATH}" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7abd761..56dee5e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,8 @@ { "dockerComposeFile": "docker-compose.main.yaml", "mounts": [ - "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" + "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind", + "source=${localEnv:USERPROFILE}/.kube,target=/root/.kube,type=bind" ], "remoteUser": "root", "service": "devcontainer", @@ -24,5 +25,5 @@ "TESTCONTAINERS_RYUK_DISABLED": "true", "QUARKUS_DEBUG_HOST": "0.0.0.0" }, - "forwardPorts": [5005] + "forwardPorts": [5005, 8080] } diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml index 0c20b6c..6a3bcb7 100644 --- a/.gitea/workflows/test.yaml +++ b/.gitea/workflows/test.yaml @@ -6,7 +6,7 @@ on: - main concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: @@ -25,8 +25,13 @@ jobs: - name: Set up JDK uses: actions/setup-java@v5 with: - distribution: "temurin" - java-version: "21" + distribution: "graalvm" + java-version: "25" + cache: "maven" + + - uses: gerlero/apt-install@v1 + with: + packages: build-essential zlib1g-dev docker.io - name: Cache Maven packages uses: actions/cache@v4 @@ -37,7 +42,9 @@ jobs: ${{ runner.os }}-maven- - 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 uses: scacap/action-surefire-report@v1 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 533e775..5447d62 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,4 +1,4 @@ wrapperVersion=3.3.4 distributionType=only-script distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip -distributionSha256Sum=305773a68d6ddfd413df58c82b3f8050e89778e777f3a745c8e5b8cbea4018ef +distributionSha256Sum= diff --git a/.releaserc.json b/.releaserc.json index ce2856c..ed8c233 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -14,7 +14,7 @@ [ "@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" } ], diff --git a/account-service/pom.xml b/account-service/pom.xml index 55972ca..cc568fd 100644 --- a/account-service/pom.xml +++ b/account-service/pom.xml @@ -18,7 +18,7 @@ 3.15.0 - 21 + 25 UTF-8 UTF-8 diff --git a/account-service/src/main/java/com/drinkool/models/UserModel.java b/account-service/src/main/java/com/drinkool/models/UserModel.java index 078614d..ef59493 100644 --- a/account-service/src/main/java/com/drinkool/models/UserModel.java +++ b/account-service/src/main/java/com/drinkool/models/UserModel.java @@ -2,6 +2,7 @@ package com.drinkool.models; import com.drinkool.entities.BaseEntity; import com.drinkool.utils.PhoneValidator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.password4j.Password; import jakarta.persistence.*; import jakarta.transaction.*; @@ -20,6 +21,7 @@ public abstract class UserModel extends BaseEntity { public String phone; @Column(nullable = false, length = 1024) + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) public String hashedPassword; @Transactional diff --git a/account-service/src/main/java/com/drinkool/services/AuthenticationService.java b/account-service/src/main/java/com/drinkool/services/AuthenticationService.java index 70f0bd0..1dde771 100644 --- a/account-service/src/main/java/com/drinkool/services/AuthenticationService.java +++ b/account-service/src/main/java/com/drinkool/services/AuthenticationService.java @@ -1,5 +1,7 @@ package com.drinkool.services; +import com.drinkool.Jwt; +import com.drinkool.Role; import com.drinkool.Url; import com.drinkool.dtos.*; import com.drinkool.entities.CustomerEntity; @@ -22,7 +24,10 @@ public class AuthenticationService { newCustomer.signup(input.name, input.phone, input.password); - return Response.status(Response.Status.CREATED).build(); + return Response.status(Response.Status.CREATED) + .header(Jwt.userId, newCustomer.id.toString()) + .header(Jwt.role, Role.Customer) + .build(); } @POST @@ -33,7 +38,10 @@ public class AuthenticationService { newCustomer.signup(input.phone); - return Response.status(Response.Status.CREATED).build(); + return Response.status(Response.Status.CREATED) + .header(Jwt.userId, newCustomer.id.toString()) + .header(Jwt.role, Role.Customer) + .build(); } @POST @@ -44,29 +52,54 @@ public class AuthenticationService { input.phone ).firstResult(); - if (customer == null) throw new BadRequestException("InvalidPhoneNumber"); + if (customer == null) return Response.status(Response.Status.BAD_REQUEST) + .entity("InvalidPhoneNumber") + .build(); - if (!customer.login(input.password)) throw new BadRequestException( - "InvalidPassword" - ); + if (!customer.login(input.password)) return Response.status( + Response.Status.BAD_REQUEST + ) + .entity("InvalidPassword") + .build(); - return Response.accepted().build(); + return Response.accepted() + .header(Jwt.userId, customer.id.toString()) + .header(Jwt.role, Role.Customer) + .build(); } @POST @Path(Url.QuickLogin) public Response quickLogin(QuickLogin input) { - if (otpService.verifyOtp(input.phone, input.otp)) { - return Response.accepted().build(); + if (!otpService.verifyOtp(input.phone, input.otp)) { + return Response.status(Response.Status.UNAUTHORIZED) + .entity("InvalidOTP") + .build(); } - return Response.status(Response.Status.UNAUTHORIZED) - .entity("InvalidOTP") + + CustomerEntity customer = CustomerEntity.find( + "phone", + input.phone + ).firstResult(); + + return Response.accepted() + .header(Jwt.userId, customer.id.toString()) + .header(Jwt.role, Role.Customer) .build(); } @POST @Path(Url.SmsOtp) public Response smsOtp(SmsOtp input) { + CustomerEntity customer = CustomerEntity.find( + "phone", + input.phone + ).firstResult(); + + if (customer == null) return Response.status(Response.Status.BAD_REQUEST) + .entity("InvalidPhoneNumber") + .build(); + String otp = otpService.generateAndSaveOtp(input.phone); // Ở đây bạn sẽ gọi Kafka để gửi SMS thực tế diff --git a/account-service/src/main/java/com/drinkool/services/UserService.java b/account-service/src/main/java/com/drinkool/services/UserService.java new file mode 100644 index 0000000..bcb8cb0 --- /dev/null +++ b/account-service/src/main/java/com/drinkool/services/UserService.java @@ -0,0 +1,24 @@ +package com.drinkool.services; + +import com.drinkool.Jwt; +import com.drinkool.Url; +import com.drinkool.models.UserModel; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.Response; +import java.util.UUID; + +@Path("") +public class UserService { + + @POST + @Path(Url.Me) + @Transactional + public Response me(@HeaderParam(Jwt.userId) UUID id) { + if (id == null) return Response.status(Response.Status.BAD_REQUEST) + .entity("InvalidUserId") + .build(); + + return Response.ok(UserModel.find("id", id).firstResult()).build(); + } +} diff --git a/account-service/src/main/resources/application.properties b/account-service/src/main/resources/application.properties index 07c1a35..ade9388 100644 --- a/account-service/src/main/resources/application.properties +++ b/account-service/src/main/resources/application.properties @@ -13,6 +13,7 @@ quarkus.container-image.name=account-service quarkus.container-image.push=true quarkus.container-image.build=true quarkus.container-image.builder=docker +quarkus.container-image.additional-tags=latest # Redis quarkus.redis.hosts=redis://localhost:6379 diff --git a/account-service/src/test/java/com/drinkool/services/AuthenticationServiceTest.java b/account-service/src/test/java/com/drinkool/services/AuthenticationServiceTest.java index 4925a41..61d5e50 100644 --- a/account-service/src/test/java/com/drinkool/services/AuthenticationServiceTest.java +++ b/account-service/src/test/java/com/drinkool/services/AuthenticationServiceTest.java @@ -1,8 +1,11 @@ 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 com.drinkool.Jwt; +import com.drinkool.Role; import com.drinkool.Url; import com.drinkool.dtos.*; import com.drinkool.entities.CustomerEntity; @@ -14,9 +17,9 @@ import org.junit.jupiter.api.Test; @QuarkusTest public class AuthenticationServiceTest { - private Random random = new Random(); + public static Random random = new Random(); - private String generateRandomPhone() { + public static String generateRandomPhone() { return "+8477" + String.format("%07d", random.nextInt(10000000)); } @@ -52,6 +55,8 @@ public class AuthenticationServiceTest { .when() .post(Url.QuickSignup) .then() + .header(Jwt.userId, notNullValue()) + .header(Jwt.role, is(Role.Customer)) .statusCode(201); assertEquals(1, CustomerEntity.find("phone", phone).count()); @@ -121,6 +126,18 @@ public class AuthenticationServiceTest { SmsOtp input = new SmsOtp(); input.phone = phone; + Signup signup = new Signup(); + signup.phone = phone; + signup.name = "sms user"; + signup.password = "password123"; + + given() + .contentType(ContentType.JSON) + .body(signup) + .when() + .post(Url.Signup) + .then(); + given() .contentType(ContentType.JSON) .body(input) @@ -128,9 +145,6 @@ public class AuthenticationServiceTest { .post(Url.SmsOtp) .then() .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 diff --git a/account-service/src/test/java/com/drinkool/services/UserServiceTest.java b/account-service/src/test/java/com/drinkool/services/UserServiceTest.java new file mode 100644 index 0000000..ee8f8d9 --- /dev/null +++ b/account-service/src/test/java/com/drinkool/services/UserServiceTest.java @@ -0,0 +1,50 @@ +package com.drinkool.services; + +import static io.restassured.RestAssured.*; +import static org.hamcrest.CoreMatchers.*; + +import com.drinkool.Jwt; +import com.drinkool.Url; +import com.drinkool.dtos.QuickSignup; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.Test; + +@QuarkusTest +public class UserServiceTest { + + @Test + public void testMeEndpointWithHeader() { + String phone = AuthenticationServiceTest.generateRandomPhone(); + QuickSignup input = new QuickSignup(); + input.phone = phone; + + String testUserId = given() + .contentType(ContentType.JSON) + .body(input) + .when() + .post(Url.QuickSignup) + .then() + .extract() + .header(Jwt.userId); + + given() + .header(Jwt.userId, testUserId) + .contentType(ContentType.JSON) + .when() + .post(Url.Me) + .then() + .statusCode(200) + .body("id", is(testUserId)); + } + + @Test + public void testMeEndpointWithoutHeader() { + given() + .when() + .post(Url.Me) + .then() + .statusCode(400); + } +} diff --git a/eatery-service/pom.xml b/eatery-service/pom.xml index 2304bdb..a9583e7 100644 --- a/eatery-service/pom.xml +++ b/eatery-service/pom.xml @@ -18,7 +18,7 @@ 3.15.0 - 21 + 25 UTF-8 UTF-8 diff --git a/gateway-service/pom.xml b/gateway-service/pom.xml index df5aec2..f76a022 100644 --- a/gateway-service/pom.xml +++ b/gateway-service/pom.xml @@ -1,8 +1,8 @@ - + 4.0.0 @@ -17,14 +17,15 @@ quarkus + false + false + true 3.15.0 - 21 + 25 UTF-8 - UTF-8 + UTF-8 quarkus-bom - io.quarkus.platform + io.quarkus.platform 3.32.4 true 3.5.4 @@ -101,8 +102,7 @@ @{argLine} - org.jboss.logmanager.LogManager + org.jboss.logmanager.LogManager ${maven.home} @@ -123,28 +123,11 @@ ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager + org.jboss.logmanager.LogManager ${maven.home} - - - - native - - - native - - - - false - false - true - - - - + \ No newline at end of file diff --git a/gateway-service/src/main/resources/application.properties b/gateway-service/src/main/resources/application.properties index c52eb42..43aa152 100644 --- a/gateway-service/src/main/resources/application.properties +++ b/gateway-service/src/main/resources/application.properties @@ -5,3 +5,7 @@ quarkus.container-image.name=gateway-service quarkus.container-image.push=true quarkus.container-image.build=true quarkus.container-image.builder=docker +quarkus.container-image.additional-tags=latest + +# Build +quarkus.native.container-build=false diff --git a/k8s.yaml b/k8s.yaml index 4487135..93864c5 100644 --- a/k8s.yaml +++ b/k8s.yaml @@ -2,25 +2,56 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: account-service + name: account-deploy spec: replicas: 1 selector: matchLabels: - app: account-service + app: account template: metadata: labels: - app: account-service + app: account spec: containers: - - name: account-service + - name: account-pod image: git.demonkernel.io.vn/foodsurf/account-service:latest + imagePullPolicy: IfNotPresent ports: - containerPort: 8080 env: - name: QUARKUS_REDIS_HOST value: "redis-service" + - name: QUARKUS_DATASOURCE_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-credentials + key: password + - name: QUARKUS_DATASOURCE_USERNAME + valueFrom: + secretKeyRef: + name: postgres-credentials + key: username + - name: QUARKUS_DATASOURCE_JDBC_URL + valueFrom: + secretKeyRef: + name: postgres-credentials + key: url + resources: + limits: + cpu: "500m" + memory: "512Mi" + requests: + cpu: "100m" + memory: "256Mi" + volumeMounts: + - name: keys-volume + mountPath: "/etc/keys" + readOnly: true + volumes: + - name: keys-volume + secret: + secretName: jwt-keys --- apiVersion: v1 kind: Service @@ -28,7 +59,7 @@ metadata: name: account-service spec: selector: - app: account-service + app: account ports: - port: 80 targetPort: 8080 @@ -49,13 +80,29 @@ spec: app: gateway spec: containers: - - name: gateway + - name: gateway-pod image: git.demonkernel.io.vn/foodsurf/gateway-service:latest + imagePullPolicy: IfNotPresent ports: - containerPort: 8080 env: - name: QUARKUS_REST_CLIENT_ACCOUNT_URL value: "http://account-service" + resources: + limits: + cpu: "500m" + memory: "512Mi" + requests: + cpu: "100m" + memory: "256Mi" + volumeMounts: + - name: keys-volume + mountPath: "/etc/keys" + readOnly: true + volumes: + - name: keys-volume + secret: + secretName: jwt-keys --- apiVersion: v1 kind: Service @@ -68,7 +115,7 @@ spec: ports: - port: 80 targetPort: 8080 - nodePort: 39080 + nodePort: 32080 --- # 3. REDIS apiVersion: apps/v1 @@ -85,25 +132,25 @@ spec: app: redis spec: containers: - - name: redis + - name: redis-pod image: redis:alpine ports: - containerPort: 6379 - resources: - limits: - cpu: "100m" - memory: "128Mi" - requests: - cpu: "20m" - memory: "64Mi" - command: - [ - "redis-server", - "--maxmemory", - "96mb", - "--maxmemory-policy", - "allkeys-lru", - ] + resources: + limits: + cpu: "100m" + memory: "128Mi" + requests: + cpu: "20m" + memory: "64Mi" + command: + [ + "redis-server", + "--maxmemory", + "96mb", + "--maxmemory-policy", + "allkeys-lru", + ] --- apiVersion: v1 kind: Service diff --git a/lib/pom.xml b/lib/pom.xml index 7ee49c2..b44cb25 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -15,8 +15,8 @@ lib - 21 - 21 + 25 + 25 UTF-8 diff --git a/lib/src/main/java/com/drinkool/Jwt.java b/lib/src/main/java/com/drinkool/Jwt.java new file mode 100644 index 0000000..9395d3f --- /dev/null +++ b/lib/src/main/java/com/drinkool/Jwt.java @@ -0,0 +1,7 @@ +package com.drinkool; + +public class Jwt { + public static final String headerId = "X-Internal-"; + public static final String userId = headerId + "UserId"; + public static final String role = headerId + "Role"; +} diff --git a/lib/src/main/java/com/drinkool/Role.java b/lib/src/main/java/com/drinkool/Role.java new file mode 100644 index 0000000..1dc5919 --- /dev/null +++ b/lib/src/main/java/com/drinkool/Role.java @@ -0,0 +1,5 @@ +package com.drinkool; + +public class Role { + public static final String Customer = "customer"; +} diff --git a/lib/src/main/java/com/drinkool/Url.java b/lib/src/main/java/com/drinkool/Url.java index 9eb1de6..8aa4a01 100644 --- a/lib/src/main/java/com/drinkool/Url.java +++ b/lib/src/main/java/com/drinkool/Url.java @@ -7,4 +7,5 @@ public class Url { public static final String Login = "/login"; public static final String QuickLogin = "/quick_login"; public static final String SmsOtp = "/sms_otp"; + public static final String Me = "/me"; } diff --git a/pom.xml b/pom.xml index 35ac368..60488c4 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 1.1.0-dev.1 pom - 21 + 25 quarkus-bom io.quarkus.platform 3.32.3