169 lines
4.9 KiB
JavaScript
169 lines
4.9 KiB
JavaScript
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;
|