05.1 ปรบปรง backend ทงหมด และ frontend/login

This commit is contained in:
admin
2025-10-01 11:14:11 +07:00
parent 5be0f5407b
commit 905afb56f5
43 changed files with 2285 additions and 2834 deletions

View File

@@ -1,178 +1,100 @@
// FILE: src/routes/view.js
// Saved Views routes
// - CRUD operations for saved views
// - Requires appropriate permissions via requirePerm middleware
// - Supports filtering and pagination on list endpoint
// - Uses ownerResolvers utility to determine org ownership for permission checks
// - Permissions required are defined in config/permissions.js
// - savedview.read
// - savedview.create
// - savedview.update
// - savedview.delete
// - Scope can be 'global' (list), 'org' (get/create/update/delete)
// - List endpoint supports filtering by project_id, org_id, shared flag, and search query (q)
// - Pagination via limit and offset query parameters
// - Results ordered by id DESC
// - Error handling for not found and no fields to update scenarios
// - Uses async/await for asynchronous operations
// - SQL queries use parameterized queries to prevent SQL injection
// - Responses are in JSON format
// - Middleware functions are used for permission checks
// - Owner resolvers are used to fetch org_id for specific view ids
// - Code is modular and organized for maintainability
// - Comments are provided for clarity/documentation
// - Follows best practices for Express.js route handling
// - Uses ES6+ features for cleaner code
// - Assumes existence of saved_views table with appropriate columns
// - Assumes existence of users table for owner
// - Assumes existence of config/permissions.js with defined permission codes
// - Assumes existence of utils/scope.js with buildScopeWhere and ownerResolvers functions
// - Assumes existence of middleware/requirePerm.js for permission checks
// - Assumes existence of db/index.js for database connection/querying
// - Assumes Express.js app is set up to use this router for /api/saved_views path
// FILE: backend/src/routes/view.js
// Saved Views: อ่านด้วย reports.view (GLOBAL); เขียนด้วย settings.manage
import { Router } from "express";
import sql from "../db/index.js";
import { requirePerm } from "../middleware/requirePerm.js";
import { buildScopeWhere, ownerResolvers } from "../utils/scope.js";
import PERM from "../config/permissions.js";
const r = Router();
const OWN = ownerResolvers(sql, "saved_views", "id");
// LIST: GET /api/view?project_id=&org_id=&shared=1
r.get(
"/",
requirePerm(PERM.savedview.read, { scope: "global" }),
async (req, res) => {
const { project_id, org_id, shared, q, limit = 50, offset = 0 } = req.query;
const base = buildScopeWhere(req.principal, {
tableAlias: "v",
orgColumn: "v.org_id",
projectColumn: "v.project_id",
permCode: PERM.savedview.read,
preferProject: true,
});
const extra = [];
const params = {
...base.params,
limit: Number(limit),
offset: Number(offset),
my: req.principal.userId,
};
if (project_id) {
extra.push("v.project_id = :project_id");
params.project_id = Number(project_id);
}
if (org_id) {
extra.push("v.org_id = :org_id");
params.org_id = Number(org_id);
}
if (shared === "1") extra.push("v.is_shared = 1");
if (q) {
extra.push("(v.name LIKE :q)");
params.q = `%${q}%`;
}
// ให้ผู้ใช้เห็นของตัวเองเสมอ + ของที่อยู่ใน scope
const where = `(${base.where}) AND (v.is_shared=1 OR v.owner_user_id=:my${
extra.length ? " OR " + extra.join(" AND ") : ""
})`;
const [rows] = await sql.query(
`SELECT v.* FROM saved_views v
WHERE ${where}
ORDER BY v.id DESC
LIMIT :limit OFFSET :offset`,
params
);
res.json(rows);
// LIST (ทุกคนที่มี reports.view)
r.get("/", requirePerm("reports.view"), async (req, res) => {
const { project_id, shared = "1", q, limit = 50, offset = 0 } = req.query;
const p = req.principal;
const cond = [];
const params = [];
// ให้เห็นของตัวเองเสมอ + shared
cond.push("(v.is_shared=1 OR v.owner_user_id=?)");
params.push(p.user_id);
if (project_id) {
cond.push("v.project_id=?");
params.push(Number(project_id));
}
if (q) {
cond.push("v.name LIKE ?");
params.push(`%${q}%`);
}
if (shared === "0") {
cond.push("v.is_shared=0");
}
);
// GET by id
r.get(
"/:id",
requirePerm(PERM.savedview.read, {
scope: "org",
getOrgId: OWN.getOrgIdById,
}),
async (req, res) => {
const id = Number(req.params.id);
const [[row]] = await sql.query("SELECT * FROM saved_views WHERE id=?", [
const where = `WHERE ${cond.join(" AND ")}`;
const [rows] = await sql.query(
`SELECT v.* FROM saved_views v ${where} ORDER BY v.id DESC LIMIT ? OFFSET ?`,
[...params, Number(limit), Number(offset)]
);
res.json(rows);
});
// GET
r.get("/:id", requirePerm("reports.view"), async (req, res) => {
const id = Number(req.params.id);
const [[row]] = await sql.query("SELECT * FROM saved_views WHERE id=?", [id]);
if (!row) return res.status(404).json({ error: "Not found" });
if (
!(
row.is_shared ||
row.owner_user_id === req.principal.user_id ||
req.principal.is_superadmin
)
) {
return res.status(403).json({ error: "FORBIDDEN" });
}
res.json(row);
});
// CREATE / UPDATE / DELETE (ต้องมี settings.manage)
r.post("/", requirePerm("settings.manage"), async (req, res) => {
const {
org_id,
project_id,
name,
payload_json,
is_shared = 0,
} = req.body || {};
const [rs] = await sql.query(
`INSERT INTO saved_views (org_id, project_id, name, payload_json, is_shared, owner_user_id)
VALUES (?,?,?,?,?,?)`,
[
org_id ?? null,
project_id ?? null,
name ?? "",
JSON.stringify(payload_json ?? {}),
Number(is_shared) ? 1 : 0,
req.principal.user_id,
]
);
res.status(201).json({ id: rs.insertId });
});
r.put("/:id", requirePerm("settings.manage"), async (req, res) => {
const id = Number(req.params.id);
const { name, payload_json, is_shared } = req.body || {};
await sql.query(
"UPDATE saved_views SET name=?, payload_json=?, is_shared=? WHERE id=?",
[
name ?? null,
JSON.stringify(payload_json ?? {}),
Number(is_shared) ? 1 : 0,
id,
]);
if (!row) return res.status(404).json({ error: "Not found" });
res.json(row);
}
);
]
);
res.json({ ok: 1 });
});
// CREATE
r.post(
"/",
requirePerm(PERM.savedview.create, {
scope: "org",
getOrgId: async (req) => req.body?.org_id ?? null,
}),
async (req, res) => {
const { org_id, project_id, name, payload_json, is_shared = 0 } = req.body;
const [rs] = await sql.query(
`INSERT INTO saved_views (org_id, project_id, name, payload_json, is_shared, owner_user_id)
VALUES (?,?,?,?,?,?)`,
[
org_id,
project_id,
name,
JSON.stringify(payload_json ?? {}),
Number(is_shared) ? 1 : 0,
req.principal.userId,
]
);
res.json({ id: rs.insertId });
}
);
// UPDATE (เฉพาะใน scope และถ้าเป็นของตนเอง หรือเป็นแอดมินตามนโยบาย)
r.put(
"/:id",
requirePerm(PERM.savedview.update, {
scope: "org",
getOrgId: OWN.getOrgIdById,
}),
async (req, res) => {
const id = Number(req.params.id);
const { name, payload_json, is_shared } = req.body;
// ตรวจ owner ถ้าต้องการบังคับเป็นของตัวเอง (option)
const [[sv]] = await sql.query(
"SELECT owner_user_id FROM saved_views WHERE id=?",
[id]
);
if (!sv) return res.status(404).json({ error: "Not found" });
// ถ้าจะจำกัดเฉพาะเจ้าของ: if (sv.owner_user_id !== req.principal.userId && !req.principal.isSuperAdmin) return res.status(403).json({ error: 'NOT_OWNER' });
await sql.query(
"UPDATE saved_views SET name=?, payload_json=?, is_shared=? WHERE id=?",
[name, JSON.stringify(payload_json ?? {}), Number(is_shared) ? 1 : 0, id]
);
res.json({ ok: 1 });
}
);
// DELETE
r.delete(
"/:id",
requirePerm(PERM.savedview.delete, {
scope: "org",
getOrgId: OWN.getOrgIdById,
}),
async (req, res) => {
const id = Number(req.params.id);
await sql.query("DELETE FROM saved_views WHERE id=?", [id]);
res.json({ ok: 1 });
}
);
r.delete("/:id", requirePerm("settings.manage"), async (req, res) => {
const id = Number(req.params.id);
await sql.query("DELETE FROM saved_views WHERE id=?", [id]);
res.json({ ok: 1 });
});
export default r;