From 6fc765feb8bed6c734650447ae49ea4be8661663 Mon Sep 17 00:00:00 2001 From: Yunom Yxvia Date: Wed, 17 Sep 2025 16:04:35 +0700 Subject: [PATCH 01/13] Update dependabot.yml --- .github/dependabot.yml | 63 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d5b04450..3ba9aa5c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,22 +1,48 @@ version: 2 updates: - # NPM (Angular app) + # (Angular app) - package-ecosystem: "npm" directory: "/" schedule: interval: "daily" time: "02:00" timezone: "Asia/Bangkok" - open-pull-requests-limit: 10 target-branch: "main" - labels: ["dependencies", "npm"] - allow: - - dependency-type: "direct" - - dependency-type: "all" + open-pull-requests-limit: 10 + labels: ["dependencies", "npm", "frontend"] + # CHỈ chặn nâng major Angular (v21+), vẫn cho phép 20.x ignore: - # ví dụ giữ cố định major của Angular 19 - dependency-name: "@angular/*" - versions: [">=20"] + update-types: ["version-update:semver-major"] + groups: + angular-core: + patterns: + - "@angular/*" + - "zone.js" + update-types: ["minor", "patch"] + tooling-and-tests: + patterns: + - "typescript" + - "karma*" + - "jasmine*" + - "@types/*" + - "cypress" + update-types: ["minor", "patch"] + ui-and-md: + patterns: + - "highlight.js" + - "marked" + - "github-markdown-css" + - "apexcharts" + - "ng-apexcharts" + - "ngx-*" + update-types: ["minor", "patch"] + codemirror-suite: + patterns: + - "codemirror" + - "@codemirror/*" + update-types: ["minor", "patch"] + # GitHub Actions - package-ecosystem: "github-actions" @@ -26,9 +52,11 @@ updates: day: "monday" time: "03:00" timezone: "Asia/Bangkok" + target-branch: "main" labels: ["dependencies", "github-actions"] + open-pull-requests-limit: 10 - # Docker base images (Nginx, Node…) + # Docker images (Nginx, Node…) - package-ecosystem: "docker" directory: "/docker" schedule: @@ -36,4 +64,21 @@ updates: day: "tuesday" time: "04:00" timezone: "Asia/Bangkok" + target-branch: "main" labels: ["dependencies", "docker"] + open-pull-requests-limit: 10 + groups: + nginx-node-base: + patterns: + - "nginx" + - "node" + update-types: ["minor", "patch"] + dotnet-base: + patterns: + - "mcr.microsoft.com/dotnet/*" + update-types: ["minor", "patch"] + jre-maven: + patterns: + - "eclipse-temurin:*" + - "maven:*" + update-types: ["minor", "patch"] From f31e1828e5b140b29e69d0d23dc371f97b10bf53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B4=20Quang=20=C4=90=E1=BB=A9c?= Date: Wed, 17 Sep 2025 16:53:36 +0700 Subject: [PATCH 02/13] config docker --- DockerFile | 5 +++++ config-local.json | 1 + config-server.json | 1 + docker-compose.prod-frontend.yml | 9 +++++++-- entrypoint.sh | 12 ++++++++++++ public/config.json | 1 + src/app/core/services/config-service/api.enpoints.ts | 3 ++- src/environments/environment.prod.ts | 3 --- src/main.ts | 11 ++++++++++- 9 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 DockerFile create mode 100644 config-local.json create mode 100644 config-server.json create mode 100644 entrypoint.sh create mode 100644 public/config.json diff --git a/DockerFile b/DockerFile new file mode 100644 index 00000000..95a6d0a8 --- /dev/null +++ b/DockerFile @@ -0,0 +1,5 @@ +# Copy entrypoint script +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/config-local.json b/config-local.json new file mode 100644 index 00000000..28b041be --- /dev/null +++ b/config-local.json @@ -0,0 +1 @@ +{ "apiUrl": "http://localhost:8888/api" } diff --git a/config-server.json b/config-server.json new file mode 100644 index 00000000..45d3b25c --- /dev/null +++ b/config-server.json @@ -0,0 +1 @@ +{ "apiUrl": "http://72.60.41.133:8888/api" } diff --git a/docker-compose.prod-frontend.yml b/docker-compose.prod-frontend.yml index 9ccfafb9..e115c1a8 100644 --- a/docker-compose.prod-frontend.yml +++ b/docker-compose.prod-frontend.yml @@ -5,8 +5,13 @@ services: container_name: codecampus-frontend image: ${DOCKERHUB_USER}/codecampus-frontend:${IMAGE_TAG:-latest} restart: unless-stopped - ports: [ "4200:4200" ] - networks: [ backend ] + ports: ["4200:4200"] + volumes: + # Dùng config local + - ./config-local.json:/usr/share/nginx/html/config.json + # Hoặc server + # - ./config-server.json:/usr/share/nginx/html/config.json + networks: [backend] networks: backend: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 00000000..e65258b6 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# entrypoint.sh + +# Tạo file config.json dựa vào biến môi trường API_URL +cat < /usr/share/nginx/html/config.json +{ + "API_URL": "${API_URL}" +} +EOF + +# Chạy nginx foreground +nginx -g 'daemon off;' diff --git a/public/config.json b/public/config.json new file mode 100644 index 00000000..28b041be --- /dev/null +++ b/public/config.json @@ -0,0 +1 @@ +{ "apiUrl": "http://localhost:8888/api" } diff --git a/src/app/core/services/config-service/api.enpoints.ts b/src/app/core/services/config-service/api.enpoints.ts index 383e3d3d..b2edc1bd 100644 --- a/src/app/core/services/config-service/api.enpoints.ts +++ b/src/app/core/services/config-service/api.enpoints.ts @@ -12,7 +12,8 @@ export const version = '/v1'; export const API_CONFIG = { BASE_URLS: { - MAIN_API: environment.IP_SERVER + version, + MAIN_API: + ((window as any).env?.API_URL || 'http://localhost:8888/api') + version, SECONDARY_API: '', }, ENDPOINTS: { diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 6a7f40fd..89f9dd68 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,7 +1,4 @@ export const environment = { production: true, IP_SERVER: 'http://72.60.41.133:8888/api', - IP_SERVER_NO_SSL: 'http://192.168.1.220:8000/api', - IP_SERVER_RADMIN: 'http://26.100.147.137:8888/api', - IP_LOCAL: 'http://localhost:8081/api', }; diff --git a/src/main.ts b/src/main.ts index 190f3418..dcd81db2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,4 +2,13 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; import { App } from './app/app'; -bootstrapApplication(App, appConfig).catch((err) => console.error(err)); +// Load runtime config trước khi bootstrap +fetch('/config.json') + .then((response) => response.json()) + .then((config) => { + (window as any).env = config; + return bootstrapApplication(App, appConfig); + }) + .catch((err) => console.error(err)); + +// bootstrapApplication(App, appConfig).catch((err) => console.error(err)); From 1ad3f6eed5132936ffbc206e9b9fee100cf1b162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B4=20Quang=20=C4=90=E1=BB=A9c?= Date: Wed, 17 Sep 2025 17:12:21 +0700 Subject: [PATCH 03/13] config docker file --- DockerFile | 16 ++++++++++++---- docker-compose.prod-frontend.yml | 2 +- entrypoint.sh | 12 ------------ 3 files changed, 13 insertions(+), 17 deletions(-) delete mode 100644 entrypoint.sh diff --git a/DockerFile b/DockerFile index 95a6d0a8..44607438 100644 --- a/DockerFile +++ b/DockerFile @@ -1,5 +1,13 @@ -# Copy entrypoint script -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +# Stage 1: build Angular app +FROM node:20-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build --prod -ENTRYPOINT ["/entrypoint.sh"] +# Stage 2: serve với Nginx +FROM nginx:alpine +COPY --from=build /app/dist/codecampus /usr/share/nginx/html +# copy sẵn default nginx config nếu cần +COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/docker-compose.prod-frontend.yml b/docker-compose.prod-frontend.yml index e115c1a8..bb262f7e 100644 --- a/docker-compose.prod-frontend.yml +++ b/docker-compose.prod-frontend.yml @@ -5,7 +5,7 @@ services: container_name: codecampus-frontend image: ${DOCKERHUB_USER}/codecampus-frontend:${IMAGE_TAG:-latest} restart: unless-stopped - ports: ["4200:4200"] + ports: ["4200:80"] volumes: # Dùng config local - ./config-local.json:/usr/share/nginx/html/config.json diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100644 index e65258b6..00000000 --- a/entrypoint.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -# entrypoint.sh - -# Tạo file config.json dựa vào biến môi trường API_URL -cat < /usr/share/nginx/html/config.json -{ - "API_URL": "${API_URL}" -} -EOF - -# Chạy nginx foreground -nginx -g 'daemon off;' From 1e3a4c299cdc0773ff10eddf0f83b7d8a69a7b82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:11:17 +0000 Subject: [PATCH 04/13] Bump actions/setup-java from 4 to 5 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci-sonar-angular.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-sonar-angular.yml b/.github/workflows/ci-sonar-angular.yml index 6569bc36..5455b3d4 100644 --- a/.github/workflows/ci-sonar-angular.yml +++ b/.github/workflows/ci-sonar-angular.yml @@ -31,7 +31,7 @@ jobs: fetch-depth: 0 # Sonar cần full history để tính blame - name: Set up JDK (for Sonar scanner) - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: "21" From 086ab5f82396044de2128abde6a453d4070bab4b Mon Sep 17 00:00:00 2001 From: Yunom Yxvia Date: Thu, 18 Sep 2025 00:47:56 +0700 Subject: [PATCH 05/13] Update before Add Package --- .github/workflows/ci-sonar-angular.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-sonar-angular.yml b/.github/workflows/ci-sonar-angular.yml index 5455b3d4..449f1eb6 100644 --- a/.github/workflows/ci-sonar-angular.yml +++ b/.github/workflows/ci-sonar-angular.yml @@ -12,6 +12,7 @@ on: - "sonar-project.properties" pull_request: +### permissions: contents: read pull-requests: write From ceb714d255f19c2d00ef32808d4f48bfc668d8b2 Mon Sep 17 00:00:00 2001 From: Yunom Yxvia Date: Thu, 18 Sep 2025 01:09:48 +0700 Subject: [PATCH 06/13] Add github package --- .github/dependabot.yml | 55 ++++++++----------- .github/workflows/dependabot-auto-merge.yml | 39 +++++++++++++ .github/workflows/deploy-frontend.yml | 8 ++- .github/workflows/frontend-docker-publish.yml | 38 ++++++++----- 4 files changed, 93 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/dependabot-auto-merge.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3ba9aa5c..046ad652 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,48 +10,31 @@ updates: target-branch: "main" open-pull-requests-limit: 10 labels: ["dependencies", "npm", "frontend"] - # CHỈ chặn nâng major Angular (v21+), vẫn cho phép 20.x ignore: - dependency-name: "@angular/*" update-types: ["version-update:semver-major"] groups: angular-core: - patterns: - - "@angular/*" - - "zone.js" + patterns: ["@angular/*", "zone.js"] update-types: ["minor", "patch"] tooling-and-tests: - patterns: - - "typescript" - - "karma*" - - "jasmine*" - - "@types/*" - - "cypress" + patterns: ["typescript","karma*","jasmine*","@types/*","cypress"] update-types: ["minor", "patch"] ui-and-md: - patterns: - - "highlight.js" - - "marked" - - "github-markdown-css" - - "apexcharts" - - "ng-apexcharts" - - "ngx-*" + patterns: ["highlight.js","marked","github-markdown-css","apexcharts","ng-apexcharts","ngx-*"] update-types: ["minor", "patch"] codemirror-suite: - patterns: - - "codemirror" - - "@codemirror/*" + patterns: ["codemirror","@codemirror/*"] update-types: ["minor", "patch"] - # GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" day: "monday" - time: "03:00" - timezone: "Asia/Bangkok" + time: "03:00" + timezone: "Asia/Bangkok" target-branch: "main" labels: ["dependencies", "github-actions"] open-pull-requests-limit: 10 @@ -67,18 +50,28 @@ updates: target-branch: "main" labels: ["dependencies", "docker"] open-pull-requests-limit: 10 + registries: + - dockerhub + - ghcr groups: nginx-node-base: - patterns: - - "nginx" - - "node" + patterns: ["nginx","node"] update-types: ["minor", "patch"] dotnet-base: - patterns: - - "mcr.microsoft.com/dotnet/*" + patterns: ["mcr.microsoft.com/dotnet/*"] update-types: ["minor", "patch"] jre-maven: - patterns: - - "eclipse-temurin:*" - - "maven:*" + patterns: ["eclipse-temurin:*","maven:*"] update-types: ["minor", "patch"] + +registries: + dockerhub: + type: docker-registry + url: https://index.docker.io/v1/ + username: ${{secrets.DOCKERHUB_USER}} + password: ${{secrets.DOCKERHUB_TOKEN}} + ghcr: + type: docker-registry + url: https://ghcr.io + username: ${{secrets.GHCR_USERNAME}} + password: ${{secrets.GHCR_TOKEN}} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 00000000..5954298e --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,39 @@ +name: Dependabot Auto-merge + +on: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: write + pull-requests: write + +jobs: + automerge: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + + steps: + - name: Fetch Dependabot metadata + id: meta + uses: dependabot/fetch-metadata@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Auto-approve patch/minor + if: | + steps.meta.outputs.update-type == 'version-update:semver-patch' || + steps.meta.outputs.update-type == 'version-update:semver-minor' + uses: hmarr/auto-approve-action@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable PR auto-merge (squash) for patch/minor + if: | + steps.meta.outputs.update-type == 'version-update:semver-patch' || + steps.meta.outputs.update-type == 'version-update:semver-minor' + uses: peter-evans/enable-pull-request-automerge@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pull-request-number: ${{ github.event.pull_request.number }} + merge-method: squash diff --git a/.github/workflows/deploy-frontend.yml b/.github/workflows/deploy-frontend.yml index fe0fcf09..be44abb0 100644 --- a/.github/workflows/deploy-frontend.yml +++ b/.github/workflows/deploy-frontend.yml @@ -106,7 +106,7 @@ jobs: fi } - # docker login (nếu có) + # docker login Docker Hub (nếu có) if [ -n "${DOCKERHUB_USER:-}" ] && [ -n "${DOCKERHUB_TOKEN:-}" ]; then echo "docker login Docker Hub..." echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USER" --password-stdin @@ -114,6 +114,12 @@ jobs: echo "Thiếu DOCKERHUB_USER/DOCKERHUB_TOKEN trong .env (image public thì vẫn OK)." fi + # docker login GHCR (nếu có) + if [ -n "${GHCR_USERNAME:-}" ] && [ -n "${GHCR_TOKEN:-}" ]; then + echo "docker login GHCR..." + echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USERNAME" --password-stdin + fi + echo "Pull image frontend tag ${IMAGE_TAG}…" compose -f docker-compose.prod-frontend.yml --env-file .env pull diff --git a/.github/workflows/frontend-docker-publish.yml b/.github/workflows/frontend-docker-publish.yml index 81019d7c..fb51e11a 100644 --- a/.github/workflows/frontend-docker-publish.yml +++ b/.github/workflows/frontend-docker-publish.yml @@ -20,6 +20,7 @@ permissions: env: DOCKER_REPO: ${{ secrets.DOCKERHUB_USER }}/codecampus-frontend + GHCR_OWNER: ${{ github.repository_owner }} DOCKERFILE_PATH: docker/angular-frontend.Dockerfile PLATFORMS: linux/amd64 @@ -28,32 +29,32 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 - - name: Set up Buildx - uses: docker/setup-buildx-action@v3 - - - name: Derive tags + - name: Derive tags (Docker Hub + GHCR) id: meta run: | - TAGS="" + OWNER_LC="${GHCR_OWNER,,}" + echo "OWNER_LC=$OWNER_LC" >> $GITHUB_ENV if [ "${GITHUB_REF_TYPE}" = "tag" ]; then VERSION="${GITHUB_REF_NAME#v}" echo "IMAGE_TAG=${VERSION}" >> $GITHUB_ENV - TAGS="${{ env.DOCKER_REPO }}:${VERSION}" + HUB="${DOCKER_REPO}:${VERSION}" + GHCR="ghcr.io/${OWNER_LC}/codecampus-frontend:${VERSION}" else SHA_TAG="${GITHUB_SHA::12}" echo "IMAGE_TAG=${SHA_TAG}" >> $GITHUB_ENV - TAGS="${{ env.DOCKER_REPO }}:${SHA_TAG}" + HUB="${DOCKER_REPO}:${SHA_TAG}" + GHCR="ghcr.io/${OWNER_LC}/codecampus-frontend:${SHA_TAG}" if [ "${GITHUB_REF_NAME}" = "main" ]; then - TAGS="${TAGS},${{ env.DOCKER_REPO }}:latest" + HUB="${HUB}"$'\n'"${DOCKER_REPO}:latest" + GHCR="${GHCR}"$'\n'"ghcr.io/${OWNER_LC}/codecampus-frontend:latest" fi fi - echo "tags=${TAGS}" >> $GITHUB_OUTPUT + echo "all_tags=${HUB}"$'\n'"${GHCR}" >> $GITHUB_OUTPUT - name: Login to Docker Hub uses: docker/login-action@v3 @@ -61,13 +62,20 @@ jobs: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build & Push + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & Push (Docker Hub + GHCR) uses: docker/build-push-action@v6 with: context: . file: ${{ env.DOCKERFILE_PATH }} platforms: ${{ env.PLATFORMS }} push: true - tags: ${{ steps.meta.outputs.tags }} + tags: ${{ steps.meta.outputs.all_tags }} cache-from: type=gha cache-to: type=gha,mode=max From eb4b6eb44daf0ddb2522499c3cf2d98db09662bf Mon Sep 17 00:00:00 2001 From: Yunom Yxvia Date: Thu, 18 Sep 2025 01:25:30 +0700 Subject: [PATCH 07/13] Update frontend-docker-publish.yml --- .github/workflows/frontend-docker-publish.yml | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/frontend-docker-publish.yml b/.github/workflows/frontend-docker-publish.yml index fb51e11a..8b2d2462 100644 --- a/.github/workflows/frontend-docker-publish.yml +++ b/.github/workflows/frontend-docker-publish.yml @@ -36,17 +36,22 @@ jobs: - name: Derive tags (Docker Hub + GHCR) id: meta + shell: bash run: | - OWNER_LC="${GHCR_OWNER,,}" - echo "OWNER_LC=$OWNER_LC" >> $GITHUB_ENV + set -euo pipefail + + # Hạ lowercase owner (an toàn POSIX) + OWNER_LC="$(printf '%s' "${GHCR_OWNER}" | tr '[:upper:]' '[:lower:]')" + echo "OWNER_LC=${OWNER_LC}" >> "$GITHUB_ENV" + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then VERSION="${GITHUB_REF_NAME#v}" - echo "IMAGE_TAG=${VERSION}" >> $GITHUB_ENV + echo "IMAGE_TAG=${VERSION}" >> "$GITHUB_ENV" HUB="${DOCKER_REPO}:${VERSION}" GHCR="ghcr.io/${OWNER_LC}/codecampus-frontend:${VERSION}" else SHA_TAG="${GITHUB_SHA::12}" - echo "IMAGE_TAG=${SHA_TAG}" >> $GITHUB_ENV + echo "IMAGE_TAG=${SHA_TAG}" >> "$GITHUB_ENV" HUB="${DOCKER_REPO}:${SHA_TAG}" GHCR="ghcr.io/${OWNER_LC}/codecampus-frontend:${SHA_TAG}" if [ "${GITHUB_REF_NAME}" = "main" ]; then @@ -54,7 +59,14 @@ jobs: GHCR="${GHCR}"$'\n'"ghcr.io/${OWNER_LC}/codecampus-frontend:latest" fi fi - echo "all_tags=${HUB}"$'\n'"${GHCR}" >> $GITHUB_OUTPUT + + # Xuất output "tags" dạng đa dòng + { + echo "tags<<__TAGS__" + printf '%s\n' "$HUB" + printf '%s\n' "$GHCR" + echo "__TAGS__" + } >> "$GITHUB_OUTPUT" - name: Login to Docker Hub uses: docker/login-action@v3 @@ -76,6 +88,6 @@ jobs: file: ${{ env.DOCKERFILE_PATH }} platforms: ${{ env.PLATFORMS }} push: true - tags: ${{ steps.meta.outputs.all_tags }} + tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max From 46a387140d728ccb64657b594cf3c2222215cef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B4=20Quang=20=C4=90=E1=BB=A9c?= Date: Thu, 18 Sep 2025 14:09:06 +0700 Subject: [PATCH 08/13] config docker --- package-lock.json | 14 ++++++++++++++ proxy.conf.json | 8 -------- 2 files changed, 14 insertions(+), 8 deletions(-) delete mode 100644 proxy.conf.json diff --git a/package-lock.json b/package-lock.json index 20fa549b..b9315f3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,7 @@ "@types/jasmine": "~5.1.0", "@types/uuid": "^10.0.0", "cypress": "^15.0.0", + "dotenv": "^17.2.2", "jasmine-core": "~5.7.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -7952,6 +7953,19 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/proxy.conf.json b/proxy.conf.json deleted file mode 100644 index 40621e54..00000000 --- a/proxy.conf.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "/api": { - "target": "https://192.168.1.220:8888", - "secure": false, - "changeOrigin": true, - "logLevel": "debug" - } -} From ff82bae8c992d6438117757efb9237df17ebabb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B4=20Quang=20=C4=90=E1=BB=A9c?= Date: Thu, 18 Sep 2025 14:38:21 +0700 Subject: [PATCH 09/13] config docker --- DockerFile | 13 ------------- angular.json | 6 ------ config-local.json | 1 - config-server.json | 1 - config/local-config.json | 1 + config/server-config.json | 1 + public/assets/config.json | 3 +++ public/config.json | 1 - .../core/services/config-service/api.enpoints.ts | 4 ++-- src/main.ts | 2 +- 10 files changed, 8 insertions(+), 25 deletions(-) delete mode 100644 DockerFile delete mode 100644 config-local.json delete mode 100644 config-server.json create mode 100644 config/local-config.json create mode 100644 config/server-config.json create mode 100644 public/assets/config.json delete mode 100644 public/config.json diff --git a/DockerFile b/DockerFile deleted file mode 100644 index 44607438..00000000 --- a/DockerFile +++ /dev/null @@ -1,13 +0,0 @@ -# Stage 1: build Angular app -FROM node:20-alpine AS build -WORKDIR /app -COPY package*.json ./ -RUN npm install -COPY . . -RUN npm run build --prod - -# Stage 2: serve với Nginx -FROM nginx:alpine -COPY --from=build /app/dist/codecampus /usr/share/nginx/html -# copy sẵn default nginx config nếu cần -COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/angular.json b/angular.json index 33c266d5..f6828a93 100644 --- a/angular.json +++ b/angular.json @@ -33,12 +33,6 @@ }, "configurations": { "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], "budgets": [ { "type": "initial", diff --git a/config-local.json b/config-local.json deleted file mode 100644 index 28b041be..00000000 --- a/config-local.json +++ /dev/null @@ -1 +0,0 @@ -{ "apiUrl": "http://localhost:8888/api" } diff --git a/config-server.json b/config-server.json deleted file mode 100644 index 45d3b25c..00000000 --- a/config-server.json +++ /dev/null @@ -1 +0,0 @@ -{ "apiUrl": "http://72.60.41.133:8888/api" } diff --git a/config/local-config.json b/config/local-config.json new file mode 100644 index 00000000..77c37244 --- /dev/null +++ b/config/local-config.json @@ -0,0 +1 @@ +{ "IP_SERVER": "http://localhost:8888/api" } diff --git a/config/server-config.json b/config/server-config.json new file mode 100644 index 00000000..a9588ebc --- /dev/null +++ b/config/server-config.json @@ -0,0 +1 @@ +{ "IP_SERVER": "http://72.60.41.133:8888/api" } diff --git a/public/assets/config.json b/public/assets/config.json new file mode 100644 index 00000000..47717c1e --- /dev/null +++ b/public/assets/config.json @@ -0,0 +1,3 @@ +{ + "IP_SERVER": "http://localhost:8888/api" +} diff --git a/public/config.json b/public/config.json deleted file mode 100644 index 28b041be..00000000 --- a/public/config.json +++ /dev/null @@ -1 +0,0 @@ -{ "apiUrl": "http://localhost:8888/api" } diff --git a/src/app/core/services/config-service/api.enpoints.ts b/src/app/core/services/config-service/api.enpoints.ts index b2edc1bd..3c46abac 100644 --- a/src/app/core/services/config-service/api.enpoints.ts +++ b/src/app/core/services/config-service/api.enpoints.ts @@ -9,11 +9,11 @@ import { import { SearchingUser } from '../../models/user.models'; export const version = '/v1'; +const runtimeConfig = (window as any).APP_CONFIG; export const API_CONFIG = { BASE_URLS: { - MAIN_API: - ((window as any).env?.API_URL || 'http://localhost:8888/api') + version, + MAIN_API: runtimeConfig.IP_SERVER + version, SECONDARY_API: '', }, ENDPOINTS: { diff --git a/src/main.ts b/src/main.ts index dcd81db2..a15ac5bd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import { appConfig } from './app/app.config'; import { App } from './app/app'; // Load runtime config trước khi bootstrap -fetch('/config.json') +fetch('/assets/config.json') .then((response) => response.json()) .then((config) => { (window as any).env = config; From 9c7993eca0561bea6a7922e58cfa4ca5b37ac05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B4=20Quang=20=C4=90=E1=BB=A9c?= Date: Thu, 18 Sep 2025 15:01:32 +0700 Subject: [PATCH 10/13] config docker --- angular.json | 6 ++++++ proxy.conf.json | 8 ++++++++ public/config.json | 1 + .../services/config-service/api.enpoints.ts | 3 +-- src/main.ts | 18 +++++++++--------- 5 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 proxy.conf.json create mode 100644 public/config.json diff --git a/angular.json b/angular.json index f6828a93..33c266d5 100644 --- a/angular.json +++ b/angular.json @@ -33,6 +33,12 @@ }, "configurations": { "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], "budgets": [ { "type": "initial", diff --git a/proxy.conf.json b/proxy.conf.json new file mode 100644 index 00000000..c6662095 --- /dev/null +++ b/proxy.conf.json @@ -0,0 +1,8 @@ +{ + "/api": { + "target": "https://localhost:8888", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + } +} diff --git a/public/config.json b/public/config.json new file mode 100644 index 00000000..28b041be --- /dev/null +++ b/public/config.json @@ -0,0 +1 @@ +{ "apiUrl": "http://localhost:8888/api" } diff --git a/src/app/core/services/config-service/api.enpoints.ts b/src/app/core/services/config-service/api.enpoints.ts index 3c46abac..383e3d3d 100644 --- a/src/app/core/services/config-service/api.enpoints.ts +++ b/src/app/core/services/config-service/api.enpoints.ts @@ -9,11 +9,10 @@ import { import { SearchingUser } from '../../models/user.models'; export const version = '/v1'; -const runtimeConfig = (window as any).APP_CONFIG; export const API_CONFIG = { BASE_URLS: { - MAIN_API: runtimeConfig.IP_SERVER + version, + MAIN_API: environment.IP_SERVER + version, SECONDARY_API: '', }, ENDPOINTS: { diff --git a/src/main.ts b/src/main.ts index a15ac5bd..21421eeb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,13 +2,13 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; import { App } from './app/app'; -// Load runtime config trước khi bootstrap -fetch('/assets/config.json') - .then((response) => response.json()) - .then((config) => { - (window as any).env = config; - return bootstrapApplication(App, appConfig); - }) - .catch((err) => console.error(err)); +// // Load runtime config trước khi bootstrap +// fetch('/assets/config.json') +// .then((response) => response.json()) +// .then((config) => { +// (window as any).env = config; +// return bootstrapApplication(App, appConfig); +// }) +// .catch((err) => console.error(err)); -// bootstrapApplication(App, appConfig).catch((err) => console.error(err)); +bootstrapApplication(App, appConfig).catch((err) => console.error(err)); From cb7e505891fc2bc5720139c6046e65f379076261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B4=20Quang=20=C4=90=E1=BB=A9c?= Date: Thu, 18 Sep 2025 15:06:00 +0700 Subject: [PATCH 11/13] config docker file --- docker-compose.prod-frontend.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docker-compose.prod-frontend.yml b/docker-compose.prod-frontend.yml index bb262f7e..4df2b179 100644 --- a/docker-compose.prod-frontend.yml +++ b/docker-compose.prod-frontend.yml @@ -6,11 +6,6 @@ services: image: ${DOCKERHUB_USER}/codecampus-frontend:${IMAGE_TAG:-latest} restart: unless-stopped ports: ["4200:80"] - volumes: - # Dùng config local - - ./config-local.json:/usr/share/nginx/html/config.json - # Hoặc server - # - ./config-server.json:/usr/share/nginx/html/config.json networks: [backend] networks: From 3772ab7fd756acefcd9cc43306c5b5f420d9d9cf Mon Sep 17 00:00:00 2001 From: Yunom Yxvia Date: Thu, 18 Sep 2025 15:21:34 +0700 Subject: [PATCH 12/13] Update fix frontend --- .github/dependabot.yml | 60 +++++++++++++++---- .github/workflows/ci-sonar-angular.yml | 3 +- .github/workflows/dependabot-auto-merge.yml | 39 ++++++++++++ .github/workflows/deploy-frontend.yml | 8 ++- .github/workflows/frontend-docker-publish.yml | 52 +++++++++++----- 5 files changed, 133 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/dependabot-auto-merge.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d5b04450..046ad652 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,22 +1,31 @@ version: 2 updates: - # NPM (Angular app) + # (Angular app) - package-ecosystem: "npm" directory: "/" schedule: interval: "daily" time: "02:00" timezone: "Asia/Bangkok" - open-pull-requests-limit: 10 target-branch: "main" - labels: ["dependencies", "npm"] - allow: - - dependency-type: "direct" - - dependency-type: "all" + open-pull-requests-limit: 10 + labels: ["dependencies", "npm", "frontend"] ignore: - # ví dụ giữ cố định major của Angular 19 - dependency-name: "@angular/*" - versions: [">=20"] + update-types: ["version-update:semver-major"] + groups: + angular-core: + patterns: ["@angular/*", "zone.js"] + update-types: ["minor", "patch"] + tooling-and-tests: + patterns: ["typescript","karma*","jasmine*","@types/*","cypress"] + update-types: ["minor", "patch"] + ui-and-md: + patterns: ["highlight.js","marked","github-markdown-css","apexcharts","ng-apexcharts","ngx-*"] + update-types: ["minor", "patch"] + codemirror-suite: + patterns: ["codemirror","@codemirror/*"] + update-types: ["minor", "patch"] # GitHub Actions - package-ecosystem: "github-actions" @@ -24,11 +33,13 @@ updates: schedule: interval: "weekly" day: "monday" - time: "03:00" - timezone: "Asia/Bangkok" + time: "03:00" + timezone: "Asia/Bangkok" + target-branch: "main" labels: ["dependencies", "github-actions"] + open-pull-requests-limit: 10 - # Docker base images (Nginx, Node…) + # Docker images (Nginx, Node…) - package-ecosystem: "docker" directory: "/docker" schedule: @@ -36,4 +47,31 @@ updates: day: "tuesday" time: "04:00" timezone: "Asia/Bangkok" + target-branch: "main" labels: ["dependencies", "docker"] + open-pull-requests-limit: 10 + registries: + - dockerhub + - ghcr + groups: + nginx-node-base: + patterns: ["nginx","node"] + update-types: ["minor", "patch"] + dotnet-base: + patterns: ["mcr.microsoft.com/dotnet/*"] + update-types: ["minor", "patch"] + jre-maven: + patterns: ["eclipse-temurin:*","maven:*"] + update-types: ["minor", "patch"] + +registries: + dockerhub: + type: docker-registry + url: https://index.docker.io/v1/ + username: ${{secrets.DOCKERHUB_USER}} + password: ${{secrets.DOCKERHUB_TOKEN}} + ghcr: + type: docker-registry + url: https://ghcr.io + username: ${{secrets.GHCR_USERNAME}} + password: ${{secrets.GHCR_TOKEN}} diff --git a/.github/workflows/ci-sonar-angular.yml b/.github/workflows/ci-sonar-angular.yml index 6569bc36..449f1eb6 100644 --- a/.github/workflows/ci-sonar-angular.yml +++ b/.github/workflows/ci-sonar-angular.yml @@ -12,6 +12,7 @@ on: - "sonar-project.properties" pull_request: +### permissions: contents: read pull-requests: write @@ -31,7 +32,7 @@ jobs: fetch-depth: 0 # Sonar cần full history để tính blame - name: Set up JDK (for Sonar scanner) - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: "21" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 00000000..5954298e --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,39 @@ +name: Dependabot Auto-merge + +on: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: write + pull-requests: write + +jobs: + automerge: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + + steps: + - name: Fetch Dependabot metadata + id: meta + uses: dependabot/fetch-metadata@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Auto-approve patch/minor + if: | + steps.meta.outputs.update-type == 'version-update:semver-patch' || + steps.meta.outputs.update-type == 'version-update:semver-minor' + uses: hmarr/auto-approve-action@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable PR auto-merge (squash) for patch/minor + if: | + steps.meta.outputs.update-type == 'version-update:semver-patch' || + steps.meta.outputs.update-type == 'version-update:semver-minor' + uses: peter-evans/enable-pull-request-automerge@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pull-request-number: ${{ github.event.pull_request.number }} + merge-method: squash diff --git a/.github/workflows/deploy-frontend.yml b/.github/workflows/deploy-frontend.yml index fe0fcf09..be44abb0 100644 --- a/.github/workflows/deploy-frontend.yml +++ b/.github/workflows/deploy-frontend.yml @@ -106,7 +106,7 @@ jobs: fi } - # docker login (nếu có) + # docker login Docker Hub (nếu có) if [ -n "${DOCKERHUB_USER:-}" ] && [ -n "${DOCKERHUB_TOKEN:-}" ]; then echo "docker login Docker Hub..." echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USER" --password-stdin @@ -114,6 +114,12 @@ jobs: echo "Thiếu DOCKERHUB_USER/DOCKERHUB_TOKEN trong .env (image public thì vẫn OK)." fi + # docker login GHCR (nếu có) + if [ -n "${GHCR_USERNAME:-}" ] && [ -n "${GHCR_TOKEN:-}" ]; then + echo "docker login GHCR..." + echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USERNAME" --password-stdin + fi + echo "Pull image frontend tag ${IMAGE_TAG}…" compose -f docker-compose.prod-frontend.yml --env-file .env pull diff --git a/.github/workflows/frontend-docker-publish.yml b/.github/workflows/frontend-docker-publish.yml index 81019d7c..8b2d2462 100644 --- a/.github/workflows/frontend-docker-publish.yml +++ b/.github/workflows/frontend-docker-publish.yml @@ -20,6 +20,7 @@ permissions: env: DOCKER_REPO: ${{ secrets.DOCKERHUB_USER }}/codecampus-frontend + GHCR_OWNER: ${{ github.repository_owner }} DOCKERFILE_PATH: docker/angular-frontend.Dockerfile PLATFORMS: linux/amd64 @@ -28,32 +29,44 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 - - name: Set up Buildx - uses: docker/setup-buildx-action@v3 - - - name: Derive tags + - name: Derive tags (Docker Hub + GHCR) id: meta + shell: bash run: | - TAGS="" + set -euo pipefail + + # Hạ lowercase owner (an toàn POSIX) + OWNER_LC="$(printf '%s' "${GHCR_OWNER}" | tr '[:upper:]' '[:lower:]')" + echo "OWNER_LC=${OWNER_LC}" >> "$GITHUB_ENV" + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then VERSION="${GITHUB_REF_NAME#v}" - echo "IMAGE_TAG=${VERSION}" >> $GITHUB_ENV - TAGS="${{ env.DOCKER_REPO }}:${VERSION}" + echo "IMAGE_TAG=${VERSION}" >> "$GITHUB_ENV" + HUB="${DOCKER_REPO}:${VERSION}" + GHCR="ghcr.io/${OWNER_LC}/codecampus-frontend:${VERSION}" else SHA_TAG="${GITHUB_SHA::12}" - echo "IMAGE_TAG=${SHA_TAG}" >> $GITHUB_ENV - TAGS="${{ env.DOCKER_REPO }}:${SHA_TAG}" + echo "IMAGE_TAG=${SHA_TAG}" >> "$GITHUB_ENV" + HUB="${DOCKER_REPO}:${SHA_TAG}" + GHCR="ghcr.io/${OWNER_LC}/codecampus-frontend:${SHA_TAG}" if [ "${GITHUB_REF_NAME}" = "main" ]; then - TAGS="${TAGS},${{ env.DOCKER_REPO }}:latest" + HUB="${HUB}"$'\n'"${DOCKER_REPO}:latest" + GHCR="${GHCR}"$'\n'"ghcr.io/${OWNER_LC}/codecampus-frontend:latest" fi fi - echo "tags=${TAGS}" >> $GITHUB_OUTPUT + + # Xuất output "tags" dạng đa dòng + { + echo "tags<<__TAGS__" + printf '%s\n' "$HUB" + printf '%s\n' "$GHCR" + echo "__TAGS__" + } >> "$GITHUB_OUTPUT" - name: Login to Docker Hub uses: docker/login-action@v3 @@ -61,7 +74,14 @@ jobs: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build & Push + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & Push (Docker Hub + GHCR) uses: docker/build-push-action@v6 with: context: . From 41f47d75dbf396a7ab252cc18a377b38ea8b5c27 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Sep 2025 08:23:58 +0000 Subject: [PATCH 13/13] Bump SonarSource/sonarqube-scan-action in /.github/workflows Bumps [SonarSource/sonarqube-scan-action](https://github.com/sonarsource/sonarqube-scan-action) from 4 to 5.3.1. - [Release notes](https://github.com/sonarsource/sonarqube-scan-action/releases) - [Commits](https://github.com/sonarsource/sonarqube-scan-action/compare/v4...v5.3.1) --- updated-dependencies: - dependency-name: SonarSource/sonarqube-scan-action dependency-version: 5.3.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/ci-sonar-angular.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-sonar-angular.yml b/.github/workflows/ci-sonar-angular.yml index 449f1eb6..b1a826c4 100644 --- a/.github/workflows/ci-sonar-angular.yml +++ b/.github/workflows/ci-sonar-angular.yml @@ -80,7 +80,7 @@ jobs: # --- SonarQube self-hosted (nếu bạn set SONAR_HOST_URL) --- - name: SonarQube Scan (self-hosted) if: ${{ env.SONAR_HOST_URL != '' }} - uses: SonarSource/sonarqube-scan-action@v4 + uses: SonarSource/sonarqube-scan-action@v5.3.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_FRONTEND }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}