feat: admin-роль (isAdmin, requireAdmin), getCompanyById, getAllCompanies, setLimit, getAudit с JOIN

This commit is contained in:
2026-05-30 09:09:24 +03:00
parent cda0b085e6
commit c6c9d01cd7
2 changed files with 92 additions and 4 deletions
+64 -3
View File
@@ -141,6 +141,58 @@ async function deleteEntry(entryId, companyId, userEmail) {
}
}
// ── Admin: компании ──
/**
* Получить одну компанию по числовому id (primary key).
* Используется когда admin работает с чужой компанией:
* нам не нужно getOrCreateCompany (которая привязывается к clientId пользователя).
*
* @param {number} id — companies.id
* @returns {object|null} строка из таблицы companies или null
*/
async function getCompanyById(id) {
const res = await pool.query('SELECT * FROM companies WHERE id = $1', [id]);
return res.rows[0] || null;
}
/**
* Получить все компании с количеством активных записей каждой.
* Используется на странице администратора: список компаний + статистика.
* LEFT JOIN гарантирует присутствие компаний с нулём записей.
*
* @returns {object[]} строки companies с доп. полем active_count
*/
async function getAllCompanies() {
const res = await pool.query(`
SELECT c.*,
COUNT(e.id)::int AS active_count
FROM companies c
LEFT JOIN whitelist_entries e
ON e.company_id = c.id AND e.deleted_at IS NULL
GROUP BY c.id
ORDER BY c.name, c.client_id
`);
return res.rows;
}
/**
* Задать индивидуальный лимит для компании.
* newLimit = null → сброс на глобальный дефолт (DEFAULT_LIMIT из .env / 15).
* newLimit = число → индивидуальный лимит, переопределяет глобальный.
*
* ТЗ 4.5: «снижение лимита не удаляет существующие записи, но блокирует создание новых».
*
* @param {number} companyId — companies.id
* @param {number|null} newLimit — новый лимит или null для сброса
*/
async function setLimit(companyId, newLimit) {
await pool.query(
'UPDATE companies SET custom_limit = $1, updated_at = NOW() WHERE id = $2',
[newLimit, companyId]
);
}
// ── Export ──
async function getExportCIDRs(companyId = null) {
@@ -165,13 +217,20 @@ async function logAudit(userEmail, companyId, action, oldValue, newValue, entryI
}
async function getAudit(companyId = null) {
let sql = 'SELECT * FROM audit_log';
// JOIN с companies чтобы выводить human-readable название компании в UI.
// Лимит 500 строк — достаточно для оперативного мониторинга.
// В проде можно добавить пагинацию.
let sql = `
SELECT a.*, c.name AS company_name, c.client_id
FROM audit_log a
JOIN companies c ON c.id = a.company_id
`;
const params = [];
if (companyId) {
sql += ' WHERE company_id = $1';
sql += ' WHERE a.company_id = $1';
params.push(companyId);
}
sql += ' ORDER BY created_at DESC LIMIT 500';
sql += ' ORDER BY a.created_at DESC LIMIT 500';
return (await pool.query(sql, params)).rows;
}
@@ -180,4 +239,6 @@ module.exports = {
listEntries, createEntry, updateEntry, deleteEntry,
getExportCIDRs,
getAudit,
// admin
getCompanyById, getAllCompanies, setLimit,
};