diff --git a/Commands/AddCategoryCommand.cs b/Commands/AddCategoryCommand.cs
index a5ad447..0ba8759 100644
--- a/Commands/AddCategoryCommand.cs
+++ b/Commands/AddCategoryCommand.cs
@@ -37,7 +37,7 @@ public class AddCategoryCommand
             return;
         }
 
-        if (Database.TryGetCategory(category.Id, out Database.Category _))
+        if (Database.Category.TryGetCategory(category.Id, out Database.Category _))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -47,7 +47,7 @@ public class AddCategoryCommand
             return;
         }
 
-        if (Database.TryGetCategory(title, out Database.Category _))
+        if (Database.Category.TryGetCategory(title, out Database.Category _))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -57,7 +57,7 @@ public class AddCategoryCommand
             return;
         }
 
-        if (Database.AddCategory(title, category.Id))
+        if (Database.Category.AddCategory(title, category.Id))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/AddCommand.cs b/Commands/AddCommand.cs
index a11b2bd..c668c0c 100644
--- a/Commands/AddCommand.cs
+++ b/Commands/AddCommand.cs
@@ -18,7 +18,7 @@ public class AddCommand
         [Parameter("user")][Description("User to add to ticket.")] DiscordUser user)
     {
         // Check if ticket exists in the database
-        if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/AddStaffCommand.cs b/Commands/AddStaffCommand.cs
index 2191431..3501505 100644
--- a/Commands/AddStaffCommand.cs
+++ b/Commands/AddStaffCommand.cs
@@ -18,7 +18,7 @@ public class AddStaffCommand
     public async Task OnExecute(SlashCommandContext command,
         [Parameter("user")][Description("User to add to staff.")] DiscordUser user)
     {
-        DiscordMember staffMember = null;
+        DiscordMember staffMember;
         try
         {
             staffMember = user == null ? command.Member : await command.Guild.GetMemberAsync(user.Id);
@@ -43,16 +43,15 @@ public class AddStaffCommand
             return;
         }
 
-        bool alreadyStaff = Database.IsStaff(staffMember.Id);
-
-        await using MySqlConnection c = Database.GetConnection();
-        MySqlCommand cmd = alreadyStaff ? new MySqlCommand(@"UPDATE staff SET name = @name WHERE user_id = @user_id", c) : new MySqlCommand(@"INSERT INTO staff (user_id, name) VALUES (@user_id, @name);", c);
-
-        c.Open();
-        cmd.Parameters.AddWithValue("@user_id", staffMember.Id);
-        cmd.Parameters.AddWithValue("@name", staffMember.DisplayName);
-        cmd.ExecuteNonQuery();
-        cmd.Dispose();
+        bool alreadyStaff = Database.StaffMember.IsStaff(staffMember.Id);
+        if (!Database.StaffMember.AddStaff(staffMember.DisplayName, staffMember.Id))
+        {
+            await command.RespondAsync(new DiscordEmbedBuilder
+            {
+                Color = DiscordColor.Red,
+                Description = "Error: Failed to add staff member to database."
+            }, true);
+        }
 
         if (alreadyStaff)
         {
diff --git a/Commands/AdminCommands.cs b/Commands/AdminCommands.cs
index ddf8b8a..ab3637f 100644
--- a/Commands/AdminCommands.cs
+++ b/Commands/AdminCommands.cs
@@ -21,7 +21,7 @@ public class AdminCommands
         [Parameter("user")][Description("(Optional) The owner of the ticket.")] DiscordUser user = null)
     {
         // Check if ticket exists in the database
-        if (Database.IsOpenTicket(command.Channel.Id))
+        if (Database.Ticket.IsOpenTicket(command.Channel.Id))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -33,7 +33,7 @@ public class AdminCommands
 
         DiscordUser ticketUser = user == null ? command.User : user;
 
-        long id = Database.NewTicket(ticketUser.Id, 0, command.Channel.Id);
+        long id = Database.Ticket.NewTicket(ticketUser.Id, 0, command.Channel.Id);
         await command.RespondAsync(new DiscordEmbedBuilder
         {
             Color = DiscordColor.Green,
@@ -57,7 +57,7 @@ public class AdminCommands
         if (ticketID == 0)
         {
             // Check if ticket exists in the database
-            if (!Database.TryGetOpenTicket(command.Channel.Id, out ticket))
+            if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out ticket))
             {
                 await command.RespondAsync(new DiscordEmbedBuilder
                 {
@@ -71,7 +71,7 @@ public class AdminCommands
         else
         {
             // Check if ticket exists in the database
-            if (!Database.TryGetOpenTicketByID((uint)ticketID, out ticket))
+            if (!Database.Ticket.TryGetOpenTicketByID((uint)ticketID, out ticket))
             {
                 await command.RespondAsync(new DiscordEmbedBuilder
                 {
@@ -96,7 +96,7 @@ public class AdminCommands
         }
 
         // Delete the ticket from the database and respond to command
-        if (Database.DeleteOpenTicket(ticket.id))
+        if (Database.Ticket.DeleteOpenTicket(ticket.id))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/AssignCommand.cs b/Commands/AssignCommand.cs
index de6e6a7..65e2656 100644
--- a/Commands/AssignCommand.cs
+++ b/Commands/AssignCommand.cs
@@ -43,7 +43,7 @@ public class AssignCommand
         }
 
         // Check if ticket exists in the database
-        if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -53,7 +53,7 @@ public class AssignCommand
             return;
         }
 
-        if (!Database.IsStaff(member.Id))
+        if (!Database.StaffMember.IsStaff(member.Id))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -63,7 +63,7 @@ public class AssignCommand
             return;
         }
 
-        if (!Database.AssignStaff(ticket, member.Id))
+        if (!Database.StaffMember.AssignStaff(ticket, member.Id))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/BlacklistCommand.cs b/Commands/BlacklistCommand.cs
index 27cf520..b566957 100644
--- a/Commands/BlacklistCommand.cs
+++ b/Commands/BlacklistCommand.cs
@@ -19,7 +19,7 @@ public class BlacklistCommand
     {
         try
         {
-            if (!Database.Blacklist(user.Id, command.User.Id))
+            if (!Database.Blacklist.Ban(user.Id, command.User.Id))
             {
                 await command.RespondAsync(new DiscordEmbedBuilder
                 {
diff --git a/Commands/CloseCommand.cs b/Commands/CloseCommand.cs
index 2ce8747..b4da76f 100644
--- a/Commands/CloseCommand.cs
+++ b/Commands/CloseCommand.cs
@@ -26,7 +26,7 @@ public class CloseCommand
         [Parameter("reason")][Description("(Optional) The reason for closing this ticket.")] string reason = "")
     {
         // Check if ticket exists in the database
-        if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket _))
+        if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket _))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -42,7 +42,7 @@ public class CloseCommand
                 Color = DiscordColor.Cyan,
                 Description = "Are you sure you wish to close this ticket? You cannot re-open it again later."
             })
-            .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, "supportchild_closeconfirm", "Confirm"));
+            .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, "supportboi_closeconfirm", "Confirm"));
 
         await command.RespondAsync(confirmation);
 
@@ -75,7 +75,7 @@ public class CloseCommand
             await interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredMessageUpdate);
 
             // Check if ticket exists in the database
-            if (!Database.TryGetOpenTicket(interaction.Channel.Id, out Database.Ticket ticket))
+            if (!Database.Ticket.TryGetOpenTicket(interaction.Channel.Id, out Database.Ticket ticket))
             {
                 currentlyClosingTickets.Remove(interaction.Channel.Id);
                 await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder
@@ -205,8 +205,8 @@ public class CloseCommand
                 catch (NullReferenceException) { /* ignore */ }
             }
 
-            Database.ArchiveTicket(ticket);
-            Database.TryDeleteInterview(interaction.Channel.Id);
+            Database.Ticket.ArchiveTicket(ticket);
+            Database.Interviews.TryDeleteInterview(interaction.Channel.Id);
 
             await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder
             {
@@ -219,7 +219,7 @@ public class CloseCommand
             // Delete the channel and database entry
             await interaction.Channel.DeleteAsync("Ticket closed.");
 
-            Database.DeleteOpenTicket(ticket.id);
+            Database.Ticket.DeleteOpenTicket(ticket.id);
 
             closeReasons.Remove(interaction.Channel.Id);
             currentlyClosingTickets.Remove(interaction.Channel.Id);
diff --git a/Commands/InterviewCommands.cs b/Commands/InterviewCommands.cs
index b70491c..76f409b 100644
--- a/Commands/InterviewCommands.cs
+++ b/Commands/InterviewCommands.cs
@@ -16,7 +16,7 @@ public class InterviewCommands
     [Description("Restarts the interview in this ticket, using an updated template if available.")]
     public async Task Restart(SlashCommandContext command)
     {
-        if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -52,7 +52,7 @@ public class InterviewCommands
     [Description("Stops the interview in this ticket.")]
     public async Task Stop(SlashCommandContext command)
     {
-        if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -62,7 +62,7 @@ public class InterviewCommands
             return;
         }
 
-        if (!Database.TryGetInterview(command.Channel.Id, out Interview _))
+        if (!Database.Interviews.TryGetInterview(command.Channel.Id, out Interview _))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/InterviewTemplateCommands.cs b/Commands/InterviewTemplateCommands.cs
index 386e41b..0dc7d84 100644
--- a/Commands/InterviewTemplateCommands.cs
+++ b/Commands/InterviewTemplateCommands.cs
@@ -39,7 +39,7 @@ public class InterviewTemplateCommands
             return;
         }
 
-        if (Database.TryGetInterviewTemplateJSON(category.Id, out string templateJSON))
+        if (Database.Interviews.TryGetInterviewTemplateJSON(category.Id, out string templateJSON))
         {
             MemoryStream stream = new(Encoding.UTF8.GetBytes(templateJSON));
             await command.RespondAsync(new DiscordInteractionResponseBuilder()
@@ -47,7 +47,7 @@ public class InterviewTemplateCommands
                 {
                     Color = DiscordColor.Green,
                     Description = "Upload the json file using the `/interviewtemplate set` command when you are done editing it.\n\n" +
-                                  "Click [here](https://toastielab.dev/toastie-stuff/SupportChild/src/branch/main/docs/InterviewTemplates.md) to learn how to edit interview templates."
+                                  "Click [here](https://github.com/KarlOfDuty/SupportChild/blob/main/docs/InterviewTemplates.md) to learn how to edit interview templates."
                 })
                 .AddFile("interview-template-" + category.Id + ".json", stream)
                 .AsEphemeral());
@@ -79,7 +79,7 @@ public class InterviewTemplateCommands
             Color = DiscordColor.Green,
             Description = "No interview template found for this category. A default template has been generated.\n\n" +
                           "Upload the json file using the `/interviewtemplate set` command when you are done editing it.\n\n" +
-                          "Click [here](https://toastielab.dev/toastie-stuff/SupportChild/src/branch/main/docs/InterviewTemplates.md) to learn how to edit interview templates."
+                          "Click [here](https://github.com/KarlOfDuty/SupportChild/blob/main/docs/InterviewTemplates.md) to learn how to edit interview templates."
         }).AddFile("interview-template-" + category.Id + ".json", defStream).AsEphemeral();
         await command.RespondAsync(response);
     }
@@ -172,7 +172,7 @@ public class InterviewTemplateCommands
                 return;
             }
 
