Files
graphs/backend/index.js

186 lines
7.0 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
);
`);
await pool.query(`ALTER TABLE orders ADD COLUMN IF NOT EXISTS pedido_id VARCHAR(100);`).catch(() => {});
await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS unique_order_product ON orders (pedido_id, produto_id);`).catch(err => {
console.error("Notice: Could not create unique index (might already exist or there are duplicates):", err.message);
});
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();
});
};
// 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,
ID_Pedido: row.pedido_id
});
// 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, pedido_id, cliente_fone
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (pedido_id, produto_id) DO UPDATE SET
cliente_nome = EXCLUDED.cliente_nome,
data_pedido = EXCLUDED.data_pedido,
valor_pedido = EXCLUDED.valor_pedido,
produto_descricao = EXCLUDED.produto_descricao,
quantidade = EXCLUDED.quantidade,
valor_unitario = EXCLUDED.valor_unitario,
cliente_fone = EXCLUDED.cliente_fone,
created_at = CURRENT_TIMESTAMP
`;
for (const item of payload) {
// Handle potential missing fields gracefully
const fallbackId = `${item.Nome_Cliente}_${item.Data_Pedido}_${item.Valor_Pedido}`;
const orderId = item.id || item.ID_Pedido || (item.json && item.json.body && item.json.body.id) || fallbackId;
const fone = item.Fone_Cliente || item.fone || item.celular || '';
const values = [
item.Nome_Cliente || 'Unknown',
item.Data_Pedido || '',
parseFloat(item.Valor_Pedido) || 0,
item.ID_Produto || '',
item.Descricao_Produto || '',
parseInt(item.Quantidade) || 0,
parseFloat(item.Valor_Unitario) || 0,
String(orderId),
String(fone)
];
await client.query(insertQuery, values);
}
await client.query('COMMIT');
} 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`);
});;
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`);
});