From f1d4051fb56e472327d4c05aa818fb415bd2bc24 Mon Sep 17 00:00:00 2001
From: flashwave <me@flash.moe>
Date: Sun, 27 Apr 2025 01:54:46 +0000
Subject: [PATCH] Split MariaDB message storage out into its own library.

---
 .../MariaDBMessageStorage.cs                  |  29 ++---
 .../MariaDBMessageStorage_Database.cs         |   6 +-
 .../MariaDBMessageStorage_Migrations.cs       |   6 +-
 .../MariaDBMessageStorage_Permissions.cs      | 101 ++++++++++++++++++
 .../MariaDBUserPermissions.cs                 |   4 +-
 SharpChat.MariaDB/SharpChat.MariaDB.csproj    |  17 +++
 .../S2CPackets/ContextMessageS2CPacket.cs     |  48 ++++-----
 SharpChat.sln                                 |   9 ++
 SharpChat/Channel.cs                          |   8 +-
 .../DeleteChannelClientCommand.cs             |   2 +-
 .../DeleteMessageClientCommand.cs             |   6 +-
 .../PasswordChannelClientCommand.cs           |   2 +-
 .../RankChannelClientCommand.cs               |   2 +-
 SharpChat/Context.cs                          |  43 ++++----
 SharpChat/EventStorage/EventStorage.cs        |  20 ----
 .../MariaDBEventStorage_Permissions.cs        | 100 -----------------
 SharpChat/Program.cs                          |  13 +--
 SharpChat/SharpChat.csproj                    |   2 +-
 SharpChat/SockChatServer.cs                   |   5 +-
 .../Messages/Message.cs                       |  10 +-
 .../Messages/MessageFlags.cs                  |   4 +-
 SharpChatCommon/Messages/MessageStorage.cs    |  20 ++++
 .../Messages/VirtualMessageStorage.cs         |  26 ++---
 23 files changed, 255 insertions(+), 228 deletions(-)
 rename SharpChat/EventStorage/MariaDBEventStorage.cs => SharpChat.MariaDB/MariaDBMessageStorage.cs (85%)
 rename SharpChat/EventStorage/MariaDBEventStorage_Database.cs => SharpChat.MariaDB/MariaDBMessageStorage_Database.cs (94%)
 rename SharpChat/EventStorage/MariaDBEventStorage_Migrations.cs => SharpChat.MariaDB/MariaDBMessageStorage_Migrations.cs (97%)
 create mode 100644 SharpChat.MariaDB/MariaDBMessageStorage_Permissions.cs
 rename SharpChat/EventStorage/StoredUserPermissions.cs => SharpChat.MariaDB/MariaDBUserPermissions.cs (88%)
 create mode 100644 SharpChat.MariaDB/SharpChat.MariaDB.csproj
 rename {SharpChat/SockChat => SharpChat.SockChat}/S2CPackets/ContextMessageS2CPacket.cs (61%)
 delete mode 100644 SharpChat/EventStorage/EventStorage.cs
 delete mode 100644 SharpChat/EventStorage/MariaDBEventStorage_Permissions.cs
 rename SharpChat/EventStorage/StoredEventInfo.cs => SharpChatCommon/Messages/Message.cs (86%)
 rename SharpChat/EventStorage/StoredEventFlags.cs => SharpChatCommon/Messages/MessageFlags.cs (60%)
 create mode 100644 SharpChatCommon/Messages/MessageStorage.cs
 rename SharpChat/EventStorage/VirtualEventStorage.cs => SharpChatCommon/Messages/VirtualMessageStorage.cs (60%)

diff --git a/SharpChat/EventStorage/MariaDBEventStorage.cs b/SharpChat.MariaDB/MariaDBMessageStorage.cs
similarity index 85%
rename from SharpChat/EventStorage/MariaDBEventStorage.cs
rename to SharpChat.MariaDB/MariaDBMessageStorage.cs
index c1b9741..976ceef 100644
--- a/SharpChat/EventStorage/MariaDBEventStorage.cs
+++ b/SharpChat.MariaDB/MariaDBMessageStorage.cs
@@ -1,13 +1,14 @@
 using MySqlConnector;
+using SharpChat.Messages;
 using System.Text;
 using System.Text.Json;
 
-namespace SharpChat.EventStorage;
+namespace SharpChat.MariaDB;
 
