feat: per-user panel permissions system
Replace hardcoded role-based access with granular per-panel permissions. Each user can now be assigned any combination of 6 panels (Corporate, BI Executive, Clientes, Providers, Usuarios, Meu Dashboard) regardless of their role. Existing users are auto-migrated with defaults based on role. - Add src/panels.js with panel registry and default permissions - Add permissions column to SQLite + migration for existing users - Add requirePermission() middleware, replace requireRole on all routes - Dynamic nav in buildHeader based on user permissions - Permissions checkbox UI in admin panel with role presets - Anti-lockout: users cannot remove 'usuarios' from themselves Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
148
server.js
148
server.js
@@ -10,7 +10,8 @@ require('dotenv').config();
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const path = require('path');
|
||||
const { authenticate, requireAuth, requireRole, createAgente, createUser } = require('./src/auth');
|
||||
const { authenticate, requireAuth, requireRole, requirePermission, createAgente, createUser } = require('./src/auth');
|
||||
const { PANELS, PANEL_ROUTE } = require('./src/panels');
|
||||
const { fetchTransacoes, fetchAllTransacoes, serialize, fetchDailyStats, fetchKPIs, fetchTrend30Days, fetchTopAgentes, fetchTrendByPeriod, fetchKPIsByPeriod, fetchBIData, fetchRevenueAnalytics, fetchBIStrategic, fetchTopClients, fetchClientSearch, fetchClientProfile, fetchClientData, fetchMerchantProfile, fetchMerchantData, fetchProviderPerformance, fetchFailedTransactions, fetchProviderTrend } = require('./src/queries');
|
||||
const { buildAdminProvidersHTML } = require('./src/admin-providers');
|
||||
const pool = require('./src/db-rds');
|
||||
@@ -47,17 +48,20 @@ app.use('/public', express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// --- Unified Login Routes ---
|
||||
|
||||
// Helper function to get redirect URL based on role
|
||||
function getRedirectByRole(role) {
|
||||
if (role === 'admin') return '/corporate'; // Admin vai direto pro Corporate Dashboard
|
||||
if (role === 'corporate') return '/corporate';
|
||||
return '/dashboard';
|
||||
// Helper function to get redirect URL based on user permissions
|
||||
function getRedirectForUser(user) {
|
||||
const perms = Array.isArray(user.permissions) ? user.permissions : JSON.parse(user.permissions || '[]');
|
||||
const priority = ['corporate', 'bi', 'cliente', 'providers', 'usuarios', 'dashboard'];
|
||||
for (const key of priority) {
|
||||
if (perms.includes(key)) return PANEL_ROUTE[key];
|
||||
}
|
||||
return '/login';
|
||||
}
|
||||
|
||||
// Root -> login page (or redirect if logged in)
|
||||
app.get('/', (req, res) => {
|
||||
if (req.session?.user) {
|
||||
return res.redirect(getRedirectByRole(req.session.user.role));
|
||||
return res.redirect(getRedirectForUser(req.session.user));
|
||||
}
|
||||
res.redirect('/login');
|
||||
});
|
||||
@@ -65,7 +69,7 @@ app.get('/', (req, res) => {
|
||||
// Login page
|
||||
app.get('/login', (req, res) => {
|
||||
if (req.session?.user) {
|
||||
return res.redirect(getRedirectByRole(req.session.user.role));
|
||||
return res.redirect(getRedirectForUser(req.session.user));
|
||||
}
|
||||
res.sendFile(path.join(__dirname, 'public', 'login.html'));
|
||||
});
|
||||
@@ -79,16 +83,18 @@ app.post('/login', async (req, res) => {
|
||||
if (!user) return res.redirect(`/login?error=1&email=${emailParam}`);
|
||||
|
||||
// Unified session
|
||||
const permissions = JSON.parse(user.permissions || '[]');
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
nome: user.nome,
|
||||
role: user.role || 'agente',
|
||||
agente_id: user.agente_id
|
||||
agente_id: user.agente_id,
|
||||
permissions
|
||||
};
|
||||
|
||||
// Redirect based on role
|
||||
res.redirect(getRedirectByRole(user.role));
|
||||
// Redirect based on permissions
|
||||
res.redirect(getRedirectForUser(req.session.user));
|
||||
} catch (err) {
|
||||
console.error('Login error:', err);
|
||||
res.redirect(`/login?error=1&email=${emailParam}`);
|
||||
@@ -112,8 +118,8 @@ app.get('/admin/logout', (req, res) => {
|
||||
|
||||
// --- Agent Routes ---
|
||||
|
||||
// Dashboard (agente only)
|
||||
app.get('/dashboard', requireRole('agente'), async (req, res) => {
|
||||
// Dashboard (requires 'dashboard' permission)
|
||||
app.get('/dashboard', requirePermission('dashboard'), async (req, res) => {
|
||||
try {
|
||||
const user = req.session.user;
|
||||
const { rowsBrlUsd, rowsUsdBrl } = await fetchTransacoes(user.agente_id);
|
||||
@@ -128,8 +134,8 @@ app.get('/dashboard', requireRole('agente'), async (req, res) => {
|
||||
|
||||
// --- Admin Routes (User Management - admin only) ---
|
||||
|
||||
// Admin home - User management panel (admin only)
|
||||
app.get('/admin', requireRole('admin'), (req, res) => {
|
||||
// Admin home - User management panel
|
||||
app.get('/admin', requirePermission('usuarios'), (req, res) => {
|
||||
try {
|
||||
const agentes = db.prepare('SELECT * FROM agentes ORDER BY id DESC').all();
|
||||
const html = buildAdminHTML(agentes, req.session.user);
|
||||
@@ -141,19 +147,19 @@ app.get('/admin', requireRole('admin'), (req, res) => {
|
||||
});
|
||||
|
||||
// Alias: /admin/usuarios -> /admin
|
||||
app.get('/admin/usuarios', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/usuarios', requirePermission('usuarios'), (req, res) => {
|
||||
res.redirect('/admin');
|
||||
});
|
||||
|
||||
// Legacy route - redirect to /admin
|
||||
app.get('/admin/agentes', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/agentes', requirePermission('usuarios'), (req, res) => {
|
||||
res.redirect('/admin');
|
||||
});
|
||||
|
||||
// --- Corporate Routes (Dashboard + Emulation - corporate and admin) ---
|
||||
|
||||
// Corporate Dashboard - Full KPIs, Trends and Ranking
|
||||
app.get('/corporate', requireRole('corporate', 'admin'), async (req, res) => {
|
||||
app.get('/corporate', requirePermission('corporate'), async (req, res) => {
|
||||
try {
|
||||
const user = req.session.user;
|
||||
const html = buildAdminDashboardHTML(user);
|
||||
@@ -165,12 +171,12 @@ app.get('/corporate', requireRole('corporate', 'admin'), async (req, res) => {
|
||||
});
|
||||
|
||||
// Legacy route - redirect to /corporate
|
||||
app.get('/corporate/dashboard', requireRole('corporate', 'admin'), (req, res) => {
|
||||
app.get('/corporate/dashboard', requirePermission('corporate'), (req, res) => {
|
||||
res.redirect('/corporate');
|
||||
});
|
||||
|
||||
// Corporate emulate agent - view dashboard as specific agent
|
||||
app.get('/corporate/emular/:agente_id', requireRole('corporate', 'admin'), async (req, res) => {
|
||||
app.get('/corporate/emular/:agente_id', requirePermission('corporate'), async (req, res) => {
|
||||
try {
|
||||
const agenteId = parseInt(req.params.agente_id);
|
||||
const agente = db.prepare('SELECT * FROM agentes WHERE agente_id = ?').get(agenteId);
|
||||
@@ -185,7 +191,8 @@ app.get('/corporate/emular/:agente_id', requireRole('corporate', 'admin'), async
|
||||
nome: agente.nome + ' (Emulando)',
|
||||
agente_id: agenteId,
|
||||
email: agente.email,
|
||||
emulatorRole: req.session.user.role // Pass the emulator's role
|
||||
emulatorRole: req.session.user.role,
|
||||
permissions: req.session.user.permissions || []
|
||||
}, true, null, false, true); // isEmulating = true
|
||||
res.send(html);
|
||||
} catch (err) {
|
||||
@@ -195,7 +202,7 @@ app.get('/corporate/emular/:agente_id', requireRole('corporate', 'admin'), async
|
||||
});
|
||||
|
||||
// Legacy route - redirect to /corporate/emular
|
||||
app.get('/admin/emular/:agente_id', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/emular/:agente_id', requirePermission('corporate'), (req, res) => {
|
||||
res.redirect(`/corporate/emular/${req.params.agente_id}`);
|
||||
});
|
||||
|
||||
@@ -221,7 +228,7 @@ app.get('/api/cotacao', async (req, res) => {
|
||||
// --- Corporate API Routes (dashboard data - corporate and admin) ---
|
||||
|
||||
// API endpoint for corporate dashboard data
|
||||
app.get('/corporate/api/data', requireRole('corporate', 'admin'), async (req, res) => {
|
||||
app.get('/corporate/api/data', requirePermission('corporate'), async (req, res) => {
|
||||
try {
|
||||
const dias = parseInt(req.query.dias) || 90;
|
||||
const { rowsBrlUsd, rowsUsdBrl } = await fetchAllTransacoes(dias);
|
||||
@@ -234,7 +241,7 @@ app.get('/corporate/api/data', requireRole('corporate', 'admin'), async (req, re
|
||||
});
|
||||
|
||||
// API: KPIs (hoje vs média 30 dias) - com cache
|
||||
app.get('/corporate/api/kpis', requireRole('corporate', 'admin'), async (req, res) => {
|
||||
app.get('/corporate/api/kpis', requirePermission('corporate'), async (req, res) => {
|
||||
try {
|
||||
const data = await cache.getOrFetch('kpis', fetchKPIs, 5 * 60 * 1000);
|
||||
res.json({ success: true, data });
|
||||
@@ -245,7 +252,7 @@ app.get('/corporate/api/kpis', requireRole('corporate', 'admin'), async (req, re
|
||||
});
|
||||
|
||||
// API: Tendência 30 dias - com cache
|
||||
app.get('/corporate/api/trend', requireRole('corporate', 'admin'), async (req, res) => {
|
||||
app.get('/corporate/api/trend', requirePermission('corporate'), async (req, res) => {
|
||||
try {
|
||||
const data = await cache.getOrFetch('trend30', fetchTrend30Days, 10 * 60 * 1000);
|
||||
res.json({ success: true, data });
|
||||
@@ -256,7 +263,7 @@ app.get('/corporate/api/trend', requireRole('corporate', 'admin'), async (req, r
|
||||
});
|
||||
|
||||
// API: Top 5 agentes - com cache por período
|
||||
app.get('/corporate/api/top-agentes', requireRole('corporate', 'admin'), async (req, res) => {
|
||||
app.get('/corporate/api/top-agentes', requirePermission('corporate'), async (req, res) => {
|
||||
try {
|
||||
const dias = parseInt(req.query.dias) || 30;
|
||||
const cacheKey = `top-agentes-${dias}`;
|
||||
@@ -281,7 +288,7 @@ app.get('/corporate/api/top-agentes', requireRole('corporate', 'admin'), async (
|
||||
});
|
||||
|
||||
// API: Corporate Dashboard - KPIs por período
|
||||
app.get('/corporate/api/kpis-period', requireRole('corporate', 'admin'), async (req, res) => {
|
||||
app.get('/corporate/api/kpis-period', requirePermission('corporate'), async (req, res) => {
|
||||
try {
|
||||
const { inicio, fim } = req.query;
|
||||
if (!inicio || !fim) {
|
||||
@@ -296,7 +303,7 @@ app.get('/corporate/api/kpis-period', requireRole('corporate', 'admin'), async (
|
||||
});
|
||||
|
||||
// API: Corporate Dashboard - Tendência por período
|
||||
app.get('/corporate/api/trend-period', requireRole('corporate', 'admin'), async (req, res) => {
|
||||
app.get('/corporate/api/trend-period', requirePermission('corporate'), async (req, res) => {
|
||||
try {
|
||||
const { inicio, fim } = req.query;
|
||||
if (!inicio || !fim) {
|
||||
@@ -311,30 +318,30 @@ app.get('/corporate/api/trend-period', requireRole('corporate', 'admin'), async
|
||||
});
|
||||
|
||||
// Legacy API routes - redirect to /corporate/api/*
|
||||
app.get('/admin/api/data', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/api/data', requirePermission('corporate'), (req, res) => {
|
||||
res.redirect(`/corporate/api/data?${new URLSearchParams(req.query)}`);
|
||||
});
|
||||
app.get('/admin/api/kpis', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/api/kpis', requirePermission('corporate'), (req, res) => {
|
||||
res.redirect('/corporate/api/kpis');
|
||||
});
|
||||
app.get('/admin/api/trend', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/api/trend', requirePermission('corporate'), (req, res) => {
|
||||
res.redirect('/corporate/api/trend');
|
||||
});
|
||||
app.get('/admin/api/top-agentes', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/api/top-agentes', requirePermission('corporate'), (req, res) => {
|
||||
res.redirect(`/corporate/api/top-agentes?${new URLSearchParams(req.query)}`);
|
||||
});
|
||||
app.get('/admin/api/corporate/kpis', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/api/corporate/kpis', requirePermission('corporate'), (req, res) => {
|
||||
res.redirect(`/corporate/api/kpis-period?${new URLSearchParams(req.query)}`);
|
||||
});
|
||||
app.get('/admin/api/corporate/trend', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/api/corporate/trend', requirePermission('corporate'), (req, res) => {
|
||||
res.redirect(`/corporate/api/trend-period?${new URLSearchParams(req.query)}`);
|
||||
});
|
||||
app.get('/admin/dashboard', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/dashboard', requirePermission('corporate'), (req, res) => {
|
||||
res.redirect('/corporate/dashboard');
|
||||
});
|
||||
|
||||
// --- Admin BI Dashboard (admin only) ---
|
||||
app.get('/admin/bi', requireRole('admin'), (req, res) => {
|
||||
// --- Admin BI Dashboard ---
|
||||
app.get('/admin/bi', requirePermission('bi'), (req, res) => {
|
||||
try {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
||||
res.set('Pragma', 'no-cache');
|
||||
@@ -346,7 +353,7 @@ app.get('/admin/bi', requireRole('admin'), (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/bi', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/bi', requirePermission('bi'), async (req, res) => {
|
||||
try {
|
||||
const start = req.query.start;
|
||||
const end = req.query.end;
|
||||
@@ -365,7 +372,7 @@ app.get('/admin/api/bi', requireRole('admin'), async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/bi/revenue', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/bi/revenue', requirePermission('bi'), async (req, res) => {
|
||||
try {
|
||||
const { start, end, granularity } = req.query;
|
||||
if (!start || !end) return res.status(400).json({ error: 'start and end required' });
|
||||
@@ -378,7 +385,7 @@ app.get('/admin/api/bi/revenue', requireRole('admin'), async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/bi/strategic', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/bi/strategic', requirePermission('bi'), async (req, res) => {
|
||||
try {
|
||||
const { start, end } = req.query;
|
||||
if (!start || !end) return res.status(400).json({ error: 'start and end required' });
|
||||
@@ -390,8 +397,8 @@ app.get('/admin/api/bi/strategic', requireRole('admin'), async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- Admin Cliente Dashboard (admin only) ---
|
||||
app.get('/admin/cliente', requireRole('admin'), (req, res) => {
|
||||
// --- Admin Cliente Dashboard ---
|
||||
app.get('/admin/cliente', requirePermission('cliente'), (req, res) => {
|
||||
try {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
||||
res.set('Pragma', 'no-cache');
|
||||
@@ -403,7 +410,7 @@ app.get('/admin/cliente', requireRole('admin'), (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/clientes/top', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/clientes/top', requirePermission('cliente'), async (req, res) => {
|
||||
try {
|
||||
const data = await cache.getOrFetch('top-clients', fetchTopClients, 15 * 60 * 1000);
|
||||
res.json(data);
|
||||
@@ -413,7 +420,7 @@ app.get('/admin/api/clientes/top', requireRole('admin'), async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/clientes/search', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/clientes/search', requirePermission('cliente'), async (req, res) => {
|
||||
try {
|
||||
const q = (req.query.q || '').trim();
|
||||
if (q.length < 2) return res.json([]);
|
||||
@@ -425,7 +432,7 @@ app.get('/admin/api/clientes/search', requireRole('admin'), async (req, res) =>
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/cliente/:id/profile', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/cliente/:id/profile', requirePermission('cliente'), async (req, res) => {
|
||||
try {
|
||||
const clienteId = parseInt(req.params.id);
|
||||
if (!clienteId) return res.status(400).json({ error: 'Invalid client ID' });
|
||||
@@ -461,7 +468,7 @@ app.get('/admin/api/cliente/:id/profile', requireRole('admin'), async (req, res)
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/cliente/:id/data', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/cliente/:id/data', requirePermission('cliente'), async (req, res) => {
|
||||
try {
|
||||
const clienteId = parseInt(req.params.id);
|
||||
const { start, end } = req.query;
|
||||
@@ -507,21 +514,21 @@ app.get('/admin/api/cliente/:id/data', requireRole('admin'), async (req, res) =>
|
||||
|
||||
// Create user (admin only)
|
||||
app.post('/admin/agentes', requireRole('admin'), async (req, res) => {
|
||||
const { nome, email, agente_id, senha, role } = req.body;
|
||||
const { nome, email, agente_id, senha, role, permissions } = req.body;
|
||||
try {
|
||||
if (!nome || !email || !senha) {
|
||||
return res.status(400).json({ error: 'Nome, email e senha sao obrigatorios' });
|
||||
}
|
||||
|
||||
const userRole = role || 'agente';
|
||||
// Admin and Corporate don't need agente_id
|
||||
const agenteId = (userRole === 'admin' || userRole === 'corporate') ? 0 : (agente_id || 0);
|
||||
const agenteId = agente_id || 0;
|
||||
|
||||
if (userRole === 'agente' && !agente_id) {
|
||||
return res.status(400).json({ error: 'Agente ID e obrigatorio para agentes' });
|
||||
// dashboard permission requires agente_id
|
||||
if (Array.isArray(permissions) && permissions.includes('dashboard') && !agenteId) {
|
||||
return res.status(400).json({ error: 'Agente ID e obrigatorio para acesso ao Meu Dashboard' });
|
||||
}
|
||||
|
||||
const result = await createUser(email, senha, nome, userRole, agenteId);
|
||||
const result = await createUser(email, senha, nome, userRole, agenteId, Array.isArray(permissions) ? permissions : null);
|
||||
res.json({ success: true, id: result.lastInsertRowid });
|
||||
} catch (err) {
|
||||
console.error('Create user error:', err);
|
||||
@@ -535,7 +542,7 @@ app.post('/admin/agentes', requireRole('admin'), async (req, res) => {
|
||||
// Update user (admin only)
|
||||
app.put('/admin/agentes/:id', requireRole('admin'), async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { nome, email, agente_id, ativo, senha, role } = req.body;
|
||||
const { nome, email, agente_id, ativo, senha, role, permissions } = req.body;
|
||||
try {
|
||||
const agent = db.prepare('SELECT * FROM agentes WHERE id = ?').get(id);
|
||||
if (!agent) {
|
||||
@@ -562,6 +569,13 @@ app.put('/admin/agentes/:id', requireRole('admin'), async (req, res) => {
|
||||
if (role !== undefined) {
|
||||
db.prepare('UPDATE agentes SET role = ? WHERE id = ?').run(role, id);
|
||||
}
|
||||
if (Array.isArray(permissions)) {
|
||||
// Anti-lockout: prevent user from removing 'usuarios' from their own permissions
|
||||
if (parseInt(id) === req.session.user.id && !permissions.includes('usuarios')) {
|
||||
return res.status(400).json({ error: 'Voce nao pode remover a permissao "Usuarios" de si mesmo' });
|
||||
}
|
||||
db.prepare('UPDATE agentes SET permissions = ? WHERE id = ?').run(JSON.stringify(permissions), id);
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
@@ -590,7 +604,7 @@ app.delete('/admin/agentes/:id', requireRole('admin'), (req, res) => {
|
||||
|
||||
// --- Excel Export Endpoints ---
|
||||
|
||||
app.get('/admin/api/export/bi-excel', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/export/bi-excel', requirePermission('bi'), async (req, res) => {
|
||||
try {
|
||||
const { start, end } = req.query;
|
||||
if (!start || !end) return res.status(400).json({ error: 'start and end required' });
|
||||
@@ -676,7 +690,7 @@ app.get('/admin/api/export/bi-excel', requireRole('admin'), async (req, res) =>
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/export/clients-excel', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/export/clients-excel', requirePermission('cliente'), async (req, res) => {
|
||||
try {
|
||||
const clients = await cache.getOrFetch('top-clients', fetchTopClients, 15 * 60 * 1000);
|
||||
await exportToExcel(res, clients, [
|
||||
@@ -692,7 +706,7 @@ app.get('/admin/api/export/clients-excel', requireRole('admin'), async (req, res
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/export/providers-excel', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/export/providers-excel', requirePermission('providers'), async (req, res) => {
|
||||
try {
|
||||
const { start, end } = req.query;
|
||||
if (!start || !end) return res.status(400).json({ error: 'start and end required' });
|
||||
@@ -714,7 +728,7 @@ app.get('/admin/api/export/providers-excel', requireRole('admin'), async (req, r
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/export/transactions-excel', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/export/transactions-excel', requirePermission('bi'), async (req, res) => {
|
||||
try {
|
||||
const { start, end } = req.query;
|
||||
const dias = start && end ? null : 90;
|
||||
@@ -761,7 +775,7 @@ app.get('/admin/api/export/transactions-excel', requireRole('admin'), async (req
|
||||
});
|
||||
|
||||
// --- Forecast API ---
|
||||
app.get('/admin/api/bi/forecast', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/bi/forecast', requirePermission('bi'), async (req, res) => {
|
||||
try {
|
||||
const metric = req.query.metric || 'volume';
|
||||
const days = parseInt(req.query.days) || 30;
|
||||
@@ -792,7 +806,7 @@ app.get('/admin/api/bi/forecast', requireRole('admin'), async (req, res) => {
|
||||
});
|
||||
|
||||
// --- Churn Risk API ---
|
||||
app.get('/admin/api/cliente/:id/churn', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/cliente/:id/churn', requirePermission('cliente'), async (req, res) => {
|
||||
try {
|
||||
const clienteId = parseInt(req.params.id);
|
||||
if (!clienteId) return res.status(400).json({ error: 'Invalid client ID' });
|
||||
@@ -845,7 +859,7 @@ app.get('/admin/api/cliente/:id/churn', requireRole('admin'), async (req, res) =
|
||||
|
||||
// --- Alert API Endpoints ---
|
||||
|
||||
app.get('/admin/api/alerts', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/api/alerts', requirePermission('bi'), (req, res) => {
|
||||
try {
|
||||
const unacked = req.query.unacked === '1';
|
||||
const alerts = getAlerts(24, unacked);
|
||||
@@ -856,7 +870,7 @@ app.get('/admin/api/alerts', requireRole('admin'), (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/admin/api/alerts/:id/ack', requireRole('admin'), (req, res) => {
|
||||
app.put('/admin/api/alerts/:id/ack', requirePermission('bi'), (req, res) => {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
acknowledgeAlert(id);
|
||||
@@ -867,7 +881,7 @@ app.put('/admin/api/alerts/:id/ack', requireRole('admin'), (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/alerts/history', requireRole('admin'), (req, res) => {
|
||||
app.get('/admin/api/alerts/history', requirePermission('bi'), (req, res) => {
|
||||
try {
|
||||
const days = parseInt(req.query.days) || 7;
|
||||
const alerts = getAlertHistory(days);
|
||||
@@ -902,8 +916,8 @@ app.get('/health', async (req, res) => {
|
||||
res.json(health);
|
||||
});
|
||||
|
||||
// --- Provider Dashboard (admin only) ---
|
||||
app.get('/admin/providers', requireRole('admin'), (req, res) => {
|
||||
// --- Provider Dashboard ---
|
||||
app.get('/admin/providers', requirePermission('providers'), (req, res) => {
|
||||
try {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
||||
const html = buildAdminProvidersHTML(req.session.user);
|
||||
@@ -914,7 +928,7 @@ app.get('/admin/providers', requireRole('admin'), (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/providers', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/providers', requirePermission('providers'), async (req, res) => {
|
||||
try {
|
||||
const { start, end } = req.query;
|
||||
if (!start || !end) return res.status(400).json({ error: 'start and end required' });
|
||||
@@ -926,7 +940,7 @@ app.get('/admin/api/providers', requireRole('admin'), async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/providers/failed', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/providers/failed', requirePermission('providers'), async (req, res) => {
|
||||
try {
|
||||
const { start, end } = req.query;
|
||||
if (!start || !end) return res.status(400).json({ error: 'start and end required' });
|
||||
@@ -938,7 +952,7 @@ app.get('/admin/api/providers/failed', requireRole('admin'), async (req, res) =>
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/admin/api/providers/trend', requireRole('admin'), async (req, res) => {
|
||||
app.get('/admin/api/providers/trend', requirePermission('providers'), async (req, res) => {
|
||||
try {
|
||||
const { start, end } = req.query;
|
||||
if (!start || !end) return res.status(400).json({ error: 'start and end required' });
|
||||
|
||||
Reference in New Issue
Block a user