diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..217cb0e --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,21 @@ +name: Build and Release +run-name: ${{ gitea.actor }} build 🚀 + +on: + push: + branches: + - master + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Create .env file + run: | + echo "${{ secrets.ENV_FILE }}" > .env + + - name: Deploy to Container + run: | + docker compose up -d --build --remove-orphans \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..17b6092 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM node:24-alpine AS base + +# Stage 1: Dependencies Installation +FROM base AS deps + +RUN apk add --no-cache libc6-compat +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN npm ci --omit=dev + +# Stage 2: Application Build +FROM base AS builder +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +COPY .env .env + +RUN npm run build + +# Stage 3: Production Runner +FROM node:24-alpine AS runner + +WORKDIR /app + +# copy env runtime +COPY --from=builder /app/.env .env +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/public ./public + +USER node + +EXPOSE 3000 + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/api.ts b/api.ts index 5c41924..9e169e2 100644 --- a/api.ts +++ b/api.ts @@ -5,8 +5,8 @@ export const API = { User : { CURRENT: `${API_URL_ROOT}/users/current`, MEDIA: `${API_URL_ROOT}/users/current/media`, - Update: (Id: number | string) => `${API_URL_ROOT}/users/${Id}`, - CHANGE_PASSWORD: (Id: number | string) => `${API_URL_ROOT}/users/${Id}/password`, + Update: `${API_URL_ROOT}/users/current`, + CHANGE_PASSWORD: `${API_URL_ROOT}/users/current/password`, APPLICATION: `${API_URL_ROOT}/users/current/application` }, Media:{ @@ -37,5 +37,6 @@ export const API = { Historian:{ CREATE_CV: `${API_URL_ROOT}/historian/application`, APPLICATION: `${API_URL_ROOT}/historian/application`, + DELETE_CV: (Id: number | string) => `${API_URL_ROOT}/historian/application/${Id}`, } } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0ac2ecc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + history-admin: + build: + context: . + dockerfile: Dockerfile + container_name: history-admin + restart: unless-stopped + ports: + - "3011:3000" + networks: + - history-admin-network + +networks: + history-admin-network: + driver: bridge diff --git a/next.config.ts b/next.config.ts index 24a47b6..532af9f 100644 --- a/next.config.ts +++ b/next.config.ts @@ -18,7 +18,7 @@ const nextConfig: NextConfig = { }, ], }, - + output: 'standalone', webpack(config) { config.module.rules.push({ test: /\.svg$/, diff --git a/package-lock.json b/package-lock.json index a135486..439f02d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,6 +95,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1945,6 +1946,7 @@ "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.20.tgz", "integrity": "sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==", "license": "MIT", + "peer": true, "dependencies": { "preact": "~10.12.1" } @@ -2894,6 +2896,7 @@ "resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.5.tgz", "integrity": "sha512-/VNHWYhNu+BS7ktbYoVGrCmsXDh+chFMaONMwGNdIBcFHrWqk2jY8fNyr3DLdtQUIalvkPfM554ZSFa3dm3nxQ==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/Fuzzyma" @@ -2917,6 +2920,7 @@ "resolved": "https://registry.npmjs.org/@svgdotjs/svg.select.js/-/svg.select.js-4.0.3.tgz", "integrity": "sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 14.18" }, @@ -3093,6 +3097,7 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -3509,6 +3514,7 @@ "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3519,6 +3525,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3529,6 +3536,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3594,6 +3602,7 @@ "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", @@ -4125,6 +4134,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4180,6 +4190,7 @@ "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-4.7.0.tgz", "integrity": "sha512-iZSrrBGvVlL+nt2B1NpqfDuBZ9jX61X9I2+XV0hlYXHtTwhwLTHDKGXjNXAgFBDLuvSYCB/rq2nPWVPRv2DrGA==", "license": "MIT", + "peer": true, "dependencies": { "@svgdotjs/svg.draggable.js": "^3.0.4", "@svgdotjs/svg.filter.js": "^3.0.8", @@ -4592,6 +4603,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -5444,6 +5456,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5629,6 +5642,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6922,7 +6936,8 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/js-tokens": { "version": "4.0.0", @@ -7987,6 +8002,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -8093,6 +8109,7 @@ "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", "license": "MIT", + "peer": true, "dependencies": { "fast-diff": "^1.3.0", "lodash.clonedeep": "^4.5.0", @@ -8107,6 +8124,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8168,6 +8186,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -8218,6 +8237,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -8240,7 +8260,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -9034,7 +9055,8 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.2", @@ -9090,6 +9112,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9252,6 +9275,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9605,6 +9629,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/components/user-profile/AccountDetails.tsx b/src/components/user-profile/AccountDetails.tsx index 3b8ad63..2a415c7 100644 --- a/src/components/user-profile/AccountDetails.tsx +++ b/src/components/user-profile/AccountDetails.tsx @@ -64,7 +64,7 @@ export default function AccountDetails({ data }: { data: UserMetaCardProps }) { new_password: formValues.new_password, }; - await apiChangePassword(userId, payload as any); + await apiChangePassword(payload as any); closeModal(); toast.success("Cập nhật thành công!"); router.refresh(); diff --git a/src/components/user-profile/UserInfoCard.tsx b/src/components/user-profile/UserInfoCard.tsx index 647cd4e..bf6bb39 100644 --- a/src/components/user-profile/UserInfoCard.tsx +++ b/src/components/user-profile/UserInfoCard.tsx @@ -7,7 +7,7 @@ import Button from "../ui/button/Button"; import Input from "../form/input/InputField"; import Label from "../form/Label"; import { Profile, UserMetaCardProps } from "@/interface/user"; -import { apiUpdateUser, apiUpdateUserCurrent } from "@/service/userService"; +import { apiUpdateUser } from "@/service/userService"; import { toast } from "sonner"; import Link from "next/link"; @@ -51,7 +51,7 @@ export default function UserInfoCard({ data }: { data: UserMetaCardProps }) { if (!userId) return; try { - const response = await apiUpdateUserCurrent(formData); + const response = await apiUpdateUser(formData); if (response && response.status === false) { toast.error(response.message || "Cập nhật thất bại."); diff --git a/src/components/user-profile/UserMetaCard.tsx b/src/components/user-profile/UserMetaCard.tsx index 531d12e..faf537d 100644 --- a/src/components/user-profile/UserMetaCard.tsx +++ b/src/components/user-profile/UserMetaCard.tsx @@ -5,7 +5,7 @@ import Image from "next/image"; import { UserMetaCardProps } from "@/interface/user"; import { uploadMedia } from "@/service/mediaService"; import { toast } from "sonner"; -import { apiUpdateUser, apiUpdateUserCurrent } from "@/service/userService"; +import { apiUpdateUser } from "@/service/userService"; import { URL_MEDIA } from "../../../api"; export default function UserMetaCard({ data }: { data: UserMetaCardProps }) { @@ -48,7 +48,7 @@ export default function UserMetaCard({ data }: { data: UserMetaCardProps }) { setPreviewImage(url); if (data.data) { try { - await apiUpdateUserCurrent({ avatar_url: url }); + await apiUpdateUser({ avatar_url: url }); toast.success("Cập nhật avatar thành công!"); setTimeout(() => { window.location.reload(); diff --git a/src/service/auth.ts b/src/service/auth.ts index 94d09fa..e4798d5 100644 --- a/src/service/auth.ts +++ b/src/service/auth.ts @@ -41,7 +41,7 @@ export const apiGetCurrentUser = async () => { return response?.data; }; -export const apiChangePassword = async (id:string,payload: any) => { - const response = await api.patch(API.User.CHANGE_PASSWORD(id), payload); +export const apiChangePassword = async (payload: any) => { + const response = await api.patch(API.User.CHANGE_PASSWORD, payload); return response?.data; }; diff --git a/src/service/userService.ts b/src/service/userService.ts index 947c24d..1134e4d 100644 --- a/src/service/userService.ts +++ b/src/service/userService.ts @@ -12,12 +12,7 @@ export const apiGetCurrentUserApplications = async () => { return response?.data; }; -export const apiUpdateUser = async (id:string, payload: Profile) => { - const response = await api.put(API.User.Update(id), payload); +export const apiUpdateUser = async (payload: Profile) => { + const response = await api.put(API.User.Update, payload); return response?.data; }; - -export const apiUpdateUserCurrent = async (payload: Profile) => { - const response = await api.put(API.User.CURRENT, payload); - return response?.data; -}; \ No newline at end of file