namespace Supercell.Laser.Bot.DataBase { using System; using Npgsql; using Supercell.Laser.Bot; using Supercell.Laser.Bot.Settings; /// /// База данных бота: подключение, создание таблиц, взаимодействие с ними. /// /// Подключается через PostgreSQL (Npgsql) — так же, как и сам Supercell.Laser.Server /// (см. Supercell.Laser.Server/Database/Accounts.cs). Либо к базе данных всего сервера /// (mysql_name: "default" в config.json бота), либо к отдельной — см. /// Configuration.ResolveMySql. Имена таблиц дополнительно префиксуются table_prefix, /// чтобы не пересекаться с таблицами самой игры при общей базе. /// public static class BotData { private static string _connectionString; private static string _prefix; public static void Init(Configuration.ResolvedMySql db, string tablePrefix) { _prefix = string.IsNullOrWhiteSpace(tablePrefix) ? "bot_" : tablePrefix; var builder = new NpgsqlConnectionStringBuilder { Host = db.Host, Port = db.Port, Username = db.User, Password = db.Password, Database = db.Database, Pooling = true, MinPoolSize = 1, MaxPoolSize = 20 }; _connectionString = builder.ToString(); CreateTables(); } public static NpgsqlConnection OpenConnection() { var connection = new NpgsqlConnection(_connectionString); connection.Open(); return connection; } private static void CreateTables() { using var connection = OpenConnection(); using (var cmd = new NpgsqlCommand($@" CREATE TABLE IF NOT EXISTS ""{_prefix}kv_store"" ( ""key"" VARCHAR(191) NOT NULL PRIMARY KEY, ""value"" TEXT NULL );", connection)) { cmd.ExecuteNonQuery(); } using (var cmd = new NpgsqlCommand($@" CREATE TABLE IF NOT EXISTS ""{_prefix}admin_log"" ( ""id"" BIGSERIAL PRIMARY KEY, ""telegram_user_id"" BIGINT NOT NULL, ""action"" VARCHAR(64) NOT NULL, ""created_at"" TIMESTAMP NOT NULL );", connection)) { cmd.ExecuteNonQuery(); } // Подробности действия (тэг игрока, баланс до/после) — чтобы по выдаче // ресурсов оставался разбираемый след, а не только имя действия. // ADD COLUMN IF NOT EXISTS — на уже созданных базах колонка просто добавится. using (var cmd = new NpgsqlCommand($@" ALTER TABLE ""{_prefix}admin_log"" ADD COLUMN IF NOT EXISTS ""details"" VARCHAR(191) NULL;", connection)) { cmd.ExecuteNonQuery(); } } /// Пишет в БД, кто из админов и какое действие выполнил в панели бота. public static void LogAdminAction(long telegramUserId, string action, string details = null) { try { using var connection = OpenConnection(); using var cmd = new NpgsqlCommand( $"INSERT INTO \"{_prefix}admin_log\" (telegram_user_id, action, details, created_at) VALUES (@uid, @action, @details, @now);", connection); cmd.Parameters.AddWithValue("@uid", telegramUserId); cmd.Parameters.AddWithValue("@action", action); cmd.Parameters.AddWithValue("@details", (object)details ?? DBNull.Value); cmd.Parameters.AddWithValue("@now", DateTime.UtcNow); cmd.ExecuteNonQuery(); } catch (Exception e) { Start.Log($"не удалось записать admin_log: {e.Message}"); } } /// Простое key-value хранилище настроек бота в БД (на будущее, для новых фич). public static string GetSetting(string key, string defaultValue = null) { using var connection = OpenConnection(); using var cmd = new NpgsqlCommand($"SELECT \"value\" FROM \"{_prefix}kv_store\" WHERE \"key\" = @key LIMIT 1;", connection); cmd.Parameters.AddWithValue("@key", key); object result = cmd.ExecuteScalar(); return result == null || result == DBNull.Value ? defaultValue : result.ToString(); } public static void SetSetting(string key, string value) { using var connection = OpenConnection(); using var cmd = new NpgsqlCommand( $"INSERT INTO \"{_prefix}kv_store\" (\"key\", \"value\") VALUES (@key, @value) " + "ON CONFLICT (\"key\") DO UPDATE SET \"value\" = EXCLUDED.\"value\";", connection); cmd.Parameters.AddWithValue("@key", key); cmd.Parameters.AddWithValue("@value", value); cmd.ExecuteNonQuery(); } } }