-api เส้น register
-เครื่องมือต่างๆ (ไม่สมบูร)
This commit is contained in:
@@ -25,7 +25,7 @@ app.use((err, req, res, next) => {
|
||||
next()
|
||||
})
|
||||
|
||||
app.use('/api', router)
|
||||
app.use('/api/login', router)
|
||||
|
||||
app.listen(process.env.PORT, () => {
|
||||
console.log(`✅ ${process.env.PJ_NAME} running on port ${process.env.PORT}`)
|
||||
|
||||
@@ -33,35 +33,7 @@ export class loginController {
|
||||
} finally {
|
||||
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
||||
if (!result) return sendError('ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง', 'Invalid credentials');
|
||||
if(result) { return result }
|
||||
return null
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// 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 { sendError } from '../utils/response.js';
|
||||
import { RegisterService } from '../services/registerservice.js'
|
||||
import { sendError } from '../utils/response.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 {
|
||||
|
||||
constructor() {
|
||||
this.registerService = new RegisterService();
|
||||
this.generalService = new GeneralService();
|
||||
this.registerService = new RegisterService();
|
||||
this.Interface = new Interface();
|
||||
}
|
||||
|
||||
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 {
|
||||
const { organization, request } = req.body;
|
||||
const { email, fname, lname, password } = request;
|
||||
result = await this.registerService.requestRegistration(organization, email, fname, lname, password);
|
||||
// 1. ดึง Sequence ล่าสุดจาก Service (เพื่อเอามา +1)
|
||||
const Seq = await this.registerService.genNum(database);
|
||||
|
||||
// 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) {
|
||||
idx = 1;
|
||||
this.generalService.devhint(1, 'registercontroller.js', 'Jumpout', error.message);
|
||||
result = error; // จะถูก Global Handler จัด format
|
||||
} finally {
|
||||
if (idx === 1) return result;
|
||||
return result;
|
||||
} finally {
|
||||
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
||||
if (!result) return sendError('ไม่สามารถลงทะเบียนได้', 'Registration failed');
|
||||
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 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)
|
||||
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);
|
||||
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);
|
||||
if (result?.code && result.code !== '200') return res.status(400).send(result.message_th);
|
||||
// ถ้า 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);
|
||||
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)
|
||||
if (result) return res.json(result)
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ export class LoginService {
|
||||
constructor() {
|
||||
this.generalService = new GeneralService()
|
||||
}
|
||||
async verifyLogin(database, username, password) {
|
||||
async verifyLogin(database, username) {
|
||||
this.generalService.devhint(2, 'loginservice.js', `verifyLogin() start for username=${username}`)
|
||||
|
||||
let user = null
|
||||
@@ -15,7 +15,7 @@ export class LoginService {
|
||||
let sql = `
|
||||
SELECT usrseq, usrnam, usrorg, usrrol, usrpwd, usrthinam, usrthilstnam
|
||||
FROM nuttakit.usrmst
|
||||
WHERE usrnam = $1
|
||||
WHERE usrnam = $1 OR (usreml = $1)
|
||||
`
|
||||
let params = [username]
|
||||
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 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';
|
||||
import { GeneralService } from '../share/generalservice.js'
|
||||
import bcrypt from 'bcrypt'
|
||||
|
||||
export class RegisterService {
|
||||
|
||||
constructor() {
|
||||
// this.redis = new Redis();
|
||||
this.generalService = new GeneralService();
|
||||
this.generalService = new GeneralService()
|
||||
}
|
||||
|
||||
async requestRegistration(database, email, fname, lname, password) {
|
||||
let result = [];
|
||||
async createUser(database, userData) {
|
||||
|
||||
// 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 {
|
||||
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: {}
|
||||
};
|
||||
const result = await this.generalService.executeQueryParam(database, sql, params);
|
||||
|
||||
// เช็คผลลัพธ์ตาม Structure ของ GeneralService
|
||||
// สมมติว่าถ้า Error ตัว executeQueryParam อาจจะ throw หรือ return null
|
||||
return { status: true, message: 'Registration successful' };
|
||||
} 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');
|
||||
console.error('Register Service Error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
code: String(code),
|
||||
message: enMsg,
|
||||
message_th: thMsg,
|
||||
data: []
|
||||
data: data
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
// 🔹 Auto Success Response (ใช้โดย Global Handler เท่านั้น)
|
||||
// ===================================================
|
||||
export function formatSuccessResponse(data) {
|
||||
return {
|
||||
code: "200",
|
||||
@@ -21,4 +15,4 @@ export function formatSuccessResponse(data) {
|
||||
message_th: "ดำเนินการสำเร็จ",
|
||||
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