184 lines
6.2 KiB
JavaScript
184 lines
6.2 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const bodyParser = require('body-parser');
|
|
const { Pool } = require('pg');
|
|
const jwt = require('jsonwebtoken');
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3004;
|
|
const API_KEY = process.env.API_KEY || "nexstar_secret_key_123";
|
|
|
|
// Admin Credentials
|
|
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@admin.com';
|
|
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin123';
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'super_secret_jwt_key_123';
|
|
|
|
app.use(cors());
|
|
app.use(bodyParser.json());
|
|
|
|
// PostgreSQL Connection Pool
|
|
const pool = new Pool({
|
|
connectionString: process.env.DATABASE_URL || 'postgres://graphuser:graphpassword@localhost:5432/graphdb',
|
|
});
|
|
|
|
// Initialize Database Table
|
|
const initDB = async () => {
|
|
try {
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS orders (
|
|
id SERIAL PRIMARY KEY,
|
|
cliente_nome VARCHAR(255),
|
|
data_pedido VARCHAR(50),
|
|
valor_pedido NUMERIC(10, 2),
|
|
produto_id VARCHAR(100),
|
|
produto_descricao TEXT,
|
|
quantidade INTEGER,
|
|
valor_unitario NUMERIC(10, 5),
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
`);
|
|
console.log("Database initialized successfully.");
|
|
} catch (err) {
|
|
console.error("Failed to initialize database:", err);
|
|
}
|
|
};
|
|
|
|
initDB();
|
|
|
|
// Middleware for Frontend Authentication
|
|
const verifyToken = (req, res, next) => {
|
|
const authHeader = req.headers['authorization'];
|
|
if (!authHeader) return res.status(403).json({ error: 'No token provided' });
|
|
|
|
const token = authHeader.split(' ')[1];
|
|
if (!token) return res.status(403).json({ error: 'Malformed token' });
|
|
|
|
jwt.verify(token, JWT_SECRET, (err, decoded) => {
|
|
if (err) return res.status(401).json({ error: 'Unauthorized' });
|
|
req.user = decoded;
|
|
next();
|
|
});
|
|
};
|
|
|
|
const sseClients = new Set();
|
|
|
|
// SSE Endpoint for real-time updates
|
|
app.get('/api/stream', (req, res) => {
|
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
res.setHeader('Connection', 'keep-alive');
|
|
res.setHeader('X-Accel-Buffering', 'no'); // Bypass Nginx Proxy Manager buffering
|
|
res.flushHeaders();
|
|
|
|
// Send initial connection event
|
|
res.write('data: {"connected": true}\n\n');
|
|
|
|
sseClients.add(res);
|
|
|
|
req.on('close', () => {
|
|
sseClients.delete(res);
|
|
});
|
|
});
|
|
|
|
// Login Endpoint
|
|
app.post('/api/login', (req, res) => {
|
|
const { email, password } = req.body;
|
|
if (email === ADMIN_EMAIL && password === ADMIN_PASSWORD) {
|
|
const token = jwt.sign({ email }, JWT_SECRET, { expiresIn: '24h' });
|
|
res.json({ token });
|
|
} else {
|
|
res.status(401).json({ error: 'Invalid credentials' });
|
|
}
|
|
});
|
|
|
|
// Helper to format rows to match the old JSON structure for the frontend
|
|
const formatRow = (row) => ({
|
|
Nome_Cliente: row.cliente_nome,
|
|
Data_Pedido: row.data_pedido,
|
|
Valor_Pedido: parseFloat(row.valor_pedido),
|
|
ID_Produto: row.produto_id,
|
|
Descricao_Produto: row.produto_descricao,
|
|
Quantidade: row.quantidade,
|
|
Valor_Unitario: parseFloat(row.valor_unitario),
|
|
Recebido_Em: row.created_at
|
|
});
|
|
|
|
// GET data (for the frontend)
|
|
app.get('/api/data', verifyToken, async (req, res) => {
|
|
try {
|
|
const result = await pool.query('SELECT * FROM orders ORDER BY id DESC');
|
|
const formattedData = result.rows.map(formatRow);
|
|
res.json(formattedData);
|
|
} catch (error) {
|
|
console.error("Error fetching data:", error);
|
|
res.status(500).json({ error: 'Internal Server Error' });
|
|
}
|
|
});
|
|
|
|
// POST data (for n8n) - Protected by API_KEY internally or via middleware if needed
|
|
// Leaving it as it was, checking API_KEY manually? Wait, the previous version didn't actually use 'authenticate' middleware on the POST!
|
|
// Let's add the authenticate middleware to the POST endpoint.
|
|
const authenticateAPIKey = (req, res, next) => {
|
|
const apiKey = req.headers['x-api-key'];
|
|
if (apiKey === API_KEY) {
|
|
next();
|
|
} else {
|
|
res.status(401).json({ error: 'Unauthorized: Invalid API Key' });
|
|
}
|
|
};
|
|
|
|
app.post('/api/data', authenticateAPIKey, async (req, res) => {
|
|
// Respond IMMEDIATELY to prevent slowing down n8n / WhatsApp flows
|
|
res.status(201).json({ message: 'Data received, processing in background' });
|
|
|
|
const newData = req.body;
|
|
const payload = Array.isArray(newData) ? newData : [newData];
|
|
|
|
// Process asynchronously
|
|
(async () => {
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
const insertQuery = `
|
|
INSERT INTO orders (
|
|
cliente_nome, data_pedido, valor_pedido,
|
|
produto_id, produto_descricao, quantidade, valor_unitario
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
`;
|
|
|
|
for (const item of payload) {
|
|
// Handle potential missing fields gracefully
|
|
const values = [
|
|
item.Nome_Cliente || 'Unknown',
|
|
item.Data_Pedido || '',
|
|
item.Valor_Pedido || 0,
|
|
item.ID_Produto || '',
|
|
item.Descricao_Produto || '',
|
|
item.Quantidade || 0,
|
|
item.Valor_Unitario || 0
|
|
];
|
|
await client.query(insertQuery, values);
|
|
}
|
|
|
|
await client.query('COMMIT');
|
|
|
|
// Broadcast update to all connected SSE clients
|
|
const broadcastMessage = `data: {"type": "update"}\n\n`;
|
|
for (const clientRes of sseClients) {
|
|
clientRes.write(broadcastMessage);
|
|
}
|
|
} catch (error) {
|
|
await client.query('ROLLBACK');
|
|
console.error("Database insert error:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
})();
|
|
});
|
|
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`Nexstar Backend running at http://localhost:${PORT}`);
|
|
console.log(`Endpoint for n8n: POST http://localhost:${PORT}/api/data`);
|
|
}); |