feat: add load question
This commit is contained in:
parent
ace28c402f
commit
38ea1f0632
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -460,6 +460,7 @@ dependencies = [
|
|||||||
"clickhouse",
|
"clickhouse",
|
||||||
"clickhouse_pool",
|
"clickhouse_pool",
|
||||||
"config",
|
"config",
|
||||||
|
"croner",
|
||||||
"database",
|
"database",
|
||||||
"poise",
|
"poise",
|
||||||
"serde",
|
"serde",
|
||||||
|
23
Cargo.toml
23
Cargo.toml
@ -2,22 +2,22 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = '2'
|
resolver = '2'
|
||||||
members = [
|
members = [
|
||||||
'apps/cli',
|
'apps/cli',
|
||||||
'libs/tool_tracing',
|
'libs/tool_tracing',
|
||||||
'libs/clickhouse_pool',
|
'libs/clickhouse_pool',
|
||||||
'libs/database',
|
'libs/database',
|
||||||
'libs/bot',
|
'libs/bot',
|
||||||
'libs/config',
|
'libs/config',
|
||||||
'libs/cron_scheduler',
|
'libs/cron_scheduler',
|
||||||
'libs/api',
|
'libs/api',
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
poise = { git = 'https://github.com/serenity-rs/poise', branch = 'current' }
|
poise = { git = 'https://github.com/serenity-rs/poise', branch = 'current' }
|
||||||
tokio = { version = '1.45.0', features = [
|
tokio = { version = '1.45.0', features = [
|
||||||
'macros',
|
'macros',
|
||||||
'rt-multi-thread',
|
'rt-multi-thread',
|
||||||
'io-std',
|
'io-std',
|
||||||
] }
|
] }
|
||||||
serde = '1.0'
|
serde = '1.0'
|
||||||
tracing = '0.1'
|
tracing = '0.1'
|
||||||
@ -25,6 +25,7 @@ serde_json = '1.0'
|
|||||||
clickhouse = { version = '0.13', features = ['native-tls', 'uuid', 'chrono'] }
|
clickhouse = { version = '0.13', features = ['native-tls', 'uuid', 'chrono'] }
|
||||||
uuid = { version = '1.16', features = ['serde', 'v4'] }
|
uuid = { version = '1.16', features = ['serde', 'v4'] }
|
||||||
chrono = { version = '0.4.41', features = ['serde'] }
|
chrono = { version = '0.4.41', features = ['serde'] }
|
||||||
|
croner = { version = "*" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
@ -23,12 +23,12 @@
|
|||||||
- [x] [ADMIN] trivial config : Modification des paramétre de base d'un event
|
- [x] [ADMIN] trivial config : Modification des paramétre de base d'un event
|
||||||
- [ ] [ADMIN] trivial add-question : Ajout de question + réponse ou X réponse
|
- [ ] [ADMIN] trivial add-question : Ajout de question + réponse ou X réponse
|
||||||
- [ ] [ADMIN] trivial del-question : Suppression de question
|
- [ ] [ADMIN] trivial del-question : Suppression de question
|
||||||
- [ ] [ADMIN] trivial load-question : Charge un fichier/lien de question
|
- [x] [ADMIN] trivial load-question : Charge un fichier/lien de question
|
||||||
- [ ] trivial scoreboard : Scoreboard
|
- [ ] trivial scoreboard : Scoreboard
|
||||||
- [ ] trivial score : Score d'une personne par défaut le sien
|
- [ ] trivial score : Score d'une personne par défaut le sien
|
||||||
- [ ] trivial top : Top X par défaut 3
|
- [ ] trivial top : Top X par défaut 3
|
||||||
- [ ] [ADMIN] trivial balance : Modifie le score d'une personne +/-/=
|
- [ ] [ADMIN] trivial balance : Modifie le score d'une personne +/-/=
|
||||||
- [ ] [ADMIN] trivial status : Return trvial status or change trivial status
|
- [x] [ADMIN] trivial status : Return trvial status or change trivial status
|
||||||
|
|
||||||
#### TODO
|
#### TODO
|
||||||
|
|
||||||
|
@ -13,5 +13,6 @@ clickhouse = { workspace = true }
|
|||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
config = { path = "../../libs/config" }
|
config = { path = "../../libs/config" }
|
||||||
database = { path = "../../libs/database" }
|
database = { path = "../../libs/database" }
|
||||||
|
croner = { workspace = true }
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::helper::is_user_admin_right;
|
use crate::helper::is_user_admin_right;
|
||||||
use crate::{Context, Error};
|
use crate::{Context, Error};
|
||||||
use clickhouse_pool::traits::Model;
|
use clickhouse_pool::traits::Model;
|
||||||
|
use croner::Cron;
|
||||||
use database::trivial::{Trivial, TrivialRewardKind};
|
use database::trivial::{Trivial, TrivialRewardKind};
|
||||||
use poise::serenity_prelude::{Channel, Role};
|
use poise::serenity_prelude::{Channel, Role};
|
||||||
use tracing::{debug, info, instrument};
|
use tracing::{debug, info, instrument};
|
||||||
@ -26,6 +27,10 @@ pub async fn config(
|
|||||||
#[description = "Reward amount for the trivia game"] reward_amount: Option<u64>,
|
#[description = "Reward amount for the trivia game"] reward_amount: Option<u64>,
|
||||||
#[description = "Whether to send an ephemeral message when the answer is taken into account"]
|
#[description = "Whether to send an ephemeral message when the answer is taken into account"]
|
||||||
taken_into_account: Option<bool>,
|
taken_into_account: Option<bool>,
|
||||||
|
#[description = "When to ask the question, in cron format (e.g. '0 0 * * *' for daily)"]
|
||||||
|
cron: Option<String>,
|
||||||
|
#[description = "When to answer the question, in cron format (e.g. '0 0 * * *' for daily)"]
|
||||||
|
cron_answer: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let guild_id = match ctx.guild_id() {
|
let guild_id = match ctx.guild_id() {
|
||||||
Some(id) => id.get(),
|
Some(id) => id.get(),
|
||||||
@ -43,7 +48,7 @@ pub async fn config(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let manager = ctx.data().datalake_config.clone();
|
let manager = ctx.data().datalake_config.clone();
|
||||||
let seach_query = Trivial::build_select_query(
|
let search_query = Trivial::build_select_query(
|
||||||
Some(&format!(
|
Some(&format!(
|
||||||
"channel_id = {} and guild_id = {}",
|
"channel_id = {} and guild_id = {}",
|
||||||
channel_id, guild_id
|
channel_id, guild_id
|
||||||
@ -52,7 +57,7 @@ pub async fn config(
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let mut trivia_exists: Trivial = match manager
|
let mut trivia_exists: Trivial = match manager
|
||||||
.execute_select_with_retry::<Trivial>(&seach_query)
|
.execute_select_with_retry::<Trivial>(&search_query)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(trivia) => {
|
Ok(trivia) => {
|
||||||
@ -111,6 +116,27 @@ pub async fn config(
|
|||||||
if let Some(taken_into_account) = taken_into_account {
|
if let Some(taken_into_account) = taken_into_account {
|
||||||
trivia_exists.taken_into_account = taken_into_account;
|
trivia_exists.taken_into_account = taken_into_account;
|
||||||
}
|
}
|
||||||
|
if let Some(cron) = cron {
|
||||||
|
match Cron::new(&cron).parse() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
ctx.say(format!("Invalid cron format: {}", e)).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
trivia_exists.question_asked = cron;
|
||||||
|
}
|
||||||
|
if let Some(cron_answer) = cron_answer {
|
||||||
|
match Cron::new(&cron_answer).parse() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
ctx.say(format!("Invalid cron answer format: {}", e))
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
trivia_exists.answer_given = cron_answer;
|
||||||
|
}
|
||||||
trivia_exists.updated_at = chrono::Utc::now();
|
trivia_exists.updated_at = chrono::Utc::now();
|
||||||
trivia_exists.updater_id = ctx.author().id.get();
|
trivia_exists.updater_id = ctx.author().id.get();
|
||||||
debug!("Updated trivia game: {:?}", trivia_exists);
|
debug!("Updated trivia game: {:?}", trivia_exists);
|
||||||
|
199
libs/bot/src/trivia/load.rs
Normal file
199
libs/bot/src/trivia/load.rs
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
use crate::helper::is_user_admin_right;
|
||||||
|
use crate::{Context, Error};
|
||||||
|
use clickhouse_pool::traits::Model;
|
||||||
|
use database::trivial::Trivial;
|
||||||
|
use database::trivial_question::TrivialQuestion;
|
||||||
|
use poise::serenity_prelude;
|
||||||
|
use poise::serenity_prelude::Channel;
|
||||||
|
use tracing::{info, instrument, warn};
|
||||||
|
|
||||||
|
/// Load a csv file containing trivia questions and answers into the database.
|
||||||
|
#[instrument(name = "trivia_load", skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))]
|
||||||
|
#[poise::command(
|
||||||
|
prefix_command,
|
||||||
|
slash_command,
|
||||||
|
guild_only,
|
||||||
|
category = "Trivia",
|
||||||
|
check = "is_user_admin_right"
|
||||||
|
)]
|
||||||
|
pub async fn load(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Fichier au format CSV contenant questions, réponses"]
|
||||||
|
file: serenity_prelude::Attachment,
|
||||||
|
#[description = "Channel of the trivia game, or current"] channel_id: Option<Channel>,
|
||||||
|
#[description = "Whether or not the file has an header row"] has_header: Option<bool>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let guild_id = match ctx.guild_id() {
|
||||||
|
Some(id) => id.get(),
|
||||||
|
None => {
|
||||||
|
ctx.say("This command can only be used in a server.")
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let channel_id = match channel_id {
|
||||||
|
Some(c) => c.id().get(),
|
||||||
|
None => {
|
||||||
|
ctx.say("No channel specified, using current channel.")
|
||||||
|
.await?;
|
||||||
|
ctx.channel_id().get()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let manager_pool = ctx.data().datalake_config.clone();
|
||||||
|
// Check if the trivia game already exists in the channel
|
||||||
|
let trivia = Trivial::build_select_query(
|
||||||
|
Some(&format!(
|
||||||
|
"channel_id = {} and guild_id = {}",
|
||||||
|
channel_id.clone(),
|
||||||
|
guild_id.clone()
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let trivia_exists: Vec<Trivial> = match manager_pool.execute_select_with_retry(&trivia).await {
|
||||||
|
Ok(trivia) => trivia,
|
||||||
|
Err(e) => {
|
||||||
|
ctx.say(format!("Failed to fetch trivia game: {}", e))
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if trivia_exists.is_empty() {
|
||||||
|
ctx.say(format!("No trivia game exists in this channel.",))
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let trivia = trivia_exists.first().unwrap();
|
||||||
|
let has_header = has_header.unwrap_or(true);
|
||||||
|
let file_content = match file.download().await {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(e) => {
|
||||||
|
ctx.say(format!("Failed to download file: {}", e)).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Assuming the file is a CSV, we can parse it here.
|
||||||
|
let content = String::from_utf8_lossy(&file_content);
|
||||||
|
let content_split = if has_header {
|
||||||
|
// Skip the first line if it is a header
|
||||||
|
let mut lines = content.lines();
|
||||||
|
lines.next(); // Skip header
|
||||||
|
lines.collect::<Vec<&str>>().into_iter()
|
||||||
|
} else {
|
||||||
|
content.lines().collect::<Vec<&str>>().into_iter()
|
||||||
|
};
|
||||||
|
// Split each line by comma and process
|
||||||
|
|
||||||
|
let parsed_content = content_split
|
||||||
|
.map(|line| {
|
||||||
|
let parts: Vec<&str> = line.split(',').collect();
|
||||||
|
if parts.len() < 2 || parts[0].trim().is_empty() || parts[1].trim().is_empty() {
|
||||||
|
warn!("Invalid line format: {}", line);
|
||||||
|
return ("".to_string(), "".to_string());
|
||||||
|
}
|
||||||
|
let question = parts[0].trim().to_string();
|
||||||
|
let answer = parts[1].trim().to_string();
|
||||||
|
(question, answer)
|
||||||
|
})
|
||||||
|
.filter(|(q, a)| !q.is_empty() && !a.is_empty())
|
||||||
|
.collect::<Vec<(String, String)>>();
|
||||||
|
if parsed_content.is_empty() {
|
||||||
|
ctx.say("No valid questions found in the file.").await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let get_trivia = TrivialQuestion::build_select_query(
|
||||||
|
Some(&format!(
|
||||||
|
"channel_id = {} and guild_id = {}",
|
||||||
|
channel_id, guild_id
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let trivial_questions: Vec<TrivialQuestion> =
|
||||||
|
match manager_pool.execute_select_with_retry(&get_trivia).await {
|
||||||
|
Ok(questions) => questions,
|
||||||
|
Err(e) => {
|
||||||
|
ctx.say(format!("Failed to fetch trivia questions: {}", e))
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let parsed_content = if !trivial_questions.is_empty() {
|
||||||
|
// Check for duplicate questions
|
||||||
|
let existing_questions: Vec<String> = trivial_questions
|
||||||
|
.iter()
|
||||||
|
.map(|q| q.question.clone())
|
||||||
|
.collect();
|
||||||
|
let no_duplicates: Vec<(String, String)> = parsed_content
|
||||||
|
.iter()
|
||||||
|
.filter(|(q, _)| !existing_questions.contains(q))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
if no_duplicates.is_empty() {
|
||||||
|
ctx.say("Questions already exist in the database, no new questions to add.")
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
no_duplicates
|
||||||
|
} else {
|
||||||
|
// No existing questions, all are new
|
||||||
|
parsed_content
|
||||||
|
};
|
||||||
|
let mut inserter = match manager_pool
|
||||||
|
.get_insert::<TrivialQuestion>(TrivialQuestion::table_name())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(inserter) => inserter,
|
||||||
|
Err(e) => {
|
||||||
|
ctx.say(format!(
|
||||||
|
"Internal Server error, please contact Bot Developer: {}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
warn!("Failed to create inserter for TrivialQuestion: {}", e);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (question, answer) in parsed_content {
|
||||||
|
let trivial_question = TrivialQuestion {
|
||||||
|
trivial_id: trivia.id,
|
||||||
|
guild_id: guild_id,
|
||||||
|
channel_id: channel_id,
|
||||||
|
question: question,
|
||||||
|
answer: answer,
|
||||||
|
creator_id: ctx.author().id.get(),
|
||||||
|
updater_id: ctx.author().id.get(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
if let Err(e) = inserter.write(&trivial_question).await {
|
||||||
|
warn!("Failed to insert trivial question: {}", e);
|
||||||
|
ctx.say(format!("Failed to insert question: {}", e)).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match inserter.end().await {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("Successfully loaded trivia questions into the database.");
|
||||||
|
ctx.say("Trivia questions loaded successfully.").await?;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to finalize insert operation: {}", e);
|
||||||
|
ctx.say(format!("Failed to load trivia questions: {}", e))
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let optimize = TrivialQuestion::optimize_table_sql();
|
||||||
|
if let Err(e) = manager_pool.execute_with_retry(optimize).await {
|
||||||
|
warn!("Failed to optimize trivial_question table: {}", e);
|
||||||
|
ctx.say(format!("Failed to optimize trivia questions: {}", e))
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
info!("Successfully optimized trivial_question table.");
|
||||||
|
ctx.say("Trivia questions loaded and table optimized successfully.")
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod create;
|
pub mod create;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
|
pub mod load;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
use crate::{Context, Error};
|
use crate::{Context, Error};
|
||||||
use config::config;
|
use config::config;
|
||||||
use create::create;
|
use create::create;
|
||||||
use list::list;
|
use list::list;
|
||||||
|
use load::load;
|
||||||
use status::status;
|
use status::status;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@ -16,7 +18,7 @@ use tracing::instrument;
|
|||||||
slash_command,
|
slash_command,
|
||||||
prefix_command,
|
prefix_command,
|
||||||
category = "Trivia",
|
category = "Trivia",
|
||||||
subcommands("create", "list", "config", "status"),
|
subcommands("create", "list", "config", "status", "load"),
|
||||||
guild_only = true
|
guild_only = true
|
||||||
)]
|
)]
|
||||||
pub async fn trivia(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn trivia(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
@ -78,6 +78,9 @@ pub struct Trivial {
|
|||||||
pub taken_into_account: bool,
|
pub taken_into_account: bool,
|
||||||
pub status: TrivialStatus,
|
pub status: TrivialStatus,
|
||||||
pub sign: i8,
|
pub sign: i8,
|
||||||
|
|
||||||
|
pub question_asked: String,
|
||||||
|
pub answer_given: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Trivial {
|
impl Default for Trivial {
|
||||||
@ -100,6 +103,8 @@ impl Default for Trivial {
|
|||||||
taken_into_account: true,
|
taken_into_account: true,
|
||||||
status: TrivialStatus::Init,
|
status: TrivialStatus::Init,
|
||||||
sign: 1,
|
sign: 1,
|
||||||
|
question_asked: "".to_string(),
|
||||||
|
answer_given: "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,7 +159,9 @@ impl Model for Trivial {
|
|||||||
reward_amount UInt64,
|
reward_amount UInt64,
|
||||||
taken_into_account Bool,
|
taken_into_account Bool,
|
||||||
status Enum8('Init' = 0, 'Started' = 1, 'Finished' = 2, 'Paused' = 3),
|
status Enum8('Init' = 0, 'Started' = 1, 'Finished' = 2, 'Paused' = 3),
|
||||||
sign Int8
|
sign Int8,
|
||||||
|
question_asked String,
|
||||||
|
answer_given String
|
||||||
) ENGINE = CollapsingMergeTree(sign)
|
) ENGINE = CollapsingMergeTree(sign)
|
||||||
PRIMARY KEY (guild_id, id)
|
PRIMARY KEY (guild_id, id)
|
||||||
ORDER BY (guild_id, id)
|
ORDER BY (guild_id, id)
|
||||||
@ -180,6 +187,8 @@ impl Model for Trivial {
|
|||||||
"taken_into_account",
|
"taken_into_account",
|
||||||
"status",
|
"status",
|
||||||
"sign",
|
"sign",
|
||||||
|
"question_asked",
|
||||||
|
"answer_given",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,6 +213,8 @@ impl Model for Trivial {
|
|||||||
self.taken_into_account.to_string(),
|
self.taken_into_account.to_string(),
|
||||||
format!("'{:?}'", serde_json::to_string(&self.status)),
|
format!("'{:?}'", serde_json::to_string(&self.status)),
|
||||||
self.sign.to_string(),
|
self.sign.to_string(),
|
||||||
|
format!("'{}'", self.question_asked),
|
||||||
|
format!("'{}'", self.answer_given),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,9 @@ pub struct TrivialQuestion {
|
|||||||
#[serde(with = "clickhouse::serde::uuid")]
|
#[serde(with = "clickhouse::serde::uuid")]
|
||||||
pub trivial_id: Uuid,
|
pub trivial_id: Uuid,
|
||||||
|
|
||||||
|
pub guild_id: u64,
|
||||||
|
pub channel_id: u64,
|
||||||
|
|
||||||
pub question: String,
|
pub question: String,
|
||||||
pub answer: String,
|
pub answer: String,
|
||||||
|
|
||||||
@ -23,6 +26,23 @@ pub struct TrivialQuestion {
|
|||||||
pub updater_id: u64,
|
pub updater_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TrivialQuestion {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
trivial_id: Uuid::new_v4(),
|
||||||
|
guild_id: 0,
|
||||||
|
channel_id: 0,
|
||||||
|
question: String::new(),
|
||||||
|
answer: String::new(),
|
||||||
|
created_at: Utc::now(),
|
||||||
|
updated_at: Utc::now(),
|
||||||
|
creator_id: 0,
|
||||||
|
updater_id: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Model for TrivialQuestion {
|
impl Model for TrivialQuestion {
|
||||||
type T = TrivialQuestion;
|
type T = TrivialQuestion;
|
||||||
|
|
||||||
@ -42,6 +62,8 @@ impl Model for TrivialQuestion {
|
|||||||
CREATE TABLE IF NOT EXISTS trivial_question (
|
CREATE TABLE IF NOT EXISTS trivial_question (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
trivial_id UUID,
|
trivial_id UUID,
|
||||||
|
guild_id UInt64,
|
||||||
|
channel_id UInt64,
|
||||||
question String,
|
question String,
|
||||||
answer String,
|
answer String,
|
||||||
created_at DateTime64(3),
|
created_at DateTime64(3),
|
||||||
@ -58,6 +80,8 @@ impl Model for TrivialQuestion {
|
|||||||
vec![
|
vec![
|
||||||
"id",
|
"id",
|
||||||
"trivial_id",
|
"trivial_id",
|
||||||
|
"guild_id",
|
||||||
|
"channel_id",
|
||||||
"question",
|
"question",
|
||||||
"answer",
|
"answer",
|
||||||
"created_at",
|
"created_at",
|
||||||
@ -73,6 +97,8 @@ impl Model for TrivialQuestion {
|
|||||||
vec![
|
vec![
|
||||||
self.id.to_string(),
|
self.id.to_string(),
|
||||||
self.trivial_id.to_string(),
|
self.trivial_id.to_string(),
|
||||||
|
self.guild_id.to_string(),
|
||||||
|
self.channel_id.to_string(),
|
||||||
self.question.clone(),
|
self.question.clone(),
|
||||||
self.answer.clone(),
|
self.answer.clone(),
|
||||||
self.created_at.to_string(),
|
self.created_at.to_string(),
|
||||||
|
4
resources/template_question.csv
Normal file
4
resources/template_question.csv
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
question;response
|
||||||
|
De qu'elle couleur est le cheval blanc d'henry IV?;blanc
|
||||||
|
Joseph ?;joestar
|
||||||
|
Rick ?;morty
|
|
Loading…
Reference in New Issue
Block a user