const db = require('../database'); const crypto = require('crypto'); const { listAliases } = db; const { isGerente } = require('../middlewares/roles'); /** * Valida o token de administração (segundo fator para SQL direto). * Comparação em tempo constante para prevenir timing attacks. */ function validarAdminToken(req) { const esperado = process.env.ADMIN_TOKEN; if (!esperado) return false; // Se não configurado, ninguém acessa const recebido = req.headers['x-admin-token'] || ''; try { return crypto.timingSafeEqual(Buffer.from(recebido), Buffer.from(esperado)); } catch (_) { return false; } } class GenericController { /** * Executa uma consulta SQL genérica (SELECT) */ static async query(req, res) { try { const { alias } = req.params; const { sql, params } = req.body; if (!(await isGerente(req))) { return res.status(403).json({ success: false, error: 'Apenas gerentes podem executar SQL direto.' }); } if (!validarAdminToken(req)) { return res.status(403).json({ success: false, error: 'Token de administração (X-Admin-Token) inválido ou não configurado.' }); } if (!sql) { return res.status(400).json({ success: false, error: 'O campo "sql" é obrigatório.', }); } const result = await db.query(alias, sql, params || []); res.json({ success: true, alias, data: result, count: result.length }); } catch (err) { console.error('Erro no controller query:', err); res.status(500).json({ success: false, error: err.message }); } } /** * Executa um comando SQL (INSERT, UPDATE, DELETE) */ static async execute(req, res) { try { const { alias } = req.params; const { sql, params } = req.body; if (!(await isGerente(req))) { return res.status(403).json({ success: false, error: 'Apenas gerentes podem executar SQL direto.' }); } if (!validarAdminToken(req)) { return res.status(403).json({ success: false, error: 'Token de administração (X-Admin-Token) inválido ou não configurado.' }); } if (!sql) { return res.status(400).json({ success: false, error: 'O campo "sql" é obrigatório.', }); } const result = await db.execute(alias, sql, params || []); res.json({ success: true, alias, ...result }); } catch (err) { console.error('Erro no controller execute:', err); res.status(500).json({ success: false, error: err.message }); } } /** * Lista todas as tabelas do banco */ static async listTables(req, res) { try { const { alias } = req.params; const tables = await db.listTables(alias); res.json({ success: true, alias, data: tables, count: tables.length }); } catch (err) { console.error('Erro ao listar tabelas:', err); res.status(500).json({ success: false, error: err.message }); } } /** * Retorna a estrutura de uma tabela (colunas e tipos) */ static async tableInfo(req, res) { try { const { alias, tableName } = req.params; if (!tableName) { return res.status(400).json({ success: false, error: 'O parâmetro "tableName" é obrigatório.', }); } const columns = await db.tableInfo(alias, tableName); res.json({ success: true, alias, tableName, columns, count: columns.length }); } catch (err) { console.error('Erro ao obter info da tabela:', err); res.status(500).json({ success: false, error: err.message }); } } /** * Health check completo do alias específico. * Verifica: conectividade Firebird, uso de memória, uptime. */ static async healthCheck(req, res) { try { const { alias } = req.params; const t0 = Date.now(); const isConnected = await db.testConnection(alias); const pingMs = Date.now() - t0; const mem = process.memoryUsage(); res.json({ success: true, alias, connected: isConnected, pingMs, uptimeSeconds: Math.floor(process.uptime()), memory: { rssMB: Math.round(mem.rss / 1024 / 1024), heapUsedMB: Math.round(mem.heapUsed / 1024 / 1024), heapTotalMB: Math.round(mem.heapTotal / 1024 / 1024), }, timestamp: new Date().toISOString(), }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } } /** * Lista todos os aliases disponíveis */ static async listAliases(req, res) { try { const aliases = listAliases(); const statuses = {}; for (const a of aliases) { statuses[a] = await db.testConnection(a); } res.json({ success: true, aliases, status: statuses }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } } } module.exports = GenericController;