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