-first commit
This commit is contained in:
32
exthernal-accountingwep-api/src/app.js
Normal file
32
exthernal-accountingwep-api/src/app.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import dotenv from 'dotenv'
|
||||
import router from './routes/route.js'
|
||||
import { validateJsonFormat } from './middlewares/validate.js'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const app = express()
|
||||
app.use(cors())
|
||||
|
||||
// ✅ ตรวจจับ JSON format error ก่อน parser
|
||||
app.use(express.json({ limit: '10mb' }))
|
||||
app.use(validateJsonFormat)
|
||||
|
||||
app.use('/api', router)
|
||||
// middleware จัดการ error กลาง
|
||||
app.use((err, req, res, next) => {
|
||||
if (err instanceof OftenError) {
|
||||
res.status(err.statusCode).json({
|
||||
type: err.type,
|
||||
messageTh: err.messageTh,
|
||||
messageEn: err.messageEn
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({ message: "Unexpected error" });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(process.env.PORT, () => {
|
||||
console.log(`✅ ${process.env.PJ_NAME} running on port ${process.env.PORT}`)
|
||||
})
|
||||
13
exthernal-accountingwep-api/src/config/db.js
Normal file
13
exthernal-accountingwep-api/src/config/db.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import pkg from 'pg'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
dotenv.config()
|
||||
const { Pool } = pkg
|
||||
|
||||
export const connection = new Pool({
|
||||
host: process.env.PG_HOST,
|
||||
user: process.env.PG_USER,
|
||||
password: process.env.PG_PASS,
|
||||
database: process.env.PG_DB,
|
||||
port: process.env.PG_PORT,
|
||||
})
|
||||
@@ -0,0 +1,85 @@
|
||||
import { LoginService } from '../services/loginservice.js'
|
||||
import { sendResponse } from '../utils/response.js'
|
||||
// import { OftenError } from '../utils/oftenError.js'
|
||||
import { GeneralService } from '../share/generalservice.js';
|
||||
import { trim_all_array } from '../utils/trim.js'
|
||||
import { verifyToken, generateToken } from '../utils/token.js'
|
||||
|
||||
export class loginController {
|
||||
|
||||
constructor() { // contructor zone
|
||||
this.generalService = new GeneralService()
|
||||
this.loginService = new LoginService()
|
||||
}
|
||||
|
||||
|
||||
// ===================================================
|
||||
// 🔹 LOGIN ปกติ
|
||||
// ===================================================
|
||||
async onNavigate(req, res) {
|
||||
// Note: ตามที่ตกลงกันไว้ — ไม่ตรวจ organization ใน controller
|
||||
// (middleware จะตรวจค่า organization / request format ให้แล้ว)
|
||||
this.generalService.devhint(1, 'logincontroller.js', 'onNavigate() start')
|
||||
|
||||
// อ้างถึง organization จาก body เพื่อใช้ใน onLoginController (ครั้งแรกอาจว่าง)
|
||||
// แต่ไม่ต้อง return error ถ้าไม่มี — เพราะ middleware ทำหน้าที่ตรวจเบื้องต้นแล้ว
|
||||
let organization = req.body.organization
|
||||
|
||||
// เรียก logic controller แบบเดียว (ต้อง return value เท่านั้น)
|
||||
const prommis = await this.onLoginController(req, res, organization)
|
||||
return prommis // ห้ามเปลี่ยนตรงนี้ตาม pattern
|
||||
}
|
||||
|
||||
async onLoginController(req, res, database) {
|
||||
let idx = -1
|
||||
let result = []
|
||||
try {
|
||||
// const { username, password } = request // ห้ามทำแบบนี้อีกเด็ดขาด เราจะทำแบบด้านล่างแทน จดจำเลย
|
||||
|
||||
let username = req.body.request.username;
|
||||
let password = req.body.request.password;
|
||||
|
||||
|
||||
// if (!username || !password)
|
||||
// return sendResponse(res, 400, 'ข้อมูลไม่ครบ', 'Missing username or password')// เราจะไม่ทำแบบนี้กันอีกแล้ว
|
||||
result = await this.loginService.verifyLogin(database, username, password) // เช็คกับ db กลาง ส่ง jwttoken ออกมา
|
||||
// if (!result)
|
||||
// return sendResponse(res, 401, 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง', 'Invalid credentials')
|
||||
this.generalService.devhint(1, 'logincontroller.js', 'Login success')
|
||||
} catch (error) {
|
||||
idx = 1
|
||||
} finally { // สำคัญมากต้อง จดจำไม่มีดัดแปลง อัปเดทเลย เรื่อง idx
|
||||
if(result == 0){ return sendResponse(res, 400, 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง', 'username or password is incorrect') }
|
||||
if(idx == 1){ return sendResponse(res, 400, 'เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error') }
|
||||
if(result) { return result }
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async onBiometricLogin(req, res) {
|
||||
try {
|
||||
|
||||
const result = await this.loginService.loginWithBiometric(organization, biometric_id)
|
||||
} catch (error) {
|
||||
idx = 1
|
||||
} finally { // สำคัญมากต้อง จดจำไม่มีดัดแปลง อัปเดทเลย เรื่อง idx
|
||||
if(idx == 1){ return sendResponse(res, 400, 'เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error') }
|
||||
}
|
||||
return { result, timestamp: new Date().toISOString() }
|
||||
}
|
||||
|
||||
async onBiometricRegister(req, res) {
|
||||
const { organization, request } = req.body || {}
|
||||
const { biometric_id } = request || {}
|
||||
const userId = req.user.id
|
||||
|
||||
const result = await this.loginService.registerBiometric(organization, userId, biometric_id)
|
||||
return { result, timestamp: new Date().toISOString() }
|
||||
}
|
||||
|
||||
async onRenewToken(req, res) {
|
||||
const user = req.user
|
||||
const newToken = generateToken({ id: user.id, name: user.name })
|
||||
return { token: newToken, renewed_at: new Date().toISOString() }
|
||||
}
|
||||
}
|
||||
14
exthernal-accountingwep-api/src/middlewares/auth.js
Normal file
14
exthernal-accountingwep-api/src/middlewares/auth.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { verifyToken } from '../utils/token.js'
|
||||
import { sendResponse } from '../utils/response.js'
|
||||
|
||||
export function authMiddleware(req, res, next) {
|
||||
const authHeader = req.headers['authorization']
|
||||
const token = authHeader && authHeader.split(' ')[1]
|
||||
if (!token) return sendResponse(res, 401, 'ไม่พบ Token', 'Missing token')
|
||||
|
||||
const decoded = verifyToken(token)
|
||||
if (!decoded) return sendResponse(res, 403, 'Token ไม่ถูกต้อง', 'Invalid token')
|
||||
|
||||
req.user = decoded
|
||||
next()
|
||||
}
|
||||
24
exthernal-accountingwep-api/src/middlewares/validate.js
Normal file
24
exthernal-accountingwep-api/src/middlewares/validate.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { sendResponse } from '../utils/response.js'
|
||||
|
||||
/**
|
||||
* ✅ Middleware สำหรับตรวจสอบความถูกต้องของ JSON body
|
||||
* ป้องกัน body-parser crash (SyntaxError)
|
||||
*/
|
||||
export function validateJsonFormat(err, req, res, next) {
|
||||
if (err instanceof SyntaxError && 'body' in err) {
|
||||
console.error('[Invalid JSON Format]', err.message)
|
||||
return sendResponse(res, 400, 'รูปแบบ บอร์ดี้ ไม่ถูกต้อง', 'Invalid Body format')
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
||||
// /**
|
||||
// * ✅ ตรวจสอบ body/query/params ว่ามีค่า organization หรือไม่
|
||||
// */
|
||||
// export function validateRequest(req, res, next) {
|
||||
// const { organization } = req.body || {}
|
||||
// if (!organization) {
|
||||
// return sendResponse(res, 400, 'ไม่พบค่า organization', 'Missing organization')
|
||||
// }
|
||||
// next()
|
||||
// }
|
||||
48
exthernal-accountingwep-api/src/routes/route.js
Normal file
48
exthernal-accountingwep-api/src/routes/route.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// ===================================================
|
||||
// ⚙️ route.js (Nuttakit Pattern vFinal++++)
|
||||
// ===================================================
|
||||
import express from 'express'
|
||||
import { loginController } from '../controllers/logincontroller.js'
|
||||
import { authMiddleware } from '../middlewares/auth.js'
|
||||
import { sendResponse } from '../utils/response.js'
|
||||
|
||||
const router = express.Router()
|
||||
const controller_login_post = new loginController()
|
||||
|
||||
// ===================================================
|
||||
// 🔹 LOGIN ปกติ
|
||||
// ===================================================
|
||||
router.post('/login', async (req, res) => {
|
||||
const data = await controller_login_post.onNavigate(req, res)
|
||||
if (data)
|
||||
return sendResponse(res, 200, 'เข้าสู่ระบบสำเร็จ', 'Login success', data)
|
||||
})
|
||||
|
||||
// ===================================================
|
||||
// 🔹 BIOMETRIC LOGIN
|
||||
// ===================================================
|
||||
router.post('/biometric/login', async (req, res) => {
|
||||
const data = await controller_login_post.onBiometricLogin(req, res)
|
||||
if (data)
|
||||
return sendResponse(res, 200, 'เข้าสู่ระบบผ่าน Biometric สำเร็จ', 'Biometric login succeed', data)
|
||||
})
|
||||
|
||||
// ===================================================
|
||||
// 🔹 BIOMETRIC REGISTER (ต้อง login ก่อน)
|
||||
// ===================================================
|
||||
router.post('/biometric/register', authMiddleware, async (req, res) => {
|
||||
const data = await controller_login_post.onBiometricRegister(req, res)
|
||||
if (data)
|
||||
return sendResponse(res, 200, 'ผูก Biometric สำเร็จ', 'Biometric registered', data)
|
||||
})
|
||||
|
||||
// ===================================================
|
||||
// 🔹 TOKEN RENEW (ต่ออายุ Token)
|
||||
// ===================================================
|
||||
router.post('/token/renew', authMiddleware, async (req, res) => {
|
||||
const data = await controller_login_post.onRenewToken(req, res)
|
||||
if (data)
|
||||
return sendResponse(res, 200, 'ออก Token ใหม่สำเร็จ', 'Token renewed', data)
|
||||
})
|
||||
|
||||
export default router
|
||||
122
exthernal-accountingwep-api/src/services/loginservice.js
Normal file
122
exthernal-accountingwep-api/src/services/loginservice.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import bcrypt from 'bcrypt'
|
||||
import { GeneralService } from '../share/generalservice.js'
|
||||
import { generateToken } from '../utils/token.js'
|
||||
// ===================================================
|
||||
// 📦 LoginService Class
|
||||
// ===================================================
|
||||
export class LoginService {
|
||||
// ===================================================
|
||||
// Zone 1️⃣ : Declaration & Constructor
|
||||
// ===================================================
|
||||
constructor() {
|
||||
this.generalService = new GeneralService()
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// 🔹 Verify Login — Username/Password
|
||||
// ===================================================
|
||||
async verifyLogin(database, username, password) {
|
||||
this.generalService.devhint(2, 'loginservice.js', `verifyLogin() start for username=${username}`)
|
||||
|
||||
// Zone 1️⃣ : Declaration
|
||||
let user = null
|
||||
let token = null
|
||||
|
||||
// Zone 2️⃣ : Query user
|
||||
let sql = `
|
||||
SELECT usrseq, usrnam, usrrol, usrpwd, usrthinam, usrthilstnam
|
||||
FROM ${database}.usrmst
|
||||
WHERE usrnam = $1
|
||||
`
|
||||
let params = [username] // ✅ ห้ามลืมเด็ดขาด
|
||||
const rows = await this.generalService.executeQueryParam(database, sql, params)
|
||||
this.generalService.devhint(3, 'loginservice.js', `query done, found=${rows.length}`)
|
||||
|
||||
if (rows.length === 0) {
|
||||
this.generalService.devhint(2, 'loginservice.js', 'no user found')
|
||||
return null
|
||||
}
|
||||
|
||||
// Zone 3️⃣ : Validate password
|
||||
user = rows[0]
|
||||
const match = await bcrypt.compare(password, user.usrpwd)
|
||||
if (!match) {
|
||||
this.generalService.devhint(2, 'loginservice.js', 'password mismatch')
|
||||
return null
|
||||
}
|
||||
|
||||
// Zone 4️⃣ : Generate JWT Token
|
||||
token = generateToken({
|
||||
id: user.usrseq,
|
||||
name: user.usrnam,
|
||||
role: user.usrrol,
|
||||
organization: database
|
||||
})
|
||||
this.generalService.devhint(2, 'loginservice.js', 'token generated successfully')
|
||||
|
||||
// Zone 5️⃣ : Return Raw Result
|
||||
return {
|
||||
token,
|
||||
...user
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// 🔹 Login ผ่าน Biometric
|
||||
// ===================================================
|
||||
async loginWithBiometric(database, biometric_id) {
|
||||
this.generalService.devhint(2, 'loginservice.js', `loginWithBiometric() start for biometric_id=${biometric_id}`)
|
||||
|
||||
// Zone 1️⃣ : Declaration
|
||||
let sql = ''
|
||||
let params = []
|
||||
|
||||
// Zone 2️⃣ : Query
|
||||
sql = `
|
||||
SELECT usrid, usrnam, usrrol
|
||||
FROM ${database}.usrmst
|
||||
WHERE biometric_id = $1
|
||||
`
|
||||
params = [biometric_id]
|
||||
const rows = await this.generalService.executeQueryParam(database, sql, params)
|
||||
if (rows.length === 0) {
|
||||
this.generalService.devhint(2, 'loginservice.js', 'no biometric found')
|
||||
return null
|
||||
}
|
||||
|
||||
// Zone 3️⃣ : Generate Token
|
||||
const user = rows[0]
|
||||
const token = generateToken({
|
||||
id: user.usrid,
|
||||
name: user.usrnam,
|
||||
role: user.usrrol,
|
||||
organization: database
|
||||
})
|
||||
|
||||
this.generalService.devhint(2, 'loginservice.js', 'biometric token generated')
|
||||
return { token, user }
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// 🔹 Register Biometric (หลัง login)
|
||||
// ===================================================
|
||||
async registerBiometric(database, usrid, biometric_id) {
|
||||
this.generalService.devhint(2, 'loginservice.js', `registerBiometric() start user=${usrid}`)
|
||||
|
||||
// Zone 1️⃣ : Declaration
|
||||
let sql = ''
|
||||
let params = []
|
||||
|
||||
// Zone 2️⃣ : Query
|
||||
sql = `
|
||||
UPDATE ${database}.usrmst
|
||||
SET biometric_id = $1
|
||||
WHERE usrid = $2
|
||||
`
|
||||
params = [biometric_id, usrid]
|
||||
await this.generalService.executeQueryParam(database, sql, params)
|
||||
|
||||
this.generalService.devhint(2, 'loginservice.js', 'biometric registered')
|
||||
return { message: 'Biometric registered successfully' }
|
||||
}
|
||||
}
|
||||
171
exthernal-accountingwep-api/src/share/generalservice.js
Normal file
171
exthernal-accountingwep-api/src/share/generalservice.js
Normal file
@@ -0,0 +1,171 @@
|
||||
import { connection } from '../config/db.js'
|
||||
import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
|
||||
export class GeneralService {
|
||||
// constructor() {
|
||||
// this.devhint = this.devhint.bind(this)
|
||||
// }
|
||||
devhint(level, location, message, extra = null) {
|
||||
const isEnabled = process.env.DEVHINT === 'true'
|
||||
const currentLevel = parseInt(process.env.DEVHINT_LEVEL || '1', 10)
|
||||
if (!isEnabled || level > currentLevel) return
|
||||
|
||||
const timestamp = new Date().toISOString()
|
||||
const prefix = `🧩 [DEVHINT:${location}]`
|
||||
const formatted = `${prefix} → ${message} (${timestamp})`
|
||||
if (extra) console.log(formatted, '\n', extra)
|
||||
else console.log(formatted)
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// ✅ executeQueryConditions()
|
||||
// ===================================================
|
||||
async executeQueryConditions(database, baseQuery, conditions = {}) {
|
||||
this.devhint(2, 'GeneralService', 'executeQueryConditions() start')
|
||||
|
||||
let whereClauses = []
|
||||
let params = []
|
||||
let idx = 1
|
||||
|
||||
for (const [key, value] of Object.entries(conditions)) {
|
||||
if (value === undefined || value === null || value === '') continue
|
||||
|
||||
const match = String(value).match(/^(ILIKE|LIKE)\s+(.+)$/i)
|
||||
if (match) {
|
||||
const operator = match[1].toUpperCase()
|
||||
const pattern = match[2].trim()
|
||||
whereClauses.push(`${key} ${operator} $${idx}`)
|
||||
params.push(pattern)
|
||||
} else {
|
||||
whereClauses.push(`${key} = $${idx}`)
|
||||
params.push(value)
|
||||
}
|
||||
idx++
|
||||
}
|
||||
|
||||
let finalQuery = baseQuery
|
||||
if (whereClauses.length > 0) finalQuery += ' AND ' + whereClauses.join(' AND ')
|
||||
const formattedSQL = finalQuery.replace(/\${database}/g, database)
|
||||
|
||||
this.devhint(2, 'executeQueryConditions', `📤 Executing Query`, {
|
||||
database,
|
||||
sql: formattedSQL,
|
||||
params,
|
||||
})
|
||||
|
||||
const result = await connection.query(formattedSQL, params)
|
||||
this.devhint(2, 'executeQueryConditions', `✅ Query Success (${result.rowCount} rows)`)
|
||||
return result.rows
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// ✅ executeQueryParam()
|
||||
// ===================================================
|
||||
// ===================================================
|
||||
async executeQueryParam(database, sql, params = []) {
|
||||
const formattedSQL = sql.replace(/\${database}/g, database)
|
||||
|
||||
this.devhint(2, 'executeQueryParam', `📤 Executing Query`, `sql = ${formattedSQL}`)
|
||||
|
||||
try {
|
||||
const result = await connection.query(formattedSQL, params)
|
||||
this.devhint(2, 'executeQueryParam', `✅ Query Success (${result.rowCount} rows)`)
|
||||
return result.rows
|
||||
} catch (err) {
|
||||
this.devhint(2, 'executeQueryParam', `❌ Query Failed`, err.message)
|
||||
console.error('SQL Error:', err)
|
||||
throw err // <– ส่งต่อ error เพื่อ controller จะจับได้
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// Export สำหรับ controller หรืออื่นๆ เรียกใช้ได้ด้วย
|
||||
// ===================================================
|
||||
|
||||
|
||||
|
||||
|
||||
// /**
|
||||
// * ✅ executeQueryParam (ของเดิม)
|
||||
// * ใช้กับ SQL + database schema + params
|
||||
// */
|
||||
// export async function executeQueryParam(sql, database, params = []) {
|
||||
// try {
|
||||
// if (!database) throw new Error('Database is not defined')
|
||||
|
||||
// const formattedSQL = sql.replace(/\${database}/g, database)
|
||||
// console.log(`[DB:${database}] → ${formattedSQL}`)
|
||||
// const result = await connection.query(formattedSQL, params)
|
||||
// return result.rows
|
||||
// } catch (err) {
|
||||
// console.error('[executeQueryParam Error]', err.message)
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* ✅ executeQueryConditions (ใหม่)
|
||||
* ใช้สร้าง WHERE อัตโนมัติจาก object เงื่อนไข
|
||||
* ตัวที่ไม่มีค่า (null, undefined, '') จะไม่ถูกนำมาสร้างใน WHERE
|
||||
*/
|
||||
// export async function executeQueryConditions(database, baseQuery, conditions = {}) {
|
||||
// try {
|
||||
// if (!database) throw new Error('Database is not defined')
|
||||
|
||||
// let whereClauses = []
|
||||
// let params = []
|
||||
// let idx = 1
|
||||
|
||||
// for (const [key, value] of Object.entries(conditions)) {
|
||||
// if (value === undefined || value === null || value === '') continue
|
||||
|
||||
// // ✅ ตรวจว่า value มีคำว่า LIKE หรือ ILIKE ไหม
|
||||
// const match = String(value).match(/^(ILIKE|LIKE)\s+(.+)$/i)
|
||||
// if (match) {
|
||||
// const operator = match[1].toUpperCase()
|
||||
// const pattern = match[2].trim()
|
||||
// whereClauses.push(`${key} ${operator} $${idx}`)
|
||||
// params.push(pattern)
|
||||
// } else {
|
||||
// whereClauses.push(`${key} = $${idx}`)
|
||||
// params.push(value)
|
||||
// }
|
||||
|
||||
// idx++
|
||||
// }
|
||||
|
||||
// let finalQuery = baseQuery
|
||||
// if (whereClauses.length > 0) {
|
||||
// finalQuery += ' AND ' + whereClauses.join(' AND ')
|
||||
// }
|
||||
|
||||
// const formattedSQL = finalQuery.replace(/\${database}/g, database)
|
||||
// console.log(`[DB:${database}] → ${formattedSQL}`)
|
||||
|
||||
// const result = await connection.query(formattedSQL, params)
|
||||
// return result.rows
|
||||
// } catch (err) {
|
||||
// console.error('[executeQueryConditions Error]', err.message)
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// /**
|
||||
// * 🧩 devhint — Debug tracer ที่เปิดปิดได้จาก .env
|
||||
// * @param {string} fileName - ชื่อไฟล์หรือโมดูล (เช่น 'usercontroller.js')
|
||||
// * @param {string} message - ข้อความหรือจุดใน flow (เช่น 'onNavigate')
|
||||
// * @param {object|string} [extra] - ข้อมูลเพิ่มเติม (optional)
|
||||
// */
|
||||
// export function devhint(fileName, message, extra = null) {
|
||||
// if (process.env.DEVHINT === 'true') {
|
||||
// const timestamp = new Date().toISOString()
|
||||
// const prefix = `🧩 [DEVHINT:${fileName}]`
|
||||
// const formatted = `${prefix} → ${message} (${timestamp})`
|
||||
// if (extra) console.log(formatted, '\n', extra)
|
||||
// else console.log(formatted)
|
||||
// }
|
||||
// }
|
||||
40
exthernal-accountingwep-api/src/utils/errorList.js
Normal file
40
exthernal-accountingwep-api/src/utils/errorList.js
Normal file
@@ -0,0 +1,40 @@
|
||||
// utils/errorList.js
|
||||
|
||||
export function manualError(key) {
|
||||
switch (key) {
|
||||
case "invalid_input":
|
||||
return {
|
||||
code: 400,
|
||||
messageTh: "ข้อมูลที่ส่งมาไม่ถูกต้อง",
|
||||
messageEn: "Invalid input data"
|
||||
};
|
||||
|
||||
case "not_found":
|
||||
return {
|
||||
code: 404,
|
||||
messageTh: "ไม่พบข้อมูลที่ร้องขอ",
|
||||
messageEn: "Resource not found"
|
||||
};
|
||||
|
||||
case "unauthorized":
|
||||
return {
|
||||
code: 401,
|
||||
messageTh: "คุณไม่มีสิทธิ์เข้าถึงข้อมูลนี้",
|
||||
messageEn: "Unauthorized access"
|
||||
};
|
||||
|
||||
case "server_error":
|
||||
return {
|
||||
code: 500,
|
||||
messageTh: "เกิดข้อผิดพลาดภายในระบบ",
|
||||
messageEn: "Internal server error"
|
||||
};
|
||||
|
||||
default:
|
||||
return {
|
||||
code: 500,
|
||||
messageTh: "ข้อผิดพลาดที่ไม่ทราบสาเหตุ",
|
||||
messageEn: "Unknown error occurred"
|
||||
};
|
||||
}
|
||||
}
|
||||
41
exthernal-accountingwep-api/src/utils/oftenError.js
Normal file
41
exthernal-accountingwep-api/src/utils/oftenError.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// utils/oftenError.js
|
||||
import { manualError } from "./errorList.js";
|
||||
|
||||
export class OftenError extends Error {
|
||||
/**
|
||||
* ใช้ได้ 2 แบบ:
|
||||
* 1. throw new OftenError("not_found")
|
||||
* 2. throw new OftenError(400, "ไทย", "English")
|
||||
*/
|
||||
constructor(arg1, arg2, arg3) {
|
||||
// แบบ lookup จาก key
|
||||
if (typeof arg1 === "string" && !arg2 && !arg3) {
|
||||
const found = manualError(arg1);
|
||||
super(found.messageEn);
|
||||
this.statusCode = found.code;
|
||||
this.messageTh = found.messageTh;
|
||||
this.messageEn = found.messageEn;
|
||||
this.key = arg1;
|
||||
}
|
||||
|
||||
// แบบ manual
|
||||
else if (typeof arg1 === "number" && arg2 && arg3) {
|
||||
super(arg3);
|
||||
this.statusCode = arg1;
|
||||
this.messageTh = arg2;
|
||||
this.messageEn = arg3;
|
||||
this.key = "manual";
|
||||
}
|
||||
|
||||
// fallback
|
||||
else {
|
||||
super("Invalid error format");
|
||||
this.statusCode = 500;
|
||||
this.messageTh = "รูปแบบการสร้าง error ไม่ถูกต้อง";
|
||||
this.messageEn = "Invalid error constructor format";
|
||||
this.key = "invalid_format";
|
||||
}
|
||||
|
||||
this.name = "OftenError";
|
||||
}
|
||||
}
|
||||
43
exthernal-accountingwep-api/src/utils/response.js
Normal file
43
exthernal-accountingwep-api/src/utils/response.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* sendResponse
|
||||
* ----------------------------------------------
|
||||
* ส่ง response แบบมาตรฐาน รองรับข้อความ 2 ภาษา
|
||||
* ----------------------------------------------
|
||||
* @param {object} res - Express response object
|
||||
* @param {number} status - HTTP Status code (200, 400, 500, etc.)
|
||||
* @param {string} msg_th - ข้อความภาษาไทย
|
||||
* @param {string} msg_en - ข้อความภาษาอังกฤษ
|
||||
* @param {any} [data=null] - optional data
|
||||
*/
|
||||
|
||||
// ===================================================
|
||||
// 🧩 Unified Response Handler (vFinal+)
|
||||
// ===================================================
|
||||
// ===================================================
|
||||
// 📁 src/utils/response.js
|
||||
// ===================================================
|
||||
export function sendResponse(res, status, msg_th = null, msg_en = null, data = null) {
|
||||
const safeData = safeJson(data)
|
||||
const success = status < 400
|
||||
const response = {
|
||||
status: success ? 'succeed' : 'error',
|
||||
message: {
|
||||
th: msg_th ?? (success ? 'สำเร็จ' : 'เกิดข้อผิดพลาด'),
|
||||
en: msg_en ?? (success ? 'Succeed' : 'Error')
|
||||
},
|
||||
data: safeData
|
||||
}
|
||||
res.status(status).json(response)
|
||||
}
|
||||
|
||||
// ✅ ป้องกัน circular reference
|
||||
function safeJson(obj) {
|
||||
try {
|
||||
if (obj && typeof obj === 'object') {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
}
|
||||
return obj
|
||||
} catch (err) {
|
||||
return '[Unserializable Object]'
|
||||
}
|
||||
}
|
||||
15
exthernal-accountingwep-api/src/utils/token.js
Normal file
15
exthernal-accountingwep-api/src/utils/token.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
|
||||
export function generateToken(payload) {
|
||||
return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '24h' })
|
||||
}
|
||||
|
||||
export function verifyToken(token) {
|
||||
try {
|
||||
return jwt.verify(token, process.env.JWT_SECRET)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
11
exthernal-accountingwep-api/src/utils/trim.js
Normal file
11
exthernal-accountingwep-api/src/utils/trim.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export function trim_all_array(data) {
|
||||
if (!Array.isArray(data)) return data
|
||||
for (let row of data) {
|
||||
for (let key in row) {
|
||||
if (typeof row[key] === 'string') {
|
||||
row[key] = row[key].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
Reference in New Issue
Block a user