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