feat(dashboard): เพมสวนจดการ user

This commit is contained in:
admin
2025-10-04 16:07:22 +07:00
parent 7f41c35cb8
commit 772239e708
19 changed files with 2477 additions and 1230 deletions

View File

@@ -1,126 +1,144 @@
// FILE: backend/src/routes/rbac_admin.js
// RBAC admin — ใช้ settings.manage ทั้งหมด
import { Router } from "express";
import sql from "../db/index.js";
import { requirePerm } from "../middleware/requirePerm.js";
import { Role, Permission, UserProjectRole, Project } from "../db/index.js";
import { authJwt, permGuard } from "../middleware/index.js";
const r = Router();
const router = Router();
// ROLES
r.get("/roles", requirePerm("settings.manage"), async (_req, res) => {
const [rows] = await sql.query(
"SELECT role_id, role_code, role_name, description FROM roles ORDER BY role_code"
);
res.json(rows);
// กำหนดให้ทุก route ในไฟล์นี้ต้องมีสิทธิ์ 'manage_rbac'
router.use(authJwt.verifyToken, permGuard("manage_rbac"));
// == ROLES Management ==
router.get("/roles", async (req, res, next) => {
try {
const roles = await Role.findAll({
include: [
{
model: Permission,
attributes: ["id", "name"],
through: { attributes: [] },
},
],
order: [["name", "ASC"]],
});
res.json(roles);
} catch (error) {
next(error);
}
});
// PERMISSIONS
r.get("/permissions", requirePerm("settings.manage"), async (_req, res) => {
const [rows] = await sql.query(
"SELECT permission_id, perm_code AS permission_code, scope_level, description FROM permissions ORDER BY perm_code"
);
res.json(rows);
router.post("/roles", async (req, res, next) => {
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.` });
}
next(error);
}
});
// role -> permissions
r.get(
"/roles/:role_id/permissions",
requirePerm("settings.manage"),
async (req, res) => {
const role_id = Number(req.params.role_id);
const [rows] = await sql.query(
`SELECT p.permission_id, p.perm_code AS permission_code, p.description
FROM role_permissions rp
JOIN permissions p ON p.permission_id = rp.permission_id
WHERE rp.role_id=? ORDER BY p.perm_code`,
[role_id]
);
res.json(rows);
}
);
router.put("/roles/:id/permissions", async (req, res, next) => {
try {
const { permissionIds } = req.body;
if (!Array.isArray(permissionIds))
return res
.status(400)
.json({ message: "permissionIds must be an array." });
r.post(
"/roles/:role_id/permissions",
requirePerm("settings.manage"),
async (req, res) => {
const role_id = Number(req.params.role_id);
const { permission_id } = req.body || {};
await sql.query(
"INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?,?)",
[role_id, Number(permission_id)]
);
res.json({ ok: 1 });
}
);
const role = await Role.findByPk(req.params.id);
if (!role) return res.status(404).json({ message: "Role not found." });
r.delete(
"/roles/:role_id/permissions/:permission_id",
requirePerm("settings.manage"),
async (req, res) => {
const role_id = Number(req.params.role_id);
const permission_id = Number(req.params.permission_id);
await sql.query(
"DELETE FROM role_permissions WHERE role_id=? AND permission_id=?",
[role_id, permission_id]
);
res.json({ ok: 1 });
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 -> roles (global/org/project scope columns มีในตาราง user_roles ตามสคีมา)
r.get(
"/users/:user_id/roles",
requirePerm("settings.manage"),
async (req, res) => {
const user_id = Number(req.params.user_id);
const [rows] = await sql.query(
`SELECT ur.user_id, ur.role_id, r.role_code, r.role_name, ur.org_id, ur.project_id
FROM user_roles ur JOIN roles r ON r.role_id = ur.role_id
WHERE ur.user_id=? ORDER BY r.role_code`,
[user_id]
);
res.json(rows);
// == 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);
}
);
});
r.post(
"/users/:user_id/roles",
requirePerm("settings.manage"),
async (req, res) => {
const user_id = Number(req.params.user_id);
const { role_id, org_id = null, project_id = null } = req.body || {};
await sql.query(
"INSERT INTO user_roles (user_id, role_id, org_id, project_id) VALUES (?,?,?,?)",
[
user_id,
Number(role_id),
org_id ? Number(org_id) : null,
project_id ? Number(project_id) : null,
]
);
res.json({ ok: 1 });
// == USER-PROJECT-ROLES Management ==
router.get("/user-project-roles", async (req, res, next) => {
const { userId } = req.query;
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"] },
],
});
res.json(assignments);
} catch (error) {
next(error);
}
);
});
r.delete(
"/users/:user_id/roles",
requirePerm("settings.manage"),
async (req, res) => {
const user_id = Number(req.params.user_id);
const { role_id, org_id = null, project_id = null } = req.body || {};
// สร้างเงื่อนไขแบบ dynamic สำหรับ NULL-safe compare
const whereOrg = org_id === null ? "ur.org_id IS NULL" : "ur.org_id = ?";
const wherePrj =
project_id === null ? "ur.project_id IS NULL" : "ur.project_id = ?";
const params = [user_id, Number(role_id)];
if (org_id !== null) params.push(Number(org_id));
if (project_id !== null) params.push(Number(project_id));
await sql.query(
`DELETE FROM user_roles ur WHERE ur.user_id=? AND ur.role_id=? AND ${whereOrg} AND ${wherePrj}`,
params
);
res.json({ ok: 1 });
router.post("/user-project-roles", 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." });
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." });
res.status(201).json(assignment);
} catch (error) {
next(error);
}
);
});
export default r;
router.delete("/user-project-roles", 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." });
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." });
res.status(204).send();
} catch (error) {
next(error);
}
});
export default router;