// RBAC data access helpers for roles, permissions, and user-role links. const { PERMISSIONS, normalizePermissionKeys } = require('#src/rbac'); const { fetchPagedRows } = require('#src/data/utils'); function parseCsvIds(value) { return String(value || '') .split(',') .map(function (item) { return Number(item); }) .filter(function (item) { return Number.isInteger(item) && item > 0; }); } async function fetchPermissions(pool) { const [rows] = await pool.query('SELECT id, permission_key, name, section_name, description FROM a_permissions ORDER BY section_name ASC, name ASC'); return rows || []; } async function fetchRoles(pool) { const [rows] = await pool.query( `SELECT r.id, r.role_key, r.name, r.description, r.created_at, r.modified_at, (SELECT COUNT(*) FROM a_user_roles ur WHERE ur.role_id = r.id) AS user_count, (SELECT COUNT(*) FROM a_role_permissions rp WHERE rp.role_id = r.id) AS permission_count FROM a_roles r ORDER BY r.name ASC` ); return rows || []; } async function fetchRolesPage(pool, page, pageSize, searchTerm, sortKey, sortDirection) { const paged = await fetchPagedRows(pool, { selectSql: `SELECT r.id, r.role_key, r.name, r.description, r.created_at, r.modified_at, (SELECT COUNT(*) FROM a_user_roles ur WHERE ur.role_id = r.id) AS user_count, (SELECT COUNT(*) FROM a_role_permissions rp WHERE rp.role_id = r.id) AS permission_count FROM a_roles r ORDER BY r.name ASC`, countSql: 'SELECT COUNT(*) AS count FROM a_roles', searchColumns: ['r.role_key', 'r.name', 'r.description'], searchTerm: searchTerm, sortColumns: { role: 'r.name', description: 'r.description', users: 'user_count', permissions: 'permission_count', created: 'r.created_at', modified: 'r.modified_at' }, sortKey: sortKey, sortDirection: sortDirection, page: page, pageSize: pageSize }); return Object.assign({ roles: paged.rows }, paged); } async function fetchRoleById(pool, roleId) { const [rows] = await pool.query( `SELECT r.id, r.role_key, r.name, r.description, r.created_at, r.modified_at, (SELECT COUNT(*) FROM a_user_roles ur WHERE ur.role_id = r.id) AS user_count, (SELECT COUNT(*) FROM a_role_permissions rp WHERE rp.role_id = r.id) AS permission_count FROM a_roles r WHERE r.id = ? LIMIT 1`, [roleId] ); return rows[0] || null; } async function fetchRolePermissionKeys(pool, roleId) { const [rows] = await pool.query( `SELECT p.permission_key FROM a_role_permissions rp JOIN a_permissions p ON p.id = rp.permission_id WHERE rp.role_id = ? ORDER BY p.section_name ASC, p.name ASC`, [roleId] ); return (rows || []).map(function (row) { return String(row.permission_key || '').trim(); }).filter(Boolean); } async function fetchRoleUserIds(pool, roleId) { const [rows] = await pool.query( `SELECT ur.user_id FROM a_user_roles ur WHERE ur.role_id = ? ORDER BY ur.user_id ASC`, [roleId] ); return (rows || []).map(function (row) { return Number(row.user_id); }).filter(function (userId) { return Number.isInteger(userId) && userId > 0; }); } async function fetchRolesForUser(pool, userId) { const [rows] = await pool.query( `SELECT r.id, r.role_key, r.name, r.description FROM a_user_roles ur JOIN a_roles r ON r.id = ur.role_id WHERE ur.user_id = ? ORDER BY r.name ASC`, [userId] ); return rows || []; } async function fetchUsersWithRoles(pool) { const [rows] = await pool.query( `SELECT u.id, u.name, u.username, u.created_at, u.modified_at, COALESCE(role_data.role_names, '') AS role_names, COALESCE(role_data.role_ids_csv, '') AS role_ids_csv FROM a_users u LEFT JOIN ( SELECT ur.user_id, GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', ') AS role_names, GROUP_CONCAT(DISTINCT r.id ORDER BY r.name SEPARATOR ',') AS role_ids_csv FROM a_user_roles ur JOIN a_roles r ON r.id = ur.role_id GROUP BY ur.user_id ) role_data ON role_data.user_id = u.id ORDER BY u.id ASC` ); return (rows || []).map(function (row) { return Object.assign({}, row, { roleIds: parseCsvIds(row.role_ids_csv), roleNames: String(row.role_names || '').trim() }); }); } async function fetchUsersWithRolesPage(pool, page, pageSize, searchTerm, sortKey, sortDirection, options) { const excludedUserId = Number(options && options.excludeUserId); const hasExcludedUserId = Number.isInteger(excludedUserId) && excludedUserId > 0; const whereSql = hasExcludedUserId ? 'WHERE u.id <> ?' : ''; const queryArgs = hasExcludedUserId ? [excludedUserId] : []; const paged = await fetchPagedRows(pool, { selectSql: `SELECT u.id, u.name, u.username, u.created_at, u.modified_at, COALESCE(role_data.role_names, '') AS role_names, COALESCE(role_data.role_ids_csv, '') AS role_ids_csv FROM a_users u LEFT JOIN ( SELECT ur.user_id, GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', ') AS role_names, GROUP_CONCAT(DISTINCT r.id ORDER BY r.name SEPARATOR ',') AS role_ids_csv FROM a_user_roles ur JOIN a_roles r ON r.id = ur.role_id GROUP BY ur.user_id ) role_data ON role_data.user_id = u.id ${whereSql} ORDER BY u.id ASC`, countSql: `SELECT COUNT(*) AS count FROM a_users u ${whereSql}`, params: queryArgs, searchColumns: ['u.name', 'u.username', 'role_data.role_names'], searchTerm: searchTerm, sortColumns: { user: 'u.username', name: 'u.name', roles: 'role_names', created: 'u.created_at', modified: 'u.modified_at' }, sortKey: sortKey, sortDirection: sortDirection, page: page, pageSize: pageSize }); return { users: (paged.rows || []).map(function (row) { return Object.assign({}, row, { roleIds: parseCsvIds(row.role_ids_csv), roleNames: String(row.role_names || '').trim() }); }), totalItems: paged.totalItems, totalPages: paged.totalPages, currentPage: paged.currentPage, pageSize: paged.pageSize }; } async function fetchUserWithRoles(pool, userId) { const [rows] = await pool.query( `SELECT u.id, u.name, u.username, u.created_at, u.modified_at, COALESCE(role_data.role_names, '') AS role_names, COALESCE(role_data.role_ids_csv, '') AS role_ids_csv FROM a_users u LEFT JOIN ( SELECT ur.user_id, GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', ') AS role_names, GROUP_CONCAT(DISTINCT r.id ORDER BY r.name SEPARATOR ',') AS role_ids_csv FROM a_user_roles ur JOIN a_roles r ON r.id = ur.role_id GROUP BY ur.user_id ) role_data ON role_data.user_id = u.id WHERE u.id = ? LIMIT 1`, [userId] ); if (!rows.length) { return null; } return Object.assign({}, rows[0], { roleIds: parseCsvIds(rows[0].role_ids_csv), roleNames: String(rows[0].role_names || '').trim() }); } async function syncUserRoles(pool, userId, roleIds) { const uniqueRoleIds = Array.from(new Set((Array.isArray(roleIds) ? roleIds : []).map(function (roleId) { return Number(roleId); }).filter(function (roleId) { return Number.isInteger(roleId) && roleId > 0; }))); await pool.query('DELETE FROM a_user_roles WHERE user_id = ?', [userId]); for (const roleId of uniqueRoleIds) { await pool.query('INSERT IGNORE INTO a_user_roles (user_id, role_id, created_by, modified_by) VALUES (?, ?, ?, ?)', [userId, roleId, null, null]); } } async function syncRoleUsers(pool, roleId, userIds) { const uniqueUserIds = Array.from(new Set((Array.isArray(userIds) ? userIds : []).map(function (userId) { return Number(userId); }).filter(function (userId) { return Number.isInteger(userId) && userId > 0; }))); await pool.query('DELETE FROM a_user_roles WHERE role_id = ?', [roleId]); for (const userId of uniqueUserIds) { await pool.query('INSERT IGNORE INTO a_user_roles (user_id, role_id, created_by, modified_by) VALUES (?, ?, ?, ?)', [userId, roleId, null, null]); } } async function syncRolePermissions(pool, roleId, permissionKeys) { const uniquePermissionKeys = normalizePermissionKeys(permissionKeys); if (!uniquePermissionKeys.length) { await pool.query('DELETE FROM a_role_permissions WHERE role_id = ?', [roleId]); return; } const [permissionRows] = await pool.query('SELECT id, permission_key FROM a_permissions WHERE permission_key IN (?)', [uniquePermissionKeys]); if (permissionRows.length !== uniquePermissionKeys.length) { throw new Error('One or more selected permissions are invalid.'); } await pool.query('DELETE FROM a_role_permissions WHERE role_id = ?', [roleId]); for (const permissionRow of permissionRows) { await pool.query('INSERT IGNORE INTO a_role_permissions (role_id, permission_id, created_by, modified_by) VALUES (?, ?, ?, ?)', [roleId, Number(permissionRow.id), null, null]); } } module.exports = { PERMISSIONS, fetchPermissions, fetchRoles, fetchRolesPage, fetchRoleById, fetchRolePermissionKeys, fetchRoleUserIds, fetchRolesForUser, fetchUsersWithRoles, fetchUsersWithRolesPage, fetchUserWithRoles, syncUserRoles, syncRoleUsers, syncRolePermissions };