-            if (!Database.SetInterviewTemplate(template))
+            if (!Database.Interviews.SetInterviewTemplate(template))
             {
                 await command.RespondAsync(new DiscordEmbedBuilder
                 {
@@ -207,7 +207,7 @@ public class InterviewTemplateCommands
 
             try
             {
-                if (Database.TryGetInterviewTemplateJSON(template.categoryID, out string templateJSON))
+                if (Database.Interviews.TryGetInterviewTemplateJSON(template.categoryID, out string templateJSON))
                 {
                     MemoryStream memStream = new(Encoding.UTF8.GetBytes(templateJSON));
                     await LogChannel.Success(command.User.Mention + " uploaded a new interview template for the `" + category.Name + "` category.", 0,
@@ -256,7 +256,7 @@ public class InterviewTemplateCommands
             return;
         }
 
-        if (!Database.TryGetInterviewFromTemplate(category.Id, 0, out Interview _))
+        if (!Database.Interviews.TryGetInterviewFromTemplate(category.Id, 0, out Interview _))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -266,7 +266,7 @@ public class InterviewTemplateCommands
             return;
         }
 
-        if (!Database.TryGetInterviewTemplateJSON(category.Id, out string templateJSON))
+        if (!Database.Interviews.TryGetInterviewTemplateJSON(category.Id, out string templateJSON))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -277,7 +277,7 @@ public class InterviewTemplateCommands
         }
 
         MemoryStream memStream = new(Encoding.UTF8.GetBytes(templateJSON));
-        if (!Database.TryDeleteInterviewTemplate(category.Id))
+        if (!Database.Interviews.TryDeleteInterviewTemplate(category.Id))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/ListAssignedCommand.cs b/Commands/ListAssignedCommand.cs
index d22e26a..aec3654 100644
--- a/Commands/ListAssignedCommand.cs
+++ b/Commands/ListAssignedCommand.cs
@@ -20,7 +20,7 @@ public class ListAssignedCommand
     {
         DiscordUser listUser = user == null ? command.User : user;
 
-        if (!Database.TryGetAssignedTickets(listUser.Id, out List<Database.Ticket> assignedTickets))
+        if (!Database.Ticket.TryGetAssignedTickets(listUser.Id, out List<Database.Ticket> assignedTickets))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/ListCommand.cs b/Commands/ListCommand.cs
index 3da5e76..45d8554 100644
--- a/Commands/ListCommand.cs
+++ b/Commands/ListCommand.cs
@@ -21,7 +21,7 @@ public class ListCommand
         DiscordUser listUser = user == null ? command.User : user;
 
         List<DiscordEmbedBuilder> openEmbeds = new List<DiscordEmbedBuilder>();
-        if (Database.TryGetOpenTickets(listUser.Id, out List<Database.Ticket> openTickets))
+        if (Database.Ticket.TryGetOpenTickets(listUser.Id, out List<Database.Ticket> openTickets))
         {
             List<string> listItems = new List<string>();
             foreach (Database.Ticket ticket in openTickets)
@@ -46,7 +46,7 @@ public class ListCommand
         }
 
         List<DiscordEmbedBuilder> closedEmbeds = new List<DiscordEmbedBuilder>();
-        if (Database.TryGetClosedTickets(listUser.Id, out List<Database.Ticket> closedTickets))
+        if (Database.Ticket.TryGetClosedTickets(listUser.Id, out List<Database.Ticket> closedTickets))
         {
             List<string> listItems = new List<string>();
             foreach (Database.Ticket ticket in closedTickets)
diff --git a/Commands/ListInvalidCommand.cs b/Commands/ListInvalidCommand.cs
index cdcaca2..d833f30 100644
--- a/Commands/ListInvalidCommand.cs
+++ b/Commands/ListInvalidCommand.cs
@@ -20,7 +20,7 @@ public class ListInvalidCommand
     [Description("List tickets which channels have been deleted. Use /admin unsetticket <id> to remove them.")]
     public async Task ListInvalid(SlashCommandContext command)
     {
-        if (!Database.TryGetOpenTickets(out List<Database.Ticket> openTickets))
+        if (!Database.Ticket.TryGetOpenTickets(out List<Database.Ticket> openTickets))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/ListOpen.cs b/Commands/ListOpen.cs
index 00666b9..d70cbc3 100644
--- a/Commands/ListOpen.cs
+++ b/Commands/ListOpen.cs
@@ -20,7 +20,7 @@ public class ListOpen
     [Description("Lists all open tickets, oldest first.")]
     public async Task OnExecute(SlashCommandContext command)
     {
-        if (!Database.TryGetOpenTickets(out List<Database.Ticket> openTickets))
+        if (!Database.Ticket.TryGetOpenTickets(out List<Database.Ticket> openTickets))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/ListUnassignedCommand.cs b/Commands/ListUnassignedCommand.cs
index b317842..1b574fe 100644
--- a/Commands/ListUnassignedCommand.cs
+++ b/Commands/ListUnassignedCommand.cs
@@ -20,7 +20,7 @@ public class ListUnassignedCommand
     [Description("Lists unassigned tickets.")]
     public async Task OnExecute(SlashCommandContext command)
     {
-        if (!Database.TryGetAssignedTickets(0, out List<Database.Ticket> unassignedTickets))
+        if (!Database.Ticket.TryGetAssignedTickets(0, out List<Database.Ticket> unassignedTickets))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/MoveCommand.cs b/Commands/MoveCommand.cs
index 4ef23a4..9a558a0 100644
--- a/Commands/MoveCommand.cs
+++ b/Commands/MoveCommand.cs
@@ -20,7 +20,7 @@ public class MoveCommand
         [Parameter("category")][Description("The category to move the ticket to. Only has to be the beginning of the name.")] string category)
     {
         // Check if ticket exists in the database
-        if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/NewCommand.cs b/Commands/NewCommand.cs
index 516d818..069aec7 100644
--- a/Commands/NewCommand.cs
+++ b/Commands/NewCommand.cs
@@ -11,6 +11,7 @@ using DSharpPlus.Exceptions;
 using SupportChild.Interviews;
 
 namespace SupportChild.Commands;
+
 public class NewCommand
 {
     [RequireGuild]
@@ -73,7 +74,7 @@ public class NewCommand
 
             for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < verifiedCategories.Count; nrOfButtons++)
             {
-                buttonRow.Add(new DiscordButtonComponent(DiscordButtonStyle.Primary, "supportchild_newcommandbutton " + verifiedCategories[nrOfButtons].id, verifiedCategories[nrOfButtons].name));
+                buttonRow.Add(new DiscordButtonComponent(DiscordButtonStyle.Primary, "supportboi_newcommandbutton " + verifiedCategories[nrOfButtons].id, verifiedCategories[nrOfButtons].name));
             }
             builder.AddComponents(buttonRow);
         }
@@ -95,7 +96,7 @@ public class NewCommand
             {
                 categoryOptions.Add(new DiscordSelectComponentOption(verifiedCategories[selectionOptions].name, verifiedCategories[selectionOptions].id.ToString()));
             }
-            selectionComponents.Add(new DiscordSelectComponent("supportchild_newcommandselector" + selectionBoxes, "Open new ticket...", categoryOptions, false, 0, 1));
+            selectionComponents.Add(new DiscordSelectComponent("supportboi_newcommandselector" + selectionBoxes, "Open new ticket...", categoryOptions, false, 0, 1));
         }
 
         await command.RespondAsync(new DiscordInteractionResponseBuilder().AddComponents(selectionComponents).AsEphemeral());
@@ -104,18 +105,18 @@ public class NewCommand
     public static async Task<(bool, string)> OpenNewTicket(ulong userID, ulong commandChannelID, ulong categoryID)
     {
         // Check if user is blacklisted
-        if (Database.IsBlacklisted(userID))
+        if (Database.Blacklist.IsBanned(userID))
         {
             return (false, "You are banned from opening tickets.");
         }
 
-        if (Database.IsOpenTicket(commandChannelID))
+        if (Database.Ticket.IsOpenTicket(commandChannelID))
         {
             return (false, "You cannot use this command in a ticket channel.");
         }
 
-        if (!Database.IsStaff(userID)
-          && Database.TryGetOpenTickets(userID, out List<Database.Ticket> ownTickets)
+        if (!Database.StaffMember.IsStaff(userID)
+          && Database.Ticket.TryGetOpenTickets(userID, out List<Database.Ticket> ownTickets)
           && (ownTickets.Count >= Config.ticketLimit && Config.ticketLimit != 0))
         {
             return (false, "You have reached the limit for maximum open tickets.");
@@ -173,7 +174,7 @@ public class NewCommand
             assignedStaff = await RandomAssignCommand.GetRandomVerifiedStaffMember(ticketChannel, userID, 0, null);
         }
 
-        long id = Database.NewTicket(member.Id, assignedStaff?.Id ?? 0, ticketChannel.Id);
+        long id = Database.Ticket.NewTicket(member.Id, assignedStaff?.Id ?? 0, ticketChannel.Id);
         try
         {
             await ticketChannel.ModifyAsync(modifiedAttributes => modifiedAttributes.Name = "ticket-" + id.ToString("00000"));
diff --git a/Commands/RandomAssignCommand.cs b/Commands/RandomAssignCommand.cs
index 1673090..72630e0 100644
--- a/Commands/RandomAssignCommand.cs
+++ b/Commands/RandomAssignCommand.cs
@@ -20,7 +20,7 @@ public class RandomAssignCommand
     public async Task OnExecute(SlashCommandContext command, [Parameter("role")][Description("(Optional) Limit the random assignment to a specific role.")] DiscordRole role = null)
     {
         // Check if ticket exists in the database
-        if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -43,7 +43,7 @@ public class RandomAssignCommand
         }
 
         // Attempt to assign the staff member to the ticket
-        if (!Database.AssignStaff(ticket, staffMember.Id))
+        if (!Database.StaffMember.AssignStaff(ticket, staffMember.Id))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -85,14 +85,14 @@ public class RandomAssignCommand
         if (targetRole == null)
         {
             // No role was specified, any active staff will be picked
-            staffMembers = Database.GetActiveStaff(ignoredUserIDs);
+            staffMembers = Database.StaffMember.GetActiveStaff(ignoredUserIDs);
         }
         else
         {
             // Check if role rassign should override staff's active status
             staffMembers = Config.randomAssignRoleOverride
-                ? Database.GetAllStaff(ignoredUserIDs)
-                : Database.GetActiveStaff(ignoredUserIDs);
+                ? Database.StaffMember.GetAllStaff(ignoredUserIDs)
+                : Database.StaffMember.GetActiveStaff(ignoredUserIDs);
         }
 
         // Randomize the list before checking for roles in order to reduce number of API calls
diff --git a/Commands/RemoveCategoryCommand.cs b/Commands/RemoveCategoryCommand.cs
index 1a20b19..b885e11 100644
--- a/Commands/RemoveCategoryCommand.cs
+++ b/Commands/RemoveCategoryCommand.cs
@@ -16,7 +16,7 @@ public class RemoveCategoryCommand
     public async Task OnExecute(SlashCommandContext command,
         [Parameter("category")][Description("The category to remove.")] DiscordChannel category)
     {
-        if (!Database.TryGetCategory(category.Id, out Database.Category _))
+        if (!Database.Category.TryGetCategory(category.Id, out Database.Category _))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -26,7 +26,7 @@ public class RemoveCategoryCommand
             return;
         }
 
-        if (Database.RemoveCategory(category.Id))
+        if (Database.Category.RemoveCategory(category.Id))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/RemoveStaffCommand.cs b/Commands/RemoveStaffCommand.cs
index 96b504b..051f744 100644
--- a/Commands/RemoveStaffCommand.cs
+++ b/Commands/RemoveStaffCommand.cs
@@ -18,7 +18,7 @@ public class RemoveStaffCommand
     public async Task OnExecute(SlashCommandContext command,
         [Parameter("user")][Description("User to remove from staff.")] DiscordUser user)
     {
-        if (!Database.IsStaff(user.Id))
+        if (!Database.StaffMember.IsStaff(user.Id))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -30,11 +30,11 @@ public class RemoveStaffCommand
 
         await command.DeferResponseAsync(true);
 
-        if (Database.TryGetAssignedTickets(user.Id, out List<Database.Ticket> assignedTickets))
+        if (Database.Ticket.TryGetAssignedTickets(user.Id, out List<Database.Ticket> assignedTickets))
         {
             foreach (Database.Ticket assignedTicket in assignedTickets)
             {
-                Database.UnassignStaff(assignedTicket);
+                Database.StaffMember.UnassignStaff(assignedTicket);
                 try
                 {
                     DiscordChannel ticketChannel = await SupportChild.client.GetChannelAsync(assignedTicket.channelID);
@@ -53,19 +53,23 @@ public class RemoveStaffCommand
             }
         }
 
-        await using MySqlConnection c = Database.GetConnection();
-        c.Open();
-        MySqlCommand deletion = new MySqlCommand(@"DELETE FROM staff WHERE user_id=@user_id", c);
-        deletion.Parameters.AddWithValue("@user_id", user.Id);
-        await deletion.PrepareAsync();
-        deletion.ExecuteNonQuery();
-
-        await command.RespondAsync(new DiscordEmbedBuilder
+        if (Database.StaffMember.RemoveStaff(user.Id))
         {
-            Color = DiscordColor.Green,
-            Description = "User was removed from staff."
-        }, true);
+            await command.RespondAsync(new DiscordEmbedBuilder
+            {
+                Color = DiscordColor.Green,
+                Description = "User was removed from staff."
+            }, true);
 
-        await LogChannel.Success(user.Mention + " was removed from staff by " + command.User.Mention + ".");
+            await LogChannel.Success(user.Mention + " was removed from staff by " + command.User.Mention + ".");
+        }
+        else
+        {
+            await command.RespondAsync(new DiscordEmbedBuilder
+            {
+                Color = DiscordColor.Red,
+                Description = "Error: Failed to remove user from staff."
+            }, true);
+        }
     }
 }
\ No newline at end of file
diff --git a/Commands/SayCommand.cs b/Commands/SayCommand.cs
index c1b44ce..9f348bd 100644
--- a/Commands/SayCommand.cs
+++ b/Commands/SayCommand.cs
@@ -27,7 +27,7 @@ public class SayCommand
             return;
         }
 
-        if (!Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture),
+        if (!Database.Message.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture),
                                     out Database.Message message))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
@@ -49,7 +49,7 @@ public class SayCommand
 
     private static async Task SendMessageList(SlashCommandContext command)
     {
-        List<Database.Message> messages = Database.GetAllMessages();
+        List<Database.Message> messages = Database.Message.GetAllMessages();
         if (messages.Count == 0)
         {
             await command.RespondAsync(new DiscordEmbedBuilder
diff --git a/Commands/SetMessageCommand.cs b/Commands/SetMessageCommand.cs
index 0e0127e..78c310d 100644
--- a/Commands/SetMessageCommand.cs
+++ b/Commands/SetMessageCommand.cs
@@ -1,7 +1,6 @@
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Globalization;
-using System.Linq;
 using DSharpPlus.Entities;
 using System.Threading.Tasks;
 using DSharpPlus.Commands;
@@ -29,7 +28,7 @@ public class SetMessageCommand
     {
 
 
-        if (Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message oldMessage))
+        if (Database.Message.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message oldMessage))
         {
             if (string.IsNullOrEmpty(message))
             {
@@ -39,8 +38,8 @@ public class SetMessageCommand
                         Color = DiscordColor.Orange,
                         Description = "Are you sure you want to delete the `" + identifier + "` message?"
                     })
-                    .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, "supportchild_confirmmessagedelete " + command.Interaction.Id, "Confirm"),
-                                   new DiscordButtonComponent(DiscordButtonStyle.Secondary, "supportchild_cancelmessagedelete " + command.Interaction.Id, "Cancel")));
+                    .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, "supportboi_confirmmessagedelete " + command.Interaction.Id, "Confirm"),
+                                   new DiscordButtonComponent(DiscordButtonStyle.Secondary, "supportboi_cancelmessagedelete " + command.Interaction.Id, "Cancel")));
             }
             else
             {
@@ -51,8 +50,8 @@ public class SetMessageCommand
                         Title = "Replace the `" + identifier + "` message?"
                     }
                     .AddField("Old message:", oldMessage.message.Truncate(1024)).AddField("New message:", message.Truncate(1024)))
