Actualizar server.js
This commit is contained in:
82
server.js
82
server.js
@@ -16,6 +16,7 @@ const {
|
|||||||
JWT_SECRET,
|
JWT_SECRET,
|
||||||
EVOLUTION_BASE_URL,
|
EVOLUTION_BASE_URL,
|
||||||
EVOLUTION_API_KEY,
|
EVOLUTION_API_KEY,
|
||||||
|
EVOLUTION_INSTANCE, // <--- ¡FALTABA ESTO! (La instancia que envía los códigos)
|
||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
// --- DIAGNÓSTICO DE INICIO ---
|
// --- DIAGNÓSTICO DE INICIO ---
|
||||||
@@ -24,17 +25,19 @@ console.log("🔧 INICIANDO SERVIDOR INTEGRA REPARA");
|
|||||||
console.log("------------------------------------------------");
|
console.log("------------------------------------------------");
|
||||||
if (!DATABASE_URL) console.error("❌ FALTA: DATABASE_URL");
|
if (!DATABASE_URL) console.error("❌ FALTA: DATABASE_URL");
|
||||||
if (!JWT_SECRET) console.error("❌ FALTA: JWT_SECRET");
|
if (!JWT_SECRET) console.error("❌ FALTA: JWT_SECRET");
|
||||||
|
|
||||||
if (!EVOLUTION_BASE_URL) {
|
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 {
|
} 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 {
|
} else {
|
||||||
// Mostramos solo los primeros 4 caracteres por seguridad
|
console.log("✅ Instancia de Notificaciones:", EVOLUTION_INSTANCE);
|
||||||
console.log("✅ Evolution API Key detectada:", EVOLUTION_API_KEY.substring(0, 4) + "...");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("------------------------------------------------");
|
console.log("------------------------------------------------");
|
||||||
|
|
||||||
if (!DATABASE_URL || !JWT_SECRET) {
|
if (!DATABASE_URL || !JWT_SECRET) {
|
||||||
@@ -207,26 +210,61 @@ async function autoUpdateDB() {
|
|||||||
|
|
||||||
// HELPERS
|
// 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 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 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 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) {
|
async function ensureInstance(instanceName) {
|
||||||
if (!EVOLUTION_BASE_URL || !EVOLUTION_API_KEY) throw new Error("Faltan variables EVOLUTION en el servidor");
|
if (!EVOLUTION_BASE_URL || !EVOLUTION_API_KEY) throw new Error("Faltan variables EVOLUTION en el servidor");
|
||||||
|
|
||||||
const baseUrl = EVOLUTION_BASE_URL.replace(/\/$/, "");
|
const baseUrl = EVOLUTION_BASE_URL.replace(/\/$/, "");
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"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 });
|
const checkRes = await fetch(`${baseUrl}/instance/connectionState/${instanceName}`, { headers });
|
||||||
|
|
||||||
if (checkRes.status === 404) {
|
if (checkRes.status === 404) {
|
||||||
console.log(`🚀 Creando instancia automática: ${instanceName}`);
|
console.log(`🚀 Creando instancia automática: ${instanceName}`);
|
||||||
// 2. Si no existe, crear
|
|
||||||
const createRes = await fetch(`${baseUrl}/instance/create`, {
|
const createRes = await fetch(`${baseUrl}/instance/create`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers,
|
||||||
@@ -238,24 +276,22 @@ async function ensureInstance(instanceName) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!createRes.ok) {
|
if (!createRes.ok) {
|
||||||
// Si falla, leemos el error para mostrarlo en el log
|
|
||||||
const errText = await createRes.text();
|
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");
|
||||||
|
|
||||||
if (createRes.status === 401) {
|
|
||||||
throw new Error("API KEY INCORRECTA: Revisa EVOLUTION_API_KEY en las variables de entorno.");
|
|
||||||
}
|
|
||||||
throw new Error(`Error creando instancia: ${errText}`);
|
throw new Error(`Error creando instancia: ${errText}`);
|
||||||
}
|
}
|
||||||
} else if (checkRes.status === 401) {
|
} 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 };
|
return { baseUrl, headers };
|
||||||
}
|
}
|
||||||
|
|
||||||
// RUTAS AUTH
|
// 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/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 }); } });
|
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) => {
|
app.get("/whatsapp/status", authMiddleware, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
// Generar nombre único para la instancia: "cliente_123"
|
|
||||||
const instanceName = `cliente_${req.user.accountId}`;
|
const instanceName = `cliente_${req.user.accountId}`;
|
||||||
const { baseUrl, headers } = await ensureInstance(instanceName);
|
const { baseUrl, headers } = await ensureInstance(instanceName);
|
||||||
|
|
||||||
// 1. Obtener estado
|
|
||||||
const stateRes = await fetch(`${baseUrl}/instance/connectionState/${instanceName}`, { headers });
|
const stateRes = await fetch(`${baseUrl}/instance/connectionState/${instanceName}`, { headers });
|
||||||
const stateData = await stateRes.json();
|
const stateData = await stateRes.json();
|
||||||
const state = stateData.instance?.state || "close";
|
const state = stateData.instance?.state || "close";
|
||||||
|
|
||||||
// 2. Si no conectado, obtener QR
|
|
||||||
let qr = null;
|
let qr = null;
|
||||||
if (state !== "open") {
|
if (state !== "open") {
|
||||||
const qrRes = await fetch(`${baseUrl}/instance/connect/${instanceName}`, { headers });
|
const qrRes = await fetch(`${baseUrl}/instance/connect/${instanceName}`, { headers });
|
||||||
const qrData = await qrRes.json();
|
const qrData = await qrRes.json();
|
||||||
qr = qrData.code || qrData.base64;
|
qr = qrData.code || qrData.base64;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ ok: true, state, qr, instanceName });
|
res.json({ ok: true, state, qr, instanceName });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error WhatsApp EndPoint:", e.message);
|
console.error("Error WhatsApp EndPoint:", e.message);
|
||||||
|
|||||||
Reference in New Issue
Block a user