Update frontend login page.jsx และ backend

This commit is contained in:
admin
2025-09-29 13:25:09 +07:00
parent aca3667a9d
commit 7dd5ce8015
52 changed files with 2903 additions and 1289 deletions

View File

@@ -1,86 +1,160 @@
// src/routes/view.js
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';
// 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
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');
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' }),
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',
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}%`; }
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 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
LIMIT :limit OFFSET :offset`,
params
);
res.json(rows);
}
);
// GET by id
r.get('/:id',
requirePerm(PERM.savedview.read, { scope: 'org', getOrgId: OWN.getOrgIdById }),
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=?', [id]);
if (!row) return res.status(404).json({ error: 'Not found' });
const [[row]] = await sql.query("SELECT * FROM saved_views WHERE id=?", [
id,
]);
if (!row) return res.status(404).json({ error: "Not found" });
res.json(row);
}
);
// CREATE
r.post('/',
requirePerm(PERM.savedview.create, { scope: 'org', getOrgId: async req => req.body?.org_id ?? null }),
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]
[
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 }),
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' });
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=?',
"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 });
@@ -88,11 +162,15 @@ r.put('/:id',
);
// DELETE
r.delete('/:id',
requirePerm(PERM.savedview.delete, { scope: 'org', getOrgId: OWN.getOrgIdById }),
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]);
await sql.query("DELETE FROM saved_views WHERE id=?", [id]);
res.json({ ok: 1 });
}
);