-                    .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Success, "supportchild_confirmmessageupdate " + command.Interaction.Id, "Confirm"),
-                                   new DiscordButtonComponent(DiscordButtonStyle.Secondary, "supportchild_cancelmessageupdate " + command.Interaction.Id, "Cancel")));
+                    .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Success, "supportboi_confirmmessageupdate " + command.Interaction.Id, "Confirm"),
+                                   new DiscordButtonComponent(DiscordButtonStyle.Secondary, "supportboi_cancelmessageupdate " + command.Interaction.Id, "Cancel")));
             }
 
             waitingCommands.Add(command.Interaction.Id, new CommandInfo(identifier, message));
@@ -76,7 +75,7 @@ public class SetMessageCommand
 
     private static async Task AddNewMessage(SlashCommandContext command, string identifier, string message)
     {
-        if (Database.AddMessage(identifier, command.Member.Id, message))
+        if (Database.Message.AddMessage(identifier, command.Member.Id, message))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -108,7 +107,7 @@ public class SetMessageCommand
             return;
         }
 
-        if (!Database.TryGetMessage(command.identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
+        if (!Database.Message.TryGetMessage(command.identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
         {
             await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
             {
@@ -118,7 +117,7 @@ public class SetMessageCommand
             return;
         }
 
-        if (Database.RemoveMessage(command.identifier))
+        if (Database.Message.RemoveMessage(command.identifier))
         {
             await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
             {
@@ -159,7 +158,7 @@ public class SetMessageCommand
             return;
         }
 
-        if (!Database.TryGetMessage(command.identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
+        if (!Database.Message.TryGetMessage(command.identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
         {
             await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
             {
@@ -169,7 +168,7 @@ public class SetMessageCommand
             return;
         }
 
-        if (Database.UpdateMessage(command.identifier, interaction.User.Id, command.message))
+        if (Database.Message.UpdateMessage(command.identifier, interaction.User.Id, command.message))
         {
             await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
             {
diff --git a/Commands/SetSummaryCommand.cs b/Commands/SetSummaryCommand.cs
index 31b4c9c..beeea00 100644
--- a/Commands/SetSummaryCommand.cs
+++ b/Commands/SetSummaryCommand.cs
@@ -16,9 +16,8 @@ public class SetSummaryCommand
     [Description("Sets a ticket's summary for the summary command.")]
     public async Task OnExecute(SlashCommandContext command, [Parameter("Summary")][Description("The ticket summary text.")] string summary)
     {
-        ulong channelID = command.Channel.Id;
         // Check if ticket exists in the database
-        if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -28,21 +27,23 @@ public class SetSummaryCommand
             return;
         }
 
-        await using MySqlConnection c = Database.GetConnection();
-        c.Open();
-        MySqlCommand update = new MySqlCommand(@"UPDATE tickets SET summary = @summary WHERE channel_id = @channel_id", c);
-        update.Parameters.AddWithValue("@summary", summary);
-        update.Parameters.AddWithValue("@channel_id", channelID);
-        await update.PrepareAsync();
-        update.ExecuteNonQuery();
-        update.Dispose();
-
-        await command.RespondAsync(new DiscordEmbedBuilder
+        if (Database.Ticket.SetSummary(command.Channel.Id, summary))
         {
-            Color = DiscordColor.Green,
-            Description = "Summary set."
-        }, true);
+            await command.RespondAsync(new DiscordEmbedBuilder
+            {
+                Color = DiscordColor.Green,
+                Description = "Summary set."
+            }, true);
 
-        await LogChannel.Success(command.User.Mention + " set the summary for " + command.Channel.Mention + " to:\n\n" + summary, ticket.id);
+            await LogChannel.Success(command.User.Mention + " set the summary for " + command.Channel.Mention + " to:\n\n" + summary, ticket.id);
+        }
+        else
+        {
+            await command.RespondAsync(new DiscordEmbedBuilder
+            {
+                Color = DiscordColor.Red,
+                Description = "Error: Failed setting the summary of a ticket in database."
+            }, true);
+        }
     }
 }
\ No newline at end of file
diff --git a/Commands/StatusCommand.cs b/Commands/StatusCommand.cs
index e562d6c..f4093c2 100644
--- a/Commands/StatusCommand.cs
+++ b/Commands/StatusCommand.cs
@@ -14,11 +14,11 @@ public class StatusCommand
     [Description("Shows bot status and information.")]
     public async Task OnExecute(SlashCommandContext command)
     {
-        long openTickets = Database.GetNumberOfTickets();
-        long closedTickets = Database.GetNumberOfClosedTickets();
+        long openTickets = Database.Ticket.GetNumberOfTickets();
+        long closedTickets = Database.Ticket.GetNumberOfClosedTickets();
 
         DiscordEmbed botInfo = new DiscordEmbedBuilder()
-            .WithAuthor("Emotion-stuff/supportchild @ Toastielab", "https://toastielab.dev/toastie-stuff/SupportChild", "https://cdn.discordapp.com/attachments/765441543100170271/914327948667011132/Ellie_Concept_2_transparent_ver.png")
+            .WithAuthor("toastie-stuff/supportchild @ Toastielab", "https://toastielab.dev/toastie-stuff/SupportChild", "https://cdn.discordapp.com/attachments/765441543100170271/914327948667011132/Ellie_Concept_2_transparent_ver.png")
             .WithTitle("Bot information")
             .WithColor(DiscordColor.Cyan)
             .AddField("Version:", SupportChild.GetVersion(), true)
diff --git a/Commands/SummaryCommand.cs b/Commands/SummaryCommand.cs
index 889ed67..f6c98f2 100644
--- a/Commands/SummaryCommand.cs
+++ b/Commands/SummaryCommand.cs
@@ -14,7 +14,7 @@ public class SummaryCommand
     [Description("Shows ticket information and summary if there is one.")]
     public async Task OnExecute(SlashCommandContext command)
     {
-        if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/ToggleActiveCommand.cs b/Commands/ToggleActiveCommand.cs
index 0519196..7532b81 100644
--- a/Commands/ToggleActiveCommand.cs
+++ b/Commands/ToggleActiveCommand.cs
@@ -18,7 +18,7 @@ public class ToggleActiveCommand
         DiscordUser staffUser = user == null ? command.User : user;
 
         // Check if ticket exists in the database
-        if (!Database.TryGetStaff(staffUser.Id, out Database.StaffMember staffMember))
+        if (!Database.StaffMember.TryGetStaff(staffUser.Id, out Database.StaffMember staffMember))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -28,7 +28,7 @@ public class ToggleActiveCommand
             return;
         }
 
-        if (Database.SetStaffActive(staffUser.Id, !staffMember.active))
+        if (Database.StaffMember.SetStaffActive(staffUser.Id, !staffMember.active))
         {
             if (user != null && user.Id != command.User.Id)
             {
diff --git a/Commands/TranscriptCommand.cs b/Commands/TranscriptCommand.cs
index 2163595..5a1ea90 100644
--- a/Commands/TranscriptCommand.cs
+++ b/Commands/TranscriptCommand.cs
@@ -23,7 +23,7 @@ public class TranscriptCommand
         Database.Ticket ticket;
         if (ticketID == 0) // If there are no arguments use current channel
         {
-            if (Database.TryGetOpenTicket(command.Channel.Id, out ticket))
+            if (Database.Ticket.TryGetOpenTicket(command.Channel.Id, out ticket))
             {
                 try
                 {
@@ -52,7 +52,7 @@ public class TranscriptCommand
         else
         {
             // If the ticket is still open, generate a new fresh transcript
-            if (Database.TryGetOpenTicketByID((uint)ticketID, out ticket) && ticket?.creatorID == command.Member.Id)
+            if (Database.Ticket.TryGetOpenTicketByID((uint)ticketID, out ticket) && ticket?.creatorID == command.Member.Id)
             {
                 try
                 {
@@ -70,7 +70,7 @@ public class TranscriptCommand
 
             }
             // If there is no open or closed ticket, send an error. If there is a closed ticket we will simply use the old transcript from when the ticket was closed.
-            else if (!Database.TryGetClosedTicket((uint)ticketID, out ticket) || (ticket?.creatorID != command.Member.Id && !Database.IsStaff(command.Member.Id)))
+            else if (!Database.Ticket.TryGetClosedTicket((uint)ticketID, out ticket) || (ticket?.creatorID != command.Member.Id && !Database.StaffMember.IsStaff(command.Member.Id)))
             {
                 await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder
                 {
diff --git a/Commands/UnassignCommand.cs b/Commands/UnassignCommand.cs
index 4e08d57..f6d1689 100644
--- a/Commands/UnassignCommand.cs
+++ b/Commands/UnassignCommand.cs
@@ -16,7 +16,7 @@ public class UnassignCommand
     public async Task OnExecute(SlashCommandContext command)
     {
         // Check if ticket exists in the database
-        if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
@@ -26,7 +26,7 @@ public class UnassignCommand
             return;
         }
 
-        if (!Database.UnassignStaff(ticket))
+        if (!Database.StaffMember.UnassignStaff(ticket))
         {
             await command.RespondAsync(new DiscordEmbedBuilder
             {
diff --git a/Commands/UnblacklistCommand.cs b/Commands/UnblacklistCommand.cs
index 5da5769..3921989 100644
--- a/Commands/UnblacklistCommand.cs
+++ b/Commands/UnblacklistCommand.cs
@@ -18,7 +18,7 @@ public class UnblacklistCommand
     {
         try
         {
-            if (!Database.Unblacklist(user.Id))
+            if (!Database.Blacklist.Unban(user.Id))
             {
                 await command.RespondAsync(new DiscordEmbedBuilder
                 {
diff --git a/Database.cs b/Database.cs
deleted file mode 100644
index 40a55a6..0000000
--- a/Database.cs
+++ /dev/null
@@ -1,1003 +0,0 @@
-using System;
-using System.Linq;
-using System.Collections.Generic;
-using System.Security.Cryptography;
-using DSharpPlus;
-using MySqlConnector;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Serialization;
-using SupportChild.Interviews;
-
-namespace SupportChild;
-
-public static class Database
-{
-    private static string connectionString = "";
-
-    public static void SetConnectionString(string host, int port, string database, string username, string password)
-    {
-        connectionString = "server=" + host +
-                           ";database=" + database +
-                           ";port=" + port +
-                           ";userid=" + username +
-                           ";password=" + password;
-    }
-
-    public static MySqlConnection GetConnection()
-    {
-        return new MySqlConnection(connectionString);
-    }
-
-    public static long GetNumberOfTickets()
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            using MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM tickets", c);
-            c.Open();
-            return (long)countTickets.ExecuteScalar();
-        }
-        catch (MySqlException e)
-        {
-            Logger.Error("Error occured when attempting to count number of open tickets.", e);
-        }
-
-        return -1;
-    }
-
-    public static long GetNumberOfClosedTickets()
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            using MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM ticket_history", c);
-            c.Open();
-            return (long)countTickets.ExecuteScalar();
-        }
-        catch (MySqlException e)
-        {
-            Logger.Error("Error occured when attempting to count number of open tickets.", e);
-        }
-
-        return -1;
-    }
-
-    public static void SetupTables()
-    {
-        using MySqlConnection c = GetConnection();
-        using MySqlCommand createTickets = new MySqlCommand(
-            "CREATE TABLE IF NOT EXISTS tickets(" +
-            "id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT," +
-            "created_time DATETIME NOT NULL," +
-            "creator_id BIGINT UNSIGNED NOT NULL," +
-            "assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," +
-            "summary VARCHAR(5000) NOT NULL," +
-            "channel_id BIGINT UNSIGNED NOT NULL UNIQUE," +
-            "INDEX(created_time, assigned_staff_id, channel_id))",
-            c);
-        using MySqlCommand createTicketHistory = new MySqlCommand(
-            "CREATE TABLE IF NOT EXISTS ticket_history(" +
-            "id INT UNSIGNED NOT NULL PRIMARY KEY," +
-            "created_time DATETIME NOT NULL," +
-            "closed_time DATETIME NOT NULL," +
-            "creator_id BIGINT UNSIGNED NOT NULL," +
-            "assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," +
-            "summary VARCHAR(5000) NOT NULL," +
-            "channel_id BIGINT UNSIGNED NOT NULL UNIQUE," +
-            "INDEX(created_time, closed_time, channel_id))",
-            c);
-        using MySqlCommand createBlacklisted = new MySqlCommand(
-            "CREATE TABLE IF NOT EXISTS blacklisted_users(" +
-            "user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
-            "time DATETIME NOT NULL," +
-            "moderator_id BIGINT UNSIGNED NOT NULL," +
-            "INDEX(user_id, time))",
-            c);
-        using MySqlCommand createStaffList = new MySqlCommand(
-            "CREATE TABLE IF NOT EXISTS staff(" +
-            "user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
-            "name VARCHAR(256) NOT NULL," +
-            "active BOOLEAN NOT NULL DEFAULT true)",
-            c);
-        using MySqlCommand createMessages = new MySqlCommand(
-            "CREATE TABLE IF NOT EXISTS messages(" +
-            "identifier VARCHAR(256) NOT NULL PRIMARY KEY," +
-            "user_id BIGINT UNSIGNED NOT NULL," +
-            "message VARCHAR(5000) NOT NULL)",
-            c);
-        using MySqlCommand createCategories = new MySqlCommand(
-            "CREATE TABLE IF NOT EXISTS categories(" +
-            "name VARCHAR(256) NOT NULL UNIQUE," +
-            "category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY)",
-            c);
-        using MySqlCommand createInterviews = new MySqlCommand(
-            "CREATE TABLE IF NOT EXISTS interviews(" +
-            "channel_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
-            "interview JSON NOT NULL," +
-            "definitions JSON NOT NULL)",
-            c);
-        using MySqlCommand createInterviewTemplates = new MySqlCommand(
-            "CREATE TABLE IF NOT EXISTS interview_templates(" +
-            "category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
-            "template JSON NOT NULL)",
-            c);
-        c.Open();
-        createTickets.ExecuteNonQuery();
-        createBlacklisted.ExecuteNonQuery();
-        createTicketHistory.ExecuteNonQuery();
-        createStaffList.ExecuteNonQuery();
-        createMessages.ExecuteNonQuery();
-        createCategories.ExecuteNonQuery();
-        createInterviews.ExecuteNonQuery();
-        createInterviewTemplates.ExecuteNonQuery();
-    }
-
-    public static bool IsOpenTicket(ulong channelID)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE channel_id=@channel_id", c);
-        selection.Parameters.AddWithValue("@channel_id", channelID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        // Check if ticket exists in the database
-        if (!results.Read())
-        {
-            return false;
-        }
-        results.Close();
-        return true;
-    }
-
-    public static bool TryGetOpenTicket(ulong channelID, out Ticket ticket)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE channel_id=@channel_id", c);
-        selection.Parameters.AddWithValue("@channel_id", channelID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        // Check if ticket exists in the database
-        if (!results.Read())
-        {
-            ticket = null;
-            return false;
-        }
-
-        ticket = new Ticket(results);
-        results.Close();
-        return true;
-    }
-
-    public static bool TryGetOpenTicketByID(uint id, out Ticket ticket)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE id=@id", c);
-        selection.Parameters.AddWithValue("@id", id);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        // Check if open ticket exists in the database
-        if (results.Read())
-        {
-            ticket = new Ticket(results);
-            results.Close();
-            return true;
-        }
-
-        results.Close();
-        ticket = null;
-        return false;
-    }
-
-    public static bool TryGetClosedTicket(uint id, out Ticket ticket)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM ticket_history WHERE id=@id", c);
-        selection.Parameters.AddWithValue("@id", id);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        // Check if closed ticket exists in the database
-        if (results.Read())
-        {
-            ticket = new Ticket(results);
-            results.Close();
-            return true;
-        }
-
-        ticket = null;
-        results.Close();
-        return false;
-    }
-
-    public static bool TryGetOpenTickets(ulong userID, out List<Ticket> tickets)
-    {
-        tickets = null;
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE creator_id=@creator_id", c);
-        selection.Parameters.AddWithValue("@creator_id", userID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            return false;
-        }
-
-        tickets = new List<Ticket> { new Ticket(results) };
-        while (results.Read())
-        {
-            tickets.Add(new Ticket(results));
-        }
-        results.Close();
-        return true;
-    }
-
-    public static bool TryGetOpenTickets(out List<Ticket> tickets)
-    {
-        tickets = null;
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets ORDER BY channel_id ASC", c);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            return false;
-        }
-
-        tickets = new List<Ticket> { new Ticket(results) };
-        while (results.Read())
-        {
-            tickets.Add(new Ticket(results));
-        }
-        results.Close();
-        return true;
-    }
-
-    public static bool TryGetClosedTickets(ulong userID, out List<Ticket> tickets)
-    {
-        tickets = null;
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM ticket_history WHERE creator_id=@creator_id", c);
-        selection.Parameters.AddWithValue("@creator_id", userID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            return false;
-        }
-
-        tickets = new List<Ticket> { new Ticket(results) };
-        while (results.Read())
-        {
-            tickets.Add(new Ticket(results));
-        }
-        results.Close();
-        return true;
-    }
-
-    public static bool TryGetAssignedTickets(ulong staffID, out List<Ticket> tickets)
-    {
-        tickets = null;
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE assigned_staff_id=@assigned_staff_id", c);
-        selection.Parameters.AddWithValue("@assigned_staff_id", staffID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            return false;
-        }
-
-        tickets = new List<Ticket> { new Ticket(results) };
-        while (results.Read())
-        {
-            tickets.Add(new Ticket(results));
-        }
-        results.Close();
-        return true;
-    }
-
-    public static long NewTicket(ulong memberID, ulong staffID, ulong ticketID)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO tickets (created_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", c);
-        cmd.Parameters.AddWithValue("@creator_id", memberID);
-        cmd.Parameters.AddWithValue("@assigned_staff_id", staffID);
-        cmd.Parameters.AddWithValue("@summary", "");
-        cmd.Parameters.AddWithValue("@channel_id", ticketID);
-        cmd.ExecuteNonQuery();
-        return cmd.LastInsertedId;
-    }
-
-    public static void ArchiveTicket(Ticket ticket)
-    {
-        // Check if ticket already exists in the archive
-        if (TryGetClosedTicket(ticket.id, out Ticket _))
-        {
-            using MySqlConnection c = GetConnection();
-            using MySqlCommand deleteTicket = new MySqlCommand(@"DELETE FROM ticket_history WHERE id=@id OR channel_id=@channel_id", c);
-            deleteTicket.Parameters.AddWithValue("@id", ticket.id);
-            deleteTicket.Parameters.AddWithValue("@channel_id", ticket.channelID);
-
-            c.Open();
-            deleteTicket.Prepare();
-            deleteTicket.ExecuteNonQuery();
-        }
-
-        // Create an entry in the ticket history database
-        using MySqlConnection conn = GetConnection();
-        using MySqlCommand archiveTicket = new MySqlCommand(@"INSERT INTO ticket_history (id, created_time, closed_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (@id, @created_time, UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", conn);
-        archiveTicket.Parameters.AddWithValue("@id", ticket.id);
-        archiveTicket.Parameters.AddWithValue("@created_time", ticket.channelID.GetSnowflakeTime());
-        archiveTicket.Parameters.AddWithValue("@creator_id", ticket.creatorID);
-        archiveTicket.Parameters.AddWithValue("@assigned_staff_id", ticket.assignedStaffID);
-        archiveTicket.Parameters.AddWithValue("@summary", ticket.summary);
-        archiveTicket.Parameters.AddWithValue("@channel_id", ticket.channelID);
-
-        conn.Open();
-        archiveTicket.Prepare();
-        archiveTicket.ExecuteNonQuery();
-    }
-
-    public static bool DeleteOpenTicket(uint ticketID)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            using MySqlCommand deletion = new MySqlCommand(@"DELETE FROM tickets WHERE id=@id", c);
-            deletion.Parameters.AddWithValue("@id", ticketID);
-
-            c.Open();
-            deletion.Prepare();
-            return deletion.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Warn("Could not delete open ticket in database.", e);
-            return false;
-        }
-    }
-
-    public static bool IsBlacklisted(ulong userID)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM blacklisted_users WHERE user_id=@user_id", c);
-        selection.Parameters.AddWithValue("@user_id", userID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        // Check if user is blacklisted
-        if (results.Read())
-        {
-            results.Close();
-            return true;
-        }
-        results.Close();
-
-        return false;
-    }
-
-    public static bool Blacklist(ulong blacklistedID, ulong staffID)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO blacklisted_users (user_id,time,moderator_id) VALUES (@user_id, UTC_TIMESTAMP(), @moderator_id);", c);
-            cmd.Parameters.AddWithValue("@user_id", blacklistedID);
-            cmd.Parameters.AddWithValue("@moderator_id", staffID);
-            cmd.Prepare();
-            return cmd.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Warn("Could not blacklist user in database.", e);
-            return false;
-        }
-    }
-
-    public static bool Unblacklist(ulong blacklistedID)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM blacklisted_users WHERE user_id=@user_id", c);
-            cmd.Parameters.AddWithValue("@user_id", blacklistedID);
-            cmd.Prepare();
-            return cmd.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Warn("Could not unblacklist user in database.", e);
-            return false;
-        }
-    }
-
-    public static bool AssignStaff(Ticket ticket, ulong staffID)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand update = new MySqlCommand(@"UPDATE tickets SET assigned_staff_id = @assigned_staff_id WHERE id = @id", c);
-            update.Parameters.AddWithValue("@assigned_staff_id", staffID);
-            update.Parameters.AddWithValue("@id", ticket.id);
-            update.Prepare();
-            return update.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Warn("Could not add staff to ticket in database.", e);
-            return false;
-        }
-    }
-
-    public static bool UnassignStaff(Ticket ticket)
-    {
-        return AssignStaff(ticket, 0);
-    }
-
-    public static bool SetStaffActive(ulong staffID, bool active)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            MySqlCommand update = new MySqlCommand(@"UPDATE staff SET active = @active WHERE user_id = @user_id", c);
-            update.Parameters.AddWithValue("@user_id", staffID);
-            update.Parameters.AddWithValue("@active", active);
-            update.Prepare();
-            return update.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Warn("Could not set staff member as active in database.", e);
-            return false;
-        }
-    }
-
-    public static List<StaffMember> GetActiveStaff(params ulong[] ignoredUserIDs)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE active = true AND user_id NOT IN (@user_ids)", c);
-        selection.Parameters.AddWithValue("@user_ids", string.Join(",", ignoredUserIDs));
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            return new List<StaffMember>();
-        }
-
-        List<StaffMember> staffMembers = new List<StaffMember> { new StaffMember(results) };
-        while (results.Read())
-        {
-            staffMembers.Add(new StaffMember(results));
-        }
-        results.Close();
-
-        return staffMembers;
-    }
-
-    public static List<StaffMember> GetAllStaff(params ulong[] ignoredUserIDs)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id NOT IN (@user_ids)", c);
-        selection.Parameters.AddWithValue("@user_ids", string.Join(",", ignoredUserIDs));
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            return new List<StaffMember>();
-        }
-
-        List<StaffMember> staffMembers = new List<StaffMember> { new StaffMember(results) };
-        while (results.Read())
-        {
-            staffMembers.Add(new StaffMember(results));
-        }
-        results.Close();
-
-        return staffMembers;
-    }
-
-    public static bool IsStaff(ulong staffID)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id=@user_id", c);
-        selection.Parameters.AddWithValue("@user_id", staffID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            return false;
-        }
-        results.Close();
-        return true;
-    }
-
-    public static bool TryGetStaff(ulong staffID, out StaffMember staffMember)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id=@user_id", c);
-        selection.Parameters.AddWithValue("@user_id", staffID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            staffMember = null;
-            return false;
-        }
-        staffMember = new StaffMember(results);
-        results.Close();
-        return true;
-    }
-
-    public static List<Message> GetAllMessages()
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM messages", c);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            return new List<Message>();
-        }
-
-        List<Message> messages = new List<Message> { new Message(results) };
-        while (results.Read())
-        {
-            messages.Add(new Message(results));
-        }
-        results.Close();
-
-        return messages;
-    }
-
-    public static bool TryGetMessage(string identifier, out Message message)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM messages WHERE identifier=@identifier", c);
-        selection.Parameters.AddWithValue("@identifier", identifier);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        // Check if ticket exists in the database
-        if (!results.Read())
-        {
-            message = null;
-            return false;
-        }
-        message = new Message(results);
-        results.Close();
-        return true;
-    }
-
-    public static bool AddMessage(string identifier, ulong userID, string message)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO messages (identifier,user_id,message) VALUES (@identifier, @user_id, @message);", c);
-            cmd.Parameters.AddWithValue("@identifier", identifier);
-            cmd.Parameters.AddWithValue("@user_id", userID);
-            cmd.Parameters.AddWithValue("@message", message);
-            cmd.Prepare();
-            return cmd.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Error("Could not add message to database.", e);
-            return false;
-        }
-    }
-
-    public static bool UpdateMessage(string identifier, ulong userID, string message)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand cmd = new MySqlCommand(@"UPDATE messages SET message = @message, user_id = @user_id WHERE identifier=@identifier", c);
-            cmd.Parameters.AddWithValue("@identifier", identifier);
-            cmd.Parameters.AddWithValue("@user_id", userID);
-            cmd.Parameters.AddWithValue("@message", message);
-            cmd.Prepare();
-            return cmd.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Error("Could not add message to database.", e);
-            return false;
-        }
-    }
-
-    public static bool RemoveMessage(string identifier)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM messages WHERE identifier=@identifier", c);
-            cmd.Parameters.AddWithValue("@identifier", identifier);
-            cmd.Prepare();
-            return cmd.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Error("Could not remove message from database.", e);
-            return false;
-        }
-    }
-
-    public static List<Category> GetAllCategories()
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories", c);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            return new List<Category>();
-        }
-
-        List<Category> categories = new List<Category> { new Category(results) };
-        while (results.Read())
-        {
-            categories.Add(new Category(results));
-        }
-        results.Close();
-
-        return categories;
-    }
-
-    public static bool TryGetCategory(ulong categoryID, out Category message)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories WHERE category_id=@category_id", c);
-        selection.Parameters.AddWithValue("@category_id", categoryID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            message = null;
-            return false;
-        }
-        message = new Category(results);
-        results.Close();
-        return true;
-    }
-
-    public static bool TryGetCategory(string name, out Category message)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories WHERE name=@name", c);
-        selection.Parameters.AddWithValue("@name", name);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            message = null;
-            return false;
-        }
-        message = new Category(results);
-        results.Close();
-        return true;
-    }
-
-    public static bool AddCategory(string name, ulong categoryID)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO categories (name,category_id) VALUES (@name, @category_id);", c);
-            cmd.Parameters.AddWithValue("@name", name);
-            cmd.Parameters.AddWithValue("@category_id", categoryID);
-            cmd.Prepare();
-            return cmd.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Error("Could not add category to database.", e);
-            return false;
-        }
-    }
-
-    public static bool RemoveCategory(ulong categoryID)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM categories WHERE category_id=@category_id", c);
-            cmd.Parameters.AddWithValue("@category_id", categoryID);
-            cmd.Prepare();
-            return cmd.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Error("Could not remove category from database.", e);
-            return false;
-        }
-    }
-
-    public static bool TryGetInterviewTemplateJSON(ulong categoryID, out string templateJSON)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand("SELECT * FROM interview_templates WHERE category_id=@category_id", c);
-        selection.Parameters.AddWithValue("@category_id", categoryID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        if (!results.Read())
-        {
-            templateJSON = null;
-            return false;
-        }
-
-        templateJSON = results.GetString("template");
-        results.Close();
-        return true;
-    }
-
-    public static bool TryGetInterviewFromTemplate(ulong categoryID, ulong channelID, out Interview interview)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand("SELECT * FROM interview_templates WHERE category_id=@category_id", c);
-        selection.Parameters.AddWithValue("@category_id", categoryID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        // Check if messages exist in the database
-        if (!results.Read())
-        {
-            interview = null;
-            return false;
-        }
-
-        string templateString = results.GetString("template");
-        results.Close();
-
-        try
-        {
-            Template template = JsonConvert.DeserializeObject<Template>(templateString, new JsonSerializerSettings
-            {
-                Error = delegate (object sender, ErrorEventArgs args)
-                {
-                    Logger.Error("Error occured when trying to read interview template '" + categoryID + "' from database: " + args.ErrorContext.Error.Message);
-                    Logger.Debug("Detailed exception:", args.ErrorContext.Error);
-                    args.ErrorContext.Handled = false;
-                }
-            });
-            interview = new Interview(channelID, template.interview, template.definitions);
-            return true;
-        }
-        catch (Exception e)
-        {
-            Logger.Warn("Unable to create interview object from the current template for category '" + categoryID + "' in the database.", e);
-            interview = null;
-            return false;
-        }
-    }
-
-    public static bool SetInterviewTemplate(Template template)
-    {
-        try
-        {
-            string templateString = JsonConvert.SerializeObject(template, new JsonSerializerSettings
-            {
-                NullValueHandling = NullValueHandling.Ignore,
-                MissingMemberHandling = MissingMemberHandling.Error,
-                Formatting = Formatting.Indented,
-                ContractResolver = new InterviewStep.StripInternalPropertiesResolver()
-            });
-
-            string query;
-            if (TryGetInterviewTemplateJSON(template.categoryID, out _))
-            {
-                query = "UPDATE interview_templates SET template = @template WHERE category_id=@category_id";
-            }
-            else
-            {
-                query = "INSERT INTO interview_templates (category_id,template) VALUES (@category_id, @template)";
-            }
-
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand cmd = new MySqlCommand(query, c);
-            cmd.Parameters.AddWithValue("@category_id", template.categoryID);
-            cmd.Parameters.AddWithValue("@template", templateString);
-            cmd.Prepare();
-            return cmd.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Error("Could not set interview template in database.", e);
-            return false;
-        }
-    }
-
-    public static bool TryDeleteInterviewTemplate(ulong categoryID)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand deletion = new MySqlCommand(@"DELETE FROM interview_templates WHERE category_id=@category_id", c);
-            deletion.Parameters.AddWithValue("@category_id", categoryID);
-            deletion.Prepare();
-            return deletion.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException)
-        {
-            return false;
-        }
-    }
-
-    public static bool SaveInterview(Interview interview)
-    {
-        try
-        {
-            string query;
-            if (TryGetInterview(interview.channelID, out _))
-            {
-                query = "UPDATE interviews SET interview = @interview, definitions = @definitions WHERE channel_id = @channel_id";
-            }
-            else
-            {
-                query = "INSERT INTO interviews (channel_id,interview, definitions) VALUES (@channel_id, @interview, @definitions)";
-            }
-
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand cmd = new MySqlCommand(query, c);
-            cmd.Parameters.AddWithValue("@channel_id", interview.channelID);
-            cmd.Parameters.AddWithValue("@interview", JsonConvert.SerializeObject(interview.interviewRoot, Formatting.Indented));
-            cmd.Parameters.AddWithValue("@definitions", JsonConvert.SerializeObject(interview.definitions, Formatting.Indented));
-            cmd.Prepare();
-            return cmd.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException e)
-        {
-            Logger.Error("Could not save interview to database.", e);
-            return false;
-        }
-    }
-
-    public static bool TryGetInterview(ulong channelID, out Interview interview)
-    {
-        using MySqlConnection c = GetConnection();
-        c.Open();
-        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM interviews WHERE channel_id=@channel_id", c);
-        selection.Parameters.AddWithValue("@channel_id", channelID);
-        selection.Prepare();
-        MySqlDataReader results = selection.ExecuteReader();
-
-        // Check if ticket exists in the database
-        if (!results.Read())
-        {
-            interview = null;
-            return false;
-        }
-        interview = new Interview(channelID,
-                                  JsonConvert.DeserializeObject<InterviewStep>(results.GetString("interview")),
-                                  JsonConvert.DeserializeObject<Dictionary<string, InterviewStep>>(results.GetString("definitions")));
-        results.Close();
-        return true;
-    }
-
-    public static bool TryDeleteInterview(ulong channelID)
-    {
-        try
-        {
-            using MySqlConnection c = GetConnection();
-            c.Open();
-            using MySqlCommand deletion = new MySqlCommand(@"DELETE FROM interviews WHERE channel_id=@channel_id", c);
-            deletion.Parameters.AddWithValue("@channel_id", channelID);
-            deletion.Prepare();
-            return deletion.ExecuteNonQuery() > 0;
-        }
-        catch (MySqlException)
-        {
-            return false;
-        }
-    }
-
-    public class Ticket
-    {
-        public uint id;
-        public ulong creatorID;
-        public ulong assignedStaffID;
-        public string summary;
-        public ulong channelID;
-
-        public Ticket(MySqlDataReader reader)
-        {
-            id = reader.GetUInt32("id");
-            creatorID = reader.GetUInt64("creator_id");
-            assignedStaffID = reader.GetUInt64("assigned_staff_id");
-            summary = reader.GetString("summary");
-            channelID = reader.GetUInt64("channel_id");
-        }
-
-        public string DiscordRelativeTime()
-        {
-            return Formatter.Timestamp(channelID.GetSnowflakeTime(), Config.timestampFormat);
-        }
-    }
-    public class StaffMember
-    {
-        public ulong userID;
-        public string name;
-        public bool active;
-
-        public StaffMember(MySqlDataReader reader)
-        {
-            userID = reader.GetUInt64("user_id");
-            name = reader.GetString("name");
-            active = reader.GetBoolean("active");
-        }
-    }
-
-    public class Message
-    {
-        public string identifier;
-        public ulong userID;
-        public string message;
-
-        public Message(MySqlDataReader reader)
-        {
-            identifier = reader.GetString("identifier");
-            userID = reader.GetUInt64("user_id");
-            message = reader.GetString("message");
-        }
-    }
-
-    public class Category
-    {
-        public string name;
-        public ulong id;
-
-        public Category(MySqlDataReader reader)
-        {
-            name = reader.GetString("name");
-            id = reader.GetUInt64("category_id");
-        }
-    }
-}
\ No newline at end of file
diff --git a/Database/Blacklist.cs b/Database/Blacklist.cs
new file mode 100644
index 0000000..3eb0167
--- /dev/null
+++ b/Database/Blacklist.cs
@@ -0,0 +1,63 @@
+using MySqlConnector;
+
+namespace SupportChild.Database;
+
+public class Blacklist
+{
+    public static bool IsBanned(ulong userID)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM blacklisted_users WHERE user_id=@user_id", c);
+        selection.Parameters.AddWithValue("@user_id", userID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        // Check if user is blacklisted
+        if (results.Read())
+        {
+            results.Close();
+            return true;
+        }
+        results.Close();
+
+        return false;
+    }
+
+    public static bool Ban(ulong blacklistedID, ulong staffID)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO blacklisted_users (user_id,time,moderator_id) VALUES (@user_id, UTC_TIMESTAMP(), @moderator_id);", c);
+            cmd.Parameters.AddWithValue("@user_id", blacklistedID);
+            cmd.Parameters.AddWithValue("@moderator_id", staffID);
+            cmd.Prepare();
+            return cmd.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Warn("Could not blacklist user in database.", e);
+            return false;
+        }
+    }
+
+    public static bool Unban(ulong blacklistedID)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM blacklisted_users WHERE user_id=@user_id", c);
+            cmd.Parameters.AddWithValue("@user_id", blacklistedID);
+            cmd.Prepare();
+            return cmd.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Warn("Could not unblacklist user in database.", e);
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Database/Category.cs b/Database/Category.cs
new file mode 100644
index 0000000..03c0300
--- /dev/null
+++ b/Database/Category.cs
@@ -0,0 +1,108 @@
+using System.Collections.Generic;
+using MySqlConnector;
+
+namespace SupportChild.Database;
+
+public class Category(MySqlDataReader reader)
+{
+    public readonly string name = reader.GetString("name");
+    public readonly ulong id = reader.GetUInt64("category_id");
+
+    public static List<Category> GetAllCategories()
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories", c);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            return [];
+        }
+
+        List<Category> categories = [new(results)];
+        while (results.Read())
+        {
+            categories.Add(new Category(results));
+        }
+        results.Close();
+
+        return categories;
+    }
+
+    public static bool TryGetCategory(ulong categoryID, out Category message)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories WHERE category_id=@category_id", c);
+        selection.Parameters.AddWithValue("@category_id", categoryID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            message = null;
+            return false;
+        }
+        message = new Category(results);
+        results.Close();
+        return true;
+    }
+
+    public static bool TryGetCategory(string name, out Category message)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories WHERE name=@name", c);
+        selection.Parameters.AddWithValue("@name", name);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            message = null;
+            return false;
+        }
+        message = new Category(results);
+        results.Close();
+        return true;
+    }
+
+    public static bool AddCategory(string name, ulong categoryID)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO categories (name,category_id) VALUES (@name, @category_id);", c);
+            cmd.Parameters.AddWithValue("@name", name);
+            cmd.Parameters.AddWithValue("@category_id", categoryID);
+            cmd.Prepare();
+            return cmd.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Error("Could not add category to database.", e);
+            return false;
+        }
+    }
+
+    public static bool RemoveCategory(ulong categoryID)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM categories WHERE category_id=@category_id", c);
+            cmd.Parameters.AddWithValue("@category_id", categoryID);
+            cmd.Prepare();
+            return cmd.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Error("Could not remove category from database.", e);
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Database/Database.cs b/Database/Database.cs
new file mode 100644
index 0000000..d5ca884
--- /dev/null
+++ b/Database/Database.cs
@@ -0,0 +1,92 @@
+using MySqlConnector;
+
+namespace SupportChild.Database;
+
+public static class Connection
+{
+    private static string connectionString = "";
+
+    public static void SetConnectionString(string host, int port, string database, string username, string password)
+    {
+        connectionString = "server=" + host +
+                           ";database=" + database +
+                           ";port=" + port +
+                           ";userid=" + username +
+                           ";password=" + password;
+    }
+
+    public static MySqlConnection GetConnection()
+    {
+        return new MySqlConnection(connectionString);
+    }
+
+    public static void SetupTables()
+    {
+        using MySqlConnection c = GetConnection();
+        using MySqlCommand createTickets = new MySqlCommand(
+            "CREATE TABLE IF NOT EXISTS tickets(" +
+            "id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT," +
+            "created_time DATETIME NOT NULL," +
+            "creator_id BIGINT UNSIGNED NOT NULL," +
+            "assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," +
+            "summary VARCHAR(5000) NOT NULL," +
+            "channel_id BIGINT UNSIGNED NOT NULL UNIQUE," +
+            "INDEX(created_time, assigned_staff_id, channel_id))",
+            c);
+        using MySqlCommand createTicketHistory = new MySqlCommand(
+            "CREATE TABLE IF NOT EXISTS ticket_history(" +
+            "id INT UNSIGNED NOT NULL PRIMARY KEY," +
+            "created_time DATETIME NOT NULL," +
+            "closed_time DATETIME NOT NULL," +
+            "creator_id BIGINT UNSIGNED NOT NULL," +
+            "assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," +
+            "summary VARCHAR(5000) NOT NULL," +
+            "channel_id BIGINT UNSIGNED NOT NULL UNIQUE," +
+            "INDEX(created_time, closed_time, channel_id))",
+            c);
+        using MySqlCommand createBlacklisted = new MySqlCommand(
+            "CREATE TABLE IF NOT EXISTS blacklisted_users(" +
+            "user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
+            "time DATETIME NOT NULL," +
+            "moderator_id BIGINT UNSIGNED NOT NULL," +
+            "INDEX(user_id, time))",
+            c);
+        using MySqlCommand createStaffList = new MySqlCommand(
+            "CREATE TABLE IF NOT EXISTS staff(" +
+            "user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
+            "name VARCHAR(256) NOT NULL," +
+            "active BOOLEAN NOT NULL DEFAULT true)",
+            c);
+        using MySqlCommand createMessages = new MySqlCommand(
+            "CREATE TABLE IF NOT EXISTS messages(" +
+            "identifier VARCHAR(256) NOT NULL PRIMARY KEY," +
+            "user_id BIGINT UNSIGNED NOT NULL," +
+            "message VARCHAR(5000) NOT NULL)",
+            c);
+        using MySqlCommand createCategories = new MySqlCommand(
+            "CREATE TABLE IF NOT EXISTS categories(" +
+            "name VARCHAR(256) NOT NULL UNIQUE," +
+            "category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY)",
+            c);
+        using MySqlCommand createInterviews = new MySqlCommand(
+            "CREATE TABLE IF NOT EXISTS interviews(" +
+            "channel_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
+            "interview JSON NOT NULL," +
+            "definitions JSON NOT NULL)",
+            c);
+        using MySqlCommand createInterviewTemplates = new MySqlCommand(
+            "CREATE TABLE IF NOT EXISTS interview_templates(" +
+            "category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
+            "template JSON NOT NULL)",
+            c);
+        c.Open();
+        createTickets.ExecuteNonQuery();
+        createBlacklisted.ExecuteNonQuery();
+        createTicketHistory.ExecuteNonQuery();
+        createStaffList.ExecuteNonQuery();
+        createMessages.ExecuteNonQuery();
+        createCategories.ExecuteNonQuery();
+        createInterviews.ExecuteNonQuery();
+        createInterviewTemplates.ExecuteNonQuery();
+    }
+}
\ No newline at end of file
diff --git a/Database/Interviews.cs b/Database/Interviews.cs
new file mode 100644
index 0000000..79b32a4
--- /dev/null
+++ b/Database/Interviews.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using MySqlConnector;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using SupportChild.Interviews;
+
+namespace SupportChild.Database;
+
+public class Interviews
+{
+    public static bool TryGetInterviewTemplateJSON(ulong categoryID, out string templateJSON)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM interview_templates WHERE category_id=@category_id", c);
+        selection.Parameters.AddWithValue("@category_id", categoryID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            templateJSON = null;
+            return false;
+        }
+
+        templateJSON = results.GetString("template");
+        results.Close();
+        return true;
+    }
+
+    public static bool TryGetInterviewFromTemplate(ulong categoryID, ulong channelID, out Interview interview)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM interview_templates WHERE category_id=@category_id", c);
+        selection.Parameters.AddWithValue("@category_id", categoryID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        // Check if messages exist in the database
+        if (!results.Read())
+        {
+            interview = null;
+            return false;
+        }
+
+        string templateString = results.GetString("template");
+        results.Close();
+
+        try
+        {
+            Template template = JsonConvert.DeserializeObject<Template>(templateString, new JsonSerializerSettings
+            {
+                Error = delegate (object _, ErrorEventArgs args)
+                {
+                    Logger.Error("Error occured when trying to read interview template '" + categoryID + "' from database: " + args.ErrorContext.Error.Message);
+                    Logger.Debug("Detailed exception:", args.ErrorContext.Error);
+                    args.ErrorContext.Handled = false;
+                }
+            });
+            interview = new Interview(channelID, template.interview, template.definitions);
+            return true;
+        }
+        catch (Exception e)
+        {
+            Logger.Warn("Unable to create interview object from the current template for category '" + categoryID + "' in the database.", e);
+            interview = null;
+            return false;
+        }
+    }
+
+    public static bool SetInterviewTemplate(Template template)
+    {
+        try
+        {
+            string templateString = JsonConvert.SerializeObject(template, new JsonSerializerSettings
+            {
+                NullValueHandling = NullValueHandling.Ignore,
+                MissingMemberHandling = MissingMemberHandling.Error,
+                Formatting = Formatting.Indented,
+                ContractResolver = new InterviewStep.StripInternalPropertiesResolver()
+            });
+
+            string query;
+            if (TryGetInterviewTemplateJSON(template.categoryID, out _))
+            {
+                query = "UPDATE interview_templates SET template = @template WHERE category_id=@category_id";
+            }
+            else
+            {
+                query = "INSERT INTO interview_templates (category_id,template) VALUES (@category_id, @template)";
+            }
+
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand cmd = new(query, c);
+            cmd.Parameters.AddWithValue("@category_id", template.categoryID);
+            cmd.Parameters.AddWithValue("@template", templateString);
+            cmd.Prepare();
+            return cmd.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Error("Could not set interview template in database.", e);
+            return false;
+        }
+    }
+
+    public static bool TryDeleteInterviewTemplate(ulong categoryID)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand deletion = new("DELETE FROM interview_templates WHERE category_id=@category_id", c);
+            deletion.Parameters.AddWithValue("@category_id", categoryID);
+            deletion.Prepare();
+            return deletion.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException)
+        {
+            return false;
+        }
+    }
+
+    public static bool SaveInterview(Interview interview)
+    {
+        try
+        {
+            string query;
+            if (TryGetInterview(interview.channelID, out _))
+            {
+                query = "UPDATE interviews SET interview = @interview, definitions = @definitions WHERE channel_id = @channel_id";
+            }
+            else
+            {
+                query = "INSERT INTO interviews (channel_id,interview, definitions) VALUES (@channel_id, @interview, @definitions)";
+            }
+
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand cmd = new(query, c);
+            cmd.Parameters.AddWithValue("@channel_id", interview.channelID);
+            cmd.Parameters.AddWithValue("@interview", JsonConvert.SerializeObject(interview.interviewRoot, Formatting.Indented));
+            cmd.Parameters.AddWithValue("@definitions", JsonConvert.SerializeObject(interview.definitions, Formatting.Indented));
+            cmd.Prepare();
+            return cmd.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Error("Could not save interview to database.", e);
+            return false;
+        }
+    }
+
+    public static bool TryGetInterview(ulong channelID, out Interview interview)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM interviews WHERE channel_id=@channel_id", c);
+        selection.Parameters.AddWithValue("@channel_id", channelID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        // Check if ticket exists in the database
+        if (!results.Read())
+        {
+            interview = null;
+            return false;
+        }
+        interview = new Interview(channelID,
+                                  JsonConvert.DeserializeObject<InterviewStep>(results.GetString("interview")),
+                                  JsonConvert.DeserializeObject<Dictionary<string, InterviewStep>>(results.GetString("definitions")));
+        results.Close();
+        return true;
+    }
+
+    public static bool TryDeleteInterview(ulong channelID)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand deletion = new("DELETE FROM interviews WHERE channel_id=@channel_id", c);
+            deletion.Parameters.AddWithValue("@channel_id", channelID);
+            deletion.Prepare();
+            return deletion.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException)
+        {
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Database/Message.cs b/Database/Message.cs
new file mode 100644
index 0000000..01c4dfc
--- /dev/null
+++ b/Database/Message.cs
@@ -0,0 +1,112 @@
+using System.Collections.Generic;
+using MySqlConnector;
+
+namespace SupportChild.Database;
+
+public class Message(MySqlDataReader reader)
+{
+    public readonly string identifier = reader.GetString("identifier");
+    public readonly ulong userID = reader.GetUInt64("user_id");
+    public readonly string message = reader.GetString("message");
+
+    public static List<Message> GetAllMessages()
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM messages", c);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            return [];
+        }
+
+        List<Message> messages = [new(results)];
+        while (results.Read())
+        {
+            messages.Add(new Message(results));
+        }
+        results.Close();
+
+        return messages;
+    }
+
+    public static bool TryGetMessage(string identifier, out Message message)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM messages WHERE identifier=@identifier", c);
+        selection.Parameters.AddWithValue("@identifier", identifier);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        // Check if ticket exists in the database
+        if (!results.Read())
+        {
+            message = null;
+            return false;
+        }
+        message = new Message(results);
+        results.Close();
+        return true;
+    }
+
+    public static bool AddMessage(string identifier, ulong userID, string message)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand cmd = new("INSERT INTO messages (identifier,user_id,message) VALUES (@identifier, @user_id, @message);", c);
+            cmd.Parameters.AddWithValue("@identifier", identifier);
+            cmd.Parameters.AddWithValue("@user_id", userID);
+            cmd.Parameters.AddWithValue("@message", message);
+            cmd.Prepare();
+            return cmd.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Error("Could not add message to database.", e);
+            return false;
+        }
+    }
+
+    public static bool UpdateMessage(string identifier, ulong userID, string message)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand cmd = new("UPDATE messages SET message = @message, user_id = @user_id WHERE identifier=@identifier", c);
+            cmd.Parameters.AddWithValue("@identifier", identifier);
+            cmd.Parameters.AddWithValue("@user_id", userID);
+            cmd.Parameters.AddWithValue("@message", message);
+            cmd.Prepare();
+            return cmd.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Error("Could not add message to database.", e);
+            return false;
+        }
+    }
+
+    public static bool RemoveMessage(string identifier)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand cmd = new("DELETE FROM messages WHERE identifier=@identifier", c);
+            cmd.Parameters.AddWithValue("@identifier", identifier);
+            cmd.Prepare();
+            return cmd.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Error("Could not remove message from database.", e);
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Database/StaffMember.cs b/Database/StaffMember.cs
new file mode 100644
index 0000000..dcfd8c6
--- /dev/null
+++ b/Database/StaffMember.cs
@@ -0,0 +1,176 @@
+using System.Collections.Generic;
+using MySqlConnector;
+
+namespace SupportChild.Database;
+
+public class StaffMember(MySqlDataReader reader)
+{
+    public readonly ulong userID = reader.GetUInt64("user_id");
+    public string name = reader.GetString("name");
+    public readonly bool active = reader.GetBoolean("active");
+
+    public static bool AddStaff(string name, ulong userID)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand update = IsStaff(userID) ? new MySqlCommand("UPDATE staff SET name = @name WHERE user_id = @user_id", c)
+                                                        : new MySqlCommand("INSERT INTO staff (user_id, name) VALUES (@user_id, @name);", c);
+            update.Parameters.AddWithValue("@name", name);
+            update.Parameters.AddWithValue("@user_id", userID);
+            update.Prepare();
+            return update.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Warn("Could not add staff to database.", e);
+            return false;
+        }
+    }
+
+    public static bool RemoveStaff(ulong userID)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand update = new("DELETE FROM staff WHERE user_id = @user_id", c);
+            update.Parameters.AddWithValue("@user_id", userID);
+            update.Prepare();
+            return update.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Warn("Could not remove staff from database.", e);
+            return false;
+        }
+    }
+
+    public static bool AssignStaff(Ticket ticket, ulong staffID)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand update = new("UPDATE tickets SET assigned_staff_id = @assigned_staff_id WHERE id = @id", c);
+            update.Parameters.AddWithValue("@assigned_staff_id", staffID);
+            update.Parameters.AddWithValue("@id", ticket.id);
+            update.Prepare();
+            return update.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Warn("Could not add staff to ticket in database.", e);
+            return false;
+        }
+    }
+
+    public static bool UnassignStaff(Ticket ticket)
+    {
+        return AssignStaff(ticket, 0);
+    }
+
+    public static bool SetStaffActive(ulong staffID, bool active)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            MySqlCommand update = new("UPDATE staff SET active = @active WHERE user_id = @user_id", c);
+            update.Parameters.AddWithValue("@user_id", staffID);
+            update.Parameters.AddWithValue("@active", active);
+            update.Prepare();
+            return update.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Warn("Could not set staff member as active in database.", e);
+            return false;
+        }
+    }
+
+    public static List<StaffMember> GetActiveStaff(params ulong[] ignoredUserIDs)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM staff WHERE active = true AND user_id NOT IN (@user_ids)", c);
+        selection.Parameters.AddWithValue("@user_ids", string.Join(",", ignoredUserIDs));
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            return [];
+        }
+
+        List<StaffMember> staffMembers = [new(results)];
+        while (results.Read())
+        {
+            staffMembers.Add(new StaffMember(results));
+        }
+        results.Close();
+
+        return staffMembers;
+    }
+
+    public static List<StaffMember> GetAllStaff(params ulong[] ignoredUserIDs)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM staff WHERE user_id NOT IN (@user_ids)", c);
+        selection.Parameters.AddWithValue("@user_ids", string.Join(",", ignoredUserIDs));
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            return [];
+        }
+
+        List<StaffMember> staffMembers = [new(results)];
+        while (results.Read())
+        {
+            staffMembers.Add(new StaffMember(results));
+        }
+        results.Close();
+
+        return staffMembers;
+    }
+
+    public static bool IsStaff(ulong staffID)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM staff WHERE user_id=@user_id", c);
+        selection.Parameters.AddWithValue("@user_id", staffID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            return false;
+        }
+        results.Close();
+        return true;
+    }
+
+    public static bool TryGetStaff(ulong staffID, out StaffMember staffMember)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM staff WHERE user_id=@user_id", c);
+        selection.Parameters.AddWithValue("@user_id", staffID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            staffMember = null;
+            return false;
+        }
+        staffMember = new StaffMember(results);
+        results.Close();
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/Database/Ticket.cs b/Database/Ticket.cs
new file mode 100644
index 0000000..434b97a
--- /dev/null
+++ b/Database/Ticket.cs
@@ -0,0 +1,312 @@
+using System.Collections.Generic;
+using DSharpPlus;
+using MySqlConnector;
+
+namespace SupportChild.Database;
+
+public class Ticket(MySqlDataReader reader)
+{
+    public readonly uint id = reader.GetUInt32("id");
+    public readonly ulong creatorID = reader.GetUInt64("creator_id");
+    public readonly ulong assignedStaffID = reader.GetUInt64("assigned_staff_id");
+    public readonly string summary = reader.GetString("summary");
+    public readonly ulong channelID = reader.GetUInt64("channel_id");
+
+    public string DiscordRelativeTime()
+    {
+        return Formatter.Timestamp(channelID.GetSnowflakeTime(), Config.timestampFormat);
+    }
+
+    public static long GetNumberOfTickets()
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            using MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM tickets", c);
+            c.Open();
+            return (long)(countTickets?.ExecuteScalar() ?? 0);
+        }
+        catch (MySqlException e)
+        {
+            Logger.Error("Error occured when attempting to count number of open tickets.", e);
+        }
+
+        return -1;
+    }
+
+    public static long GetNumberOfClosedTickets()
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            using MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM ticket_history", c);
+            c.Open();
+            return (long)(countTickets?.ExecuteScalar() ?? 0);
+        }
+        catch (MySqlException e)
+        {
+            Logger.Error("Error occured when attempting to count number of open tickets.", e);
+        }
+
+        return -1;
+    }
+
+    public static bool IsOpenTicket(ulong channelID)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM tickets WHERE channel_id=@channel_id", c);
+        selection.Parameters.AddWithValue("@channel_id", channelID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        // Check if ticket exists in the database
+        if (!results.Read())
+        {
+            return false;
+        }
+        results.Close();
+        return true;
+    }
+
+    public static bool TryGetOpenTicket(ulong channelID, out Ticket ticket)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM tickets WHERE channel_id=@channel_id", c);
+        selection.Parameters.AddWithValue("@channel_id", channelID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        // Check if ticket exists in the database
+        if (!results.Read())
+        {
+            ticket = null;
+            return false;
+        }
+
+        ticket = new Ticket(results);
+        results.Close();
+        return true;
+    }
+
+    public static bool TryGetOpenTicketByID(uint id, out Ticket ticket)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM tickets WHERE id=@id", c);
+        selection.Parameters.AddWithValue("@id", id);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        // Check if open ticket exists in the database
+        if (results.Read())
+        {
+            ticket = new Ticket(results);
+            results.Close();
+            return true;
+        }
+
+        results.Close();
+        ticket = null;
+        return false;
+    }
+
+    public static bool TryGetClosedTicket(uint id, out Ticket ticket)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM ticket_history WHERE id=@id", c);
+        selection.Parameters.AddWithValue("@id", id);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        // Check if closed ticket exists in the database
+        if (results.Read())
+        {
+            ticket = new Ticket(results);
+            results.Close();
+            return true;
+        }
+
+        ticket = null;
+        results.Close();
+        return false;
+    }
+
+    public static bool TryGetOpenTickets(ulong userID, out List<Ticket> tickets)
+    {
+        tickets = null;
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM tickets WHERE creator_id=@creator_id", c);
+        selection.Parameters.AddWithValue("@creator_id", userID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            return false;
+        }
+
+        tickets = [new Ticket(results)];
+        while (results.Read())
+        {
+            tickets.Add(new Ticket(results));
+        }
+        results.Close();
+        return true;
+    }
+
+    public static bool TryGetOpenTickets(out List<Ticket> tickets)
+    {
+        tickets = null;
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM tickets ORDER BY channel_id", c);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            return false;
+        }
+
+        tickets = [new Ticket(results)];
+        while (results.Read())
+        {
+            tickets.Add(new Ticket(results));
+        }
+        results.Close();
+        return true;
+    }
+
+    public static bool TryGetClosedTickets(ulong userID, out List<Ticket> tickets)
+    {
+        tickets = null;
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM ticket_history WHERE creator_id=@creator_id", c);
+        selection.Parameters.AddWithValue("@creator_id", userID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            return false;
+        }
+
+        tickets = [new Ticket(results)];
+        while (results.Read())
+        {
+            tickets.Add(new Ticket(results));
+        }
+        results.Close();
+        return true;
+    }
+
+    public static bool TryGetAssignedTickets(ulong staffID, out List<Ticket> tickets)
+    {
+        tickets = null;
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand selection = new("SELECT * FROM tickets WHERE assigned_staff_id=@assigned_staff_id", c);
+        selection.Parameters.AddWithValue("@assigned_staff_id", staffID);
+        selection.Prepare();
+        MySqlDataReader results = selection.ExecuteReader();
+
+        if (!results.Read())
+        {
+            return false;
+        }
+
+        tickets = [new Ticket(results)];
+        while (results.Read())
+        {
+            tickets.Add(new Ticket(results));
+        }
+        results.Close();
+        return true;
+    }
+
+    public static long NewTicket(ulong memberID, ulong staffID, ulong ticketID)
+    {
+        using MySqlConnection c = Connection.GetConnection();
+        c.Open();
+        using MySqlCommand cmd = new("INSERT INTO tickets (created_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", c);
+        cmd.Parameters.AddWithValue("@creator_id", memberID);
+        cmd.Parameters.AddWithValue("@assigned_staff_id", staffID);
+        cmd.Parameters.AddWithValue("@summary", "");
+        cmd.Parameters.AddWithValue("@channel_id", ticketID);
+        cmd.ExecuteNonQuery();
+        return cmd.LastInsertedId;
+    }
+
+    public static void ArchiveTicket(Ticket ticket)
+    {
+        // Check if ticket already exists in the archive
+        if (TryGetClosedTicket(ticket.id, out Ticket _))
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            using MySqlCommand deleteTicket = new("DELETE FROM ticket_history WHERE id=@id OR channel_id=@channel_id", c);
+            deleteTicket.Parameters.AddWithValue("@id", ticket.id);
+            deleteTicket.Parameters.AddWithValue("@channel_id", ticket.channelID);
+
+            c.Open();
+            deleteTicket.Prepare();
+            deleteTicket.ExecuteNonQuery();
+        }
+
+        // Create an entry in the ticket history database
+        using MySqlConnection conn = Connection.GetConnection();
+        using MySqlCommand archiveTicket = new("INSERT INTO ticket_history (id, created_time, closed_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (@id, @created_time, UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", conn);
+        archiveTicket.Parameters.AddWithValue("@id", ticket.id);
+        archiveTicket.Parameters.AddWithValue("@created_time", ticket.channelID.GetSnowflakeTime());
+        archiveTicket.Parameters.AddWithValue("@creator_id", ticket.creatorID);
+        archiveTicket.Parameters.AddWithValue("@assigned_staff_id", ticket.assignedStaffID);
+        archiveTicket.Parameters.AddWithValue("@summary", ticket.summary);
+        archiveTicket.Parameters.AddWithValue("@channel_id", ticket.channelID);
+
+        conn.Open();
+        archiveTicket.Prepare();
+        archiveTicket.ExecuteNonQuery();
+    }
+
+    public static bool DeleteOpenTicket(uint ticketID)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            using MySqlCommand deletion = new("DELETE FROM tickets WHERE id=@id", c);
+            deletion.Parameters.AddWithValue("@id", ticketID);
+
+            c.Open();
+            deletion.Prepare();
+            return deletion.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Warn("Could not delete open ticket in database.", e);
+            return false;
+        }
+    }
+
+    public static bool SetSummary(ulong channelID, string summary)
+    {
+        try
+        {
+            using MySqlConnection c = Connection.GetConnection();
+            c.Open();
+            using MySqlCommand update = new("UPDATE tickets SET summary = @summary WHERE channel_id = @channel_id", c);
+            update.Parameters.AddWithValue("@summary", summary);
+            update.Parameters.AddWithValue("@channel_id", channelID);
+            update.Prepare();
+            return update.ExecuteNonQuery() > 0;
+        }
+        catch (MySqlException e)
+        {
+            Logger.Warn("Could not set summary in database.", e);
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/EventHandler.cs b/EventHandler.cs
index 04ae879..0fc766c 100644
--- a/EventHandler.cs
+++ b/EventHandler.cs
@@ -66,7 +66,7 @@ public static class EventHandler
         }
 
         // Ignore messages outside of tickets.
-        if (!Database.TryGetOpenTicket(e.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(e.Channel.Id, out Database.Ticket ticket))
         {
             return;
         }
@@ -87,7 +87,7 @@ public static class EventHandler
     private static async Task SendTicketUpdatedMessage(MessageCreatedEventArgs e, Database.Ticket ticket)
     {
         // Ignore staff messages
-        if (Database.IsStaff(e.Author.Id))
+        if (Database.StaffMember.IsStaff(e.Author.Id))
         {
             return;
         }
@@ -112,7 +112,7 @@ public static class EventHandler
 
     public static async Task OnMemberAdded(DiscordClient client, GuildMemberAddedEventArgs e)
     {
-        if (!Database.TryGetOpenTickets(e.Member.Id, out List<Database.Ticket> ownTickets))
+        if (!Database.Ticket.TryGetOpenTickets(e.Member.Id, out List<Database.Ticket> ownTickets))
         {
             return;
         }
@@ -147,7 +147,7 @@ public static class EventHandler
 
     public static async Task OnMemberRemoved(DiscordClient client, GuildMemberRemovedEventArgs e)
     {
-        if (Database.TryGetOpenTickets(e.Member.Id, out List<Database.Ticket> ownTickets))
+        if (Database.Ticket.TryGetOpenTickets(e.Member.Id, out List<Database.Ticket> ownTickets))
         {
             foreach (Database.Ticket ticket in ownTickets)
             {
@@ -167,7 +167,7 @@ public static class EventHandler
             }
         }
 
-        if (LogChannel.IsEnabled && Database.TryGetAssignedTickets(e.Member.Id, out List<Database.Ticket> assignedTickets))
+        if (LogChannel.IsEnabled && Database.Ticket.TryGetAssignedTickets(e.Member.Id, out List<Database.Ticket> assignedTickets))
         {
             foreach (Database.Ticket ticket in assignedTickets)
             {
diff --git a/Interviews/Interviewer.cs b/Interviews/Interviewer.cs
index 5fa1804..1abfa11 100644
--- a/Interviews/Interviewer.cs
+++ b/Interviews/Interviewer.cs
@@ -13,7 +13,7 @@ public static class Interviewer
 {
     public static async Task<bool> StartInterview(DiscordChannel channel)
     {
-        if (!Database.TryGetInterviewFromTemplate(channel.Parent.Id, channel.Id, out Interview interview))
+        if (!Database.Interviews.TryGetInterviewFromTemplate(channel.Parent.Id, channel.Id, out Interview interview))
         {
             return false;
         }
@@ -26,12 +26,12 @@ public static class Interviewer
                 Color = DiscordColor.Red
             });
             interview.interviewRoot.AddRelatedMessageIDs(errorMessage.Id);
-            Database.SaveInterview(interview);
+            Database.Interviews.SaveInterview(interview);
             return false;
         }
 
         await SendNextMessage(interview, channel, interview.interviewRoot);
-        return Database.SaveInterview(interview);
+        return Database.Interviews.SaveInterview(interview);
     }
 
     public static async Task<bool> RestartInterview(DiscordChannel channel)
@@ -47,14 +47,14 @@ public static class Interviewer
 
     public static async Task<bool> StopInterview(DiscordChannel channel)
     {
-        if (Database.TryGetInterview(channel.Id, out Interview interview))
+        if (Database.Interviews.TryGetInterview(channel.Id, out Interview interview))
         {
             if (Config.deleteMessagesAfterInterviewEnd)
             {
                 await DeletePreviousMessages(interview, channel);
             }
 
-            if (!Database.TryDeleteInterview(channel.Id))
+            if (!Database.Interviews.TryDeleteInterview(channel.Id))
             {
                 Logger.Warn("Could not delete interview from database. Channel ID: " + channel.Id);
             }
@@ -70,7 +70,7 @@ public static class Interviewer
             return;
         }
 
-        if (!Database.TryGetOpenTicket(interaction.Channel.Id, out Database.Ticket ticket))
+        if (!Database.Ticket.TryGetOpenTicket(interaction.Channel.Id, out Database.Ticket ticket))
         {
             await interaction.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
                 .AddEmbed(new DiscordEmbedBuilder()
@@ -98,7 +98,7 @@ public static class Interviewer
         }
 
         // Return if there is no active interview in this channel
-        if (!Database.TryGetInterview(interaction.Channel.Id, out Interview interview))
+        if (!Database.Interviews.TryGetInterview(interaction.Channel.Id, out Interview interview))
         {
             await interaction.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
                 .AddEmbed(new DiscordEmbedBuilder()
@@ -171,7 +171,7 @@ public static class Interviewer
                 componentID = interaction.Data.Values[0];
                 break;
             case DiscordComponentType.Button:
-                componentID = interaction.Data.CustomId.Replace("supportchild_interviewbutton ", "");
+                componentID = interaction.Data.CustomId.Replace("supportboi_interviewbutton ", "");
                 break;
             case DiscordComponentType.ActionRow:
             case DiscordComponentType.FormInput:
@@ -214,7 +214,7 @@ public static class Interviewer
                 Description = "Error: Could not determine the next question based on your answer. Check your response and ask an admin to check the bot logs if this seems incorrect."
             }).AsEphemeral());
             currentStep.AddRelatedMessageIDs(followupMessage.Id);
-            Database.SaveInterview(interview);
+            Database.Interviews.SaveInterview(interview);
         }
         else
         {
@@ -265,7 +265,7 @@ public static class Interviewer
         }
 
         // The channel does not have an active interview.
-        if (!Database.TryGetInterview(answerMessage.ReferencedMessage.Channel.Id,
+        if (!Database.Interviews.TryGetInterview(answerMessage.ReferencedMessage.Channel.Id,
                                       out Interview interview))
         {
             return;
@@ -299,7 +299,7 @@ public static class Interviewer
                 Color = DiscordColor.Red
             });
             currentStep.AddRelatedMessageIDs(answerMessage.Id, lengthMessage.Id);
-            Database.SaveInterview(interview);
+            Database.Interviews.SaveInterview(interview);
             return;
         }
 
@@ -311,7 +311,7 @@ public static class Interviewer
                 Color = DiscordColor.Red
             });
             currentStep.AddRelatedMessageIDs(answerMessage.Id, lengthMessage.Id);
-            Database.SaveInterview(interview);
+            Database.Interviews.SaveInterview(interview);
             return;
         }
 
@@ -334,7 +334,7 @@ public static class Interviewer
             Color = DiscordColor.Red
         });
         currentStep.AddRelatedMessageIDs(answerMessage.Id, errorMessage.Id);
