poker/database.js

568 lines
16 KiB
JavaScript

/**
* =============================================================================
* 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
};