feat: ...

This commit is contained in:
admin
2025-09-24 15:05:42 +07:00
parent 58a2fc3f5c
commit b78a95e244
13 changed files with 330 additions and 96 deletions

View File

@@ -17,6 +17,7 @@ RUN mkdir -p ${RUNTIME_HOME} && mv node_modules ${RUNTIME_HOME}/node_modules
########## Deps สำหรับ Development (รวม devDeps) ##########
FROM base AS deps-dev
RUN apk add --no-cache git openssh-client ca-certificates
WORKDIR /work
COPY package*.json ./
RUN npm ci || npm install

View File

@@ -1,22 +1,69 @@
FROM node:20-alpine
# สำหรับอ่านค่า .env ที่วางไว้ระดับ compose (ไม่ copy เข้า image)
ENV NODE_ENV=development
ENV TZ=Asia/Bangkok
# syntax=docker/dockerfile:1.6
########## Base (apk + common tools ติดตั้งตอน build) ##########
FROM node:20-alpine AS base
WORKDIR /app
RUN apk add --no-cache bash curl tzdata python3 make g++ \
&& ln -snf /usr/share/zoneinfo/Asia/Bangkok /etc/localtime \
&& echo "Asia/Bangkok" > /etc/timezone
ENV TZ=Asia/Bangkok APP_HOME=/app RUNTIME_HOME=/opt/runtime
# สร้าง user ไม่ใช่ root (ปลอดภัยขึ้น)
RUN addgroup -S dms && adduser -S dms -G dms
########## Deps สำหรับ Production (no devDeps) ##########
FROM base AS deps-prod
WORKDIR /work
COPY package*.json ./
RUN npm ci --omit=dev || npm install --omit=dev
RUN mkdir -p ${RUNTIME_HOME} && mv node_modules ${RUNTIME_HOME}/node_modules
# runtime tools + build deps ชั่วคราว (สำหรับ bcrypt ฯลฯ)
RUN apk add --no-cache python3 make g++ curl
# ติดตั้ง nodemon ไว้ให้
RUN npm i -g nodemon
########## Deps สำหรับ Development (รวม devDeps) ##########
FROM base AS deps-dev
RUN apk add --no-cache git openssh-client ca-certificates
WORKDIR /work
COPY package*.json ./
RUN npm ci || npm install
RUN mkdir -p ${RUNTIME_HOME} && mv node_modules ${RUNTIME_HOME}/node_modules
COPY package.json package-lock.json* ./
RUN (npm ci --omit=dev || npm install --omit=dev)
########## Runtime: Development ##########
FROM base AS dev
WORKDIR /app
# ทำงานเป็น root ชั่วคราวเพื่อจัดสิทธิ์/ลิงก์ แล้วค่อยเปลี่ยนเป็น node
# 1) คัดลอก deps dev
COPY --from=deps-dev /opt/runtime/node_modules /opt/runtime/node_modules
# 2) สร้าง symlink /app/node_modules → /opt/runtime/node_modules (กันปัญหา NODE_PATH/permission)
RUN ln -sfn /opt/runtime/node_modules /app/node_modules \
&& chown -R node:node /app
# 3) ใส่สคริปต์ start-dev แล้วค่อยสลับ USER
COPY --chown=node:node ./start-dev.sh /app/start-dev.sh
RUN chmod +x /app/start-dev.sh
USER node
# ให้หา nodemon ได้จาก node_modules/.bin ที่ bake มาแล้ว
# ENV NODE_ENV=development PATH="/opt/runtime/node_modules/.bin:${PATH}"
# ให้หา nodemon ได้ และระบุพอร์ตดีฟอลต์
ENV NODE_ENV=development \
PORT=3001 \
PATH="/opt/runtime/node_modules/.bin:${PATH}"
EXPOSE 3001 9229
HEALTHCHECK --interval=15s --timeout=5s --retries=10 \
CMD wget -qO- http://127.0.0.1:3001/health || exit 1
# HEALTHCHECK --interval=15s --timeout=5s --retries=10 CMD curl -fsS http://127.0.0.1:7001/health || exit 1
CMD ["/app/start-dev.sh"]
########## Runtime: Production ##########
FROM base AS prod
WORKDIR /app
ENV NODE_ENV=production
# ใส่ deps สำหรับ prod
COPY --from=deps-prod /opt/runtime/node_modules /opt/runtime/node_modules
# สร้าง symlink เช่นกัน เพื่อให้ Node resolve deps ได้จาก /app เหมือน dev
RUN ln -sfn /opt/runtime/node_modules /app/node_modules
# ใส่ซอร์ส (prod ไม่ bind โค้ด)
COPY . .
USER node
EXPOSE 3001
USER dms
CMD ["node", "src/index.js"]
# backend/Dockerfile (Node.js ESM)
HEALTHCHECK --interval=30s --timeout=5s --retries=10 \
CMD wget -qO- http://127.0.0.1:3001/health || exit 1
CMD ["node","src/index.js"]