-        Database.SaveInterview(interview);
+        Database.Interviews.SaveInterview(interview);
     }
 
     private static async Task HandleAnswer(string answer,
@@ -375,13 +375,13 @@ public static class Interviewer
                         nextStep.AddRelatedMessageIDs(answerMessage.Id, errorMessage.Id);
                         previousStep.answer = null;
                         previousStep.answerID = 0;
-                        Database.SaveInterview(interview);
+                        Database.Interviews.SaveInterview(interview);
                     }
                     return;
                 }
 
                 await SendNextMessage(interview, channel, nextStep);
-                Database.SaveInterview(interview);
+                Database.Interviews.SaveInterview(interview);
                 break;
             case StepType.INTERVIEW_END:
                 DiscordEmbedBuilder endEmbed = new()
@@ -403,7 +403,7 @@ public static class Interviewer
                     await DeletePreviousMessages(interview, channel);
                 }
 
-                if (!Database.TryDeleteInterview(channel.Id))
+                if (!Database.Interviews.TryDeleteInterview(channel.Id))
                 {
                     Logger.Error("Could not delete interview from database. Channel ID: " + channel.Id);
                 }
@@ -451,7 +451,7 @@ public static class Interviewer
                     previousStep.AddRelatedMessageIDs(errorMessage.Id, answerMessage.Id);
                 }
 
