Actualizar server.js
This commit is contained in:
82
server.js
82
server.js
@@ -16,6 +16,7 @@ const {
|
||||
JWT_SECRET,
|
||||
EVOLUTION_BASE_URL,
|
||||
EVOLUTION_API_KEY,
|
||||
EVOLUTION_INSTANCE, // <--- ¡FALTABA ESTO! (La instancia que envía los códigos)
|
||||
} = process.env;
|
||||
|
||||
// --- DIAGNÓSTICO DE INICIO ---
|
||||
@@ -24,17 +25,19 @@ console.log("🔧 INICIANDO SERVIDOR INTEGRA REPARA");
|
||||
console.log("------------------------------------------------");
|
||||
if (!DATABASE_URL) console.error("❌ FALTA: DATABASE_URL");
|
||||
if (!JWT_SECRET) console.error("❌ FALTA: JWT_SECRET");
|
||||
|
||||
if (!EVOLUTION_BASE_URL) {
|
||||
console.error("⚠️ AVISO: No has puesto EVOLUTION_BASE_URL (WhatsApp no funcionará)");
|
||||
console.error("⚠️ AVISO: No has puesto EVOLUTION_BASE_URL");
|
||||
} else {
|
||||
console.log("✅ Evolution URL detectada:", EVOLUTION_BASE_URL);
|
||||
console.log("✅ Evolution URL:", EVOLUTION_BASE_URL);
|
||||
}
|
||||
if (!EVOLUTION_API_KEY) {
|
||||
console.error("⚠️ AVISO: No has puesto EVOLUTION_API_KEY (WhatsApp no funcionará)");
|
||||
|
||||
if (!EVOLUTION_INSTANCE) {
|
||||
console.error("⚠️ AVISO: No has puesto EVOLUTION_INSTANCE (No se enviarán códigos de registro)");
|
||||
} else {
|
||||
// Mostramos solo los primeros 4 caracteres por seguridad
|
||||
console.log("✅ Evolution API Key detectada:", EVOLUTION_API_KEY.substring(0, 4) + "...");
|
||||
console.log("✅ Instancia de Notificaciones:", EVOLUTION_INSTANCE);
|
||||
}
|
||||
|
||||
console.log("------------------------------------------------");
|
||||
|
||||
if (!DATABASE_URL || !JWT_SECRET) {
|
||||
@@ -207,26 +210,61 @@ async function autoUpdateDB() {
|
||||
|
||||
// HELPERS
|
||||
function normalizePhone(phone) { let p = String(phone || "").trim().replace(/\s+/g, "").replace(/-/g, ""); if (!p) return ""; if (!p.startsWith("+") && /^[6789]\d{8}$/.test(p)) return "+34" + p; return p; }
|
||||
function genCode6() { return String(Math.floor(100000 + Math.random() * 900000)); }
|
||||
function signToken(user) { const accountId = user.owner_id || user.id; return jwt.sign({ sub: user.id, email: user.email, phone: user.phone, role: user.role || 'operario', accountId }, JWT_SECRET, { expiresIn: "30d" }); }
|
||||
function authMiddleware(req, res, next) { const h = req.headers.authorization || ""; const token = h.startsWith("Bearer ") ? h.slice(7) : ""; if (!token) return res.status(401).json({ ok: false, error: "No token" }); try { req.user = jwt.verify(token, JWT_SECRET); next(); } catch { return res.status(401).json({ ok: false, error: "Token inválido" }); } }
|
||||
function genCode6() { return String(Math.floor(100000 + Math.random() * 900000)); }
|
||||
|
||||
// --- WHATSAPP HELPER (Automatización) ---
|
||||
// --- FUNCIÓN DE ENVÍO DE CÓDIGO (REGISTRO) ---
|
||||
async function sendWhatsAppCode(phone, code) {
|
||||
if (!EVOLUTION_BASE_URL || !EVOLUTION_API_KEY || !EVOLUTION_INSTANCE) {
|
||||
console.error("❌ ERROR: Faltan variables para enviar WhatsApp (URL, APIKEY o INSTANCE)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Aseguramos que la URL no tenga barra al final y añadimos la instancia
|
||||
const url = `${EVOLUTION_BASE_URL.replace(/\/$/, "")}/message/sendText/${EVOLUTION_INSTANCE}`;
|
||||
const number = phone.replace("+", ""); // Quitar el + para la API
|
||||
|
||||
console.log(`📤 Enviando código a ${number} desde instancia ${EVOLUTION_INSTANCE}...`);
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"apikey": EVOLUTION_API_KEY
|
||||
},
|
||||
body: JSON.stringify({
|
||||
number: number,
|
||||
text: `🔐 Código de verificación IntegraRepara: *${code}*`
|
||||
})
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.text();
|
||||
console.error("❌ Error enviando WhatsApp:", res.status, err);
|
||||
} else {
|
||||
console.log("✅ WhatsApp enviado correctamente.");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("❌ Excepción enviando WhatsApp:", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// --- AUTOMATIZACIÓN DE INSTANCIAS (CLIENTES) ---
|
||||
async function ensureInstance(instanceName) {
|
||||
if (!EVOLUTION_BASE_URL || !EVOLUTION_API_KEY) throw new Error("Faltan variables EVOLUTION en el servidor");
|
||||
|
||||
const baseUrl = EVOLUTION_BASE_URL.replace(/\/$/, "");
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"apikey": EVOLUTION_API_KEY.trim() // Trim por si se copió con espacios
|
||||
"apikey": EVOLUTION_API_KEY.trim()
|
||||
};
|
||||
|
||||
// 1. Verificar si existe (intentando pedir estado)
|
||||
const checkRes = await fetch(`${baseUrl}/instance/connectionState/${instanceName}`, { headers });
|
||||
|
||||
if (checkRes.status === 404) {
|
||||
console.log(`🚀 Creando instancia automática: ${instanceName}`);
|
||||
// 2. Si no existe, crear
|
||||
const createRes = await fetch(`${baseUrl}/instance/create`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
@@ -238,24 +276,22 @@ async function ensureInstance(instanceName) {
|
||||
});
|
||||
|
||||
if (!createRes.ok) {
|
||||
// Si falla, leemos el error para mostrarlo en el log
|
||||
const errText = await createRes.text();
|
||||
console.error("❌ Error Evolution API al crear:", createRes.status, errText);
|
||||
|
||||
if (createRes.status === 401) {
|
||||
throw new Error("API KEY INCORRECTA: Revisa EVOLUTION_API_KEY en las variables de entorno.");
|
||||
}
|
||||
if (createRes.status === 401) throw new Error("API KEY INCORRECTA");
|
||||
throw new Error(`Error creando instancia: ${errText}`);
|
||||
}
|
||||
} else if (checkRes.status === 401) {
|
||||
throw new Error("API KEY INCORRECTA: Revisa EVOLUTION_API_KEY en las variables de entorno.");
|
||||
throw new Error("API KEY INCORRECTA");
|
||||
}
|
||||
|
||||
return { baseUrl, headers };
|
||||
}
|
||||
|
||||
// RUTAS AUTH
|
||||
app.post("/auth/register", async (req, res) => { const client = await pool.connect(); try { const { fullName, phone, address, dni, email, password } = req.body; const p = normalizePhone(phone); if (!fullName || !p || !email || !password) return res.status(400).json({ ok: false }); const passwordHash = await bcrypt.hash(password, 10); await client.query('BEGIN'); const insert = await client.query("INSERT INTO users (full_name, phone, address, dni, email, password_hash, role, owner_id) VALUES ($1, $2, $3, $4, $5, $6, 'admin', NULL) RETURNING id", [fullName, p, address, dni, email, passwordHash]); const userId = insert.rows[0].id; const code = genCode6(); const codeHash = await bcrypt.hash(code, 10); const expiresAt = new Date(Date.now() + 10 * 60 * 1000); await client.query("INSERT INTO login_codes (user_id, phone, code_hash, expires_at) VALUES ($1, $2, $3, $4)", [userId, p, codeHash, expiresAt]); await client.query('COMMIT'); res.json({ ok: true, phone: p }); } catch (e) { await client.query('ROLLBACK'); res.status(500).json({ ok: false }); } finally { client.release(); } });
|
||||
app.post("/auth/register", async (req, res) => { const client = await pool.connect(); try { const { fullName, phone, address, dni, email, password } = req.body; const p = normalizePhone(phone); if (!fullName || !p || !email || !password) return res.status(400).json({ ok: false }); const passwordHash = await bcrypt.hash(password, 10); await client.query('BEGIN'); const insert = await client.query("INSERT INTO users (full_name, phone, address, dni, email, password_hash, role, owner_id) VALUES ($1, $2, $3, $4, $5, $6, 'admin', NULL) RETURNING id", [fullName, p, address, dni, email, passwordHash]); const userId = insert.rows[0].id; const code = genCode6(); const codeHash = await bcrypt.hash(code, 10); const expiresAt = new Date(Date.now() + 10 * 60 * 1000); await client.query("INSERT INTO login_codes (user_id, phone, code_hash, expires_at) VALUES ($1, $2, $3, $4)", [userId, p, codeHash, expiresAt]);
|
||||
// ENVÍO DE WHATSAPP
|
||||
await sendWhatsAppCode(p, code);
|
||||
await client.query('COMMIT'); res.json({ ok: true, phone: p }); } catch (e) { await client.query('ROLLBACK'); console.error(e); res.status(500).json({ ok: false }); } finally { client.release(); } });
|
||||
|
||||
app.post("/auth/verify", async (req, res) => { try { const { phone, code } = req.body; const p = normalizePhone(phone); const q = await pool.query(`SELECT lc.*, u.id as uid, u.email, u.role, u.owner_id FROM login_codes lc JOIN users u ON lc.user_id = u.id WHERE lc.phone=$1 AND lc.consumed_at IS NULL AND lc.expires_at > NOW() ORDER BY lc.created_at DESC LIMIT 1`, [p]); if (q.rowCount === 0) return res.status(400).json({ ok: false }); const row = q.rows[0]; if (!(await bcrypt.compare(String(code), row.code_hash))) return res.status(400).json({ ok: false }); await pool.query("UPDATE login_codes SET consumed_at=NOW() WHERE id=$1", [row.id]); await pool.query("UPDATE users SET is_verified=TRUE WHERE id=$1", [row.uid]); res.json({ ok: true, token: signToken({ id: row.uid, email: row.email, phone: p, role: row.role, owner_id: row.owner_id }) }); } catch (e) { res.status(500).json({ ok: false }); } });
|
||||
app.post("/auth/login", async (req, res) => { try { const { email, password } = req.body; const q = await pool.query("SELECT * FROM users WHERE email=$1", [email]); if (q.rowCount === 0) return res.status(401).json({ ok: false }); let user = null; for (const u of q.rows) { if (await bcrypt.compare(password, u.password_hash)) { user = u; break; } } if (!user) return res.status(401).json({ ok: false }); res.json({ ok: true, token: signToken(user) }); } catch(e) { res.status(500).json({ ok: false }); } });
|
||||
|
||||
@@ -264,23 +300,17 @@ app.post("/auth/login", async (req, res) => { try { const { email, password } =
|
||||
// ==========================================
|
||||
app.get("/whatsapp/status", authMiddleware, async (req, res) => {
|
||||
try {
|
||||
// Generar nombre único para la instancia: "cliente_123"
|
||||
const instanceName = `cliente_${req.user.accountId}`;
|
||||
const { baseUrl, headers } = await ensureInstance(instanceName);
|
||||
|
||||
// 1. Obtener estado
|
||||
const stateRes = await fetch(`${baseUrl}/instance/connectionState/${instanceName}`, { headers });
|
||||
const stateData = await stateRes.json();
|
||||
const state = stateData.instance?.state || "close";
|
||||
|
||||
// 2. Si no conectado, obtener QR
|
||||
let qr = null;
|
||||
if (state !== "open") {
|
||||
const qrRes = await fetch(`${baseUrl}/instance/connect/${instanceName}`, { headers });
|
||||
const qrData = await qrRes.json();
|
||||
qr = qrData.code || qrData.base64;
|
||||
}
|
||||
|
||||
res.json({ ok: true, state, qr, instanceName });
|
||||
} catch (e) {
|
||||
console.error("Error WhatsApp EndPoint:", e.message);
|
||||
|
||||
Reference in New Issue
Block a user