05.1 ปรบปรง backend ทงหมด และ frontend/login
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user