-                Database.SaveInterview(interview);
+                Database.Interviews.SaveInterview(interview);
 
                 Logger.Error("Could not find a step to return to after a reference step in channel " + channel.Id);
                 return;
@@ -480,7 +480,7 @@ public static class Interviewer
                     previousStep.AddRelatedMessageIDs(errorMessage.Id, answerMessage.Id);
                 }
 
-                Database.SaveInterview(interview);
+                Database.Interviews.SaveInterview(interview);
                 return;
         }
     }
@@ -556,7 +556,7 @@ public static class Interviewer
                     for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < step.steps.Count; nrOfButtons++)
                     {
                         (string stepPattern, InterviewStep nextStep) = step.steps.ToArray()[nrOfButtons];
-                        buttonRow.Add(new DiscordButtonComponent(nextStep.GetButtonStyle(), "supportchild_interviewbutton " + nrOfButtons, stepPattern));
+                        buttonRow.Add(new DiscordButtonComponent(nextStep.GetButtonStyle(), "supportboi_interviewbutton " + nrOfButtons, stepPattern));
                     }
                     msgBuilder.AddComponents(buttonRow);
                 }
@@ -574,26 +574,26 @@ public static class Interviewer
                         categoryOptions.Add(new DiscordSelectComponentOption(stepPattern, selectionOptions.ToString(), nextStep.selectorDescription));
                     }
 
