first commit

This commit is contained in:
levelcode developed 2026-04-30 22:37:50 -03:00
commit eaae6cfecf
7 changed files with 2358 additions and 0 deletions

0
.env.example Normal file
View File

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules/
.env
logs/
teste.js
DALCliente.cs

21
middleware/auth.js Normal file
View File

@ -0,0 +1,21 @@
require('dotenv').config();
const VALID_KEYS = new Set(
(process.env.API_KEYS || '').split(',').map(k => k.trim()).filter(Boolean)
);
function authMiddleware(req, res, next) {
const key = req.headers['x-api-key'];
if (!key) {
return res.status(401).json({ error: 'API key ausente. Use o header: x-api-key' });
}
if (!VALID_KEYS.has(key)) {
return res.status(403).json({ error: 'API key inválida.' });
}
next();
}
module.exports = authMiddleware;

2138
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "db-gateway",
"version": "1.0.0",
"description": "",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^17.4.2",
"express": "^5.2.1",
"express-rate-limit": "^8.4.1",
"helmet": "^8.1.0",
"morgan": "^1.10.1",
"mssql": "^12.5.0"
},
"devDependencies": {
"nodemon": "^3.1.14"
}
}

37
src/db.js Normal file
View File

@ -0,0 +1,37 @@
const sql = require('mssql');
const config = {
server: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
options: {
encrypt: process.env.DB_ENCRYPT === 'true',
trustServerCertificate: process.env.DB_TRUST_CERT === 'true',
},
pool: {
max: 10,
min: 2,
idleTimeoutMillis: 30000,
},
};
let pool = null;
async function getPool() {
if (!pool) {
pool = await sql.connect(config);
console.log('✅ Conectado ao SQL Server');
}
return pool;
}
async function closePool() {
if (pool) {
await pool.close();
pool = null;
}
}
module.exports = { getPool, closePool, sql };

133
src/server.js Normal file
View File

@ -0,0 +1,133 @@
require('dotenv').config({ path: require('path').resolve(__dirname, '../.env') });
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const morgan = require('morgan');
const fs = require('fs');
const path = require('path');
const { getPool, closePool, sql } = require('./db');
const auth = require('../middleware/auth');
const app = express();
const PORT = process.env.PORT || 3000;
// Cria a pasta de logs se não existir
const logsDir = path.join(__dirname, '../logs');
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir);
const logStream = fs.createWriteStream(path.join(logsDir, 'api.log'), { flags: 'a' });
app.set('trust proxy', 1); // Pega o IP real do cliente passado pelo Nginx
app.use(helmet());
app.use(express.json({ limit: '1mb' }));
app.disable('x-powered-by');
app.use(morgan('dev')); // Log no terminal
app.use(morgan(':date[iso] :method :url :status :response-time ms - IP: :req[x-forwarded-for]', { stream: logStream })); // Log em arquivo
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
message: { error: 'Muitas requisições. Tente novamente em 1 minuto.' },
});
app.use(limiter);
// Rota pública
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Rotas protegidas
app.use(auth);
app.post('/query', async (req, res) => {
const { query, params = {} } = req.body;
if (!query || typeof query !== 'string') {
return res.status(400).json({ error: 'Campo "query" é obrigatório.' });
}
const dangerous = /^\s*(DROP|TRUNCATE|ALTER|CREATE)\s/i;
if (dangerous.test(query)) {
return res.status(403).json({ error: 'Operação não permitida via gateway.' });
}
try {
const pool = await getPool();
const request = pool.request();
for (const [key, value] of Object.entries(params)) {
request.input(key, value);
}
const result = await request.query(query);
res.json({
recordset: result.recordset ?? [],
rowsAffected: result.rowsAffected ?? [],
});
} catch (err) {
console.error('Erro na query:', err.message);
res.status(500).json({ error: 'Erro ao executar a query.', detail: err.message });
}
});
app.post('/transaction', async (req, res) => {
const { queries } = req.body;
if (!Array.isArray(queries) || queries.length === 0) {
return res.status(400).json({ error: 'Campo "queries" deve ser um array não vazio.' });
}
const pool = await getPool();
const transaction = new sql.Transaction(pool);
try {
await transaction.begin();
const results = [];
for (const item of queries) {
const { query, params = {} } = item;
const request = new sql.Request(transaction);
for (const [key, value] of Object.entries(params)) {
request.input(key, value);
}
const result = await request.query(query);
results.push({
recordset: result.recordset ?? [],
rowsAffected: result.rowsAffected ?? [],
});
}
await transaction.commit();
res.json({ success: true, results });
} catch (err) {
await transaction.rollback();
console.error('Erro na transação (rollback):', err.message);
res.status(500).json({ error: 'Transação revertida.', detail: err.message });
}
});
app.use((req, res) => {
res.status(404).json({ error: 'Rota não encontrada.' });
});
async function start() {
try {
await getPool();
app.listen(PORT, () => {
console.log(`🚀 Gateway rodando em http://localhost:${PORT}`);
});
} catch (err) {
console.error('❌ Falha ao iniciar:', err.message);
process.exit(1);
}
}
process.on('SIGINT', async () => {
await closePool();
process.exit(0);
});
start();