cangku/backend/src/services/pageAccess.js

356 lines
10 KiB
JavaScript

const database = require('../config/database');
const { ROLES, SIDEBAR_PAGES, SIDEBAR_PAGE_MAP } = require('../constants/sidebarPages');
const SETTINGS_PAGE_KEY = 'settings';
const PLANNING_UPLOAD_PAGE_KEY = 'planning_upload';
function getDefaultRoleAllowed(pageKey, role) {
return Boolean(SIDEBAR_PAGE_MAP[pageKey]?.defaultRoles?.[role]);
}
function getDefaultDepartmentAllowed(pageKey) {
return Boolean(SIDEBAR_PAGE_MAP[pageKey]?.departmentDefaultAllowed);
}
function shouldRepairExistingPermission(pageKey, role, allowed) {
if (pageKey === SETTINGS_PAGE_KEY && role === 'admin') {
return !(allowed === 1 || allowed === true);
}
return false;
}
async function ensureSidebarRolePermissions() {
for (const page of SIDEBAR_PAGES) {
for (const role of ROLES) {
const existing = await database.local.query(
'SELECT id, page_name, allowed FROM permissions WHERE page_key = ? AND role = ? LIMIT 1',
[page.pageKey, role]
);
if (existing.length > 0) {
const shouldRepairAllowed = shouldRepairExistingPermission(page.pageKey, role, existing[0].allowed);
const shouldRepairPageName = existing[0].page_name !== page.pageName;
if (shouldRepairAllowed || shouldRepairPageName) {
await database.local.query(
'UPDATE permissions SET page_name = ?, allowed = ? WHERE id = ?',
[
page.pageName,
shouldRepairAllowed ? (getDefaultRoleAllowed(page.pageKey, role) ? 1 : 0) : existing[0].allowed,
existing[0].id,
]
);
}
continue;
}
await database.local.insert('permissions', {
page_key: page.pageKey,
page_name: page.pageName,
role,
allowed: getDefaultRoleAllowed(page.pageKey, role) ? 1 : 0,
});
}
}
}
async function getSidebarRolePermissions() {
const rows = await database.local.query(
`SELECT page_key as pageKey, page_name as pageName, role, allowed
FROM permissions
WHERE page_key IN (${SIDEBAR_PAGES.map(() => '?').join(', ')})`,
SIDEBAR_PAGES.map((item) => item.pageKey)
);
return rows;
}
async function ensureDepartmentPagePermissions() {
const departments = await database.local.query('SELECT id FROM departments');
for (const department of departments) {
const rows = await database.local.query(
`SELECT id, page_key as pageKey, page_name as pageName
FROM department_page_permissions
WHERE department_id = ?`,
[department.id]
);
const rowMap = rows.reduce((acc, row) => {
acc[row.pageKey] = row;
return acc;
}, {});
for (const page of SIDEBAR_PAGES) {
const existing = rowMap[page.pageKey];
if (existing) {
if (existing.pageName !== page.pageName) {
await database.local.query(
`UPDATE department_page_permissions
SET page_name = ?
WHERE id = ?`,
[page.pageName, existing.id]
);
}
continue;
}
await database.local.insert('department_page_permissions', {
department_id: department.id,
page_key: page.pageKey,
page_name: page.pageName,
allowed: getDefaultDepartmentAllowed(page.pageKey) ? 1 : 0,
});
}
}
}
async function getDepartmentPagePermissions(departmentId) {
const rows = await database.local.query(
`SELECT page_key as pageKey, allowed
FROM department_page_permissions
WHERE department_id = ?`,
[departmentId]
);
const rowMap = rows.reduce((acc, row) => {
acc[row.pageKey] = row.allowed === 1 || row.allowed === true;
return acc;
}, {});
return SIDEBAR_PAGES.map((page) => ({
pageKey: page.pageKey,
pageName: page.pageName,
path: page.path,
category: page.category,
allowed: rowMap[page.pageKey] === undefined ? getDefaultDepartmentAllowed(page.pageKey) : rowMap[page.pageKey],
}));
}
async function saveDepartmentPagePermissions(departmentId, pagePermissions = []) {
const normalized = SIDEBAR_PAGES.map((page) => {
const incoming = pagePermissions.find((item) => item.pageKey === page.pageKey);
return {
pageKey: page.pageKey,
pageName: page.pageName,
allowed: incoming?.allowed !== undefined ? Boolean(incoming.allowed) : getDefaultDepartmentAllowed(page.pageKey),
};
});
await database.local.query('DELETE FROM department_page_permissions WHERE department_id = ?', [departmentId]);
for (const permission of normalized) {
await database.local.insert('department_page_permissions', {
department_id: departmentId,
page_key: permission.pageKey,
page_name: permission.pageName,
allowed: permission.allowed ? 1 : 0,
});
}
return getDepartmentPagePermissions(departmentId);
}
async function getUserPageOverrideRows(userId) {
return database.local.query(
`SELECT page_key as pageKey, allowed
FROM user_page_permissions
WHERE user_id = ?`,
[userId]
);
}
async function getBaseAllowedPageKeysForUser(user) {
if (!user) {
return [];
}
if (user.role === 'admin') {
return SIDEBAR_PAGES.map((page) => page.pageKey);
}
const roleRows = await database.local.query(
`SELECT page_key as pageKey, allowed
FROM permissions
WHERE role = ?
AND page_key IN (${SIDEBAR_PAGES.map(() => '?').join(', ')})`,
[user.role, ...SIDEBAR_PAGES.map((item) => item.pageKey)]
);
const roleAllowed = new Set(
SIDEBAR_PAGES.filter((page) => {
const row = roleRows.find((item) => item.pageKey === page.pageKey);
if (!row) {
return getDefaultRoleAllowed(page.pageKey, user.role);
}
return row.allowed === 1 || row.allowed === true;
}).map((page) => page.pageKey)
);
if (!user.department_id) {
if (user.role === 'admin') {
return Array.from(roleAllowed);
}
return Array.from(roleAllowed).filter((pageKey) => pageKey !== PLANNING_UPLOAD_PAGE_KEY);
}
const departmentRows = await database.local.query(
`SELECT page_key as pageKey, allowed
FROM department_page_permissions
WHERE department_id = ?`,
[user.department_id]
);
if (departmentRows.length === 0) {
return Array.from(roleAllowed).filter((pageKey) => getDefaultDepartmentAllowed(pageKey));
}
const departmentRowMap = departmentRows.reduce((acc, row) => {
acc[row.pageKey] = row.allowed === 1 || row.allowed === true;
return acc;
}, {});
const departmentAllowed = new Set(
SIDEBAR_PAGES
.filter((page) => departmentRowMap[page.pageKey] === undefined ? getDefaultDepartmentAllowed(page.pageKey) : departmentRowMap[page.pageKey])
.map((page) => page.pageKey)
);
let pageKeys = Array.from(roleAllowed).filter((pageKey) => departmentAllowed.has(pageKey));
return pageKeys;
}
async function getAllowedPageKeysForUser(user) {
const basePageKeys = await getBaseAllowedPageKeysForUser(user);
if (!user || user.role === 'admin') {
return basePageKeys;
}
const overrideRows = await getUserPageOverrideRows(user.id);
const userAllowed = overrideRows
.filter((row) => row.allowed === 1 || row.allowed === true)
.map((row) => row.pageKey);
return Array.from(new Set([...basePageKeys, ...userAllowed]));
}
async function getUserPagePermissions(userId) {
const users = await database.local.query(
`SELECT id, username, name, role, department_id
FROM users
WHERE id = ?
LIMIT 1`,
[userId]
);
const user = users[0];
if (!user) {
return null;
}
const [basePageKeys, overrideRows] = await Promise.all([
getBaseAllowedPageKeysForUser(user),
getUserPageOverrideRows(userId),
]);
const baseAllowed = new Set(basePageKeys);
const overrideMap = overrideRows.reduce((acc, row) => {
acc[row.pageKey] = row.allowed === 1 || row.allowed === true;
return acc;
}, {});
return {
user,
permissions: SIDEBAR_PAGES.map((page) => {
const userAllowed = overrideMap[page.pageKey] === true;
return {
pageKey: page.pageKey,
pageName: page.pageName,
path: page.path,
category: page.category,
baseAllowed: baseAllowed.has(page.pageKey),
userAllowed,
effectiveAllowed: baseAllowed.has(page.pageKey) || userAllowed,
};
}),
};
}
async function saveUserPagePermissions(userId, pagePermissions = []) {
const pageMap = new Map(SIDEBAR_PAGES.map((page) => [page.pageKey, page]));
const normalized = pagePermissions
.filter((item) => item?.allowed === true && pageMap.has(item.pageKey))
.map((item) => {
const page = pageMap.get(item.pageKey);
return {
pageKey: page.pageKey,
pageName: page.pageName,
};
});
const deduped = Array.from(
normalized.reduce((acc, item) => acc.set(item.pageKey, item), new Map()).values()
);
await database.local.query('DELETE FROM user_page_permissions WHERE user_id = ?', [userId]);
for (const permission of deduped) {
await database.local.insert('user_page_permissions', {
user_id: userId,
page_key: permission.pageKey,
page_name: permission.pageName,
allowed: 1,
});
}
return getUserPagePermissions(userId);
}
async function getResolvedUserWithPageAccess(userId) {
const rows = await database.local.query(
`SELECT
u.id,
u.username,
u.name,
u.email,
u.role,
u.status,
u.department_id,
u.avatar_url,
u.auth_source,
d.name as departmentName
FROM users u
LEFT JOIN departments d ON u.department_id = d.id
WHERE u.id = ?
LIMIT 1`,
[userId]
);
const user = rows[0];
if (!user) {
return null;
}
const allowedPageKeys = await getAllowedPageKeysForUser(user);
return {
...user,
picture: user.avatar_url || '',
departmentId: user.department_id || null,
allowedPageKeys,
};
}
module.exports = {
SIDEBAR_PAGES,
ensureSidebarRolePermissions,
ensureDepartmentPagePermissions,
getAllowedPageKeysForUser,
getDepartmentPagePermissions,
getResolvedUserWithPageAccess,
getSidebarRolePermissions,
getUserPagePermissions,
saveDepartmentPagePermissions,
saveUserPagePermissions,
};