const mysql = require('mysql2/promise'); let pool = null; function getPool() { if (!pool) { const config = { host: process.env.DB_HOST || '127.0.0.1', port: parseInt(process.env.DB_PORT || '3306'), user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, waitForConnections: true, connectionLimit: 10, queueLimit: 0 }; console.log(`[DB] Initializing MySQL Connection Pool to ${config.host}:${config.port}/${config.database}`); pool = mysql.createPool(config); } return pool; } // ─── Automatic Database Schema Migration ────────────────────────────────── async function initDatabase() { const connectionPool = getPool(); try { // 1. Create Slots Table await connectionPool.query(` CREATE TABLE IF NOT EXISTS slots ( id INT PRIMARY KEY, phone_number VARCHAR(30) NULL, status VARCHAR(50) DEFAULT 'disconnected', updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; `); // Seed exactly 6 slots if they don't exist yet for (let slotId = 1; slotId <= 6; slotId++) { await connectionPool.query(` INSERT INTO slots (id, status) VALUES (?, 'disconnected') ON DUPLICATE KEY UPDATE id=id; `, [slotId]); } // 2. Create Messages Table await connectionPool.query(` CREATE TABLE IF NOT EXISTS messages ( id VARCHAR(150) PRIMARY KEY, slot_id INT NOT NULL, chat_id VARCHAR(100) NOT NULL, sender_name VARCHAR(150) NULL, body TEXT NULL, from_me BOOLEAN DEFAULT FALSE, timestamp INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (slot_id) REFERENCES slots(id) ON DELETE CASCADE, INDEX idx_slot_chat (slot_id, chat_id), INDEX idx_timestamp (timestamp) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; `); // Check and add sender_jid column if it doesn't exist try { const [columns] = await connectionPool.query(`SHOW COLUMNS FROM messages LIKE 'sender_jid'`); if (columns.length === 0) { console.log('[DB] Adding column sender_jid to messages table...'); await connectionPool.query(`ALTER TABLE messages ADD COLUMN sender_jid VARCHAR(100) NULL AFTER sender_name;`); } } catch (columnErr) { console.error('[DB ERROR] Failed to check/add sender_jid column:', columnErr.message); } console.log('[DB] MySQL Tables initialized successfully.'); } catch (err) { console.error('[DB ERROR] Migration failed:', err.message); throw err; } } // ─── Helper Queries ──────────────────────────────────────────────────────── async function updateSlotStatus(slotId, status, phoneNumber = null) { try { const connectionPool = getPool(); await connectionPool.query(` UPDATE slots SET status = ?, phone_number = COALESCE(?, phone_number) WHERE id = ?; `, [status, phoneNumber, slotId]); console.log(`[DB] Slot ${slotId} status updated to: ${status}`); } catch (err) { console.error(`[DB ERROR] Failed to update slot ${slotId} status:`, err.message); } } async function archiveMessage(slotId, msg) { try { const connectionPool = getPool(); // We only archive text-based body messages (or custom media representations) let bodyText = msg.body || ''; if (!bodyText && msg.hasMedia) { bodyText = '📷 Media/Attachment'; } let senderJid = null; if (msg.author) { senderJid = typeof msg.author === 'string' ? msg.author : msg.author._serialized; } else if (msg.id && msg.id.participant) { senderJid = typeof msg.id.participant === 'string' ? msg.id.participant : msg.id.participant._serialized; } else if (msg.fromMe) { senderJid = 'me'; } else { senderJid = typeof msg.from === 'string' ? msg.from : msg.from._serialized; } await connectionPool.query(` INSERT INTO messages (id, slot_id, chat_id, sender_name, sender_jid, body, from_me, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE body = VALUES(body); `, [ msg.id._serialized || msg.id.id, slotId, msg.to || msg.from, msg.senderName || null, senderJid || null, bodyText, msg.fromMe ? 1 : 0, msg.timestamp ]); console.log(`[DB] Message ${msg.id.id} archived successfully in Slot ${slotId}`); } catch (err) { console.error('[DB ERROR] Failed to archive message:', err.message); } } async function getChatHistory(slotId, chatId, limit = 50, offset = 0) { try { const connectionPool = getPool(); const [rows] = await connectionPool.query(` SELECT * FROM messages WHERE slot_id = ? AND chat_id = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?; `, [slotId, chatId, parseInt(limit), parseInt(offset)]); return rows.reverse(); // Return in chronological order } catch (err) { console.error('[DB ERROR] Failed to get chat history:', err.message); return []; } } async function searchMessages(slotId, query, limit = 50) { try { const connectionPool = getPool(); const [rows] = await connectionPool.query(` SELECT * FROM messages WHERE slot_id = ? AND body LIKE ? ORDER BY timestamp DESC LIMIT ?; `, [slotId, `%${query}%`, parseInt(limit)]); return rows; } catch (err) { console.error('[DB ERROR] Failed to search messages:', err.message); return []; } } module.exports = { initDatabase, updateSlotStatus, archiveMessage, getChatHistory, searchMessages };