xxx
This commit is contained in:
150
backend/src/db/sequelize.js
Normal file → Executable file
150
backend/src/db/sequelize.js
Normal file → Executable file
@@ -5,11 +5,7 @@
|
||||
import { Sequelize } from "sequelize";
|
||||
import { config } from "../config.js";
|
||||
|
||||
export const sequelize = new Sequelize(
|
||||
config.DB.NAME,
|
||||
config.DB.USER,
|
||||
config.DB.PASS,
|
||||
{
|
||||
export const sequelize = new Sequelize(config.DB.NAME, config.DB.USER, config.DB.PASS, {
|
||||
host: config.DB.HOST,
|
||||
port: config.DB.PORT,
|
||||
dialect: "mariadb",
|
||||
@@ -17,79 +13,91 @@ export const sequelize = new Sequelize(
|
||||
dialectOptions: { timezone: "Z" },
|
||||
define: { freezeTableName: true, underscored: false, timestamps: false },
|
||||
pool: { max: 10, min: 0, idle: 10000 },
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// --- 1. ประกาศตัวแปรสำหรับ Export Model ทั้งหมด ---
|
||||
export let User = null;
|
||||
export let Role = null;
|
||||
export let Permission = null;
|
||||
export let UserRole = null;
|
||||
export let RolePermission = null;
|
||||
export let Project = null; // <-- เพิ่มเข้ามา
|
||||
export let UserProjectRole = null; // <-- เพิ่มเข้ามา
|
||||
export let User, Role, Permission, Organization, Project, UserRole, RolePermission,
|
||||
UserProjectRole, Correspondence, CorrespondenceVersion, Document, CorrDocumentMap,
|
||||
Drawing, DrawingRevision, FileObject, RFA, RFARevision, RfaDrawingMap,
|
||||
Transmittal, TransmittalItem, Volume, ContractDwg, SubCategory;
|
||||
|
||||
if (process.env.ENABLE_SEQUELIZE === "1") {
|
||||
// --- 2. โหลดโมเดลทั้งหมดแบบ on-demand ---
|
||||
const mdlUser = await import("./models/User.js").catch(() => null);
|
||||
const mdlRole = await import("./models/Role.js").catch(() => null);
|
||||
const mdlPerm = await import("./models/Permission.js").catch(() => null);
|
||||
const mdlUR = await import("./models/UserRole.js").catch(() => null);
|
||||
const mdlRP = await import("./models/RolePermission.js").catch(() => null);
|
||||
const mdlProj = await import("./models/Project.js").catch(() => null); // <-- เพิ่มเข้ามา
|
||||
const mdlUPR = await import("./models/UserProjectRole.js").catch(() => null); // <-- เพิ่มเข้ามา
|
||||
// --- 2. สร้าง Object ของ Models ทั้งหมดที่จะโหลด ---
|
||||
const modelsToLoad = {
|
||||
User: await import("./models/User.js").catch(() => null),
|
||||
Role: await import("./models/Role.js").catch(() => null),
|
||||
Permission: await import("./models/Permission.js").catch(() => null),
|
||||
Organization: await import("./models/Organization.js").catch(() => null),
|
||||
Project: await import("./models/Project.js").catch(() => null),
|
||||
UserRole: await import("./models/UserRole.js").catch(() => null),
|
||||
RolePermission: await import("./models/RolePermission.js").catch(() => null),
|
||||
UserProjectRole: await import("./models/UserProjectRole.js").catch(() => null),
|
||||
Correspondence: await import("./models/Correspondence.js").catch(() => null),
|
||||
CorrespondenceVersion: await import("./models/CorrespondenceVersion.js").catch(() => null),
|
||||
Document: await import("./models/Document.js").catch(() => null),
|
||||
CorrDocumentMap: await import("./models/CorrDocumentMap.js").catch(() => null),
|
||||
Drawing: await import("./models/Drawing.js").catch(() => null),
|
||||
DrawingRevision: await import("./models/DrawingRevision.js").catch(() => null),
|
||||
FileObject: await import("./models/FileObject.js").catch(() => null),
|
||||
RFA: await import("./models/RFA.js").catch(() => null),
|
||||
RFARevision: await import("./models/RFARevision.js").catch(() => null),
|
||||
RfaDrawingMap: await import("./models/RfaDrawingMap.js").catch(() => null),
|
||||
Transmittal: await import("./models/Transmittal.js").catch(() => null),
|
||||
TransmittalItem: await import("./models/TransmittalItem.js").catch(() => null),
|
||||
Volume: await import("./models/Volume.js").catch(() => null),
|
||||
ContractDwg: await import("./models/ContractDwg.js").catch(() => null),
|
||||
SubCategory: await import("./models/SubCategory.js").catch(() => null),
|
||||
};
|
||||
|
||||
// --- 3. Initialize Model ทั้งหมด ---
|
||||
if (mdlUser?.default) User = mdlUser.default(sequelize);
|
||||
if (mdlRole?.default) Role = mdlRole.default(sequelize);
|
||||
if (mdlPerm?.default) Permission = mdlPerm.default(sequelize);
|
||||
if (mdlUR?.default) UserRole = mdlUR.default(sequelize);
|
||||
if (mdlRP?.default) RolePermission = mdlRP.default(sequelize);
|
||||
if (mdlProj?.default) Project = mdlProj.default(sequelize); // <-- เพิ่มเข้ามา
|
||||
if (mdlUPR?.default) UserProjectRole = mdlUPR.default(sequelize); // <-- เพิ่มเข้ามา
|
||||
// --- 3. Initialize Model ทั้งหมด ---
|
||||
User = modelsToLoad.User?.default ? modelsToLoad.User.default(sequelize) : null;
|
||||
Role = modelsToLoad.Role?.default ? modelsToLoad.Role.default(sequelize) : null;
|
||||
Permission = modelsToLoad.Permission?.default ? modelsToLoad.Permission.default(sequelize) : null;
|
||||
Organization = modelsToLoad.Organization?.default ? modelsToLoad.Organization.default(sequelize) : null;
|
||||
Project = modelsToLoad.Project?.default ? modelsToLoad.Project.default(sequelize) : null;
|
||||
UserRole = modelsToLoad.UserRole?.default ? modelsToLoad.UserRole.default(sequelize) : null;
|
||||
RolePermission = modelsToLoad.RolePermission?.default ? modelsToLoad.RolePermission.default(sequelize) : null;
|
||||
UserProjectRole = modelsToLoad.UserProjectRole?.default ? modelsToLoad.UserProjectRole.default(sequelize) : null;
|
||||
Correspondence = modelsToLoad.Correspondence?.default ? modelsToLoad.Correspondence.default(sequelize) : null;
|
||||
CorrespondenceVersion = modelsToLoad.CorrespondenceVersion?.default ? modelsToLoad.CorrespondenceVersion.default(sequelize) : null;
|
||||
Document = modelsToLoad.Document?.default ? modelsToLoad.Document.default(sequelize) : null;
|
||||
CorrDocumentMap = modelsToLoad.CorrDocumentMap?.default ? modelsToLoad.CorrDocumentMap.default(sequelize) : null;
|
||||
Drawing = modelsToLoad.Drawing?.default ? modelsToLoad.Drawing.default(sequelize) : null;
|
||||
DrawingRevision = modelsToLoad.DrawingRevision?.default ? modelsToLoad.DrawingRevision.default(sequelize) : null;
|
||||
FileObject = modelsToLoad.FileObject?.default ? modelsToLoad.FileObject.default(sequelize) : null;
|
||||
RFA = modelsToLoad.RFA?.default ? modelsToLoad.RFA.default(sequelize) : null;
|
||||
RFARevision = modelsToLoad.RFARevision?.default ? modelsToLoad.RFARevision.default(sequelize) : null;
|
||||
RfaDrawingMap = modelsToLoad.RfaDrawingMap?.default ? modelsToLoad.RfaDrawingMap.default(sequelize) : null;
|
||||
Transmittal = modelsToLoad.Transmittal?.default ? modelsToLoad.Transmittal.default(sequelize) : null;
|
||||
TransmittalItem = modelsToLoad.TransmittalItem?.default ? modelsToLoad.TransmittalItem.default(sequelize) : null;
|
||||
Volume = modelsToLoad.Volume?.default ? modelsToLoad.Volume.default(sequelize) : null;
|
||||
ContractDwg = modelsToLoad.ContractDwg?.default ? modelsToLoad.ContractDwg.default(sequelize) : null;
|
||||
SubCategory = modelsToLoad.SubCategory?.default ? modelsToLoad.SubCategory.default(sequelize) : null;
|
||||
|
||||
// --- 4. สร้างความสัมพันธ์ (Associations) ทั้งหมด ---
|
||||
if (User && Role && Permission && UserRole && RolePermission) {
|
||||
// ความสัมพันธ์ระดับระบบ (System-level)
|
||||
User.belongsToMany(Role, { through: UserRole, foreignKey: "user_id" });
|
||||
Role.belongsToMany(User, { through: UserRole, foreignKey: "role_id" });
|
||||
Role.belongsToMany(Permission, {
|
||||
through: RolePermission,
|
||||
foreignKey: "role_id",
|
||||
});
|
||||
Permission.belongsToMany(Role, {
|
||||
through: RolePermission,
|
||||
foreignKey: "permission_id",
|
||||
});
|
||||
}
|
||||
|
||||
// ความสัมพันธ์ระดับโปรเจกต์ (Project-level) - ส่วนที่เพิ่มเข้ามา
|
||||
if (User && Project && Role && UserProjectRole) {
|
||||
// ทำให้ UserProjectRole เป็นตัวกลางเชื่อม 3 ตาราง
|
||||
UserProjectRole.belongsTo(User, { foreignKey: "user_id" });
|
||||
UserProjectRole.belongsTo(Project, { foreignKey: "project_id" });
|
||||
UserProjectRole.belongsTo(Role, { foreignKey: "role_id" });
|
||||
|
||||
// ทำให้สามารถ include ข้อมูลที่เกี่ยวข้องได้ง่ายขึ้น
|
||||
User.hasMany(UserProjectRole, { foreignKey: "user_id" });
|
||||
Project.hasMany(UserProjectRole, { foreignKey: "project_id" });
|
||||
Role.hasMany(UserProjectRole, { foreignKey: "role_id" });
|
||||
}
|
||||
// --- 4. สร้างความสัมพันธ์ (Associations) ---
|
||||
const loadedModels = { User, Role, Permission, Organization, Project, UserRole, RolePermission,
|
||||
UserProjectRole, Correspondence, CorrespondenceVersion, Document, CorrDocumentMap,
|
||||
Drawing, DrawingRevision, FileObject, RFA, RFARevision, RfaDrawingMap,
|
||||
Transmittal, TransmittalItem, Volume, ContractDwg, SubCategory };
|
||||
|
||||
for (const modelName in loadedModels) {
|
||||
if (loadedModels[modelName] && loadedModels[modelName].associate) {
|
||||
loadedModels[modelName].associate(loadedModels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ฟังก์ชันสำหรับทดสอบการเชื่อมต่อ Sequelize
|
||||
*/
|
||||
export async function dbReady() {
|
||||
if (process.env.ENABLE_SEQUELIZE !== "1") {
|
||||
console.log("Sequelize is disabled.");
|
||||
return Promise.resolve();
|
||||
}
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log("Sequelize connection has been established successfully.");
|
||||
} catch (error) {
|
||||
console.error("Unable to connect to the database via Sequelize:", error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
if (process.env.ENABLE_SEQUELIZE !== "1") {
|
||||
console.log("Sequelize is disabled.");
|
||||
return Promise.resolve();
|
||||
}
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log("Sequelize connection has been established successfully.");
|
||||
} catch (error) {
|
||||
console.error("Unable to connect to the database via Sequelize:", error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
61
backend/src/middleware/auth copy.js
Executable file
61
backend/src/middleware/auth copy.js
Executable file
@@ -0,0 +1,61 @@
|
||||
// FILE: backend/src/middleware/auth.js
|
||||
|
||||
import jwt from "jsonwebtoken";
|
||||
import { config } from "../config.js";
|
||||
import { User, Role, UserRole } from "../db/sequelize.js";
|
||||
|
||||
export function signAccessToken(payload) {
|
||||
return jwt.sign(payload, config.JWT.SECRET, {
|
||||
expiresIn: config.JWT.EXPIRES_IN,
|
||||
});
|
||||
}
|
||||
export function signRefreshToken(payload) {
|
||||
return jwt.sign(payload, config.JWT.REFRESH_SECRET, {
|
||||
expiresIn: config.JWT.REFRESH_EXPIRES_IN,
|
||||
});
|
||||
}
|
||||
|
||||
export function extractToken(req) {
|
||||
// ให้คุกกี้มาก่อน แล้วค่อย Bearer (รองรับทั้งสองทาง)
|
||||
const cookieTok = req.cookies?.access_token || null;
|
||||
if (cookieTok) return cookieTok;
|
||||
const hdr = req.headers.authorization || "";
|
||||
return hdr.startsWith("Bearer ") ? hdr.slice(7) : null;
|
||||
}
|
||||
|
||||
export function requireAuth(req, res, next) {
|
||||
if (req.path === "/health") return next(); // อนุญาต health เสมอ
|
||||
const token = extractToken(req);
|
||||
if (!token) return res.status(401).json({ error: "Missing token" });
|
||||
|
||||
try {
|
||||
req.user = jwt.verify(token, config.JWT.SECRET);
|
||||
next();
|
||||
} catch {
|
||||
return res.status(401).json({ error: "Invalid/Expired token" });
|
||||
}
|
||||
}
|
||||
// ใช้กับเส้นทางที่ login แล้วจะ enrich ต่อได้ แต่ไม่บังคับ
|
||||
export function optionalAuth(req, _res, next) {
|
||||
const token = extractToken(req);
|
||||
if (!token) return next();
|
||||
try {
|
||||
req.user = jwt.verify(token, config.JWT.SECRET);
|
||||
} catch {}
|
||||
next();
|
||||
}
|
||||
|
||||
export async function enrichRoles(req, _res, next) {
|
||||
if (!req.user?.user_id) return next();
|
||||
const rows = await UserRole.findAll({
|
||||
where: { user_id: req.user.user_id },
|
||||
include: [{ model: Role }],
|
||||
}).catch(() => []);
|
||||
req.user.roles = rows.map((r) => r.role?.role_name).filter(Boolean);
|
||||
next();
|
||||
}
|
||||
|
||||
export function hasPerm(req, perm) {
|
||||
const set = new Set(req?.user?.permissions || []);
|
||||
return set.has(perm);
|
||||
}
|
||||
0
backend/src/middleware/auth.js
Normal file → Executable file
0
backend/src/middleware/auth.js
Normal file → Executable file
6
backend/src/middleware/index.js
Normal file → Executable file
6
backend/src/middleware/index.js
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
// File: backend/src/middleware/index.js
|
||||
import * as authJwt from "./authJwt.js";
|
||||
import * as abac from "./abac.js";
|
||||
import * as auth from "./auth.js";
|
||||
import * as authJwt from "./authJwt.js";
|
||||
import * as errorHandler from "./errorHandler.js";
|
||||
import * as loadPrincipal from "./loadPrincipal.js";
|
||||
import * as permGuard from "./permGuard.js";
|
||||
@@ -12,9 +12,9 @@ import * as requirePerm from "./requirePerm.js";
|
||||
// Export ทุกอย่างออกมาเป็น named exports
|
||||
// เพื่อให้สามารถ import แบบ `import { authJwt, permGuard } from '../middleware';` ได้
|
||||
export {
|
||||
authJwt,
|
||||
abac,
|
||||
auth,
|
||||
authJwt,
|
||||
errorHandler,
|
||||
loadPrincipal,
|
||||
permGuard,
|
||||
@@ -25,9 +25,9 @@ export {
|
||||
|
||||
// (Optional) สร้าง default export สำหรับกรณีที่ต้องการ import ทั้งหมดใน object เดียว
|
||||
const middleware = {
|
||||
authJwt,
|
||||
abac,
|
||||
auth,
|
||||
authJwt,
|
||||
errorHandler,
|
||||
loadPrincipal,
|
||||
permGuard,
|
||||
|
||||
0
backend/src/middleware/loadPrincipal.js
Normal file → Executable file
0
backend/src/middleware/loadPrincipal.js
Normal file → Executable file
74
backend/src/routes/dashboard.js
Normal file → Executable file
74
backend/src/routes/dashboard.js
Normal file → Executable file
@@ -1,23 +1,63 @@
|
||||
import { Router } from "express";
|
||||
import { User } from "../db/index.js";
|
||||
import { authJwt } from "../middleware/index.js";
|
||||
// backend/src/routes/dashboard.js
|
||||
import { Router } from 'express';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
// 1. Import Middleware ที่ถูกต้อง
|
||||
import { authJwt } from '../middleware/authJwt.js';
|
||||
import { loadPrincipalMw } from '../middleware/loadPrincipal.js';
|
||||
|
||||
// 2. Import Sequelize Models จาก `sequelize.js` ไม่ใช่ `index.js`
|
||||
import { Correspondence, Document, RFA, User } from '../db/sequelize.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use(authJwt.verifyToken);
|
||||
// 3. ใช้ Middleware Chain ที่ถูกต้อง 100%
|
||||
router.use(authJwt(), loadPrincipalMw());
|
||||
|
||||
router.get("/users/summary", async (req, res, next) => {
|
||||
try {
|
||||
const totalUsers = await User.count();
|
||||
const activeUsers = await User.count({ where: { is_active: true } });
|
||||
res.json({
|
||||
total: totalUsers,
|
||||
active: activeUsers,
|
||||
inactive: totalUsers - activeUsers,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
|
||||
// === API สำหรับ User Management Widget ===
|
||||
router.get('/users/summary', async (req, res, next) => {
|
||||
try {
|
||||
// ตรวจสอบว่า Model ถูกโหลดแล้วหรือยัง (จำเป็นสำหรับโหมด lazy-load)
|
||||
if (!User) {
|
||||
return res.status(503).json({ message: 'Database models not available. Is ENABLE_SEQUELIZE=1 set?' });
|
||||
}
|
||||
const totalUsers = await User.count();
|
||||
const activeUsers = await User.count({ where: { is_active: true } });
|
||||
|
||||
res.json({
|
||||
total: totalUsers,
|
||||
active: activeUsers,
|
||||
inactive: totalUsers - activeUsers,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
// === API อื่นๆ สำหรับ Dashboard ที่เราคุยกันไว้ก่อนหน้า ===
|
||||
router.get('/stats', async (req, res, next) => {
|
||||
try {
|
||||
if (!Document || !RFA) {
|
||||
return res.status(503).json({ message: 'Database models not available. Is ENABLE_SEQUELIZE=1 set?' });
|
||||
}
|
||||
|
||||
const sevenDaysAgo = new Date(new Date().setDate(new Date().getDate() - 7));
|
||||
|
||||
const totalDocuments = await Document.count();
|
||||
const newThisWeek = await Document.count({ where: { createdAt: { [Op.gte]: sevenDaysAgo } } });
|
||||
const pendingRfas = await RFA.count({ where: { status: 'pending' } }); // สมมติตาม status
|
||||
|
||||
res.json({
|
||||
totalDocuments,
|
||||
newThisWeek,
|
||||
pendingRfas
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default router;
|
||||
130
backend/src/routes/rbac_admin.js
Normal file → Executable file
130
backend/src/routes/rbac_admin.js
Normal file → Executable file
@@ -1,144 +1,88 @@
|
||||
// FILE: backend/src/routes/rbac_admin.js
|
||||
import { Router } from "express";
|
||||
import { Role, Permission, UserProjectRole, Project } from "../db/sequelize.js";
|
||||
import { authJwt, permGuard } from "../middleware/index.js";
|
||||
import { authJwt } from "../middleware/authJwt.js";
|
||||
import { loadPrincipalMw } from "../middleware/loadPrincipal.js"; // แก้ไข: import ให้ถูกต้อง
|
||||
import { requirePerm } from "../middleware/requirePerm.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
// กำหนดให้ทุก route ในไฟล์นี้ต้องมีสิทธิ์ 'manage_rbac'
|
||||
router.use(authJwt.verifyToken, permGuard("manage_rbac"));
|
||||
// Middleware Chain ที่ถูกต้อง 100% ตามสถาปัตยกรรมของคุณ
|
||||
router.use(authJwt(), loadPrincipalMw());
|
||||
|
||||
// == ROLES Management ==
|
||||
router.get("/roles", async (req, res, next) => {
|
||||
router.get("/roles", requirePerm("roles.manage"), async (req, res, next) => {
|
||||
try {
|
||||
const roles = await Role.findAll({
|
||||
include: [
|
||||
{
|
||||
model: Permission,
|
||||
attributes: ["id", "name"],
|
||||
through: { attributes: [] },
|
||||
},
|
||||
],
|
||||
include: [{ model: Permission, attributes: ["id", "name"], through: { attributes: [] } }],
|
||||
order: [["name", "ASC"]],
|
||||
});
|
||||
res.json(roles);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
router.post("/roles", async (req, res, next) => {
|
||||
router.post("/roles", requirePerm("roles.manage"), async (req, res, next) => {
|
||||
const { name, description } = req.body;
|
||||
if (!name) return res.status(400).json({ message: "Role name is required." });
|
||||
try {
|
||||
const { name, description } = req.body;
|
||||
if (!name)
|
||||
return res.status(400).json({ message: "Role name is required." });
|
||||
const newRole = await Role.create({ name, description });
|
||||
res.status(201).json(newRole);
|
||||
} catch (error) {
|
||||
if (error.name === "SequelizeUniqueConstraintError") {
|
||||
return res
|
||||
.status(409)
|
||||
.json({ message: `Role '${name}' already exists.` });
|
||||
return res.status(409).json({ message: `Role '${name}' already exists.` });
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.put("/roles/:id/permissions", async (req, res, next) => {
|
||||
try {
|
||||
router.put("/roles/:id/permissions", requirePerm("roles.manage"), async (req, res, next) => {
|
||||
const { permissionIds } = req.body;
|
||||
if (!Array.isArray(permissionIds))
|
||||
return res
|
||||
.status(400)
|
||||
.json({ message: "permissionIds must be an array." });
|
||||
|
||||
const role = await Role.findByPk(req.params.id);
|
||||
if (!role) return res.status(404).json({ message: "Role not found." });
|
||||
|
||||
await role.setPermissions(permissionIds);
|
||||
const updatedRole = await Role.findByPk(req.params.id, {
|
||||
include: [
|
||||
{
|
||||
model: Permission,
|
||||
attributes: ["id", "name"],
|
||||
through: { attributes: [] },
|
||||
},
|
||||
],
|
||||
});
|
||||
res.json(updatedRole);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// == PERMISSIONS Management ==
|
||||
router.get("/permissions", async (req, res, next) => {
|
||||
try {
|
||||
const permissions = await Permission.findAll({ order: [["name", "ASC"]] });
|
||||
res.json(permissions);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
if (!Array.isArray(permissionIds)) return res.status(400).json({ message: "permissionIds must be an array." });
|
||||
try {
|
||||
const role = await Role.findByPk(req.params.id);
|
||||
if (!role) return res.status(404).json({ message: "Role not found." });
|
||||
await role.setPermissions(permissionIds);
|
||||
const updatedRole = await Role.findByPk(req.params.id, {
|
||||
include: [{ model: Permission, attributes: ['id', 'name'], through: { attributes: [] } }]
|
||||
});
|
||||
res.json(updatedRole);
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
// == USER-PROJECT-ROLES Management ==
|
||||
router.get("/user-project-roles", async (req, res, next) => {
|
||||
router.get("/user-project-roles", requirePerm("users.manage"), async (req, res, next) => {
|
||||
const { userId } = req.query;
|
||||
if (!userId)
|
||||
return res
|
||||
.status(400)
|
||||
.json({ message: "userId query parameter is required." });
|
||||
if (!userId) return res.status(400).json({ message: "userId query parameter is required." });
|
||||
try {
|
||||
const assignments = await UserProjectRole.findAll({
|
||||
where: { user_id: userId },
|
||||
include: [
|
||||
{ model: Project, attributes: ["id", "name"] },
|
||||
{ model: Role, attributes: ["id", "name"] },
|
||||
],
|
||||
include: [ { model: Project, attributes: ["id", "name"] }, { model: Role, attributes: ["id", "name"] } ],
|
||||
});
|
||||
res.json(assignments);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
router.post("/user-project-roles", async (req, res, next) => {
|
||||
router.post("/user-project-roles", requirePerm("users.manage"), async (req, res, next) => {
|
||||
const { userId, projectId, roleId } = req.body;
|
||||
if (!userId || !projectId || !roleId)
|
||||
return res
|
||||
.status(400)
|
||||
.json({ message: "userId, projectId, and roleId are required." });
|
||||
if (!userId || !projectId || !roleId) return res.status(400).json({ message: "userId, projectId, and roleId are required." });
|
||||
try {
|
||||
const [assignment, created] = await UserProjectRole.findOrCreate({
|
||||
where: { user_id: userId, project_id: projectId, role_id: roleId },
|
||||
defaults: { user_id: userId, project_id: projectId, role_id: roleId },
|
||||
});
|
||||
if (!created)
|
||||
return res
|
||||
.status(409)
|
||||
.json({ message: "This assignment already exists." });
|
||||
if (!created) return res.status(409).json({ message: "This assignment already exists." });
|
||||
res.status(201).json(assignment);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
router.delete("/user-project-roles", async (req, res, next) => {
|
||||
router.delete("/user-project-roles", requirePerm("users.manage"), async (req, res, next) => {
|
||||
const { userId, projectId, roleId } = req.body;
|
||||
if (!userId || !projectId || !roleId)
|
||||
return res
|
||||
.status(400)
|
||||
.json({ message: "userId, projectId, and roleId are required." });
|
||||
if (!userId || !projectId || !roleId) return res.status(400).json({ message: "userId, projectId, and roleId are required." });
|
||||
try {
|
||||
const deletedCount = await UserProjectRole.destroy({
|
||||
where: { user_id: userId, project_id: projectId, role_id: roleId },
|
||||
});
|
||||
if (deletedCount === 0)
|
||||
return res.status(404).json({ message: "Assignment not found." });
|
||||
const deletedCount = await UserProjectRole.destroy({ where: { user_id: userId, project_id: projectId, role_id: roleId } });
|
||||
if (deletedCount === 0) return res.status(404).json({ message: 'Assignment not found.' });
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
205
backend/src/routes/users.js
Normal file → Executable file
205
backend/src/routes/users.js
Normal file → Executable file
@@ -1,136 +1,99 @@
|
||||
// File: backend/src/routes/users.js
|
||||
import { Router } from "express";
|
||||
import { User, Role } from "../db/sequelize.js";
|
||||
import { authJwt, permGuard } from "../middleware/index.js";
|
||||
import { hashPassword } from "../utils/passwords.js";
|
||||
import { Router } from 'express';
|
||||
import { User, Role } from '../db/sequelize.js';
|
||||
import { authJwt } from "../middleware/authJwt.js";
|
||||
import { loadPrincipalMw } from "../middleware/loadPrincipal.js"; // แก้ไข: import ให้ถูกต้อง
|
||||
import { requirePerm } from '../middleware/requirePerm.js';
|
||||
import { hashPassword } from '../utils/passwords.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Middleware สำหรับทุก route ในไฟล์นี้
|
||||
router.use(authJwt.verifyToken);
|
||||
// Middleware Chain ที่ถูกต้อง 100%
|
||||
router.use(authJwt(), loadPrincipalMw());
|
||||
|
||||
// GET /api/users - ดึงรายชื่อผู้ใช้ทั้งหมด
|
||||
router.get(
|
||||
"/",
|
||||
permGuard("manage_users"), // ตรวจสอบสิทธิ์
|
||||
async (req, res, next) => {
|
||||
// GET /api/users
|
||||
router.get('/', requirePerm('users.view'), async (req, res, next) => {
|
||||
try {
|
||||
const users = await User.findAll({
|
||||
attributes: { exclude: ["password_hash"] }, // **สำคัญมาก: ห้ามส่ง password hash ออกไป**
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
attributes: ["id", "name"],
|
||||
through: { attributes: [] }, // ไม่ต้องเอาข้อมูลจากตาราง join (UserRoles) มา
|
||||
},
|
||||
],
|
||||
order: [["username", "ASC"]],
|
||||
});
|
||||
res.json(users);
|
||||
const users = await User.findAll({
|
||||
attributes: { exclude: ['password_hash'] },
|
||||
include: [{ model: Role, attributes: ['id', 'name'], through: { attributes: [] } }],
|
||||
order: [['username', 'ASC']]
|
||||
});
|
||||
res.json(users);
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
// POST /api/users
|
||||
router.post('/', requirePerm('users.manage'), async (req, res, next) => {
|
||||
const { username, email, password, first_name, last_name, is_active, roles } = req.body;
|
||||
if (!username || !email || !password) {
|
||||
return res.status(400).json({ message: 'Username, email, and password are required' });
|
||||
}
|
||||
try {
|
||||
const password_hash = await hashPassword(password);
|
||||
const newUser = await User.create({
|
||||
username, email, password_hash, first_name, last_name, is_active: is_active !== false,
|
||||
created_by: req.principal.user_id,
|
||||
updated_by: req.principal.user_id,
|
||||
org_id: req.principal.org_ids[0] || null,
|
||||
});
|
||||
|
||||
if (roles && roles.length > 0) {
|
||||
await newUser.setRoles(roles);
|
||||
}
|
||||
|
||||
const userWithRoles = await User.findByPk(newUser.id, {
|
||||
attributes: { exclude: ['password_hash'] },
|
||||
include: [{ model: Role, attributes: ['id', 'name'], through: { attributes: [] } }]
|
||||
});
|
||||
res.status(201).json(userWithRoles);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
if (error.name === 'SequelizeUniqueConstraintError') {
|
||||
return res.status(409).json({ message: 'Username or email already exists.' });
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// POST /api/users - สร้างผู้ใช้ใหม่
|
||||
router.post("/", permGuard("manage_users"), async (req, res, next) => {
|
||||
const { username, email, password, first_name, last_name, is_active, roles } =
|
||||
req.body;
|
||||
|
||||
if (!username || !email || !password) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ message: "Username, email, and password are required" });
|
||||
}
|
||||
|
||||
try {
|
||||
const password_hash = await hashPassword(password);
|
||||
const newUser = await User.create({
|
||||
username,
|
||||
email,
|
||||
password_hash,
|
||||
first_name,
|
||||
last_name,
|
||||
is_active: is_active !== false,
|
||||
});
|
||||
|
||||
if (roles && roles.length > 0) {
|
||||
await newUser.setRoles(roles);
|
||||
}
|
||||
|
||||
const userWithRoles = await User.findByPk(newUser.id, {
|
||||
attributes: { exclude: ["password_hash"] },
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
attributes: ["id", "name"],
|
||||
through: { attributes: [] },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
res.status(201).json(userWithRoles);
|
||||
} catch (error) {
|
||||
if (error.name === "SequelizeUniqueConstraintError") {
|
||||
return res
|
||||
.status(409)
|
||||
.json({ message: "Username or email already exists." });
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/users/:id - อัปเดตข้อมูลผู้ใช้
|
||||
router.put("/:id", permGuard("manage_users"), async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
const { email, first_name, last_name, is_active, roles } = req.body;
|
||||
// PUT /api/users/:id
|
||||
router.put('/:id', requirePerm('users.manage'), async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
const { email, first_name, last_name, is_active, roles } = req.body;
|
||||
try {
|
||||
const user = await User.findByPk(id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
user.email = email ?? user.email;
|
||||
user.first_name = first_name ?? user.first_name;
|
||||
user.last_name = last_name ?? user.last_name;
|
||||
user.is_active = is_active ?? user.is_active;
|
||||
user.updated_by = req.principal.user_id;
|
||||
await user.save();
|
||||
|
||||
try {
|
||||
const user = await User.findByPk(id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
}
|
||||
|
||||
user.email = email ?? user.email;
|
||||
user.first_name = first_name ?? user.first_name;
|
||||
user.last_name = last_name ?? user.last_name;
|
||||
user.is_active = is_active ?? user.is_active;
|
||||
await user.save();
|
||||
|
||||
if (roles) {
|
||||
await user.setRoles(roles);
|
||||
}
|
||||
|
||||
const updatedUser = await User.findByPk(id, {
|
||||
attributes: { exclude: ["password_hash"] },
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
attributes: ["id", "name"],
|
||||
through: { attributes: [] },
|
||||
},
|
||||
],
|
||||
});
|
||||
res.json(updatedUser);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
if (roles) {
|
||||
await user.setRoles(roles);
|
||||
}
|
||||
const updatedUser = await User.findByPk(id, {
|
||||
attributes: { exclude: ['password_hash'] },
|
||||
include: [{ model: Role, attributes: ['id', 'name'], through: { attributes: [] } }]
|
||||
});
|
||||
res.json(updatedUser);
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
// DELETE /api/users/:id - ลบผู้ใช้ (Soft Delete)
|
||||
router.delete("/:id", permGuard("manage_users"), async (req, res, next) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.params.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
}
|
||||
user.is_active = false; // Soft Delete
|
||||
await user.save();
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
// DELETE /api/users/:id
|
||||
router.delete('/:id', requirePerm('users.manage'), async (req, res, next) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.params.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
user.is_active = false;
|
||||
user.updated_by = req.principal.user_id;
|
||||
await user.save();
|
||||
res.status(204).send();
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
Reference in New Issue
Block a user