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,149 +1,162 @@
// FILE: src/routes/maps.js
// Maps routes
// - Manage relationships between RFAs and Drawings, Correspondences and Documents
// - Requires appropriate permissions via requirePerm middleware
// - Uses project scope for RFA-Drawing maps and Correspondence-Document maps
// - rfa:update for RFA-Drawing maps
// - correspondence:update for Correspondence-Document maps
// FILE: backend/src/routes/maps.js
// Map ความสัมพันธ์ระหว่าง RFA<->Drawing และ Correspondence<->Document
import { Router } from "express";
import { requireAuth } from "../middleware/auth.js";
import { enrichPermissions } from "../middleware/permissions.js";
import { requireRole } from "../middleware/rbac.js";
import { requirePerm } from "../middleware/permGuard.js";
import { sequelize } from "../db/sequelize.js";
import RfaModel from "../db/models/RFA.js";
import DrawingModel from "../db/models/Drawing.js";
import RfaDrawMapModel from "../db/models/RfaDrawingMap.js";
import CorrModel from "../db/models/Correspondence.js";
import DocModel from "../db/models/Document.js";
import CorrDocMapModel from "../db/models/CorrDocumentMap.js";
import sql from "../db/index.js";
import { requirePerm } from "../middleware/requirePerm.js";
const r = Router();
const RFA = RfaModel(sequelize);
const Drawing = DrawingModel(sequelize);
const RfaDraw = RfaDrawMapModel(sequelize);
const Corr = CorrModel(sequelize);
const Doc = DocModel(sequelize);
const CorrDoc = CorrDocMapModel(sequelize);
async function ensureRfaMembership(req, res) {
const rfaId = Number(req.params.rfa_id);
const row = await RFA.findByPk(rfaId);
if (!row) {
res.status(404).json({ error: "RFA not found" });
return false;
}
const roles = req.user?.roles || [];
const isAdmin = roles.includes("Admin");
if (isAdmin) return true;
const { getUserProjectIds } = await import("../middleware/abac.js");
const memberProjects = await getUserProjectIds(req.user?.user_id);
if (!memberProjects.includes(Number(row.project_id))) {
res.status(403).json({ error: "Forbidden: not a project member" });
return false;
}
return true;
}
async function ensureCorrMembership(req, res) {
const corrId = Number(req.params.corr_id);
const row = await Corr.findByPk(corrId);
if (!row) {
res.status(404).json({ error: "Correspondence not found" });
return false;
}
const roles = req.user?.roles || [];
const isAdmin = roles.includes("Admin");
if (isAdmin) return true;
const { getUserProjectIds } = await import("../middleware/abac.js");
const memberProjects = await getUserProjectIds(req.user?.user_id);
if (!memberProjects.includes(Number(row.project_id))) {
res.status(403).json({ error: "Forbidden: not a project member" });
return false;
}
return true;
}
// RFA <-> Drawing
r.get("/maps/rfa/:rfa_id/drawings", requireAuth, async (req, res) => {
const rows = await RfaDraw.findAll({
where: { rfa_id: Number(req.params.rfa_id) },
});
res.json(rows);
});
r.post(
"/maps/rfa/:rfa_id/drawings/:drawing_id",
requireAuth,
enrichPermissions(),
requirePerm("rfa:update"),
async (req, res) => {
if (!(await ensureRfaMembership(req, res))) return;
const { rfa_id, drawing_id } = {
rfa_id: Number(req.params.rfa_id),
drawing_id: Number(req.params.drawing_id),
};
await RfaDraw.create({ rfa_id, drawing_id });
res.status(201).json({ ok: true });
}
);
r.delete(
"/maps/rfa/:rfa_id/drawings/:drawing_id",
requireAuth,
enrichPermissions(),
requirePerm("rfa:update"),
async (req, res) => {
if (!(await ensureRfaMembership(req, res))) return;
const { rfa_id, drawing_id } = {
rfa_id: Number(req.params.rfa_id),
drawing_id: Number(req.params.drawing_id),
};
const count = await RfaDraw.destroy({ where: { rfa_id, drawing_id } });
res.json({ ok: count > 0 });
}
);
// Correspondence <-> Document
// ========= RFA <-> Drawing =========
// LIST
r.get(
"/maps/correspondence/:corr_id/documents",
requireAuth,
"/maps/rfa/:rfa_id/drawings",
requirePerm("rfas.view", { projectParam: "project_id" }), // ABAC enforced เมื่อส่ง query project_id; ถ้าไม่ส่งเราจะตรวจจากเรคคอร์ด
async (req, res) => {
const rows = await CorrDoc.findAll({
where: { correspondence_id: Number(req.params.corr_id) },
});
const rfa_id = Number(req.params.rfa_id);
const [[rfa]] = await sql.query("SELECT project_id FROM rfas WHERE id=?", [
rfa_id,
]);
if (!rfa) return res.status(404).json({ error: "RFA not found" });
if (
!req.principal.is_superadmin &&
!req.principal.inProject(rfa.project_id)
) {
return res.status(403).json({ error: "FORBIDDEN_PROJECT" });
}
const [rows] = await sql.query(
`SELECT m.* FROM rfa_drawing_map m WHERE m.rfa_id=? ORDER BY m.id DESC`,
[rfa_id]
);
res.json(rows);
}
);
// ADD
r.post(
"/maps/correspondence/:corr_id/documents/:doc_id",
requireAuth,
enrichPermissions(),
requirePerm("correspondence:update"),
"/maps/rfa/:rfa_id/drawings/:drawing_id",
requirePerm("rfas.respond", { projectParam: "project_id" }),
async (req, res) => {
if (!(await ensureCorrMembership(req, res))) return;
const { corr_id, doc_id } = {
corr_id: Number(req.params.corr_id),
doc_id: Number(req.params.doc_id),
};
await CorrDoc.create({ correspondence_id: corr_id, document_id: doc_id });
const rfa_id = Number(req.params.rfa_id);
const drawing_id = Number(req.params.drawing_id);
const [[rfa]] = await sql.query("SELECT project_id FROM rfas WHERE id=?", [
rfa_id,
]);
if (!rfa) return res.status(404).json({ error: "RFA not found" });
if (
!req.principal.is_superadmin &&
!req.principal.inProject(rfa.project_id)
) {
return res.status(403).json({ error: "FORBIDDEN_PROJECT" });
}
await sql.query(
"INSERT IGNORE INTO rfa_drawing_map (rfa_id, drawing_id, created_by) VALUES (?,?,?)",
[rfa_id, drawing_id, req.principal.user_id]
);
res.status(201).json({ ok: true });
}
);
// REMOVE
r.delete(
"/maps/rfa/:rfa_id/drawings/:drawing_id",
requirePerm("rfas.respond"),
async (req, res) => {
const rfa_id = Number(req.params.rfa_id);
const drawing_id = Number(req.params.drawing_id);
const [[rfa]] = await sql.query("SELECT project_id FROM rfas WHERE id=?", [
rfa_id,
]);
if (!rfa) return res.status(404).json({ error: "RFA not found" });
if (
!req.principal.is_superadmin &&
!req.principal.inProject(rfa.project_id)
) {
return res.status(403).json({ error: "FORBIDDEN_PROJECT" });
}
const [rs] = await sql.query(
"DELETE FROM rfa_drawing_map WHERE rfa_id=? AND drawing_id=?",
[rfa_id, drawing_id]
);
res.json({ ok: rs.affectedRows > 0 });
}
);
// ========= Correspondence <-> Document =========
r.get(
"/maps/correspondence/:corr_id/documents",
requirePerm("corr.view", { projectParam: "project_id" }),
async (req, res) => {
const corr_id = Number(req.params.corr_id);
const [[corr]] = await sql.query(
"SELECT project_id FROM correspondences WHERE id=?",
[corr_id]
);
if (!corr)
return res.status(404).json({ error: "Correspondence not found" });
if (
!req.principal.is_superadmin &&
!req.principal.inProject(corr.project_id)
) {
return res.status(403).json({ error: "FORBIDDEN_PROJECT" });
}
const [rows] = await sql.query(
`SELECT m.* FROM corr_document_map m WHERE m.correspondence_id=? ORDER BY m.id DESC`,
[corr_id]
);
res.json(rows);
}
);
r.post(
"/maps/correspondence/:corr_id/documents/:doc_id",
requirePerm("corr.manage", { projectParam: "project_id" }),
async (req, res) => {
const corr_id = Number(req.params.corr_id);
const doc_id = Number(req.params.doc_id);
const [[corr]] = await sql.query(
"SELECT project_id FROM correspondences WHERE id=?",
[corr_id]
);
if (!corr)
return res.status(404).json({ error: "Correspondence not found" });
if (
!req.principal.is_superadmin &&
!req.principal.inProject(corr.project_id)
) {
return res.status(403).json({ error: "FORBIDDEN_PROJECT" });
}
await sql.query(
"INSERT IGNORE INTO corr_document_map (correspondence_id, document_id, created_by) VALUES (?,?,?)",
[corr_id, doc_id, req.principal.user_id]
);
res.status(201).json({ ok: true });
}
);
r.delete(
"/maps/correspondence/:corr_id/documents/:doc_id",
requireAuth,
enrichPermissions(),
requirePerm("correspondence:update"),
requirePerm("corr.manage"),
async (req, res) => {
if (!(await ensureCorrMembership(req, res))) return;
const { corr_id, doc_id } = {
corr_id: Number(req.params.corr_id),
doc_id: Number(req.params.doc_id),
};
const count = await CorrDoc.destroy({
where: { correspondence_id: corr_id, document_id: doc_id },
});
res.json({ ok: count > 0 });
const corr_id = Number(req.params.corr_id);
const doc_id = Number(req.params.doc_id);
const [[corr]] = await sql.query(
"SELECT project_id FROM correspondences WHERE id=?",
[corr_id]
);
if (!corr)
return res.status(404).json({ error: "Correspondence not found" });
if (
!req.principal.is_superadmin &&
!req.principal.inProject(corr.project_id)
) {
return res.status(403).json({ error: "FORBIDDEN_PROJECT" });
}
const [rs] = await sql.query(
"DELETE FROM corr_document_map WHERE correspondence_id=? AND document_id=?",
[corr_id, doc_id]
);
res.json({ ok: rs.affectedRows > 0 });
}
);