-public partial class MariaDBEventStorage(string connString) : EventStorage {
+public partial class MariaDBMessageStorage(string connString) : MessageStorage {
     private string ConnectionString { get; } = connString ?? throw new ArgumentNullException(nameof(connString));
 
-    public async Task AddEvent(
+    public async Task LogMessage(
         long id,
         string type,
         string channelName,
@@ -18,7 +19,7 @@ public partial class MariaDBEventStorage(string connString) : EventStorage {
         string senderNick,
         UserPermissions senderPerms,
         object? data = null,
-        StoredEventFlags flags = StoredEventFlags.None
+        MessageFlags flags = MessageFlags.None
     ) {
         await RunCommand(
             "INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`"
@@ -39,7 +40,7 @@ public partial class MariaDBEventStorage(string connString) : EventStorage {
         );
     }
 
-    public async Task<StoredEventInfo?> GetEvent(long seqId) {
+    public async Task<Message?> GetMessage(long seqId) {
         try {
             using MySqlDataReader? reader = await RunQuery(
                 "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
@@ -55,7 +56,7 @@ public partial class MariaDBEventStorage(string connString) : EventStorage {
                 return null;
 
             while(reader.Read()) {
-                StoredEventInfo evt = ReadEvent(reader);
+                Message evt = ReadEvent(reader);
                 if(evt != null)
                     return evt;
             }
@@ -66,26 +67,26 @@ public partial class MariaDBEventStorage(string connString) : EventStorage {
         return null;
     }
 
-    private static StoredEventInfo ReadEvent(MySqlDataReader reader) {
-        return new StoredEventInfo(
+    private static Message ReadEvent(MySqlDataReader reader) {
+        return new Message(
             reader.GetInt64("event_id"),
             Encoding.ASCII.GetString((byte[])reader["event_type"]),
             reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : reader.GetInt64("event_sender").ToString(),
             reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
             ColourInheritable.FromMisuzu(reader.GetInt32("event_sender_colour")),
             reader.GetInt32("event_sender_rank"),
-            FromStoredPermissions((StoredUserPermissions)reader.GetInt32("event_sender_perms")),
+            FromMessagePermissions((MariaDBUserPermissions)reader.GetInt32("event_sender_perms")),
             reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick"),
             DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
             reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")),
             reader.IsDBNull(reader.GetOrdinal("event_target")) ? null : Encoding.ASCII.GetString((byte[])reader["event_target"]),
             JsonDocument.Parse(Encoding.ASCII.GetString((byte[])reader["event_data"])),
-            (StoredEventFlags)reader.GetByte("event_flags")
+            (MessageFlags)reader.GetByte("event_flags")
         );
     }
 
-    public async Task<IEnumerable<StoredEventInfo>> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
-        List<StoredEventInfo> events = [];
+    public async Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0) {
+        List<Message> events = [];
 
         try {
             using MySqlDataReader? reader = await RunQuery(
@@ -106,7 +107,7 @@ public partial class MariaDBEventStorage(string connString) : EventStorage {
                 return events;
 
             while(reader.Read()) {
-                StoredEventInfo evt = ReadEvent(reader);
+                Message evt = ReadEvent(reader);
                 if(evt != null)
                     events.Add(evt);
             }
@@ -119,7 +120,7 @@ public partial class MariaDBEventStorage(string connString) : EventStorage {
         return events;
     }
 
-    public async Task RemoveEvent(StoredEventInfo evt) {
+    public async Task DeleteMessage(Message evt) {
         await RunCommand(
             "UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL",
             new MySqlParameter("id", evt.Id)
diff --git a/SharpChat/EventStorage/MariaDBEventStorage_Database.cs b/SharpChat.MariaDB/MariaDBMessageStorage_Database.cs
similarity index 94%
rename from SharpChat/EventStorage/MariaDBEventStorage_Database.cs
rename to SharpChat.MariaDB/MariaDBMessageStorage_Database.cs
index d6ecdf7..6f83f80 100644
--- a/SharpChat/EventStorage/MariaDBEventStorage_Database.cs
+++ b/SharpChat.MariaDB/MariaDBMessageStorage_Database.cs
@@ -1,10 +1,10 @@
 using MySqlConnector;
 using SharpChat.Configuration;
 
-namespace SharpChat.EventStorage;
+namespace SharpChat.MariaDB;
 
-public partial class MariaDBEventStorage {
-    public static string BuildConnString(Configuration.Config config) {
+public partial class MariaDBMessageStorage {
+    public static string BuildConnString(Config config) {
         return BuildConnString(
             config.ReadValue("host", "localhost"),
             config.ReadValue("user", string.Empty),
diff --git a/SharpChat/EventStorage/MariaDBEventStorage_Migrations.cs b/SharpChat.MariaDB/MariaDBMessageStorage_Migrations.cs
similarity index 97%
rename from SharpChat/EventStorage/MariaDBEventStorage_Migrations.cs
rename to SharpChat.MariaDB/MariaDBMessageStorage_Migrations.cs
index 7ab07b7..0737fd3 100644
--- a/SharpChat/EventStorage/MariaDBEventStorage_Migrations.cs
+++ b/SharpChat.MariaDB/MariaDBMessageStorage_Migrations.cs
@@ -1,8 +1,8 @@
-using MySqlConnector;
+using MySqlConnector;
 
-namespace SharpChat.EventStorage;
+namespace SharpChat.MariaDB;
 
-public partial class MariaDBEventStorage {
+public partial class MariaDBMessageStorage {
     private async Task DoMigration(string name, Func<Task> action) {
         bool done = await RunQueryValue<long>(
             "SELECT COUNT(*) FROM `sqc_migrations` WHERE `migration_name` = @name",
diff --git a/SharpChat.MariaDB/MariaDBMessageStorage_Permissions.cs b/SharpChat.MariaDB/MariaDBMessageStorage_Permissions.cs
new file mode 100644
index 0000000..9854583
--- /dev/null
+++ b/SharpChat.MariaDB/MariaDBMessageStorage_Permissions.cs
@@ -0,0 +1,101 @@
+namespace SharpChat.MariaDB;
+
+public partial class MariaDBMessageStorage {
+    public static UserPermissions FromMessagePermissions(MariaDBUserPermissions mup) {
+        UserPermissions perms = 0;
+
+        if(mup.HasFlag(MariaDBUserPermissions.KickUser))
+            perms |= UserPermissions.KickUser;
+        if(mup.HasFlag(MariaDBUserPermissions.BanUser))
+            perms |= UserPermissions.BanUser;
+        if(mup.HasFlag(MariaDBUserPermissions.Broadcast))
+            perms |= UserPermissions.SendBroadcast;
+        if(mup.HasFlag(MariaDBUserPermissions.SetOwnNickname))
+            perms |= UserPermissions.SetOwnNickname;
+        if(mup.HasFlag(MariaDBUserPermissions.SetOthersNickname))
+            perms |= UserPermissions.SetOthersNickname;
+        if(mup.HasFlag(MariaDBUserPermissions.CreateChannel))
+            perms |= UserPermissions.CreateChannel;
+        if(mup.HasFlag(MariaDBUserPermissions.DeleteChannel))
+            perms |= UserPermissions.DeleteChannel;
+        if(mup.HasFlag(MariaDBUserPermissions.SetChannelPermanent))
+            perms |= UserPermissions.SetChannelPermanent;
+        if(mup.HasFlag(MariaDBUserPermissions.SetChannelPassword))
+            perms |= UserPermissions.SetChannelPassword;
+        if(mup.HasFlag(MariaDBUserPermissions.SetChannelHierarchy))
+            perms |= UserPermissions.SetChannelMinimumRank;
+        if(mup.HasFlag(MariaDBUserPermissions.JoinAnyChannel))
+            perms |= UserPermissions.JoinAnyChannel;
+        if(mup.HasFlag(MariaDBUserPermissions.SendMessage))
+            perms |= UserPermissions.SendMessage;
+        if(mup.HasFlag(MariaDBUserPermissions.DeleteOwnMessage))
+            perms |= UserPermissions.DeleteOwnMessage;
+        if(mup.HasFlag(MariaDBUserPermissions.DeleteAnyMessage))
+            perms |= UserPermissions.DeleteAnyMessage;
+        if(mup.HasFlag(MariaDBUserPermissions.EditOwnMessage))
+            perms |= UserPermissions.EditOwnMessage;
+        if(mup.HasFlag(MariaDBUserPermissions.EditAnyMessage))
+            perms |= UserPermissions.EditAnyMessage;
+        if(mup.HasFlag(MariaDBUserPermissions.SeeIPAddress))
+            perms |= UserPermissions.ViewIPAddress;
+        if(mup.HasFlag(MariaDBUserPermissions.ViewLogs))
+            perms |= UserPermissions.ViewLogs;
+        if(mup.HasFlag(MariaDBUserPermissions.ViewBanList))
+            perms |= UserPermissions.ViewBanList;
+        if(mup.HasFlag(MariaDBUserPermissions.PardonUser))
+            perms |= UserPermissions.PardonUser;
+        if(mup.HasFlag(MariaDBUserPermissions.PardonIPAddress))
+            perms |= UserPermissions.PardonIPAddress;
+
+        return perms;
+    }
+
+    public static MariaDBUserPermissions ToStoredPermissions(UserPermissions up) {
+        MariaDBUserPermissions perms = 0;
+
+        if(up.HasFlag(UserPermissions.KickUser))
+            perms |= MariaDBUserPermissions.KickUser;
+        if(up.HasFlag(UserPermissions.BanUser))
+            perms |= MariaDBUserPermissions.BanUser;
+        if(up.HasFlag(UserPermissions.SendBroadcast))
+            perms |= MariaDBUserPermissions.Broadcast;
+        if(up.HasFlag(UserPermissions.SetOwnNickname))
+            perms |= MariaDBUserPermissions.SetOwnNickname;
+        if(up.HasFlag(UserPermissions.SetOthersNickname))
+            perms |= MariaDBUserPermissions.SetOthersNickname;
+        if(up.HasFlag(UserPermissions.CreateChannel))
+            perms |= MariaDBUserPermissions.CreateChannel;
+        if(up.HasFlag(UserPermissions.DeleteChannel))
+            perms |= MariaDBUserPermissions.DeleteChannel;
+        if(up.HasFlag(UserPermissions.SetChannelPermanent))
+            perms |= MariaDBUserPermissions.SetChannelPermanent;
+        if(up.HasFlag(UserPermissions.SetChannelPassword))
+            perms |= MariaDBUserPermissions.SetChannelPassword;
+        if(up.HasFlag(UserPermissions.SetChannelMinimumRank))
+            perms |= MariaDBUserPermissions.SetChannelHierarchy;
+        if(up.HasFlag(UserPermissions.JoinAnyChannel))
+            perms |= MariaDBUserPermissions.JoinAnyChannel;
+        if(up.HasFlag(UserPermissions.SendMessage))
+            perms |= MariaDBUserPermissions.SendMessage;
+        if(up.HasFlag(UserPermissions.DeleteOwnMessage))
+            perms |= MariaDBUserPermissions.DeleteOwnMessage;
+        if(up.HasFlag(UserPermissions.DeleteAnyMessage))
+            perms |= MariaDBUserPermissions.DeleteAnyMessage;
+        if(up.HasFlag(UserPermissions.EditOwnMessage))
+            perms |= MariaDBUserPermissions.EditOwnMessage;
+        if(up.HasFlag(UserPermissions.EditAnyMessage))
+            perms |= MariaDBUserPermissions.EditAnyMessage;
+        if(up.HasFlag(UserPermissions.ViewIPAddress))
+            perms |= MariaDBUserPermissions.SeeIPAddress;
+        if(up.HasFlag(UserPermissions.ViewLogs))
+            perms |= MariaDBUserPermissions.ViewLogs;
+        if(up.HasFlag(UserPermissions.ViewBanList))
+            perms |= MariaDBUserPermissions.ViewBanList;
+        if(up.HasFlag(UserPermissions.PardonUser))
+            perms |= MariaDBUserPermissions.PardonUser;
+        if(up.HasFlag(UserPermissions.PardonIPAddress))
+            perms |= MariaDBUserPermissions.PardonIPAddress;
+
+        return perms;
+    }
+}
diff --git a/SharpChat/EventStorage/StoredUserPermissions.cs b/SharpChat.MariaDB/MariaDBUserPermissions.cs
similarity index 88%
rename from SharpChat/EventStorage/StoredUserPermissions.cs
rename to SharpChat.MariaDB/MariaDBUserPermissions.cs
index 3e29d2d..d50ab14 100644
--- a/SharpChat/EventStorage/StoredUserPermissions.cs
+++ b/SharpChat.MariaDB/MariaDBUserPermissions.cs
@@ -1,7 +1,7 @@
-namespace SharpChat.EventStorage;
+namespace SharpChat.MariaDB;
 
 [Flags]
-public enum StoredUserPermissions : int {
+public enum MariaDBUserPermissions : int {
     KickUser = 0x1,
     BanUser = 0x2,
     //SilenceUser = 0x4,
diff --git a/SharpChat.MariaDB/SharpChat.MariaDB.csproj b/SharpChat.MariaDB/SharpChat.MariaDB.csproj
new file mode 100644
index 0000000..25c0291
--- /dev/null
+++ b/SharpChat.MariaDB/SharpChat.MariaDB.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net9.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MySqlConnector" Version="2.4.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\SharpChatCommon\SharpChatCommon.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/SharpChat/SockChat/S2CPackets/ContextMessageS2CPacket.cs b/SharpChat.SockChat/S2CPackets/ContextMessageS2CPacket.cs
similarity index 61%
rename from SharpChat/SockChat/S2CPackets/ContextMessageS2CPacket.cs
rename to SharpChat.SockChat/S2CPackets/ContextMessageS2CPacket.cs
index 500fb78..a8f1a27 100644
--- a/SharpChat/SockChat/S2CPackets/ContextMessageS2CPacket.cs
+++ b/SharpChat.SockChat/S2CPackets/ContextMessageS2CPacket.cs
@@ -1,43 +1,41 @@
-using SharpChat.EventStorage;
+using SharpChat.Messages;
 using System.Text;
 
 namespace SharpChat.SockChat.S2CPackets;
 
-public class ContextMessageS2CPacket(StoredEventInfo evt, bool notify = false) : S2CPacket {
-    public StoredEventInfo Event { get; private set; } = evt ?? throw new ArgumentNullException(nameof(evt));
-
+public class ContextMessageS2CPacket(Message msg, bool notify = false) : S2CPacket {
     public string Pack() {
-        bool isAction = Event.Flags.HasFlag(StoredEventFlags.Action);
-        bool isBroadcast = Event.Flags.HasFlag(StoredEventFlags.Broadcast);
-        bool isPrivate = Event.Flags.HasFlag(StoredEventFlags.Private);
+        bool isAction = msg.Flags.HasFlag(MessageFlags.Action);
+        bool isBroadcast = msg.Flags.HasFlag(MessageFlags.Broadcast);
+        bool isPrivate = msg.Flags.HasFlag(MessageFlags.Private);
 
         StringBuilder sb = new();
 
         sb.Append("7\t1\t");
-        sb.Append(Event.Created.ToUnixTimeSeconds());
+        sb.Append(msg.Created.ToUnixTimeSeconds());
         sb.Append('\t');
 
-        switch(Event.Type) {
+        switch(msg.Type) {
             case "msg:add":
             case "SharpChat.Events.ChatMessage":
-                if(isBroadcast || Event.SenderId is null) {
+                if(isBroadcast || msg.SenderId is null) {
                     sb.Append("-1\tChatBot\tinherit\t\t0\fsay\f");
                 } else {
-                    sb.Append(Event.SenderId);
+                    sb.Append(msg.SenderId);
                     sb.Append('\t');
-                    sb.Append(Event.SenderLegacyName);
+                    sb.Append(msg.SenderLegacyName);
                     sb.Append('\t');
-                    sb.Append(Event.SenderColour);
+                    sb.Append(msg.SenderColour);
                     sb.Append('\t');
-                    sb.Append(Event.SenderRank);
+                    sb.Append(msg.SenderRank);
                     sb.Append(' ');
-                    sb.Append(Event.SenderPermissions.HasFlag(UserPermissions.KickUser) ? '1' : '0');
+                    sb.Append(msg.SenderPermissions.HasFlag(UserPermissions.KickUser) ? '1' : '0');
                     sb.Append(' ');
-                    sb.Append(Event.SenderPermissions.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
+                    sb.Append(msg.SenderPermissions.HasFlag(UserPermissions.ViewLogs) ? '1' : '0');
                     sb.Append(' ');
-                    sb.Append(Event.SenderPermissions.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
+                    sb.Append(msg.SenderPermissions.HasFlag(UserPermissions.SetOwnNickname) ? '1' : '0');
                     sb.Append(' ');
-                    sb.Append(Event.SenderPermissions.HasFlag(UserPermissions.CreateChannel) ? (Event.SenderPermissions.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
+                    sb.Append(msg.SenderPermissions.HasFlag(UserPermissions.CreateChannel) ? (msg.SenderPermissions.HasFlag(UserPermissions.SetChannelPermanent) ? '2' : '1') : '0');
                     sb.Append('\t');
                 }
 
@@ -45,7 +43,7 @@ public class ContextMessageS2CPacket(StoredEventInfo evt, bool notify = false) :
                     sb.Append("<i>");
 
                 sb.Append(
-                    (Event.Data.RootElement.GetProperty("text").GetString()?
+                    (msg.Data.RootElement.GetProperty("text").GetString()?
                         .Replace("<", "&lt;")
                         .Replace(">", "&gt;")
                         .Replace("\n", " <br/> ")
@@ -59,26 +57,26 @@ public class ContextMessageS2CPacket(StoredEventInfo evt, bool notify = false) :
             case "user:connect":
             case "SharpChat.Events.UserConnectEvent":
                 sb.Append("-1\tChatBot\tinherit\t\t0\fjoin\f");
-                sb.Append(Event.SenderLegacyName);
+                sb.Append(msg.SenderLegacyName);
                 break;
 
             case "chan:join":
             case "SharpChat.Events.UserChannelJoinEvent":
                 sb.Append("-1\tChatBot\tinherit\t\t0\fjchan\f");
-                sb.Append(Event.SenderLegacyName);
+                sb.Append(msg.SenderLegacyName);
                 break;
 
             case "chan:leave":
             case "SharpChat.Events.UserChannelLeaveEvent":
                 sb.Append("-1\tChatBot\tinherit\t\t0\flchan\f");
-                sb.Append(Event.SenderLegacyName);
+                sb.Append(msg.SenderLegacyName);
                 break;
 
             case "user:disconnect":
             case "SharpChat.Events.UserDisconnectEvent":
                 sb.Append("-1\tChatBot\tinherit\t\t0\f");
 
-                switch((UserDisconnectS2CPacket.Reason)Event.Data.RootElement.GetProperty("reason").GetByte()) {
+                switch((UserDisconnectS2CPacket.Reason)msg.Data.RootElement.GetProperty("reason").GetByte()) {
                     case UserDisconnectS2CPacket.Reason.Flood:
                         sb.Append("flood");
                         break;
@@ -95,12 +93,12 @@ public class ContextMessageS2CPacket(StoredEventInfo evt, bool notify = false) :
                 }
 
                 sb.Append('\f');
-                sb.Append(Event.SenderLegacyName);
+                sb.Append(msg.SenderLegacyName);
                 break;
         }
 
         sb.Append('\t');
-        sb.Append(Event.Id);
+        sb.Append(msg.Id);
         sb.Append('\t');
         sb.Append(notify ? '1' : '0');
         sb.AppendFormat(
diff --git a/SharpChat.sln b/SharpChat.sln
index 62be5d0..f92ff48 100644
--- a/SharpChat.sln
+++ b/SharpChat.sln
@@ -25,6 +25,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Protos", "Protos", "{02EA68
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Providers", "Providers", "{5BB7CDAA-06BB-4746-BA07-7EF9090774D8}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Storage", "Storage", "{D5EB4BD7-7C69-41F5-9D94-AA5E25B26BDA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpChat.MariaDB", "SharpChat.MariaDB\SharpChat.MariaDB.csproj", "{5B760B2D-F0AD-46E5-B701-8C53D25E2355}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -47,6 +51,10 @@ Global
 		{FEDDC565-B784-4D6F-BEF5-121C383D7AB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{FEDDC565-B784-4D6F-BEF5-121C383D7AB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{FEDDC565-B784-4D6F-BEF5-121C383D7AB2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5B760B2D-F0AD-46E5-B701-8C53D25E2355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5B760B2D-F0AD-46E5-B701-8C53D25E2355}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5B760B2D-F0AD-46E5-B701-8C53D25E2355}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5B760B2D-F0AD-46E5-B701-8C53D25E2355}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -54,6 +62,7 @@ Global
 	GlobalSection(NestedProjects) = preSolution
 		{A9B0B652-C20F-4C62-A96A-EF7ACD2079E9} = {5BB7CDAA-06BB-4746-BA07-7EF9090774D8}
 		{FEDDC565-B784-4D6F-BEF5-121C383D7AB2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+		{5B760B2D-F0AD-46E5-B701-8C53D25E2355} = {D5EB4BD7-7C69-41F5-9D94-AA5E25B26BDA}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {42279FE1-5980-440A-87F8-25338DFE54CF}
diff --git a/SharpChat/Channel.cs b/SharpChat/Channel.cs
index 4cc0361..cfdd371 100644
--- a/SharpChat/Channel.cs
+++ b/SharpChat/Channel.cs
@@ -20,10 +20,10 @@ public class Channel(
         return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase);
     }
 
-    public bool IsOwner(User user) {
-        return string.IsNullOrEmpty(OwnerId)
-            && user != null
-            && OwnerId == user.UserId;
+    public bool IsOwner(string userId) {
+        return !string.IsNullOrEmpty(OwnerId)
+            && !string.IsNullOrEmpty(userId)
+            && OwnerId == userId;
     }
 
     public override int GetHashCode() {
diff --git a/SharpChat/ClientCommands/DeleteChannelClientCommand.cs b/SharpChat/ClientCommands/DeleteChannelClientCommand.cs
index ec0984c..68f2c83 100644
--- a/SharpChat/ClientCommands/DeleteChannelClientCommand.cs
+++ b/SharpChat/ClientCommands/DeleteChannelClientCommand.cs
@@ -26,7 +26,7 @@ public class DeleteChannelClientCommand : ClientCommand {
             return;
         }
 
-        if(!ctx.User.Permissions.HasFlag(UserPermissions.DeleteChannel) && delChan.IsOwner(ctx.User)) {
+        if(!ctx.User.Permissions.HasFlag(UserPermissions.DeleteChannel) && delChan.IsOwner(ctx.User.UserId)) {
             await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_DELETE_FAILED, true, delChan.Name));
             return;
         }
diff --git a/SharpChat/ClientCommands/DeleteMessageClientCommand.cs b/SharpChat/ClientCommands/DeleteMessageClientCommand.cs
index 775476b..654886b 100644
--- a/SharpChat/ClientCommands/DeleteMessageClientCommand.cs
+++ b/SharpChat/ClientCommands/DeleteMessageClientCommand.cs
@@ -1,4 +1,4 @@
-using SharpChat.EventStorage;
+using SharpChat.Messages;
 using SharpChat.SockChat.S2CPackets;
 
 namespace SharpChat.ClientCommands;
@@ -27,14 +27,14 @@ public class DeleteMessageClientCommand : ClientCommand {
             return;
         }
 
-        StoredEventInfo? delMsg = await ctx.Chat.Events.GetEvent(delSeqId);
+        Message? delMsg = await ctx.Chat.Messages.GetMessage(delSeqId);
 
         if(delMsg?.SenderId is null || delMsg.SenderRank > ctx.User.Rank || (!deleteAnyMessage && delMsg.SenderId != ctx.User.UserId)) {
             await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.MESSAGE_DELETE_ERROR));
             return;
         }
 
-        await ctx.Chat.Events.RemoveEvent(delMsg);
+        await ctx.Chat.Messages.DeleteMessage(delMsg);
         await ctx.Chat.Send(new ChatMessageDeleteS2CPacket(delMsg.Id));
     }
 }
diff --git a/SharpChat/ClientCommands/PasswordChannelClientCommand.cs b/SharpChat/ClientCommands/PasswordChannelClientCommand.cs
index c8cd882..af9ebae 100644
--- a/SharpChat/ClientCommands/PasswordChannelClientCommand.cs
+++ b/SharpChat/ClientCommands/PasswordChannelClientCommand.cs
@@ -11,7 +11,7 @@ public class PasswordChannelClientCommand : ClientCommand {
     public async Task Dispatch(ClientCommandContext ctx) {
         long msgId = ctx.Chat.RandomSnowflake.Next();
 
-        if(!ctx.User.Permissions.HasFlag(UserPermissions.SetChannelPassword) || ctx.Channel.IsOwner(ctx.User)) {
+        if(!ctx.User.Permissions.HasFlag(UserPermissions.SetChannelPassword) || ctx.Channel.IsOwner(ctx.User.UserId)) {
             await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
             return;
         }
diff --git a/SharpChat/ClientCommands/RankChannelClientCommand.cs b/SharpChat/ClientCommands/RankChannelClientCommand.cs
index 0ff0fa6..fa98c88 100644
--- a/SharpChat/ClientCommands/RankChannelClientCommand.cs
+++ b/SharpChat/ClientCommands/RankChannelClientCommand.cs
@@ -12,7 +12,7 @@ public class RankChannelClientCommand : ClientCommand {
     public async Task Dispatch(ClientCommandContext ctx) {
         long msgId = ctx.Chat.RandomSnowflake.Next();
 
-        if(!ctx.User.Permissions.HasFlag(UserPermissions.SetChannelMinimumRank) || ctx.Channel.IsOwner(ctx.User)) {
+        if(!ctx.User.Permissions.HasFlag(UserPermissions.SetChannelMinimumRank) || ctx.Channel.IsOwner(ctx.User.UserId)) {
             await ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
             return;
         }
diff --git a/SharpChat/Context.cs b/SharpChat/Context.cs
index bf9b32f..3354cb9 100644
--- a/SharpChat/Context.cs
+++ b/SharpChat/Context.cs
@@ -1,5 +1,5 @@
 using SharpChat.Events;
-using SharpChat.EventStorage;
+using SharpChat.Messages;
 using SharpChat.Snowflake;
 using SharpChat.SockChat;
 using SharpChat.SockChat.S2CPackets;
@@ -18,13 +18,13 @@ public class Context {
     public HashSet<Channel> Channels { get; } = [];
     public HashSet<Connection> Connections { get; } = [];
     public HashSet<User> Users { get; } = [];
-    public EventStorage.EventStorage Events { get; }
+    public MessageStorage Messages { get; }
     public HashSet<ChannelUserAssoc> ChannelUsers { get; } = [];
     public Dictionary<string, RateLimiter> UserRateLimiters { get; } = [];
     public Dictionary<string, Channel> UserLastChannel { get; } = [];
 
-    public Context(EventStorage.EventStorage evtStore) {
-        Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
+    public Context(MessageStorage msgs) {
+        Messages = msgs;
         RandomSnowflake = new(SnowflakeGenerator);
     }
 
@@ -71,14 +71,14 @@ public class Context {
                     ));
             }
 
-            await Events.AddEvent(
+            await Messages.LogMessage(
                 mce.MessageId, "msg:add",
                 mce.ChannelName,
                 mce.SenderId, mce.SenderName, mce.SenderColour, mce.SenderRank, mce.SenderNickName, mce.SenderPerms,
                 new { text = mce.MessageText },
-                (mce.IsBroadcast ? StoredEventFlags.Broadcast : 0)
-                | (mce.IsAction ? StoredEventFlags.Action : 0)
-                | (mce.IsPrivate ? StoredEventFlags.Private : 0)
+                (mce.IsBroadcast ? MessageFlags.Broadcast : 0)
+                | (mce.IsAction ? MessageFlags.Action : 0)
+                | (mce.IsPrivate ? MessageFlags.Private : 0)
             );
             return;
         }
@@ -212,7 +212,7 @@ public class Context {
         if(!IsInChannel(user, chan)) {
             long msgId = RandomSnowflake.Next();
             await SendTo(chan, new UserConnectS2CPacket(msgId, DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions));
-            await Events.AddEvent(msgId, "user:connect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
+            await Messages.LogMessage(msgId, "user:connect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, MessageFlags.Log);
         }
 
         await conn.Send(new AuthSuccessS2CPacket(
@@ -236,8 +236,8 @@ public class Context {
                 ))
         ));
 
-        IEnumerable<StoredEventInfo> msgs = await Events.GetChannelEventLog(chan.Name);
-        foreach(StoredEventInfo msg in msgs)
+        IEnumerable<Message> msgs = await Messages.GetMessages(chan.Name);
+        foreach(Message msg in msgs)
             await conn.Send(new ContextMessageS2CPacket(msg));
 
         await conn.Send(new ContextChannelsS2CPacket(
@@ -263,9 +263,9 @@ public class Context {
 
             long msgId = RandomSnowflake.Next();
             await SendTo(chan, new UserDisconnectS2CPacket(msgId, DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, reason));
-            await Events.AddEvent(msgId, "user:disconnect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, new { reason = (int)reason }, StoredEventFlags.Log);
+            await Messages.LogMessage(msgId, "user:disconnect", chan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, new { reason = (int)reason }, MessageFlags.Log);
 
-            if(chan.IsTemporary && chan.IsOwner(user))
+            if(chan.IsTemporary && chan.IsOwner(user.UserId))
                 await RemoveChannel(chan);
         }
     }
@@ -276,7 +276,7 @@ public class Context {
             return;
         }
 
-        if(!user.Permissions.HasFlag(UserPermissions.JoinAnyChannel) && chan.IsOwner(user)) {
+        if(!user.Permissions.HasFlag(UserPermissions.JoinAnyChannel) && chan.IsOwner(user.UserId)) {
             if(chan.Rank > user.Rank) {
                 await SendTo(user, new CommandResponseS2CPacket(RandomSnowflake.Next(), LCR.CHANNEL_INSUFFICIENT_HIERARCHY, true, chan.Name));
                 await ForceChannel(user);
@@ -294,18 +294,18 @@ public class Context {
     }
 
     public async Task ForceChannelSwitch(User user, Channel chan) {
-        if(!Channels.Contains(chan))
+        if(!Channels.Any(c => c.NameEquals(chan.Name)))
             return;
 
         Channel oldChan = UserLastChannel[user.UserId];
 
         long leaveId = RandomSnowflake.Next();
         await SendTo(oldChan, new UserChannelLeaveS2CPacket(leaveId, user.UserId));
-        await Events.AddEvent(leaveId, "chan:leave", oldChan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
+        await Messages.LogMessage(leaveId, "chan:leave", oldChan.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions, null, MessageFlags.Log);
 
         long joinId = RandomSnowflake.Next();
         await SendTo(chan, new UserChannelJoinS2CPacket(joinId, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions));
-        await Events.AddEvent(joinId, "chan:join", chan.Name, user.UserId, user.LegacyName, user.Colour, user.Rank, user.NickName, user.Permissions, null, StoredEventFlags.Log);
+        await Messages.LogMessage(joinId, "chan:join", chan.Name, user.UserId, user.LegacyName, user.Colour, user.Rank, user.NickName, user.Permissions, null, MessageFlags.Log);
 
         await SendTo(user, new ContextClearS2CPacket(ContextClearS2CPacket.Mode.MessagesUsers));
         await SendTo(user, new ContextUsersS2CPacket(
@@ -320,8 +320,8 @@ public class Context {
                 ))
         ));
 
-        IEnumerable<StoredEventInfo> msgs = await Events.GetChannelEventLog(chan.Name);
-        foreach(StoredEventInfo msg in msgs)
+        IEnumerable<Message> msgs = await Messages.GetMessages(chan.Name);
+        foreach(Message msg in msgs)
             await SendTo(user, new ContextMessageS2CPacket(msg));
 
         await ForceChannel(user, chan);
@@ -330,7 +330,7 @@ public class Context {
         ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
         UserLastChannel[user.UserId] = chan;
 
-        if(oldChan.IsTemporary && oldChan.IsOwner(user))
+        if(oldChan.IsTemporary && oldChan.IsOwner(user.UserId))
             await RemoveChannel(oldChan);
     }
 
@@ -374,8 +374,7 @@ public class Context {
     }
 
     public async Task UpdateChannel(Channel channel, bool? temporary = null, int? hierarchy = null, string? password = null) {
-        ArgumentNullException.ThrowIfNull(channel);
-        if(!Channels.Contains(channel))
+        if(!Channels.Any(c => c.NameEquals(channel.Name)))
             throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel));
 
         if(temporary.HasValue)
diff --git a/SharpChat/EventStorage/EventStorage.cs b/SharpChat/EventStorage/EventStorage.cs
deleted file mode 100644
index 3a9a90e..0000000
--- a/SharpChat/EventStorage/EventStorage.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace SharpChat.EventStorage;
-
-public interface EventStorage {
-    Task AddEvent(
-        long id,
-        string type,
-        string channelName,
-        string senderId,
-        string senderName,
-        ColourInheritable senderColour,
-        int senderRank,
-        string senderNick,
-        UserPermissions senderPerms,
-        object? data = null,
-        StoredEventFlags flags = StoredEventFlags.None
-    );
-    Task RemoveEvent(StoredEventInfo evt);
-    Task<StoredEventInfo?> GetEvent(long seqId);
-    Task<IEnumerable<StoredEventInfo>> GetChannelEventLog(string channelName, int amount = 20, int offset = 0);
-}
diff --git a/SharpChat/EventStorage/MariaDBEventStorage_Permissions.cs b/SharpChat/EventStorage/MariaDBEventStorage_Permissions.cs
deleted file mode 100644
index cbbf1c0..0000000
--- a/SharpChat/EventStorage/MariaDBEventStorage_Permissions.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-namespace SharpChat.EventStorage;
-public partial class MariaDBEventStorage {
-    public static UserPermissions FromStoredPermissions(StoredUserPermissions sup) {
-        UserPermissions perms = 0;
-
-        if(sup.HasFlag(StoredUserPermissions.KickUser))
-            perms |= UserPermissions.KickUser;
-        if(sup.HasFlag(StoredUserPermissions.BanUser))
-            perms |= UserPermissions.BanUser;
-        if(sup.HasFlag(StoredUserPermissions.Broadcast))
-            perms |= UserPermissions.SendBroadcast;
-        if(sup.HasFlag(StoredUserPermissions.SetOwnNickname))
-            perms |= UserPermissions.SetOwnNickname;
-        if(sup.HasFlag(StoredUserPermissions.SetOthersNickname))
-            perms |= UserPermissions.SetOthersNickname;
-        if(sup.HasFlag(StoredUserPermissions.CreateChannel))
-            perms |= UserPermissions.CreateChannel;
-        if(sup.HasFlag(StoredUserPermissions.DeleteChannel))
-            perms |= UserPermissions.DeleteChannel;
-        if(sup.HasFlag(StoredUserPermissions.SetChannelPermanent))
-            perms |= UserPermissions.SetChannelPermanent;
-        if(sup.HasFlag(StoredUserPermissions.SetChannelPassword))
-            perms |= UserPermissions.SetChannelPassword;
-        if(sup.HasFlag(StoredUserPermissions.SetChannelHierarchy))
-            perms |= UserPermissions.SetChannelMinimumRank;
-        if(sup.HasFlag(StoredUserPermissions.JoinAnyChannel))
-            perms |= UserPermissions.JoinAnyChannel;
-        if(sup.HasFlag(StoredUserPermissions.SendMessage))
-            perms |= UserPermissions.SendMessage;
-        if(sup.HasFlag(StoredUserPermissions.DeleteOwnMessage))
-            perms |= UserPermissions.DeleteOwnMessage;
-        if(sup.HasFlag(StoredUserPermissions.DeleteAnyMessage))
-            perms |= UserPermissions.DeleteAnyMessage;
-        if(sup.HasFlag(StoredUserPermissions.EditOwnMessage))
-            perms |= UserPermissions.EditOwnMessage;
-        if(sup.HasFlag(StoredUserPermissions.EditAnyMessage))
-            perms |= UserPermissions.EditAnyMessage;
-        if(sup.HasFlag(StoredUserPermissions.SeeIPAddress))
-            perms |= UserPermissions.ViewIPAddress;
-        if(sup.HasFlag(StoredUserPermissions.ViewLogs))
-            perms |= UserPermissions.ViewLogs;
-        if(sup.HasFlag(StoredUserPermissions.ViewBanList))
-            perms |= UserPermissions.ViewBanList;
-        if(sup.HasFlag(StoredUserPermissions.PardonUser))
-            perms |= UserPermissions.PardonUser;
-        if(sup.HasFlag(StoredUserPermissions.PardonIPAddress))
-            perms |= UserPermissions.PardonIPAddress;
-
-        return perms;
-    }
-
-    public static StoredUserPermissions ToStoredPermissions(UserPermissions up) {
-        StoredUserPermissions perms = 0;
-
-        if(up.HasFlag(UserPermissions.KickUser))
-            perms |= StoredUserPermissions.KickUser;
-        if(up.HasFlag(UserPermissions.BanUser))
-            perms |= StoredUserPermissions.BanUser;
-        if(up.HasFlag(UserPermissions.SendBroadcast))
-            perms |= StoredUserPermissions.Broadcast;
-        if(up.HasFlag(UserPermissions.SetOwnNickname))
-            perms |= StoredUserPermissions.SetOwnNickname;
-        if(up.HasFlag(UserPermissions.SetOthersNickname))
-            perms |= StoredUserPermissions.SetOthersNickname;
-        if(up.HasFlag(UserPermissions.CreateChannel))
-            perms |= StoredUserPermissions.CreateChannel;
-        if(up.HasFlag(UserPermissions.DeleteChannel))
-            perms |= StoredUserPermissions.DeleteChannel;
-        if(up.HasFlag(UserPermissions.SetChannelPermanent))
-            perms |= StoredUserPermissions.SetChannelPermanent;
-        if(up.HasFlag(UserPermissions.SetChannelPassword))
-            perms |= StoredUserPermissions.SetChannelPassword;
-        if(up.HasFlag(UserPermissions.SetChannelMinimumRank))
-            perms |= StoredUserPermissions.SetChannelHierarchy;
-        if(up.HasFlag(UserPermissions.JoinAnyChannel))
-            perms |= StoredUserPermissions.JoinAnyChannel;
-        if(up.HasFlag(UserPermissions.SendMessage))
-            perms |= StoredUserPermissions.SendMessage;
-        if(up.HasFlag(UserPermissions.DeleteOwnMessage))
-            perms |= StoredUserPermissions.DeleteOwnMessage;
-        if(up.HasFlag(UserPermissions.DeleteAnyMessage))
-            perms |= StoredUserPermissions.DeleteAnyMessage;
-        if(up.HasFlag(UserPermissions.EditOwnMessage))
-            perms |= StoredUserPermissions.EditOwnMessage;
-        if(up.HasFlag(UserPermissions.EditAnyMessage))
-            perms |= StoredUserPermissions.EditAnyMessage;
-        if(up.HasFlag(UserPermissions.ViewIPAddress))
-            perms |= StoredUserPermissions.SeeIPAddress;
-        if(up.HasFlag(UserPermissions.ViewLogs))
-            perms |= StoredUserPermissions.ViewLogs;
-        if(up.HasFlag(UserPermissions.ViewBanList))
-            perms |= StoredUserPermissions.ViewBanList;
-        if(up.HasFlag(UserPermissions.PardonUser))
-            perms |= StoredUserPermissions.PardonUser;
-        if(up.HasFlag(UserPermissions.PardonIPAddress))
-            perms |= StoredUserPermissions.PardonIPAddress;
-
-        return perms;
-    }
-}
diff --git a/SharpChat/Program.cs b/SharpChat/Program.cs
index 1fa9da8..8cc8f32 100644
--- a/SharpChat/Program.cs
+++ b/SharpChat/Program.cs
@@ -1,8 +1,9 @@
 using SharpChat;
 using SharpChat.Configuration;
-using SharpChat.EventStorage;
+using SharpChat.Messages;
 using SharpChat.Flashii;
 using System.Text;
+using SharpChat.MariaDB;
 
 const string CONFIG = "sharpchat.cfg";
 
@@ -123,18 +124,18 @@ FlashiiClient flashii = new(httpClient, config.ScopeTo("msz"));
 
 if(hasCancelled) return;
 
-EventStorage evtStore;
+MessageStorage msgStore;
 if(string.IsNullOrWhiteSpace(config.SafeReadValue("mariadb:host", string.Empty))) {
-    evtStore = new VirtualEventStorage();
+    msgStore = new VirtualMessageStorage();
 } else {
-    MariaDBEventStorage mdbes = new(MariaDBEventStorage.BuildConnString(config.ScopeTo("mariadb")));
-    evtStore = mdbes;
+    MariaDBMessageStorage mdbes = new(MariaDBMessageStorage.BuildConnString(config.ScopeTo("mariadb")));
+    msgStore = mdbes;
     await mdbes.RunMigrations();
 }
 
 if(hasCancelled) return;
 
-using SockChatServer scs = new(flashii, flashii, evtStore, config.ScopeTo("chat"));
+using SockChatServer scs = new(flashii, flashii, msgStore, config.ScopeTo("chat"));
 scs.Listen(mre);
 
 mre.WaitOne();
diff --git a/SharpChat/SharpChat.csproj b/SharpChat/SharpChat.csproj
index 9472b1a..16cd97b 100644
--- a/SharpChat/SharpChat.csproj
+++ b/SharpChat/SharpChat.csproj
@@ -17,7 +17,6 @@
 
   <ItemGroup>
     <PackageReference Include="Fleck" Version="1.2.0" />
-    <PackageReference Include="MySqlConnector" Version="2.4.0" />
   </ItemGroup>
 
   <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
@@ -34,6 +33,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\SharpChat.Flashii\SharpChat.Flashii.csproj" />
+    <ProjectReference Include="..\SharpChat.MariaDB\SharpChat.MariaDB.csproj" />
     <ProjectReference Include="..\SharpChat.SockChat\SharpChat.SockChat.csproj" />
     <ProjectReference Include="..\SharpChatCommon\SharpChatCommon.csproj" />
   </ItemGroup>
diff --git a/SharpChat/SockChatServer.cs b/SharpChat/SockChatServer.cs
index 6cbd976..11b75ae 100644
--- a/SharpChat/SockChatServer.cs
+++ b/SharpChat/SockChatServer.cs
@@ -4,6 +4,7 @@ using SharpChat.Bans;
 using SharpChat.C2SPacketHandlers;
 using SharpChat.ClientCommands;
 using SharpChat.Configuration;
+using SharpChat.Messages;
 using SharpChat.SockChat.S2CPackets;
 using System.Net;
 
@@ -39,7 +40,7 @@ public class SockChatServer : IDisposable {
     public SockChatServer(
         AuthClient authClient,
         BansClient bansClient,
-        EventStorage.EventStorage evtStore,
+        MessageStorage msgStorage,
         Config config
     ) {
         Logger.Write("Initialising Sock Chat server...");
@@ -51,7 +52,7 @@ public class SockChatServer : IDisposable {
         FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH);
         FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK);
 
-        Context = new Context(evtStore);
+        Context = new Context(msgStorage ?? throw new ArgumentNullException(nameof(msgStorage)));
 
         string[]? channelNames = config.ReadValue("channels", DEFAULT_CHANNELS);
         if(channelNames is not null)
diff --git a/SharpChat/EventStorage/StoredEventInfo.cs b/SharpChatCommon/Messages/Message.cs
similarity index 86%
rename from SharpChat/EventStorage/StoredEventInfo.cs
rename to SharpChatCommon/Messages/Message.cs
index a1f7c59..9e6ab2f 100644
--- a/SharpChat/EventStorage/StoredEventInfo.cs
+++ b/SharpChatCommon/Messages/Message.cs
@@ -1,8 +1,8 @@
-using System.Text.Json;
+using System.Text.Json;
 
-namespace SharpChat.EventStorage;
+namespace SharpChat.Messages;
 
-public class StoredEventInfo(
+public class Message(
     long id,
     string type,
     string? senderId,
@@ -15,7 +15,7 @@ public class StoredEventInfo(
     DateTimeOffset? deleted,
     string? channelName,
     JsonDocument data,
-    StoredEventFlags flags
+    MessageFlags flags
 ) {
     public long Id { get; } = id;
     public string Type { get; } = type;
@@ -28,7 +28,7 @@ public class StoredEventInfo(
     public DateTimeOffset Created { get; } = created;
     public DateTimeOffset? Deleted { get; } = deleted;
     public string? ChannelName { get; } = channelName;
-    public StoredEventFlags Flags { get; } = flags;
+    public MessageFlags Flags { get; } = flags;
     public JsonDocument Data { get; } = data;
 
     public string SenderLegacyName => string.IsNullOrWhiteSpace(SenderNickName) ? SenderName : $"~{SenderNickName}";
diff --git a/SharpChat/EventStorage/StoredEventFlags.cs b/SharpChatCommon/Messages/MessageFlags.cs
similarity index 60%
rename from SharpChat/EventStorage/StoredEventFlags.cs
rename to SharpChatCommon/Messages/MessageFlags.cs
index 4cc6b81..17b47cb 100644
--- a/SharpChat/EventStorage/StoredEventFlags.cs
+++ b/SharpChatCommon/Messages/MessageFlags.cs
@@ -1,7 +1,7 @@
-namespace SharpChat.EventStorage;
+namespace SharpChat.Messages;
 
 [Flags]
-public enum StoredEventFlags {
+public enum MessageFlags {
     None = 0,
     Action = 1,
     Broadcast = 1 << 1,
diff --git a/SharpChatCommon/Messages/MessageStorage.cs b/SharpChatCommon/Messages/MessageStorage.cs
new file mode 100644
index 0000000..4b26fc4
--- /dev/null
+++ b/SharpChatCommon/Messages/MessageStorage.cs
@@ -0,0 +1,20 @@
+namespace SharpChat.Messages;
+
+public interface MessageStorage {
+    Task LogMessage(
+        long msgId,
+        string type,
+        string channelName,
+        string senderId,
+        string senderName,
+        ColourInheritable senderColour,
+        int senderRank,
+        string senderNick,
+        UserPermissions senderPerms,
+        object? data = null,
+        MessageFlags flags = MessageFlags.None
+    );
+    Task DeleteMessage(Message evt);
+    Task<Message?> GetMessage(long msgId);
+    Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0);
+}
diff --git a/SharpChat/EventStorage/VirtualEventStorage.cs b/SharpChatCommon/Messages/VirtualMessageStorage.cs
similarity index 60%
rename from SharpChat/EventStorage/VirtualEventStorage.cs
rename to SharpChatCommon/Messages/VirtualMessageStorage.cs
index c6692c7..36018a0 100644
--- a/SharpChat/EventStorage/VirtualEventStorage.cs
+++ b/SharpChatCommon/Messages/VirtualMessageStorage.cs
@@ -1,11 +1,11 @@
 using System.Text.Json;
 
-namespace SharpChat.EventStorage;
+namespace SharpChat.Messages;
 
-public class VirtualEventStorage : EventStorage {
-    private readonly Dictionary<long, StoredEventInfo> Events = [];
+public class VirtualMessageStorage : MessageStorage {
+    private readonly Dictionary<long, Message> Messages = [];
 
-    public Task AddEvent(
+    public Task LogMessage(
         long id,
         string type,
         string channelName,
@@ -16,9 +16,9 @@ public class VirtualEventStorage : EventStorage {
         string senderNick,
         UserPermissions senderPerms,
         object? data = null,
-        StoredEventFlags flags = StoredEventFlags.None
+        MessageFlags flags = MessageFlags.None
     ) {
-        Events.Add(
+        Messages.Add(
             id,
             new(
                 id,
@@ -40,17 +40,17 @@ public class VirtualEventStorage : EventStorage {
         return Task.CompletedTask;
     }
 
-    public Task<StoredEventInfo?> GetEvent(long seqId) {
-        return Task.FromResult(Events.TryGetValue(seqId, out StoredEventInfo? evt) ? evt : null);
+    public Task<Message?> GetMessage(long seqId) {
+        return Task.FromResult(Messages.TryGetValue(seqId, out Message? evt) ? evt : null);
     }
 
-    public Task RemoveEvent(StoredEventInfo evt) {
-        Events.Remove(evt.Id);
+    public Task DeleteMessage(Message evt) {
+        Messages.Remove(evt.Id);
         return Task.CompletedTask;
     }
 
-    public Task<IEnumerable<StoredEventInfo>> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
-        IEnumerable<StoredEventInfo> subset = Events.Values.Where(ev => ev.ChannelName == channelName);
+    public Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0) {
+        IEnumerable<Message> subset = Messages.Values.Where(ev => ev.ChannelName == channelName);
 
         int start = subset.Count() - offset - amount;
         if(start < 0) {
@@ -58,6 +58,6 @@ public class VirtualEventStorage : EventStorage {
             start = 0;
         }
 
-        return Task.FromResult(subset.Skip(start).Take(amount).ToArray() as IEnumerable<StoredEventInfo>);
+        return Task.FromResult(subset.Skip(start).Take(amount).ToArray() as IEnumerable<Message>);
     }
 }