diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 12d0c67..71702aa 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -26,7 +26,8 @@ RUN apt-get update --fix-missing && apt-get install --no-install-recommends --no
kubectl \
openjdk-25-jdk-headless \
redis-server \
- maven
+ maven \
+ python3-venv
RUN jbang trust add https://repo1.maven.org/maven2/io/quarkus/quarkus-cli/
RUN jbang app install --fresh --force quarkus@quarkusio
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index e44a899..b76a75a 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -23,7 +23,9 @@
"esbenp.prettier-vscode",
"PeterSchmalfeldt.explorer-exclude",
"redhat.java",
- "ryanluker.vscode-coverage-gutters"
+ "ryanluker.vscode-coverage-gutters",
+ "ms-python.black-formatter",
+ "ms-python.python"
]
}
},
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3e30470..efcd922 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -8,7 +8,8 @@
"**/target": true,
"**/docker": true,
"node_modules": true,
- "**/.mvn": true
+ "**/.mvn": true,
+ "**/venv": true
},
"explorerExclude.backup": {},
"java.compile.nullAnalysis.mode": "automatic",
diff --git a/cart-service/pom.xml b/cart-service/pom.xml
index febe854..d659d3f 100644
--- a/cart-service/pom.xml
+++ b/cart-service/pom.xml
@@ -111,16 +111,6 @@
quarkus-maven-plugin
${quarkus.platform.version}
true
-
-
-
- build
- generate-code
- generate-code-tests
- native-image-agent
-
-
-
maven-compiler-plugin
diff --git a/cart-service/src/main/docker/Dockerfile.jvm b/cart-service/src/main/docker/Dockerfile.jvm
index cc8756b..0936d37 100644
--- a/cart-service/src/main/docker/Dockerfile.jvm
+++ b/cart-service/src/main/docker/Dockerfile.jvm
@@ -80,7 +80,7 @@
# You can find more information about the UBI base runtime images and their configuration here:
# https://rh-openjdk.github.io/redhat-openjdk-containers/
###
-FROM registry.access.redhat.com/ubi8/openjdk-21:1.23
+FROM registry.access.redhat.com/ubi9/openjdk-25:1.24
ENV LANGUAGE='en_US:en'
diff --git a/cart-service/src/main/docker/Dockerfile.legacy-jar b/cart-service/src/main/docker/Dockerfile.legacy-jar
index bbabecc..0b34a70 100644
--- a/cart-service/src/main/docker/Dockerfile.legacy-jar
+++ b/cart-service/src/main/docker/Dockerfile.legacy-jar
@@ -80,7 +80,7 @@
# You can find more information about the UBI base runtime images and their configuration here:
# https://rh-openjdk.github.io/redhat-openjdk-containers/
###
-FROM registry.access.redhat.com/ubi8/openjdk-21:1.23
+FROM registry.access.redhat.com/ubi9/openjdk-25:1.24
ENV LANGUAGE='en_US:en'
diff --git a/cart-service/src/main/docker/Dockerfile.native b/cart-service/src/main/docker/Dockerfile.native
index ceaf1ba..133e8fa 100644
--- a/cart-service/src/main/docker/Dockerfile.native
+++ b/cart-service/src/main/docker/Dockerfile.native
@@ -19,7 +19,7 @@
# The `quay.io/quarkus/quarkus-micro-image:2.0` base image is based on UBI 9.
# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`.
###
-FROM quay.io/quarkus/quarkus-micro-image:2.0
+FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
diff --git a/eatery-service/src/main/java/com/drinkool/dtos/DtoMapper.java b/eatery-service/src/main/java/com/drinkool/dtos/DtoMapper.java
index 659bec5..62456e6 100644
--- a/eatery-service/src/main/java/com/drinkool/dtos/DtoMapper.java
+++ b/eatery-service/src/main/java/com/drinkool/dtos/DtoMapper.java
@@ -3,12 +3,14 @@ package com.drinkool.dtos;
import com.drinkool.entities.MenuItemEntity;
import com.drinkool.entities.ReviewEntity;
import com.drinkool.lib.dtos.SendReview;
+import jakarta.enterprise.context.ApplicationScoped;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;
-@Mapper(componentModel = "cdi")
+@ApplicationScoped
+@Mapper(componentModel = "jakarta")
public interface DtoMapper {
@BeanMapping(
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE
diff --git a/file-service/.mvn/wrapper/maven-wrapper.properties b/file-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..ce33c1c
--- /dev/null
+++ b/file-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,4 @@
+wrapperVersion=3.3.4
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.15/apache-maven-3.9.15-bin.zip
+distributionSha256Sum=b0d9292f06c5faded31ddcb6cb69099f316d6fea40a7778624b259bad9fed18a
diff --git a/file-service/pom.xml b/file-service/pom.xml
new file mode 100644
index 0000000..96d44cc
--- /dev/null
+++ b/file-service/pom.xml
@@ -0,0 +1,102 @@
+
+
+ 4.0.0
+
+
+ com.drinkool
+ drinkool
+ 1.5.24
+ ../pom.xml
+
+
+ file-service
+
+
+ vps.demonkernel.io.vn
+ foodsurf
+ file-service
+ true
+
+
+ ${quarkus.container-image.registry}/${quarkus.container-image.group}/${quarkus.container-image.name}
+
+
+
+
+ io.quarkus
+ quarkus-container-image-docker
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+
+ docker-build-python
+ package
+
+ exec
+
+
+ docker
+ ${project.basedir}/python
+
+ build
+ -t
+ ${python.image.full.name}:${parent.version}
+ -t
+ ${python.image.full.name}:latest
+ .
+
+
+
+
+
+
+
+
+
+
+
+ push-python-image
+
+
+ quarkus.container-image.push
+ true
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ docker-push-python
+ install
+
+ exec
+
+
+ docker
+
+ push
+ ${python.image.full.name}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/file-service/python/Dockerfile b/file-service/python/Dockerfile
new file mode 100644
index 0000000..4b4333e
--- /dev/null
+++ b/file-service/python/Dockerfile
@@ -0,0 +1,32 @@
+# --- Stage 1: Build (Cài đặt thư viện) ---
+FROM python:3.9-slim AS builder
+
+WORKDIR /build
+
+# Khởi tạo môi trường ảo để tách biệt thư viện
+RUN python -m venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+COPY requirements.txt .
+RUN pip install --upgrade --no-cache-dir -r requirements.txt
+
+
+# --- Stage 2: Runtime (Chỉ chạy Prod) ---
+FROM python:3.9-slim
+
+WORKDIR /app
+
+# Chỉ copy môi trường ảo đã build xong từ Stage 1
+COPY --from=builder /opt/venv /opt/venv
+ENV PATH="/opt/venv/bin:$PATH"
+
+# Copy mã nguồn
+COPY main.py .
+
+# Tạo folder lưu ảnh và phân quyền (tăng tính bảo mật)
+RUN mkdir images && chmod 777 images
+
+# Chạy bằng Uvicorn với chế độ production (tắt reload)
+# Port 80 theo yêu cầu trước đó của bạn
+EXPOSE 80
+CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80", "--workers", "4"]
\ No newline at end of file
diff --git a/file-service/python/main.py b/file-service/python/main.py
new file mode 100644
index 0000000..3ca515d
--- /dev/null
+++ b/file-service/python/main.py
@@ -0,0 +1,52 @@
+import uuid
+import io
+from fastapi import FastAPI, Request, HTTPException
+from fastapi.responses import FileResponse
+from pathlib import Path
+from PIL import Image
+import filetype
+
+app = FastAPI()
+
+STORAGE_DIR = Path("images")
+STORAGE_DIR.mkdir(exist_ok=True)
+
+
+@app.post("/")
+async def upload_image(request: Request):
+ body = await request.body()
+
+ if not body:
+ raise HTTPException(status_code=400, detail="Dữ liệu trống")
+
+ kind = filetype.guess(body)
+ if kind is None or not kind.mime.startswith("image/"):
+ mime_type = kind.mime if kind else "không xác định"
+ raise HTTPException(
+ status_code=400, detail=f"Định dạng {mime_type} không được hỗ trợ"
+ )
+
+ try:
+ img = Image.open(io.BytesIO(body))
+
+ random_filename = f"{uuid.uuid4()}.webp"
+ file_path = STORAGE_DIR / random_filename
+
+ img.save(file_path, format="WEBP", quality=80)
+
+ return {
+ "status": "success",
+ "filename": random_filename,
+ "original_type": kind.mime,
+ }
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Lỗi xử lý ảnh: {str(e)}")
+
+
+@app.get("/{name}")
+async def get_image(name: str):
+ file_path = STORAGE_DIR / name
+ if not file_path.exists():
+ raise HTTPException(status_code=404, detail="NotFoundImage")
+ return FileResponse(file_path, media_type="image/webp")
diff --git a/file-service/python/requirements.txt b/file-service/python/requirements.txt
new file mode 100644
index 0000000..357be8c
--- /dev/null
+++ b/file-service/python/requirements.txt
@@ -0,0 +1,5 @@
+fastapi
+uvicorn
+python-multipart
+Pillow
+filetype
\ No newline at end of file
diff --git a/gateway-service/pom.xml b/gateway-service/pom.xml
index 527d8b9..c143104 100644
--- a/gateway-service/pom.xml
+++ b/gateway-service/pom.xml
@@ -151,8 +151,8 @@
@{argLine}
-
- ${project.build.directory}/${project.build.finalName}-runner
+ ${project.build.directory}/${project.build.finalName}-runner
org.jboss.logmanager.LogManager
${maven.home}
diff --git a/gateway-service/src/main/java/com/drinkool/controller/FileController.java b/gateway-service/src/main/java/com/drinkool/controller/FileController.java
new file mode 100644
index 0000000..d87ef15
--- /dev/null
+++ b/gateway-service/src/main/java/com/drinkool/controller/FileController.java
@@ -0,0 +1,32 @@
+package com.drinkool.controller;
+
+import com.drinkool.services.FileService;
+import io.smallrye.mutiny.Uni;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Response;
+import java.io.File;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.RestForm;
+
+@Path("/api/file")
+public class FileController {
+
+ @Inject
+ @RestClient
+ FileService fileService;
+
+ @POST
+ public Uni upload(@RestForm File file) {
+ return fileService.upload(file);
+ }
+
+ @GET
+ @Path("/{name}")
+ public Uni get(@PathParam("name") String name) {
+ return fileService.get(name);
+ }
+}
diff --git a/gateway-service/src/main/java/com/drinkool/controller/MainController.java b/gateway-service/src/main/java/com/drinkool/controller/MainController.java
index 7f378df..eb31be8 100644
--- a/gateway-service/src/main/java/com/drinkool/controller/MainController.java
+++ b/gateway-service/src/main/java/com/drinkool/controller/MainController.java
@@ -4,6 +4,7 @@ import com.drinkool.Url;
import com.drinkool.lib.dtos.Login;
import com.drinkool.services.CustomerService;
import com.drinkool.services.ManagerService;
+import com.drinkool.services.StaffService;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
@@ -22,6 +23,10 @@ public class MainController extends BaseController {
@RestClient
CustomerService customerService;
+ @Inject
+ @RestClient
+ StaffService staffService;
+
@POST
@Path(Url.Login)
public Uni proxyLogin(Login input) {
@@ -29,7 +34,12 @@ public class MainController extends BaseController {
.login(input)
.onFailure()
.recoverWithUni(() -> {
- return customerService.login(input);
+ return staffService
+ .login(input)
+ .onFailure()
+ .recoverWithUni(() -> {
+ return customerService.login(input);
+ });
});
}
}
diff --git a/gateway-service/src/main/java/com/drinkool/controller/StaffController.java b/gateway-service/src/main/java/com/drinkool/controller/StaffController.java
new file mode 100644
index 0000000..47fe405
--- /dev/null
+++ b/gateway-service/src/main/java/com/drinkool/controller/StaffController.java
@@ -0,0 +1,33 @@
+package com.drinkool.controller;
+
+import com.drinkool.InternalValue;
+import com.drinkool.Role;
+import com.drinkool.Url;
+import com.drinkool.lib.dtos.Signup;
+import com.drinkool.services.StaffService;
+import io.smallrye.mutiny.Uni;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Response;
+import java.util.UUID;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+@Path("/api/" + Role.Staff)
+public class StaffController {
+
+ @Inject
+ @RestClient
+ StaffService staffService;
+
+ @POST
+ @Path(Url.Signup)
+ public Uni signup(
+ Signup input,
+ @HeaderParam(InternalValue.userId) UUID userId,
+ @HeaderParam(InternalValue.role) String role
+ ) {
+ return staffService.signup(input, userId, role);
+ }
+}
diff --git a/gateway-service/src/main/java/com/drinkool/services/FileService.java b/gateway-service/src/main/java/com/drinkool/services/FileService.java
new file mode 100644
index 0000000..cd04fb7
--- /dev/null
+++ b/gateway-service/src/main/java/com/drinkool/services/FileService.java
@@ -0,0 +1,24 @@
+package com.drinkool.services;
+
+import io.smallrye.mutiny.Uni;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Response;
+
+import java.io.File;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.jboss.resteasy.reactive.RestForm;
+
+@RegisterRestClient(configKey = "file")
+@Path("/")
+public interface FileService {
+ @POST
+ Uni upload(@RestForm File file);
+
+ @GET
+ @Path("/{name}")
+ Uni get(@PathParam("name") String name);
+}
diff --git a/gateway-service/src/main/java/com/drinkool/services/StaffService.java b/gateway-service/src/main/java/com/drinkool/services/StaffService.java
new file mode 100644
index 0000000..6b6b2eb
--- /dev/null
+++ b/gateway-service/src/main/java/com/drinkool/services/StaffService.java
@@ -0,0 +1,31 @@
+package com.drinkool.services;
+
+import com.drinkool.InternalValue;
+import com.drinkool.Role;
+import com.drinkool.Url;
+import com.drinkool.lib.dtos.*;
+import io.smallrye.mutiny.Uni;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.Response;
+import java.util.*;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient(configKey = "account")
+@Path(Role.Staff)
+public interface StaffService {
+ @POST
+ @Path(Url.Signup)
+ Uni signup(
+ Signup input,
+ @HeaderParam(InternalValue.userId) UUID userId,
+ @HeaderParam(InternalValue.role) String role
+ );
+
+ @POST
+ @Path(Url.Login)
+ Uni login(Login input);
+
+ @GET
+ @Path(Url.Me)
+ Uni me(@HeaderParam(InternalValue.userId) UUID id);
+}
diff --git a/gateway-service/src/main/resources/application.properties b/gateway-service/src/main/resources/application.properties
index 9f7a285..e974aed 100644
--- a/gateway-service/src/main/resources/application.properties
+++ b/gateway-service/src/main/resources/application.properties
@@ -15,6 +15,7 @@ quarkus.native.additional-build-args=--future-defaults=all
# Rest Client
quarkus.rest-client.account.url=http://account-service
quarkus.rest-client.eatery.url=http://eatery-service
+quarkus.rest-client.file.url=http://file-service
# GraphQL Client
quarkus.smallrye-graphql-client.eatery.url=http://eatery-service/graphql
diff --git a/k8s/base/file.yaml b/k8s/base/file.yaml
new file mode 100644
index 0000000..d69d2a4
--- /dev/null
+++ b/k8s/base/file.yaml
@@ -0,0 +1,69 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: file-deploy
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: file
+ template:
+ metadata:
+ labels:
+ app: file
+ spec:
+ containers:
+ - name: file-pod
+ image: git.demonkernel.io.vn/foodsurf/file-service:1.5.24
+ imagePullPolicy: IfNotPresent
+ volumeMounts:
+ - name: file-volume
+ mountPath: /app/images
+ ports:
+ - containerPort: 8080
+ resources:
+ requests:
+ memory: "128Mi"
+ cpu: "150m"
+ limits:
+ memory: "512Mi"
+ cpu: "450m"
+ volumes:
+ - name: file-volume
+ persistentVolumeClaim:
+ claimName: file-pvc
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: file-service
+spec:
+ selector:
+ app: file
+ ports:
+ - port: 80
+ targetPort: 80
+---
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: file-pv
+spec:
+ capacity:
+ storage: 10Gi
+ accessModes:
+ - ReadWriteMany
+ nfs:
+ server: 192.168.1.100
+ path: /data/images
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: file-pvc
+spec:
+ accessModes:
+ - ReadWriteMany
+ resources:
+ requests:
+ storage: 10Gi
diff --git a/k8s/base/kustomization.yaml b/k8s/base/kustomization.yaml
index 76af622..678d10b 100644
--- a/k8s/base/kustomization.yaml
+++ b/k8s/base/kustomization.yaml
@@ -1,6 +1,8 @@
resources:
- account.yaml
- eatery.yaml
+ - cart.yaml
+ - file.yaml
- gateway.yaml
- redis.yaml
- kafdrop.yaml
diff --git a/pom.xml b/pom.xml
index ec9ebe2..e109e52 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,7 @@
account-service
gateway-service
cart-service
+ file-service