using MySqlConnector; using SharpChat.Events; using System; using System.Collections.Generic; using System.Reflection; using System.Text; using System.Text.Json; namespace SharpChat.EventStorage { public partial class MariaDBEventStorage : IEventStorage { private string ConnectionString { get; } private readonly JsonSerializerOptions jsonOpts = new() { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault, }; private static readonly Dictionary EventDataTypes = new(); static MariaDBEventStorage() { foreach(Type type in Assembly.GetExecutingAssembly().GetTypes()) { ChatEventDataForAttribute? forAttr = type.GetCustomAttribute(); if(forAttr == null) continue; if(EventDataTypes.ContainsKey(forAttr.EventName)) throw new InvalidOperationException($"Attempted to register more than one data type for {forAttr.EventName}!"); EventDataTypes.Add(forAttr.EventName, type); } } public MariaDBEventStorage(string connString) { ConnectionString = connString; } public void HandleEvent(ChatEventInfo info) { MySqlParameter dataParam = new("data", MySqlDbType.Blob) { Value = JsonSerializer.SerializeToUtf8Bytes(info.Data, info.Data.GetType(), jsonOpts) }; RunCommand( "INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_data`" + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`)" + " VALUES (@id, NOW(), @type, @target, @data" + ", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)", new MySqlParameter("id", info.Id), new MySqlParameter("type", info.Type), new MySqlParameter("target", string.IsNullOrWhiteSpace(info.ChannelName) ? null : info.ChannelName), dataParam, new MySqlParameter("sender", info.SenderId < 1 ? null : info.SenderId), new MySqlParameter("sender_name", info.SenderName), new MySqlParameter("sender_colour", info.SenderColour.ToMisuzu()), new MySqlParameter("sender_rank", info.SenderRank), new MySqlParameter("sender_nick", info.SenderNickName), new MySqlParameter("sender_perms", info.SenderPerms) ); if(info.Type.Equals("msg:delete") && info.Data is MessageDeleteEventData msgDelete) RunCommand( "UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL", new MySqlParameter("id", msgDelete.MessageId) ); } public ChatEventInfo? GetEvent(long eventId) { try { using MySqlDataReader? reader = RunQuery( "SELECT `event_id`, `event_type`, `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`" + ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`" + " FROM `sqc_events`" + " WHERE `event_id` = @id", new MySqlParameter("id", eventId) ); if(reader != null) while(reader.Read()) { ChatEventInfo evt = ReadEvent(reader); if(evt != null) return evt; } } catch(MySqlException ex) { Logger.Write(ex); } return null; } private static ChatEventInfo ReadEvent(MySqlDataReader reader) { string eventType = Encoding.ASCII.GetString((byte[])reader["event_type"]); ChatEventData eventData = EventDataTypes.ContainsKey(eventType) ? (ChatEventData)(JsonSerializer.Deserialize((byte[])reader["event_data"], EventDataTypes[eventType]) ?? ChatEventData.EmptyInstance) : ChatEventData.EmptyInstance; return new ChatEventInfo( reader.GetInt64("event_id"), eventType, DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")), reader.IsDBNull(reader.GetOrdinal("event_target")) ? string.Empty : Encoding.ASCII.GetString((byte[])reader["event_target"]), reader.IsDBNull(reader.GetOrdinal("event_sender")) ? -1 : reader.GetInt64("event_sender"), reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"), reader.IsDBNull(reader.GetOrdinal("event_sender_colour")) ? Colour.None : Colour.FromMisuzu(reader.GetInt32("event_sender_colour")), reader.GetInt32("event_sender_rank"), reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : reader.GetString("event_sender_nick"), (UserPermissions)reader.GetInt32("event_sender_perms"), eventData ); } public IEnumerable GetChannelEventLog(string channelName, int amount = 20, int startAt = 0) { List events = new(); try { using MySqlDataReader? reader = RunQuery( "SELECT `event_id`, `event_type`, `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`" + ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`" + " FROM `sqc_events`" + " WHERE `event_deleted` IS NULL AND (`event_target` = @target OR `event_target` IS NULL)" + " AND `event_id` > @offset" + " ORDER BY `event_id` DESC" + " LIMIT @amount", new MySqlParameter("target", channelName), new MySqlParameter("amount", amount), new MySqlParameter("offset", startAt) ); if(reader != null) while(reader.Read()) { ChatEventInfo evt = ReadEvent(reader); if(evt != null) events.Add(evt); } } catch(MySqlException ex) { Logger.Write(ex); } events.Reverse(); return events; } } }