/** * ============================================================================= * Texas Hold'em - Модуль базы данных (SQLite) * ============================================================================= */ const initSqlJs = require('sql.js'); const fs = require('fs'); const path = require('path'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const { v4: uuidv4 } = require('uuid'); // Путь к файлу БД const DB_PATH = path.join(__dirname, 'poker.db'); const JWT_SECRET = process.env.JWT_SECRET || 'poker-secret-key-change-in-production'; const JWT_EXPIRES = '7d'; let db = null; /** * Инициализация базы данных */ async function initDatabase() { const SQL = await initSqlJs(); // Загружаем существующую БД или создаём новую if (fs.existsSync(DB_PATH)) { const buffer = fs.readFileSync(DB_PATH); db = new SQL.Database(buffer); console.log('📦 База данных загружена'); } else { db = new SQL.Database(); console.log('📦 Создана новая база данных'); } // Создаём таблицы createTables(); // Создаём админа по умолчанию await createDefaultAdmin(); return db; } /** * Создание таблиц */ function createTables() { // Пользователи db.run(` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT DEFAULT 'user', created_at TEXT DEFAULT CURRENT_TIMESTAMP, last_login TEXT, total_games INTEGER DEFAULT 0, total_wins INTEGER DEFAULT 0, total_chips_won INTEGER DEFAULT 0 ) `); // Настройки пользователей db.run(` CREATE TABLE IF NOT EXISTS user_settings ( user_id TEXT PRIMARY KEY, sound INTEGER DEFAULT 1, animations INTEGER DEFAULT 1, show_hand_strength INTEGER DEFAULT 1, autofold INTEGER DEFAULT 1, card_back_style TEXT DEFAULT 'default', card_back_custom TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ) `); // Админские настройки (глобальные) db.run(` CREATE TABLE IF NOT EXISTS admin_settings ( key TEXT PRIMARY KEY, value TEXT, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_by TEXT ) `); // Логи действий db.run(` CREATE TABLE IF NOT EXISTS action_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, user_id TEXT, username TEXT, action_type TEXT NOT NULL, action_data TEXT, room_id TEXT, ip_address TEXT ) `); // Чат сообщения db.run(` CREATE TABLE IF NOT EXISTS chat_messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, room_id TEXT, room_type TEXT, user_id TEXT, username TEXT NOT NULL, message TEXT NOT NULL, is_system INTEGER DEFAULT 0 ) `); // Сессии игр db.run(` CREATE TABLE IF NOT EXISTS game_sessions ( id TEXT PRIMARY KEY, room_id TEXT, started_at TEXT DEFAULT CURRENT_TIMESTAMP, ended_at TEXT, players TEXT, winner_id TEXT, pot_size INTEGER, game_data TEXT ) `); saveDatabase(); console.log('📋 Таблицы созданы'); } /** * Создать админа по умолчанию */ async function createDefaultAdmin() { const adminExists = db.exec("SELECT id FROM users WHERE role = 'admin' LIMIT 1"); if (adminExists.length === 0 || adminExists[0].values.length === 0) { const adminId = uuidv4(); const passwordHash = bcrypt.hashSync('admin123', 10); db.run(` INSERT INTO users (id, username, password_hash, role) VALUES (?, ?, ?, 'admin') `, [adminId, 'admin', passwordHash]); saveDatabase(); console.log('👑 Создан администратор по умолчанию: admin / admin123'); } } /** * Сохранить БД на диск */ function saveDatabase() { if (db) { const data = db.export(); const buffer = Buffer.from(data); fs.writeFileSync(DB_PATH, buffer); } } // ============================================================================= // ПОЛЬЗОВАТЕЛИ И АВТОРИЗАЦИЯ // ============================================================================= /** * Регистрация пользователя */ function registerUser(username, password) { try { // Проверяем, существует ли пользователь const existing = db.exec("SELECT id FROM users WHERE username = ?", [username]); if (existing.length > 0 && existing[0].values.length > 0) { return { success: false, error: 'Пользователь уже существует' }; } const userId = uuidv4(); const passwordHash = bcrypt.hashSync(password, 10); db.run(` INSERT INTO users (id, username, password_hash) VALUES (?, ?, ?) `, [userId, username, passwordHash]); // Создаём настройки по умолчанию db.run(` INSERT INTO user_settings (user_id) VALUES (?) `, [userId]); saveDatabase(); // Логируем logAction(userId, username, 'register', { username }); // Генерируем токен const token = generateToken(userId, username, 'user'); return { success: true, user: { id: userId, username, role: 'user' }, token }; } catch (error) { console.error('Ошибка регистрации:', error); return { success: false, error: 'Ошибка регистрации' }; } } /** * Авторизация пользователя */ function loginUser(username, password) { try { const result = db.exec(` SELECT id, username, password_hash, role FROM users WHERE username = ? `, [username]); if (result.length === 0 || result[0].values.length === 0) { return { success: false, error: 'Неверный логин или пароль' }; } const [userId, storedUsername, passwordHash, role] = result[0].values[0]; if (!bcrypt.compareSync(password, passwordHash)) { return { success: false, error: 'Неверный логин или пароль' }; } // Обновляем last_login db.run(` UPDATE users SET last_login = datetime('now') WHERE id = ? `, [userId]); saveDatabase(); // Логируем logAction(userId, storedUsername, 'login', { username: storedUsername }); // Генерируем токен const token = generateToken(userId, storedUsername, role); return { success: true, user: { id: userId, username: storedUsername, role }, token }; } catch (error) { console.error('Ошибка авторизации:', error); return { success: false, error: 'Ошибка авторизации' }; } } /** * Генерировать JWT токен */ function generateToken(userId, username, role) { return jwt.sign( { userId, username, role }, JWT_SECRET, { expiresIn: JWT_EXPIRES } ); } /** * Проверить JWT токен */ function verifyToken(token) { try { return jwt.verify(token, JWT_SECRET); } catch (error) { return null; } } /** * Получить пользователя по ID */ function getUserById(userId) { const result = db.exec(` SELECT id, username, role, created_at, last_login, total_games, total_wins, total_chips_won FROM users WHERE id = ? `, [userId]); if (result.length === 0 || result[0].values.length === 0) { return null; } const [id, username, role, created_at, last_login, total_games, total_wins, total_chips_won] = result[0].values[0]; return { id, username, role, created_at, last_login, total_games, total_wins, total_chips_won }; } // ============================================================================= // НАСТРОЙКИ // ============================================================================= /** * Получить настройки пользователя */ function getUserSettings(userId) { const result = db.exec(` SELECT sound, animations, show_hand_strength, autofold, card_back_style, card_back_custom FROM user_settings WHERE user_id = ? `, [userId]); if (result.length === 0 || result[0].values.length === 0) { return { sound: true, animations: true, showHandStrength: true, autofold: true, cardBackStyle: 'default', cardBackCustom: null }; } const [sound, animations, showHandStrength, autofold, cardBackStyle, cardBackCustom] = result[0].values[0]; return { sound: !!sound, animations: !!animations, showHandStrength: !!showHandStrength, autofold: !!autofold, cardBackStyle: cardBackStyle || 'default', cardBackCustom }; } /** * Сохранить настройки пользователя */ function saveUserSettings(userId, settings) { db.run(` INSERT OR REPLACE INTO user_settings (user_id, sound, animations, show_hand_strength, autofold, card_back_style, card_back_custom) VALUES (?, ?, ?, ?, ?, ?, ?) `, [ userId, settings.sound ? 1 : 0, settings.animations ? 1 : 0, settings.showHandStrength ? 1 : 0, settings.autofold ? 1 : 0, settings.cardBackStyle || 'default', settings.cardBackCustom || null ]); saveDatabase(); return true; } /** * Получить админские настройки */ function getAdminSettings() { const result = db.exec(`SELECT key, value FROM admin_settings`); const settings = { llmEnabled: false, llmProvider: 'ollama', llmApiUrl: 'http://localhost:11434', llmModel: 'llama3.2', llmApiKey: '', serverUrl: 'ws://localhost:3000' }; if (result.length > 0) { result[0].values.forEach(([key, value]) => { if (key === 'llmEnabled') { settings.llmEnabled = value === 'true'; } else { settings[key] = value; } }); } return settings; } /** * Сохранить админские настройки */ function saveAdminSettings(settings, adminId) { const keys = ['llmEnabled', 'llmProvider', 'llmApiUrl', 'llmModel', 'llmApiKey', 'serverUrl']; keys.forEach(key => { if (settings[key] !== undefined) { const value = typeof settings[key] === 'boolean' ? settings[key].toString() : settings[key]; db.run(` INSERT OR REPLACE INTO admin_settings (key, value, updated_at, updated_by) VALUES (?, ?, datetime('now'), ?) `, [key, value, adminId]); } }); saveDatabase(); return true; } // ============================================================================= // ЛОГИРОВАНИЕ // ============================================================================= /** * Логировать действие */ function logAction(userId, username, actionType, actionData = {}, roomId = null, ipAddress = null) { db.run(` INSERT INTO action_logs (user_id, username, action_type, action_data, room_id, ip_address) VALUES (?, ?, ?, ?, ?, ?) `, [userId, username, actionType, JSON.stringify(actionData), roomId, ipAddress]); saveDatabase(); } /** * Получить логи (для админа) */ function getLogs(options = {}) { const { limit = 100, offset = 0, actionType = null, userId = null, startDate = null, endDate = null } = options; let sql = `SELECT * FROM action_logs WHERE 1=1`; const params = []; if (actionType) { sql += ` AND action_type = ?`; params.push(actionType); } if (userId) { sql += ` AND user_id = ?`; params.push(userId); } if (startDate) { sql += ` AND timestamp >= ?`; params.push(startDate); } if (endDate) { sql += ` AND timestamp <= ?`; params.push(endDate); } sql += ` ORDER BY timestamp DESC LIMIT ? OFFSET ?`; params.push(limit, offset); const result = db.exec(sql, params); if (result.length === 0) return []; return result[0].values.map(row => ({ id: row[0], timestamp: row[1], userId: row[2], username: row[3], actionType: row[4], actionData: JSON.parse(row[5] || '{}'), roomId: row[6], ipAddress: row[7] })); } /** * Получить статистику логов */ function getLogStats() { const totalResult = db.exec(`SELECT COUNT(*) FROM action_logs`); const total = totalResult[0]?.values[0]?.[0] || 0; const byTypeResult = db.exec(` SELECT action_type, COUNT(*) as count FROM action_logs GROUP BY action_type ORDER BY count DESC `); const byType = byTypeResult.length > 0 ? byTypeResult[0].values.map(([type, count]) => ({ type, count })) : []; const todayResult = db.exec(` SELECT COUNT(*) FROM action_logs WHERE date(timestamp) = date('now') `); const today = todayResult[0]?.values[0]?.[0] || 0; return { total, byType, today }; } // ============================================================================= // ЧАТ // ============================================================================= /** * Сохранить сообщение чата */ function saveChatMessage(roomId, roomType, userId, username, message, isSystem = false) { db.run(` INSERT INTO chat_messages (room_id, room_type, user_id, username, message, is_system) VALUES (?, ?, ?, ?, ?, ?) `, [roomId, roomType, userId, username, message, isSystem ? 1 : 0]); saveDatabase(); } /** * Получить историю чата комнаты */ function getChatHistory(roomId, limit = 50) { const result = db.exec(` SELECT timestamp, username, message, is_system FROM chat_messages WHERE room_id = ? ORDER BY timestamp DESC LIMIT ? `, [roomId, limit]); if (result.length === 0) return []; return result[0].values.map(row => ({ timestamp: row[0], username: row[1], message: row[2], isSystem: !!row[3] })).reverse(); } /** * Очистить чат комнаты */ function clearRoomChat(roomId) { db.run(`DELETE FROM chat_messages WHERE room_id = ?`, [roomId]); saveDatabase(); } // ============================================================================= // ЭКСПОРТ // ============================================================================= module.exports = { initDatabase, saveDatabase, // Пользователи registerUser, loginUser, verifyToken, getUserById, // Настройки getUserSettings, saveUserSettings, getAdminSettings, saveAdminSettings, // Логирование logAction, getLogs, getLogStats, // Чат saveChatMessage, getChatHistory, clearRoomChat };