Added database functions for interviews

This commit is contained in:
Toastie 2024-12-26 18:07:36 +13:00
parent c34a396c78
commit ce836a465f
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
4 changed files with 125 additions and 14 deletions

View file

@ -32,6 +32,8 @@ internal static class Config
internal static string username = "supportchild"; internal static string username = "supportchild";
internal static string password = ""; internal static string password = "";
internal static JToken interviews;
private static string configPath = "./config.yml"; private static string configPath = "./config.yml";
public static void LoadConfig() public static void LoadConfig()
@ -100,6 +102,6 @@ internal static class Config
password = json.SelectToken("database.password")?.Value<string>() ?? ""; password = json.SelectToken("database.password")?.Value<string>() ?? "";
// Set up interviewer // Set up interviewer
Interviewer.ParseConfig(json.SelectToken("interviews")); interviews = json.SelectToken("interviews");
} }
} }

View file

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Security.Cryptography; using System.Security.Cryptography;
using DSharpPlus; using DSharpPlus;
using MySqlConnector; using MySqlConnector;
using Newtonsoft.Json;
namespace SupportChild; namespace SupportChild;
@ -107,6 +108,11 @@ public static class Database
"name VARCHAR(256) NOT NULL UNIQUE," + "name VARCHAR(256) NOT NULL UNIQUE," +
"category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY)", "category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY)",
c); c);
using MySqlCommand createInterviews = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS interviews(" +
"channel_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
"interview JSON NOT NULL)",
c);
c.Open(); c.Open();
createTickets.ExecuteNonQuery(); createTickets.ExecuteNonQuery();
createBlacklisted.ExecuteNonQuery(); createBlacklisted.ExecuteNonQuery();
@ -114,6 +120,7 @@ public static class Database
createStaffList.ExecuteNonQuery(); createStaffList.ExecuteNonQuery();
createMessages.ExecuteNonQuery(); createMessages.ExecuteNonQuery();
createCategories.ExecuteNonQuery(); createCategories.ExecuteNonQuery();
createInterviews.ExecuteNonQuery();
} }
public static bool IsOpenTicket(ulong channelID) public static bool IsOpenTicket(ulong channelID)
@ -724,6 +731,84 @@ public static class Database
} }
} }
public static Dictionary<ulong, Interviewer.InterviewQuestion> GetAllInterviews()
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand("SELECT * FROM interviews", c);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if messages exist in the database
if (!results.Read())
{
return new Dictionary<ulong, Interviewer.InterviewQuestion>();
}
Dictionary<ulong, Interviewer.InterviewQuestion> questions = new Dictionary<ulong, Interviewer.InterviewQuestion>
{
{ results.GetUInt64("channel_id"), JsonConvert.DeserializeObject<Interviewer.InterviewQuestion>(results.GetString("interview")) }
};
while (results.Read())
{
questions.Add(results.GetUInt64("channel_id"), JsonConvert.DeserializeObject<Interviewer.InterviewQuestion>(results.GetString("interview")));
}
results.Close();
return questions;
}
public static bool SaveInterview(ulong channelID, Interviewer.InterviewQuestion interview)
{
try
{
if (TryGetInterview(channelID, out _))
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"UPDATE interviews SET interview = @interview WHERE channel_id = @channel_id;", c);
cmd.Parameters.AddWithValue("@channel_id", channelID);
cmd.Parameters.AddWithValue("@interview", JsonConvert.SerializeObject(interview));
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
else
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO interviews (channel_id,interview) VALUES (@channel_id, @interview);", c);
cmd.Parameters.AddWithValue("@channel_id", channelID);
cmd.Parameters.AddWithValue("@interview", JsonConvert.SerializeObject(interview));
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
}
catch (MySqlException)
{
return false;
}
}
public static bool TryGetInterview(ulong channelID, out Interviewer.InterviewQuestion 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 = JsonConvert.DeserializeObject<Interviewer.InterviewQuestion>(results.GetString("interview"));
results.Close();
return true;
}
public class Ticket public class Ticket
{ {
public uint id; public uint id;

View file

@ -22,7 +22,7 @@ public static class Interviewer
// The tree is generated by the config file when a new ticket is opened or the restart interview command is used. // The tree is generated by the config file when a new ticket is opened or the restart interview command is used.
// Additional components not specified in the config file are populated as the interview progresses. // Additional components not specified in the config file are populated as the interview progresses.
// The entire interview tree is serialized and stored in the database in order to record responses as they are made. // The entire interview tree is serialized and stored in the database in order to record responses as they are made.
public struct InterviewQuestion public class InterviewQuestion
{ {
// Message contents sent to the user. // Message contents sent to the user.
public string message; public string message;
@ -51,11 +51,18 @@ public static class Interviewer
private static Dictionary<ulong, InterviewQuestion> categoryInterviews = []; private static Dictionary<ulong, InterviewQuestion> categoryInterviews = [];
private static Dictionary<ulong, InterviewQuestion> activeInterviews = [];
public static void ParseConfig(JToken interviewConfig) public static void ParseConfig(JToken interviewConfig)
{ {
categoryInterviews = JsonConvert.DeserializeObject<Dictionary<ulong, InterviewQuestion>>(interviewConfig.ToString()); categoryInterviews = JsonConvert.DeserializeObject<Dictionary<ulong, InterviewQuestion>>(interviewConfig.ToString());
} }
public static void LoadActiveInterviews()
{
activeInterviews = Database.GetAllInterviews();
}
public static void StartInterview(DiscordChannel channel) public static void StartInterview(DiscordChannel channel)
{ {
if (channel.Parent == null) if (channel.Parent == null)
@ -66,23 +73,29 @@ public static class Interviewer
if (categoryInterviews.TryGetValue(channel.Parent.Id, out InterviewQuestion interview)) if (categoryInterviews.TryGetValue(channel.Parent.Id, out InterviewQuestion interview))
{ {
CreateQuestion(channel, interview); CreateQuestion(channel, interview);
Database.SaveInterview(channel.Id, interview);
} }
} }
public static void ProcessResponse(DiscordChannel channel, string response) public static void ProcessResponse(DiscordMessage message)
{ {
// TODO: Find if channel has open interview.
// TODO: Find if message is replying to interview message.
// TODO: Handle other interactions like button presses.
// TODO: Find out where in the interview tree we are.
// TODO: Handle FAIL event, cancelling the interview.
// TODO: Handle DONE event, creating a summary.
//Database.SaveInterview(channel.Id, interview);
} }
private static async void CreateQuestion(DiscordChannel channel, InterviewQuestion question) private static async void CreateQuestion(DiscordChannel channel, InterviewQuestion question)
{ {
DiscordMessageBuilder msgBuilder = new DiscordMessageBuilder DiscordMessageBuilder msgBuilder = new DiscordMessageBuilder();
DiscordEmbedBuilder embed = new DiscordEmbedBuilder()
{ {
Embed = new DiscordEmbedBuilder Color = Utilities.StringToColor(question.color),
{ Description = question.message
Color = Utilities.StringToColor(question.color),
Description = question.message
}
}; };
switch (question.type) switch (question.type)
@ -94,7 +107,7 @@ public static class Interviewer
List<DiscordButtonComponent> buttonRow = []; List<DiscordButtonComponent> buttonRow = [];
for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < question.paths.Count; nrOfButtons++) for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < question.paths.Count; nrOfButtons++)
{ {
buttonRow.Add(new DiscordButtonComponent(ButtonStyle.Primary, "supportboi_interviewbutton " + nrOfButtons, question.paths.ToArray()[nrOfButtons].Key)); buttonRow.Add(new DiscordButtonComponent(ButtonStyle.Primary, "supportchild_interviewbutton " + nrOfButtons, question.paths.ToArray()[nrOfButtons].Key));
} }
msgBuilder.AddComponents(buttonRow); msgBuilder.AddComponents(buttonRow);
} }
@ -110,13 +123,13 @@ public static class Interviewer
{ {
categoryOptions.Add(new DiscordSelectComponentOption(question.paths.ToArray()[selectionOptions].Key, selectionOptions.ToString())); categoryOptions.Add(new DiscordSelectComponentOption(question.paths.ToArray()[selectionOptions].Key, selectionOptions.ToString()));
} }
selectionComponents.Add(new DiscordSelectComponent("supportboi_interviewselector " + selectionBoxes, "Select an option...", categoryOptions, false, 0, 1)); selectionComponents.Add(new DiscordSelectComponent("supportchild_interviewselector " + selectionBoxes, "Select an option...", categoryOptions, false, 0, 1));
} }
msgBuilder.AddComponents(selectionComponents); msgBuilder.AddComponents(selectionComponents);
break; break;
case QuestionType.TEXT_INPUT: case QuestionType.TEXT_INPUT:
// TODO: Add embed footer with response info embed.WithFooter("Reply to this message with your answer.");
break; break;
case QuestionType.DONE: case QuestionType.DONE:
case QuestionType.FAIL: case QuestionType.FAIL:
@ -124,10 +137,9 @@ public static class Interviewer
break; break;
} }
msgBuilder.AddEmbed(embed);
DiscordMessage message = await channel.SendMessageAsync(msgBuilder); DiscordMessage message = await channel.SendMessageAsync(msgBuilder);
question.messageID = message.Id; question.messageID = message.Id;
// TODO: Save interview progress
} }
public static void CreateSummary() public static void CreateSummary()

View file

@ -152,6 +152,18 @@ internal static class SupportChild
throw; throw;
} }
try
{
Logger.Log("Connecting to database... (" + Config.hostName + ":" + Config.port + ")");
Interviewer.ParseConfig(Config.interviews);
Interviewer.LoadActiveInterviews();
}
catch (Exception e)
{
Logger.Fatal("Could not set up database tables, please confirm connection settings, status of the server and permissions of MySQL user. Error: " + e);
throw;
}
Logger.Log("Setting up Discord client..."); Logger.Log("Setting up Discord client...");
// Setting up client configuration // Setting up client configuration