View File

@@ -7,6 +7,7 @@
"": {
"name": "dms-backend",
"version": "0.6.0",
"hasInstallScript": true,
"dependencies": {
"bcrypt": "5.1.1",
"bcryptjs": "^2.4.3",
@@ -2698,7 +2699,7 @@
"node": ">= 12.0.0"
}
},
"node_modules/winston/node_modules/winston-transport": {
"node_modules/winston-transport": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
"integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",

View File

@@ -10,6 +10,7 @@
"scripts": {
"dev": "nodemon --watch src src/index.js",
"dev:desktop": "node --watch src/index.js",
"start": "node src/index.js",
"lint": "echo 'lint placeholder'",
"health": "node -e \"fetch('http://localhost:'+ (process.env.BACKEND_PORT||3001) +'/health').then(r=>r.text()).then(console.log).catch(e=>{console.error(e);process.exit(1)})\"",

View File

@@ -40,7 +40,13 @@ const PORT = Number(process.env.PORT || 3001);
const NODE_ENV = process.env.NODE_ENV || 'production';
// Origin ของ Frontend (ถ้ามี Nginx ด้านหน้า ให้ใช้โดเมน/พอร์ตของ Frontend)
const FRONTEND_ORIGIN = process.env.FRONTEND_ORIGIN || 'https://dcs.mycloudnas.com'; // ใส่เช่น 'https://dcs.mycloudnas.com'
// Origin ของ Frontend (ตั้งผ่าน ENV ในแต่ละสภาพแวดล้อม; dev ใช้ localhost)
const FRONTEND_ORIGIN = process.env.FRONTEND_ORIGIN || 'https://lcbp3.mycloudnas.com';
const ALLOW_ORIGINS = [
'http://localhost:3000',
'http://127.0.0.1:3000',
FRONTEND_ORIGIN,
].filter(Boolean);
// ที่เก็บ log ภายใน container ถูก bind ไปที่ /share/Container/dms/logs/backend
const LOG_DIR = process.env.BACKEND_LOG_DIR || '/app/logs';
@@ -57,23 +63,29 @@ try {
* ========================== */
const app = express();
// หลัง Nginx/Reverse Proxy ควรเปิด trust proxy เพื่ออ่าน X-Forwarded-*
app.set('trust proxy', 1);
// CORS แบบกำหนด origin ตามรายการที่อนุญาต + อนุญาต credentials
app.use(cors({
origin(origin, cb) {
// อนุญาต server-to-server / curl ที่ไม่มี Origin
if (!origin) return cb(null, true);
return cb(null, ALLOW_ORIGINS.includes(origin));
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
exposedHeaders: ['Content-Disposition', 'Content-Length'],
}));
// จัดการ preflight ให้ครบ
app.options('*', cors({
origin(origin, cb) {
if (!origin) return cb(null, true);
return cb(null, ALLOW_ORIGINS.includes(origin));
},
credentials: true,
}));
app.use(cors({ origin: true, credentials: true }));
app.use(express.json());
app.use(cookieParser());
// CORS แบบง่าย (ต้องการละเอียดกว่านี้ใช้ cors package ได้ แต่ที่นี่ทำ manual)
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', FRONTEND_ORIGIN);
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition, Content-Length');
if (req.method === 'OPTIONS') return res.sendStatus(204);
next();
});
// Payload limits
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));