first commit
This commit is contained in:
commit
eaae6cfecf
0
.env.example
Normal file
0
.env.example
Normal file
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
.env
|
||||||
|
logs/
|
||||||
|
teste.js
|
||||||
|
DALCliente.cs
|
||||||
21
middleware/auth.js
Normal file
21
middleware/auth.js
Normal 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
2138
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
Normal file
24
package.json
Normal 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
37
src/db.js
Normal 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
133
src/server.js
Normal 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();
|
||||||
Loading…
Reference in New Issue
Block a user