using MySqlConnector; using SharpChat.Config; using SharpChat.Events; using System; using System.Collections.Generic; using System.Text; using System.Text.Json; namespace SharpChat { public static partial class Database { private static string ConnectionString = null; public static bool HasDatabase => !string.IsNullOrWhiteSpace(ConnectionString); public static void Init(IConfig config) { Init( config.ReadValue("host", "localhost"), config.ReadValue("user", string.Empty), config.ReadValue("pass", string.Empty), config.ReadValue("db", "sharpchat") ); } public static void Init(string host, string username, string password, string database) { ConnectionString = new MySqlConnectionStringBuilder { Server = host, UserID = username, Password = password, Database = database, OldGuids = false, TreatTinyAsBoolean = false, CharacterSet = "utf8mb4", SslMode = MySqlSslMode.None, ForceSynchronous = true, ConnectionTimeout = 5, }.ToString(); RunMigrations(); } public static void Deinit() { ConnectionString = null; } private static MySqlConnection GetConnection() { if(!HasDatabase) return null; MySqlConnection conn = new(ConnectionString); conn.Open(); return conn; } private static int RunCommand(string command, params MySqlParameter[] parameters) { if(!HasDatabase) return 0; try { using MySqlConnection conn = GetConnection(); using MySqlCommand cmd = conn.CreateCommand(); if(parameters?.Length > 0) cmd.Parameters.AddRange(parameters); cmd.CommandText = command; return cmd.ExecuteNonQuery(); } catch(MySqlException ex) { Logger.Write(ex); } return 0; } private static MySqlDataReader RunQuery(string command, params MySqlParameter[] parameters) { if(!HasDatabase) return null; try { MySqlConnection conn = GetConnection(); MySqlCommand cmd = conn.CreateCommand(); if(parameters?.Length > 0) cmd.Parameters.AddRange(parameters); cmd.CommandText = command; return cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection); } catch(MySqlException ex) { Logger.Write(ex); } return null; } private static object RunQueryValue(string command, params MySqlParameter[] parameters) { if(!HasDatabase) return null; try { using MySqlConnection conn = GetConnection(); using MySqlCommand cmd = conn.CreateCommand(); if(parameters?.Length > 0) cmd.Parameters.AddRange(parameters); cmd.CommandText = command; cmd.Prepare(); return cmd.ExecuteScalar(); } catch(MySqlException ex) { Logger.Write(ex); } return null; } public static void LogEvent(IChatEvent evt) { if(evt.SequenceId < 1) evt.SequenceId = SharpId.Next(); RunCommand( "INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`" + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`)" + " VALUES (@id, FROM_UNIXTIME(@created), @type, @target, @flags, @data" + ", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)", new MySqlParameter("id", evt.SequenceId), new MySqlParameter("created", evt.DateTime.ToUnixTimeSeconds()), new MySqlParameter("type", evt.GetType().FullName), new MySqlParameter("target", evt.Target.TargetName), new MySqlParameter("flags", (byte)evt.Flags), new MySqlParameter("data", JsonSerializer.SerializeToUtf8Bytes(evt, evt.GetType())), new MySqlParameter("sender", evt.Sender?.UserId < 1 ? null : (long?)evt.Sender.UserId), new MySqlParameter("sender_name", evt.Sender?.Username), new MySqlParameter("sender_colour", evt.Sender?.Colour.ToMisuzu()), new MySqlParameter("sender_rank", evt.Sender?.Rank), new MySqlParameter("sender_nick", evt.Sender?.Nickname), new MySqlParameter("sender_perms", evt.Sender?.Permissions) ); } public static void DeleteEvent(IChatEvent evt) { RunCommand( "UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL", new MySqlParameter("id", evt.SequenceId) ); } private static IChatEvent ReadEvent(MySqlDataReader reader, IPacketTarget target = null) { Type evtType = Type.GetType(Encoding.ASCII.GetString((byte[])reader["event_type"])); IChatEvent evt = JsonSerializer.Deserialize(Encoding.ASCII.GetString((byte[])reader["event_data"]), evtType) as IChatEvent; evt.SequenceId = reader.GetInt64("event_id"); evt.Target = target; evt.TargetName = target?.TargetName ?? Encoding.ASCII.GetString((byte[])reader["event_target"]); evt.Flags = (ChatMessageFlags)reader.GetByte("event_flags"); evt.DateTime = DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")); if(!reader.IsDBNull(reader.GetOrdinal("event_sender"))) { evt.Sender = new BasicUser { UserId = reader.GetInt64("event_sender"), Username = reader.GetString("event_sender_name"), Colour = ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")), Rank = reader.GetInt32("event_sender_rank"), Nickname = reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : reader.GetString("event_sender_nick"), Permissions = (ChatUserPermissions)reader.GetInt32("event_sender_perms") }; } return evt; } public static IEnumerable GetEvents(IPacketTarget target, int amount, int offset) { List events = new(); try { using MySqlDataReader reader = RunQuery( "SELECT `event_id`, `event_type`, `event_flags`, `event_data`" + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`" + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`" + " FROM `sqc_events`" + " WHERE `event_deleted` IS NULL AND `event_target` = @target" + " AND `event_id` > @offset" + " ORDER BY `event_id` DESC" + " LIMIT @amount", new MySqlParameter("target", target.TargetName), new MySqlParameter("amount", amount), new MySqlParameter("offset", offset) ); while(reader.Read()) { IChatEvent evt = ReadEvent(reader, target); if(evt != null) events.Add(evt); } } catch(MySqlException ex) { Logger.Write(ex); } return events; } public static IChatEvent GetEvent(long seqId) { try { using MySqlDataReader reader = RunQuery( "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`" + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`" + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`" + " FROM `sqc_events`" + " WHERE `event_id` = @id", new MySqlParameter("id", seqId) ); while(reader.Read()) { IChatEvent evt = ReadEvent(reader); if(evt != null) return evt; } } catch(MySqlException ex) { Logger.Write(ex); } return null; } } }