-api เส้น register
-เครื่องมือต่างๆ (ไม่สมบูร)
This commit is contained in:
@@ -25,7 +25,7 @@ app.use((err, req, res, next) => {
|
|||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use('/api', router)
|
app.use('/api/login', router)
|
||||||
|
|
||||||
app.listen(process.env.PORT, () => {
|
app.listen(process.env.PORT, () => {
|
||||||
console.log(`✅ ${process.env.PJ_NAME} running on port ${process.env.PORT}`)
|
console.log(`✅ ${process.env.PJ_NAME} running on port ${process.env.PORT}`)
|
||||||
|
|||||||
@@ -33,35 +33,7 @@ export class loginController {
|
|||||||
} finally {
|
} finally {
|
||||||
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
||||||
if (!result) return sendError('ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง', 'Invalid credentials');
|
if (!result) return sendError('ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง', 'Invalid credentials');
|
||||||
if(result) { return 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() }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,72 @@
|
|||||||
import { RegisterService } from '../services/registerservice.js';
|
import { RegisterService } from '../services/registerservice.js'
|
||||||
import { sendError } from '../utils/response.js';
|
import { sendError } from '../utils/response.js'
|
||||||
import { GeneralService } from '../share/generalservice.js';
|
import { GeneralService } from '../share/generalservice.js';
|
||||||
|
import bcrypt from 'bcrypt'
|
||||||
|
import { Interface } from '../interfaces/Interface.js';
|
||||||
|
import { validateSave } from '../utils/validate.js'; // import เข้ามา
|
||||||
|
|
||||||
export class RegisterController {
|
export class RegisterController {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.registerService = new RegisterService();
|
|
||||||
this.generalService = new GeneralService();
|
this.generalService = new GeneralService();
|
||||||
|
this.registerService = new RegisterService();
|
||||||
|
this.Interface = new Interface();
|
||||||
}
|
}
|
||||||
|
|
||||||
async onNavigate(req, res) {
|
async onNavigate(req, res) {
|
||||||
let idx = -1, result = [];
|
this.generalService.devhint(1, 'registercontroller.js', 'onNavigate() start');
|
||||||
|
let organization = req.body.organization;
|
||||||
|
const prommis = await this.onRegisterController(req, res, organization);
|
||||||
|
return prommis;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onRegisterController(req, res, database) {
|
||||||
|
let idx = -1
|
||||||
|
let result = []
|
||||||
try {
|
try {
|
||||||
const { organization, request } = req.body;
|
// 1. ดึง Sequence ล่าสุดจาก Service (เพื่อเอามา +1)
|
||||||
const { email, fname, lname, password } = request;
|
const Seq = await this.registerService.genNum(database);
|
||||||
result = await this.registerService.requestRegistration(organization, email, fname, lname, password);
|
|
||||||
|
// 2. Hash Password
|
||||||
|
let passwordRaw = req.body.request.password;
|
||||||
|
const saltRounds = 10;
|
||||||
|
const hashedPassword = await bcrypt.hash(passwordRaw, saltRounds);
|
||||||
|
|
||||||
|
// 3. เรียก makeArySave เพื่อเตรียมข้อมูลและบันทึกผ่าน saveInterface
|
||||||
|
// ส่ง nextSeq และ hashedPassword เข้าไป
|
||||||
|
result = await this.makeArySave(req, Seq, hashedPassword);
|
||||||
|
|
||||||
|
this.generalService.devhint(1, 'registercontroller.js', 'Register success');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
idx = 1;
|
idx = 1;
|
||||||
this.generalService.devhint(1, 'registercontroller.js', 'Jumpout', error.message);
|
} finally {
|
||||||
result = error; // จะถูก Global Handler จัด format
|
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
||||||
} finally {
|
if (!result) return sendError('ไม่สามารถลงทะเบียนได้', 'Registration failed');
|
||||||
if (idx === 1) return result;
|
return result
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
async makeArySave(req, seq, hashedPassword) {
|
||||||
|
// Map ข้อมูลเข้า Field ตามตาราง usrmst
|
||||||
|
let arysave = {
|
||||||
|
methods: 'post', // สั่งให้ saveInterface ทำการ INSERT
|
||||||
|
usrseq: seq + 1, // PK: integer
|
||||||
|
usrnam: validateSave(req.body.request.username, 'username'),
|
||||||
|
usrpwd: hashedPassword, // character varying(255)
|
||||||
|
usreml: validateSave(req.body.request.email, 'Email'), // character varying(50)
|
||||||
|
usrthinam: validateSave(req.body.request.firstname, 'firstname'), // character varying(100)
|
||||||
|
usrthilstnam: validateSave(req.body.request.lastname, 'lastname'), // character varying(100)
|
||||||
|
|
||||||
|
// Field อื่นๆ ที่อาจต้อง Default ค่าไว้ก่อน (ตาม Schema)
|
||||||
|
usrrol: 'U', // Default User Role
|
||||||
|
adpdte: '', // Approved Date (รออนุมัติ)
|
||||||
|
expdte: '', // Expire Date
|
||||||
|
lstlgn: '', // Last Login
|
||||||
|
usrorg: req.body.request.organization, // ถ้ามี field นี้
|
||||||
|
}
|
||||||
|
|
||||||
|
// เรียก saveInterface ผ่าน generalService (ระบุชื่อตาราง 'usrmst')
|
||||||
|
return this.Interface.saveInterface('usrmst', arysave, req);
|
||||||
|
}
|
||||||
|
}
|
||||||
70
exthernal-login-api/src/interfaces/Interface.js
Normal file
70
exthernal-login-api/src/interfaces/Interface.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import jwt from 'jsonwebtoken'
|
||||||
|
import { BdgmstInterface } from './table/bdgmstInterface.js'
|
||||||
|
import { ActmstInterface } from './table/actmstInterface.js'
|
||||||
|
import { UsrmstInterface } from './table/usrmstInterface.js'
|
||||||
|
import { sendError } from '../utils/response.js'
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// GLOBAL FILE
|
||||||
|
// -----------------------------
|
||||||
|
|
||||||
|
export class Interface {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.map = {
|
||||||
|
bdgmst: new BdgmstInterface(),
|
||||||
|
actmst: new ActmstInterface(),
|
||||||
|
usrmst: new UsrmstInterface(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===============================================================
|
||||||
|
// saveInterface → แกะ token เอง และ route ไปยัง interface เฉพาะ table
|
||||||
|
// ===============================================================
|
||||||
|
async saveInterface(tableName, data, req) {
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// 1) จับ Interface ที่ตรงกับ table
|
||||||
|
// ------------------------------
|
||||||
|
const handler = this.map[tableName.toLowerCase()]
|
||||||
|
if (!handler) {
|
||||||
|
return new sendError(`Interface not found for table: ${tableName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema;
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// 2) ตรวจสอบเงื่อนไข (Exception for usrmst)
|
||||||
|
// ------------------------------
|
||||||
|
if (tableName.toLowerCase() === 'usrmst') {
|
||||||
|
// กรณี usrmst (เช่น Register/Login) ไม่บังคับ Token
|
||||||
|
// เราต้องกำหนด schema เอง เพราะไม่มี token ให้แกะ
|
||||||
|
schema = 'nuttakit'
|
||||||
|
} else {
|
||||||
|
// ------------------------------
|
||||||
|
// 3) ตารางอื่น ๆ บังคับ Token ตามปกติ
|
||||||
|
// ------------------------------
|
||||||
|
const token = req.headers.authorization?.split(' ')[1]
|
||||||
|
if (!token) {
|
||||||
|
return new sendError('ไม่พบการยืนยันตัวตน' ,'Missing token in request header')
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded
|
||||||
|
try {
|
||||||
|
decoded = jwt.verify(token, process.env.JWT_SECRET)
|
||||||
|
} catch (err) {
|
||||||
|
return new sendError('Invalid token: ' + err.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
schema = decoded.organization
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!schema) return new sendError("Token missing 'organization' field or Schema undefined")
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// ✔ 4) ส่งงานไปยัง interface ของ table นั้น ๆ
|
||||||
|
// ------------------------------
|
||||||
|
return await handler.saveInterface(schema, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
86
exthernal-login-api/src/interfaces/table/actmstInterface.js
Normal file
86
exthernal-login-api/src/interfaces/table/actmstInterface.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { GeneralService } from '../../share/generalservice.js'
|
||||||
|
|
||||||
|
export class ActmstInterface {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.general = new GeneralService()
|
||||||
|
this.table = 'actmst'
|
||||||
|
this.pk = ['actseq', 'actnum'] // ⭐ PK ตาม CREATE TABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveInterface(database, data) {
|
||||||
|
const method = data.methods.toLowerCase()
|
||||||
|
const { methods, ...payload } = data
|
||||||
|
|
||||||
|
if (method === 'put') return this.update(database, payload)
|
||||||
|
if (method === 'post') return this.insert(database, payload)
|
||||||
|
if (method === 'delete') return this.remove(database, payload)
|
||||||
|
|
||||||
|
throw new Error(`Unknown method: ${method}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async insert(database, payload) {
|
||||||
|
const cols = Object.keys(payload)
|
||||||
|
const vals = Object.values(payload)
|
||||||
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(', ')
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
INSERT INTO ${database}.${this.table} (${cols.join(', ')})
|
||||||
|
VALUES (${placeholders})
|
||||||
|
RETURNING *
|
||||||
|
`
|
||||||
|
return await this.general.executeQueryParam(database, sql, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(database, payload) {
|
||||||
|
const where = {}
|
||||||
|
const update = {}
|
||||||
|
|
||||||
|
for (const col in payload) {
|
||||||
|
if (this.pk.includes(col)) where[col] = payload[col]
|
||||||
|
else update[col] = payload[col]
|
||||||
|
}
|
||||||
|
|
||||||
|
const setCols = Object.keys(update)
|
||||||
|
.map((col, i) => `${col} = $${i + 1}`)
|
||||||
|
.join(', ')
|
||||||
|
|
||||||
|
const whereCols = Object.keys(where)
|
||||||
|
.map((col, i) => `${col} = $${i + 1 + Object.keys(update).length}`)
|
||||||
|
.join(' AND ')
|
||||||
|
|
||||||
|
const params = [...Object.values(update), ...Object.values(where)]
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
UPDATE ${database}.${this.table}
|
||||||
|
SET ${setCols}
|
||||||
|
WHERE ${whereCols}
|
||||||
|
RETURNING *
|
||||||
|
`
|
||||||
|
|
||||||
|
return await this.general.executeQueryParam(database, sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(database, payload) {
|
||||||
|
const where = {}
|
||||||
|
|
||||||
|
this.pk.forEach(pk => {
|
||||||
|
if (!payload[pk]) throw new Error(`Missing PK: ${pk}`)
|
||||||
|
where[pk] = payload[pk]
|
||||||
|
})
|
||||||
|
|
||||||
|
const whereCols = Object.keys(where)
|
||||||
|
.map((col, i) => `${col} = $${i + 1}`)
|
||||||
|
.join(' AND ')
|
||||||
|
|
||||||
|
const params = Object.values(where)
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
DELETE FROM ${database}.${this.table}
|
||||||
|
WHERE ${whereCols}
|
||||||
|
RETURNING *
|
||||||
|
`
|
||||||
|
|
||||||
|
return await this.general.executeQueryParam(database, sql, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
86
exthernal-login-api/src/interfaces/table/bdgmstInterface.js
Normal file
86
exthernal-login-api/src/interfaces/table/bdgmstInterface.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { GeneralService } from '../../share/generalservice.js'
|
||||||
|
|
||||||
|
export class BdgmstInterface {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.general = new GeneralService()
|
||||||
|
this.table = 'bdgmst'
|
||||||
|
this.pk = ['bdgseq']
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveInterface(database, data) {
|
||||||
|
const method = data.method.toLowerCase()
|
||||||
|
const payload = { ...data }
|
||||||
|
delete payload.method
|
||||||
|
|
||||||
|
if (method === 'put') return this.update(database, payload)
|
||||||
|
if (method === 'post') return this.insert(database, payload)
|
||||||
|
if (method === 'delete') return this.remove(database, payload)
|
||||||
|
|
||||||
|
throw new Error(`Unknown method: ${method}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async insert(database, payload) {
|
||||||
|
const cols = Object.keys(payload)
|
||||||
|
const vals = Object.values(payload)
|
||||||
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(', ')
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
INSERT INTO ${database}.${this.table} (${cols.join(', ')})
|
||||||
|
VALUES (${placeholders})
|
||||||
|
RETURNING *
|
||||||
|
`
|
||||||
|
return await this.general.executeQueryParam(database, sql, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(database, payload) {
|
||||||
|
const where = {}
|
||||||
|
const update = {}
|
||||||
|
|
||||||
|
for (const col in payload) {
|
||||||
|
if (this.pk.includes(col)) where[col] = payload[col]
|
||||||
|
else update[col] = payload[col]
|
||||||
|
}
|
||||||
|
|
||||||
|
const setCols = Object.keys(update)
|
||||||
|
.map((col, i) => `${col} = $${i + 1}`)
|
||||||
|
.join(', ')
|
||||||
|
|
||||||
|
const whereCols = Object.keys(where)
|
||||||
|
.map((col, i) => `${col} = $${i + 1 + Object.keys(update).length}`)
|
||||||
|
.join(' AND ')
|
||||||
|
|
||||||
|
const params = [...Object.values(update), ...Object.values(where)]
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
UPDATE ${database}.${this.table}
|
||||||
|
SET ${setCols}
|
||||||
|
WHERE ${whereCols}
|
||||||
|
RETURNING *
|
||||||
|
`
|
||||||
|
|
||||||
|
return await this.general.executeQueryParam(database, sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(database, payload) {
|
||||||
|
const where = {}
|
||||||
|
this.pk.forEach(pk => {
|
||||||
|
if (!payload[pk]) throw new Error(`Missing PK: ${pk}`)
|
||||||
|
where[pk] = payload[pk]
|
||||||
|
})
|
||||||
|
|
||||||
|
const whereCols = Object.keys(where)
|
||||||
|
.map((col, i) => `${col} = $${i + 1}`)
|
||||||
|
.join(' AND ')
|
||||||
|
|
||||||
|
const params = Object.values(where)
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
DELETE FROM ${database}.${this.table}
|
||||||
|
WHERE ${whereCols}
|
||||||
|
RETURNING *
|
||||||
|
`
|
||||||
|
|
||||||
|
return await this.general.executeQueryParam(database, sql, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
86
exthernal-login-api/src/interfaces/table/usrmstInterface.js
Normal file
86
exthernal-login-api/src/interfaces/table/usrmstInterface.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { GeneralService } from '../../share/generalservice.js'
|
||||||
|
|
||||||
|
export class UsrmstInterface {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.general = new GeneralService()
|
||||||
|
this.table = 'usrmst'
|
||||||
|
this.pk = ['usrseq'] // ⭐ PK ตาม CREATE TABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveInterface(database, data) {
|
||||||
|
const method = data.methods.toLowerCase()
|
||||||
|
const { methods, ...payload } = data
|
||||||
|
|
||||||
|
if (method === 'put') return this.update(database, payload)
|
||||||
|
if (method === 'post') return this.insert(database, payload)
|
||||||
|
if (method === 'delete') return this.remove(database, payload)
|
||||||
|
|
||||||
|
throw new Error(`Unknown method: ${method}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async insert(database, payload) {
|
||||||
|
const cols = Object.keys(payload)
|
||||||
|
const vals = Object.values(payload)
|
||||||
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(', ')
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
INSERT INTO ${database}.${this.table} (${cols.join(', ')})
|
||||||
|
VALUES (${placeholders})
|
||||||
|
RETURNING *
|
||||||
|
`
|
||||||
|
return await this.general.executeQueryParam(database, sql, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(database, payload) {
|
||||||
|
const where = {}
|
||||||
|
const update = {}
|
||||||
|
|
||||||
|
for (const col in payload) {
|
||||||
|
if (this.pk.includes(col)) where[col] = payload[col]
|
||||||
|
else update[col] = payload[col]
|
||||||
|
}
|
||||||
|
|
||||||
|
const setCols = Object.keys(update)
|
||||||
|
.map((col, i) => `${col} = $${i + 1}`)
|
||||||
|
.join(', ')
|
||||||
|
|
||||||
|
const whereCols = Object.keys(where)
|
||||||
|
.map((col, i) => `${col} = $${i + 1 + Object.keys(update).length}`)
|
||||||
|
.join(' AND ')
|
||||||
|
|
||||||
|
const params = [...Object.values(update), ...Object.values(where)]
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
UPDATE ${database}.${this.table}
|
||||||
|
SET ${setCols}
|
||||||
|
WHERE ${whereCols}
|
||||||
|
RETURNING *
|
||||||
|
`
|
||||||
|
|
||||||
|
return await this.general.executeQueryParam(database, sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(database, payload) {
|
||||||
|
const where = {}
|
||||||
|
|
||||||
|
this.pk.forEach(pk => {
|
||||||
|
if (!payload[pk]) throw new Error(`Missing PK: ${pk}`)
|
||||||
|
where[pk] = payload[pk]
|
||||||
|
})
|
||||||
|
|
||||||
|
const whereCols = Object.keys(where)
|
||||||
|
.map((col, i) => `${col} = $${i + 1}`)
|
||||||
|
.join(' AND ')
|
||||||
|
|
||||||
|
const params = Object.values(where)
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
DELETE FROM ${database}.${this.table}
|
||||||
|
WHERE ${whereCols}
|
||||||
|
RETURNING *
|
||||||
|
`
|
||||||
|
|
||||||
|
return await this.general.executeQueryParam(database, sql, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,18 +18,18 @@ const controller_login_post = new loginController()
|
|||||||
const otpController = new OtpController()
|
const otpController = new OtpController()
|
||||||
const otpVerifyController = new OtpVerifyController()
|
const otpVerifyController = new OtpVerifyController()
|
||||||
|
|
||||||
router.post('/login/login', async (req, res) => {const result = await controller_login_post.onNavigate(req, res); return res.json(result)})
|
router.post('/login', async (req, res) => {const result = await controller_login_post.onNavigate(req, res); return res.json(result)})
|
||||||
|
|
||||||
router.post('/login/otp/send', async (req, res) => {
|
router.post('/otp/send', async (req, res) => {
|
||||||
const result = await otpController.onSendOtp(req, res)
|
const result = await otpController.onSendOtp(req, res)
|
||||||
if (result) return res.json(result)
|
if (result) return res.json(result)
|
||||||
})
|
})
|
||||||
router.post('/login/register', async (req, res) => {
|
router.post('/register', async (req, res) => {
|
||||||
const result = await registerController.onNavigate(req, res);
|
const result = await registerController.onNavigate(req, res);
|
||||||
if (result) return res.json(result);
|
if (result) return res.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/login/verify-email', async (req, res) => {
|
router.get('/verify-email', async (req, res) => {
|
||||||
const result = await verifyEmailController.onVerifyEmail(req, res);
|
const result = await verifyEmailController.onVerifyEmail(req, res);
|
||||||
if (result?.code && result.code !== '200') return res.status(400).send(result.message_th);
|
if (result?.code && result.code !== '200') return res.status(400).send(result.message_th);
|
||||||
// ถ้า controller ส่ง HTML string กลับมา → render ตรง ๆ
|
// ถ้า controller ส่ง HTML string กลับมา → render ตรง ๆ
|
||||||
@@ -38,13 +38,13 @@ router.get('/login/verify-email', async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
router.post('/login/reset-password', async (req, res) => {
|
router.post('/reset-password', async (req, res) => {
|
||||||
const result = await resetPasswordController.onNavigate(req, res);
|
const result = await resetPasswordController.onNavigate(req, res);
|
||||||
if (result) return res.json(result);
|
if (result) return res.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
router.post('/login/otp/verify', async (req, res) => {
|
router.post('/otp/verify', async (req, res) => {
|
||||||
const result = await otpVerifyController.onNavigate(req, res)
|
const result = await otpVerifyController.onNavigate(req, res)
|
||||||
if (result) return res.json(result)
|
if (result) return res.json(result)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export class LoginService {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.generalService = new GeneralService()
|
this.generalService = new GeneralService()
|
||||||
}
|
}
|
||||||
async verifyLogin(database, username, password) {
|
async verifyLogin(database, username) {
|
||||||
this.generalService.devhint(2, 'loginservice.js', `verifyLogin() start for username=${username}`)
|
this.generalService.devhint(2, 'loginservice.js', `verifyLogin() start for username=${username}`)
|
||||||
|
|
||||||
let user = null
|
let user = null
|
||||||
@@ -15,7 +15,7 @@ export class LoginService {
|
|||||||
let sql = `
|
let sql = `
|
||||||
SELECT usrseq, usrnam, usrorg, usrrol, usrpwd, usrthinam, usrthilstnam
|
SELECT usrseq, usrnam, usrorg, usrrol, usrpwd, usrthinam, usrthilstnam
|
||||||
FROM nuttakit.usrmst
|
FROM nuttakit.usrmst
|
||||||
WHERE usrnam = $1
|
WHERE usrnam = $1 OR (usreml = $1)
|
||||||
`
|
`
|
||||||
let params = [username]
|
let params = [username]
|
||||||
const rows = await this.generalService.executeQueryParam(database, sql, params)
|
const rows = await this.generalService.executeQueryParam(database, sql, params)
|
||||||
|
|||||||
88
exthernal-login-api/src/services/registerservice OLD.js
Normal file
88
exthernal-login-api/src/services/registerservice OLD.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import { GeneralService } from '../share/generalservice.js';
|
||||||
|
import { sendError } from '../utils/response.js';
|
||||||
|
import redis from '../utils/redis.js';
|
||||||
|
|
||||||
|
export class RegisterService {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// this.redis = new Redis();
|
||||||
|
this.generalService = new GeneralService();
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestRegistration(database, email, fname, lname, password) {
|
||||||
|
let result = [];
|
||||||
|
try {
|
||||||
|
let sql = `
|
||||||
|
SELECT usrseq FROM ${database}.usrmst WHERE usrnam = $1
|
||||||
|
`;
|
||||||
|
let param = [email];
|
||||||
|
const userCheck = await this.generalService.executeQueryParam(database, sql, param);
|
||||||
|
|
||||||
|
if (userCheck.length > 0) {
|
||||||
|
this.generalService.devhint(1, 'registerservice.js', `❌ Duplicate email (${email})`);
|
||||||
|
throw sendError('อีเมลนี้ถูกใช้แล้ว', 'Email already registered');
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedPwd = await bcrypt.hash(password, 10);
|
||||||
|
const token = crypto.randomBytes(32).toString('hex');
|
||||||
|
|
||||||
|
|
||||||
|
const payload = JSON.stringify({ fname, lname, hashedPwd, token, database });
|
||||||
|
await redis.set(`verify:${email}`, payload, 'EX', 86400); // 24h
|
||||||
|
|
||||||
|
|
||||||
|
const verifyUrl = `http://localhost:1012/login/verify-email?token=${token}&email=${encodeURIComponent(email)}&organization=${database}`;
|
||||||
|
await this.sendVerifyEmail(email, verifyUrl);
|
||||||
|
|
||||||
|
this.generalService.devhint(2, 'registerservice.js', `✅ Verify link sent to ${email}`);
|
||||||
|
|
||||||
|
result = {
|
||||||
|
code: '200',
|
||||||
|
message_th: 'ส่งลิงก์ยืนยันอีเมลแล้ว',
|
||||||
|
data: {}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.generalService.devhint(1, 'registerservice.js', '❌ Registration Error', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendVerifyEmail(email, verifyUrl) {
|
||||||
|
try {
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
service: 'gmail',
|
||||||
|
auth: {
|
||||||
|
user: process.env.SMTP_USER,
|
||||||
|
pass: process.env.SMTP_PASS,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const html = `
|
||||||
|
<div style="font-family: sans-serif;">
|
||||||
|
<h2>ยืนยันการสมัครสมาชิก</h2>
|
||||||
|
<p>กรุณากดยืนยันภายใน 24 ชั่วโมง เพื่อเปิดใช้งานบัญชีของคุณ</p>
|
||||||
|
<a href="${verifyUrl}"
|
||||||
|
style="display:inline-block;background:#0078d4;color:white;
|
||||||
|
padding:10px 20px;text-decoration:none;border-radius:5px;">ยืนยันอีเมล</a>
|
||||||
|
<p style="margin-top:16px;font-size:13px;color:#555;">หากคุณไม่ได้สมัคร โปรดละเว้นอีเมลนี้</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: `"System" <${process.env.SMTP_USER}>`,
|
||||||
|
to: email,
|
||||||
|
subject: '📩 ยืนยันอีเมลสำหรับสมัครสมาชิก',
|
||||||
|
html,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.generalService.devhint(2, 'registerservice.js', `📤 Verification email sent (${email})`);
|
||||||
|
} catch (error) {
|
||||||
|
this.generalService.devhint(1, 'registerservice.js', '❌ Email Send Failed', error.message);
|
||||||
|
throw sendError('ไม่สามารถส่งอีเมลได้', 'Email send failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,88 +1,57 @@
|
|||||||
import bcrypt from 'bcrypt';
|
import { GeneralService } from '../share/generalservice.js'
|
||||||
import crypto from 'crypto';
|
import bcrypt from 'bcrypt'
|
||||||
import nodemailer from 'nodemailer';
|
|
||||||
import { GeneralService } from '../share/generalservice.js';
|
|
||||||
import { sendError } from '../utils/response.js';
|
|
||||||
import redis from '../utils/redis.js';
|
|
||||||
|
|
||||||
export class RegisterService {
|
export class RegisterService {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// this.redis = new Redis();
|
this.generalService = new GeneralService()
|
||||||
this.generalService = new GeneralService();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestRegistration(database, email, fname, lname, password) {
|
async createUser(database, userData) {
|
||||||
let result = [];
|
|
||||||
|
// 1. ทำการ Hash Password
|
||||||
|
const saltRounds = 10;
|
||||||
|
const hashedPassword = await bcrypt.hash(userData.password, saltRounds);
|
||||||
|
|
||||||
|
// 2. เตรียม SQL
|
||||||
|
const sql = `
|
||||||
|
INSERT INTO ${database}.usrmst
|
||||||
|
(username, password, email, firstname, lastname, created_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, NOW())
|
||||||
|
`
|
||||||
|
|
||||||
|
// 3. ใช้ hashedPassword แทน password เดิม
|
||||||
|
const params = [
|
||||||
|
userData.username,
|
||||||
|
hashedPassword, // ส่งตัวที่ Hash แล้วเข้า DB
|
||||||
|
userData.email,
|
||||||
|
userData.firstname,
|
||||||
|
userData.lastname
|
||||||
|
]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let sql = `
|
const result = await this.generalService.executeQueryParam(database, sql, params);
|
||||||
SELECT usrseq FROM ${database}.usrmst WHERE usrnam = $1
|
|
||||||
`;
|
// เช็คผลลัพธ์ตาม Structure ของ GeneralService
|
||||||
let param = [email];
|
// สมมติว่าถ้า Error ตัว executeQueryParam อาจจะ throw หรือ return null
|
||||||
const userCheck = await this.generalService.executeQueryParam(database, sql, param);
|
return { status: true, message: 'Registration successful' };
|
||||||
|
|
||||||
if (userCheck.length > 0) {
|
|
||||||
this.generalService.devhint(1, 'registerservice.js', `❌ Duplicate email (${email})`);
|
|
||||||
throw sendError('อีเมลนี้ถูกใช้แล้ว', 'Email already registered');
|
|
||||||
}
|
|
||||||
|
|
||||||
const hashedPwd = await bcrypt.hash(password, 10);
|
|
||||||
const token = crypto.randomBytes(32).toString('hex');
|
|
||||||
|
|
||||||
|
|
||||||
const payload = JSON.stringify({ fname, lname, hashedPwd, token, database });
|
|
||||||
await redis.set(`verify:${email}`, payload, 'EX', 86400); // 24h
|
|
||||||
|
|
||||||
|
|
||||||
const verifyUrl = `http://localhost:1012/login/verify-email?token=${token}&email=${encodeURIComponent(email)}&organization=${database}`;
|
|
||||||
await this.sendVerifyEmail(email, verifyUrl);
|
|
||||||
|
|
||||||
this.generalService.devhint(2, 'registerservice.js', `✅ Verify link sent to ${email}`);
|
|
||||||
|
|
||||||
result = {
|
|
||||||
code: '200',
|
|
||||||
message_th: 'ส่งลิงก์ยืนยันอีเมลแล้ว',
|
|
||||||
data: {}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.generalService.devhint(1, 'registerservice.js', '❌ Registration Error', error.message);
|
console.error('Register Service Error:', error);
|
||||||
throw error;
|
return null;
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendVerifyEmail(email, verifyUrl) {
|
|
||||||
try {
|
|
||||||
const transporter = nodemailer.createTransport({
|
|
||||||
service: 'gmail',
|
|
||||||
auth: {
|
|
||||||
user: process.env.SMTP_USER,
|
|
||||||
pass: process.env.SMTP_PASS,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<div style="font-family: sans-serif;">
|
|
||||||
<h2>ยืนยันการสมัครสมาชิก</h2>
|
|
||||||
<p>กรุณากดยืนยันภายใน 24 ชั่วโมง เพื่อเปิดใช้งานบัญชีของคุณ</p>
|
|
||||||
<a href="${verifyUrl}"
|
|
||||||
style="display:inline-block;background:#0078d4;color:white;
|
|
||||||
padding:10px 20px;text-decoration:none;border-radius:5px;">ยืนยันอีเมล</a>
|
|
||||||
<p style="margin-top:16px;font-size:13px;color:#555;">หากคุณไม่ได้สมัคร โปรดละเว้นอีเมลนี้</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
await transporter.sendMail({
|
|
||||||
from: `"System" <${process.env.SMTP_USER}>`,
|
|
||||||
to: email,
|
|
||||||
subject: '📩 ยืนยันอีเมลสำหรับสมัครสมาชิก',
|
|
||||||
html,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.generalService.devhint(2, 'registerservice.js', `📤 Verification email sent (${email})`);
|
|
||||||
} catch (error) {
|
|
||||||
this.generalService.devhint(1, 'registerservice.js', '❌ Email Send Failed', error.message);
|
|
||||||
throw sendError('ไม่สามารถส่งอีเมลได้', 'Email send failed');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
async genNum(database) {
|
||||||
|
const sql = `
|
||||||
|
SELECT
|
||||||
|
MAX(usrseq) as max_seq
|
||||||
|
FROM nuttakit.usrmst
|
||||||
|
`
|
||||||
|
const params = []
|
||||||
|
const aryResult = await this.generalService.executeQueryParam(database, sql, params);
|
||||||
|
|
||||||
|
const lastSeq = aryResult[0]?.max_seq || 0;
|
||||||
|
|
||||||
|
return lastSeq + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
// ===================================================
|
|
||||||
// ⚙️ Nuttakit Response Layer vFinal++++++
|
|
||||||
// ===================================================
|
|
||||||
|
|
||||||
export function sendError(thMsg = 'เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', enMsg = 'Unexpected error', code = 400) {
|
export function sendError(thMsg = 'เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', enMsg = 'Unexpected error', code = 400, data = []) {
|
||||||
return {
|
return {
|
||||||
code: String(code),
|
code: String(code),
|
||||||
message: enMsg,
|
message: enMsg,
|
||||||
message_th: thMsg,
|
message_th: thMsg,
|
||||||
data: []
|
data: data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================
|
|
||||||
// 🔹 Auto Success Response (ใช้โดย Global Handler เท่านั้น)
|
|
||||||
// ===================================================
|
|
||||||
export function formatSuccessResponse(data) {
|
export function formatSuccessResponse(data) {
|
||||||
return {
|
return {
|
||||||
code: "200",
|
code: "200",
|
||||||
@@ -21,4 +15,4 @@ export function formatSuccessResponse(data) {
|
|||||||
message_th: "ดำเนินการสำเร็จ",
|
message_th: "ดำเนินการสำเร็จ",
|
||||||
data: data || null
|
data: data || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
25
exthernal-login-api/src/utils/validate.js
Normal file
25
exthernal-login-api/src/utils/validate.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { sendError } from './response.js';
|
||||||
|
|
||||||
|
export const validateSave = (value, columnName) => {
|
||||||
|
// เช็คว่าค่าเป็น null, undefined หรือ empty string
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
|
||||||
|
// สร้างก้อน data ที่จะบอกว่า column ไหนหายไป
|
||||||
|
// ตามโจทย์: data: { "email": "ไม่พบข้อมูล" }
|
||||||
|
const errorDetail = {};
|
||||||
|
errorDetail[columnName] = "ไม่พบข้อมูล";
|
||||||
|
|
||||||
|
// เรียก sendError ใส่ message และ errorDetail ลงไปใน parameter ตัวที่ 4
|
||||||
|
sendError(
|
||||||
|
'ข้อมูลพารามิเตอร์ ไม่ถูกต้อง', // thMsg
|
||||||
|
'Invalid Parameter', // enMsg
|
||||||
|
400, // code
|
||||||
|
errorDetail // data
|
||||||
|
);
|
||||||
|
|
||||||
|
// ปาลูกระเบิดออกไปให้ Controller รับ
|
||||||
|
// throw errorObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user