-                    selectionComponents.Add(new DiscordSelectComponent("supportchild_interviewselector " + selectionBoxes,
+                    selectionComponents.Add(new DiscordSelectComponent("supportboi_interviewselector " + selectionBoxes,
                                               string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select an option..." : step.selectorPlaceholder, categoryOptions));
                 }
 
                 msgBuilder.AddComponents(selectionComponents);
                 break;
             case StepType.ROLE_SELECTOR:
-                msgBuilder.AddComponents(new DiscordRoleSelectComponent("supportchild_interviewroleselector",
+                msgBuilder.AddComponents(new DiscordRoleSelectComponent("supportboi_interviewroleselector",
                                            string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a role..." : step.selectorPlaceholder));
                 break;
             case StepType.USER_SELECTOR:
-                msgBuilder.AddComponents(new DiscordUserSelectComponent("supportchild_interviewuserselector",
+                msgBuilder.AddComponents(new DiscordUserSelectComponent("supportboi_interviewuserselector",
                                            string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a user..." : step.selectorPlaceholder));
                 break;
             case StepType.CHANNEL_SELECTOR:
-                msgBuilder.AddComponents(new DiscordChannelSelectComponent("supportchild_interviewchannelselector",
+                msgBuilder.AddComponents(new DiscordChannelSelectComponent("supportboi_interviewchannelselector",
                                            string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a channel..." : step.selectorPlaceholder));
                 break;
             case StepType.MENTIONABLE_SELECTOR:
-                msgBuilder.AddComponents(new DiscordMentionableSelectComponent("supportchild_interviewmentionableselector",
+                msgBuilder.AddComponents(new DiscordMentionableSelectComponent("supportboi_interviewmentionableselector",
                                            string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a user or role..." : step.selectorPlaceholder));
                 break;
             case StepType.TEXT_INPUT:
@@ -604,7 +604,7 @@ public static class Interviewer
                 }
                 else
                 {
-                    lengthInfo = " (Maximum " + (step?.maxLength ?? InterviewStep.DEFAULT_MAX_FIELD_LENGTH) + " characters)";
+                    lengthInfo = " (Maximum " + (step.maxLength ?? InterviewStep.DEFAULT_MAX_FIELD_LENGTH) + " characters)";
                 }
                 embed.WithFooter("Reply to this message with your answer" + lengthInfo + ". You cannot include images or files.");
                 break;
diff --git a/README.md b/README.md
index 2f0c6be..ba1bae8 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[![Build Status](https://ci.toastielab.dev/job/toastie-stuff/job/SupportChild/job/main/badge/icon)](https://ci.toastielab.dev/job/toastie-stuff/job/SupportChild/job/main/)
 # SupportChild
 
 A support ticket Discord bot. Uses a MySQL database for storage of ticket information. Creates formatted HTML ticket transcripts when tickets are closed.
diff --git a/SupportChild.cs b/SupportChild.cs
index 4484a28..bf1618b 100644
--- a/SupportChild.cs
+++ b/SupportChild.cs
@@ -147,8 +147,8 @@ internal static class SupportChild
         try
         {
             Logger.Log("Connecting to database. (" + Config.hostName + ":" + Config.port + ")");
-            Database.SetConnectionString(Config.hostName, Config.port, Config.database, Config.username, Config.password);
-            Database.SetupTables();
+            Database.Connection.SetConnectionString(Config.hostName, Config.port, Config.database, Config.username, Config.password);
+            Database.Connection.SetupTables();
         }
         catch (Exception e)
         {
diff --git a/Utilities.cs b/Utilities.cs
index 5a5d6f2..71149dc 100644
--- a/Utilities.cs
+++ b/Utilities.cs
@@ -70,7 +70,7 @@ public static class Utilities
     public static async Task<List<Database.Category>> GetVerifiedCategories()
     {
         List<Database.Category> verifiedCategories = new List<Database.Category>();
-        foreach (Database.Category category in Database.GetAllCategories())
+        foreach (Database.Category category in Database.Category.GetAllCategories())
         {
             DiscordChannel channel = null;
             try