feat: Start/Stop/Add keywords are working YIPEEE and update with surrealdb are fixed

This commit is contained in:
Max batleforc 2024-06-25 02:53:13 +02:00
parent c99692fbea
commit ecadf7a90d
No known key found for this signature in database
GPG Key ID: 25D243AB4B6AC9E7
15 changed files with 515 additions and 64 deletions

1
Cargo.lock generated
View File

@ -703,6 +703,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"serde_with",
"serde_yaml", "serde_yaml",
"serial_test", "serial_test",
"surrealdb", "surrealdb",

View File

@ -47,6 +47,7 @@ tokio-cron-scheduler = { version = "0.10", features = [
"tracing-subscriber", "tracing-subscriber",
"signal", "signal",
] } ] }
serde_with = "3.8.1"
[[bin]] [[bin]]

View File

@ -75,17 +75,22 @@ pub async fn keyword(
.color(colour::Color::RED) .color(colour::Color::RED)
} else { } else {
let concour = concour.unwrap(); let concour = concour.unwrap();
CreateEmbed::new() let output = CreateEmbed::new()
.title(concour.title) .title(concour.title)
.description(concour.description) .description(concour.description)
.field("Start date", concour.start_date.to_string(), false) .field("Start date", concour.start_date.to_string(), false)
.field("Periode", concour.periode.to_string(), false) .field("Periode", concour.periode.to_string(), false)
.field( .field("keyword", concour.keywords.len().to_string(), true);
if concour.role_recompense != 0 {
output.field(
"Role récompense", "Role récompense",
RoleId::new(concour.role_recompense).mention().to_string(), RoleId::new(concour.role_recompense).mention().to_string(),
false, false,
) )
.field("keyword", concour.keywords.len().to_string(), true) } else {
output
}
} }
} }
Err(err) => match err { Err(err) => match err {

View File

@ -1,5 +1,8 @@
use crate::botv2::{ use crate::botv2::{
cmd::concour::{create::create, keyword::keyword, list::list, period::period, update::update}, cmd::concour::{
create::create, keyword::keyword, list::list, period::period, start::start, stop::stop,
update::update,
},
domain::concour::{ domain::concour::{
check_if_allowed::check_if_concour_allowed, get_channel_concour::get_channel_concour, check_if_allowed::check_if_concour_allowed, get_channel_concour::get_channel_concour,
}, },
@ -17,7 +20,9 @@ use tracing::instrument;
slash_command, slash_command,
prefix_command, prefix_command,
category = "concour", category = "concour",
subcommands("get", "list", "update", "create", "keyword", "period"), subcommands(
"get", "list", "update", "create", "keyword", "period", "start", "stop"
),
guild_only = true guild_only = true
)] )]
pub async fn concour( pub async fn concour(

View File

@ -3,4 +3,6 @@ pub mod keyword;
pub mod list; pub mod list;
pub mod main; pub mod main;
pub mod period; pub mod period;
pub mod start;
pub mod stop;
pub mod update; pub mod update;

View File

@ -0,0 +1,137 @@
use crate::botv2::{
domain::concour::{
check_if_allowed::check_if_allowed,
start_concour::{start_concour, start_concour_step2, StartConcourError},
},
init::{Context, Error},
};
use poise::{
serenity_prelude::{model::colour, CreateEmbed, CreateEmbedFooter},
CreateReply,
};
use tracing::instrument;
/// Start concour (only for admin)
#[instrument(skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))]
#[poise::command(
slash_command,
prefix_command,
category = "server_config",
guild_only = true
)]
pub async fn start(ctx: Context<'_>) -> Result<(), Error> {
let guild = match ctx.guild_id() {
Some(guild) => guild,
None => return Ok(()),
};
let entity_name = ctx.data().entity_name.clone();
let mut cron_schedule = ctx.data().scheduler.clone();
let footer = CreateEmbedFooter::new(entity_name.clone());
match check_if_allowed(guild.get(), ctx.author().id.get(), ctx.http()).await {
Ok(ok) => {
if !ok {
let embed = CreateEmbed::new()
.title("You are not an admin")
.color(colour::Color::RED)
.footer(footer);
if let Err(why) = ctx
.send(CreateReply::default().embed(embed).ephemeral(true))
.await
{
tracing::error!("Error sending message: {:?}", why);
}
return Ok(());
}
}
Err(_) => {
let embed = CreateEmbed::new()
.title("You are not an admin")
.color(colour::Color::RED)
.footer(footer);
if let Err(why) = ctx
.send(CreateReply::default().embed(embed).ephemeral(true))
.await
{
tracing::error!("Error sending message: {:?}", why);
}
return Ok(());
}
};
let (concour, success) =
match start_concour(guild.get(), ctx.channel_id().get(), &mut cron_schedule).await {
Ok(concour) => {
if concour.is_none() {
(
CreateEmbed::new()
.title("No concour created")
.color(colour::Color::RED),
false,
)
} else {
let concour = concour.unwrap();
let keyword_index = concour.index_keyword as usize;
let keyword = concour.keywords.get(keyword_index).unwrap();
let output = CreateEmbed::new()
.title(format!(
"Concour: {} Jour : {}",
concour.title,
concour.index_keyword + 1
))
.description(concour.description)
.field("Mot du jours ", keyword.to_string(), false)
.field("Good luck !", "", false)
.field(
"Vous avez jusqu'a demain 17h",
"HARD CODED FOR THE MOMENT",
false,
)
.color(colour::Color::DARK_GREEN);
(output, true)
}
}
Err(err) => (
match err {
StartConcourError::AlreadyOnGoing => CreateEmbed::new()
.title("Concour already OnGoing")
.color(colour::Color::RED),
StartConcourError::DoesntExist => CreateEmbed::new()
.title("Concour doesn't exist")
.color(colour::Color::RED),
StartConcourError::KeyWordListEmpty => CreateEmbed::new()
.title("Keyword list empty")
.color(colour::Color::RED),
StartConcourError::FinishedKeyWordList => CreateEmbed::new()
.title("Finished keyword list, add new one")
.color(colour::Color::RED),
_ => CreateEmbed::new()
.title("Error while creating concour")
.field("Please contact your administrator", "", false)
.field("Your concour is possibly not initied correctly", "", false)
.color(colour::Color::RED),
},
false,
),
};
let mut builder = CreateReply::default().ephemeral(!success);
builder = builder.embed(concour.footer(footer));
match ctx.send(builder).await {
Ok(handler) => {
if success {
let message_id = handler.message().await.unwrap().id.get();
tracing::info!(msg_id = message_id, "Concour started");
match start_concour_step2(guild.get(), ctx.channel_id().get(), message_id).await {
Ok(_) => {}
Err(err) => {
tracing::error!("Error starting concour step 2: {:?}", err);
}
}
}
}
Err(why) => {
tracing::error!("Error sending message: {:?}", why);
}
}
Ok(())
}

View File

@ -0,0 +1,108 @@
use crate::botv2::{
domain::concour::{
check_if_allowed::check_if_allowed,
stop_concour::{stop_concour, StopConcourError},
},
init::{Context, Error},
};
use poise::{
serenity_prelude::{model::colour, CreateEmbed, CreateEmbedFooter},
CreateReply,
};
use tracing::instrument;
/// Stop concour (only for admin)
#[instrument(skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))]
#[poise::command(
slash_command,
prefix_command,
category = "server_config",
guild_only = true
)]
pub async fn stop(ctx: Context<'_>) -> Result<(), Error> {
let guild = match ctx.guild_id() {
Some(guild) => guild,
None => return Ok(()),
};
let entity_name = ctx.data().entity_name.clone();
let mut cron_schedule = ctx.data().scheduler.clone();
let footer = CreateEmbedFooter::new(entity_name.clone());
match check_if_allowed(guild.get(), ctx.author().id.get(), ctx.http()).await {
Ok(ok) => {
if !ok {
let embed = CreateEmbed::new()
.title("You are not an admin")
.color(colour::Color::RED)
.footer(footer);
if let Err(why) = ctx
.send(CreateReply::default().embed(embed).ephemeral(true))
.await
{
tracing::error!("Error sending message: {:?}", why);
}
return Ok(());
}
}
Err(_) => {
let embed = CreateEmbed::new()
.title("You are not an admin")
.color(colour::Color::RED)
.footer(footer);
if let Err(why) = ctx
.send(CreateReply::default().embed(embed).ephemeral(true))
.await
{
tracing::error!("Error sending message: {:?}", why);
}
return Ok(());
}
};
let (concour, success) =
match stop_concour(guild.get(), ctx.channel_id().get(), &mut cron_schedule).await {
Ok(concour) => {
if concour.is_none() {
(
CreateEmbed::new()
.title("No concour created")
.color(colour::Color::RED),
false,
)
} else {
let concour = concour.unwrap();
let output = CreateEmbed::new()
.title(format!("Concour: {}", concour.title))
.description(concour.description)
.field("Concour mis en pause ", "", false)
.color(colour::Color::DARK_GREEN);
(output, true)
}
}
Err(err) => (
match err {
StopConcourError::AlreadyStopped => CreateEmbed::new()
.title("Concour already Stopped")
.color(colour::Color::RED),
StopConcourError::DoesntExist => CreateEmbed::new()
.title("Concour doesn't exist")
.color(colour::Color::RED),
StopConcourError::NotScheduled => CreateEmbed::new()
.title("Concour not scheduled")
.color(colour::Color::RED),
_ => CreateEmbed::new()
.title("Error while creating concour")
.field("Please contact your administrator", "", false)
.field("Your concour is possibly not initied correctly", "", false)
.color(colour::Color::RED),
},
false,
),
};
let mut builder = CreateReply::default().ephemeral(!success);
builder = builder.embed(concour.footer(footer));
if let Err(why) = ctx.send(builder).await {
tracing::error!("Error sending message: {:?}", why);
}
Ok(())
}

View File

@ -51,7 +51,7 @@ pub async fn add_keyword_concour(
let text = res.text().await.unwrap(); let text = res.text().await.unwrap();
let split_delimiter = delimiter.unwrap_or("\n".to_string()); let split_delimiter = delimiter.unwrap_or("\n".to_string());
text.split(&split_delimiter).for_each(|x| { text.split(&split_delimiter).for_each(|x| {
concour.keywords.push(x.to_string()); concour.keywords.push(x.to_string().replace('\r', ""));
}); });
} }
Err(err) => { Err(err) => {
@ -61,7 +61,6 @@ pub async fn add_keyword_concour(
)); ));
} }
} }
concour.keywords.push(url);
} }
KeyWordSource::Text(text, delimiter) => { KeyWordSource::Text(text, delimiter) => {
let split_delimiter = delimiter.unwrap_or("\n".to_string()); let split_delimiter = delimiter.unwrap_or("\n".to_string());

View File

@ -1,19 +1,26 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{info, instrument}; use tracing::{info, instrument};
use crate::db::concour::Concour; use crate::{
db::concour::{Concour, ConcourStatus},
event::schedule_job::ScheduleJob,
};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum StartConcourError { pub enum StartConcourError {
AlreadyOnGoing,
DoesntExist, DoesntExist,
KeyWordListEmpty,
FinishedKeyWordList,
FindError(String), FindError(String),
UnknownError(String), UnknownError(String),
} }
#[instrument(level = "info")] #[instrument(level = "info", skip(cron_scheduler))]
pub async fn start_concour( pub async fn start_concour(
server_id: u64, server_id: u64,
channel_id: u64, channel_id: u64,
cron_scheduler: &mut ScheduleJob,
) -> Result<Option<Concour>, StartConcourError> { ) -> Result<Option<Concour>, StartConcourError> {
let concour = match Concour::find_by_server_id_channel_id(&server_id, &channel_id).await { let concour = match Concour::find_by_server_id_channel_id(&server_id, &channel_id).await {
Ok(list_concour) => list_concour, Ok(list_concour) => list_concour,
@ -29,19 +36,76 @@ pub async fn start_concour(
info!("Concour doesn't exist"); info!("Concour doesn't exist");
return Err(StartConcourError::DoesntExist); return Err(StartConcourError::DoesntExist);
} }
todo!("Setup logic to start the waiting period for the concour"); let mut concour = concour.unwrap();
// let mut concour = concour.unwrap(); if concour.keywords.is_empty() {
tracing::warn!("Keyword list is empty");
return Err(StartConcourError::KeyWordListEmpty);
}
let keyword_len = concour.keywords.len() as u64;
if concour.index_keyword.gt(&keyword_len) {
tracing::warn!(keyword_len, concour.index_keyword, "Finished keyword list");
return Err(StartConcourError::FinishedKeyWordList);
}
if concour.status == ConcourStatus::OnGoing {
tracing::warn!("Concour already OnGoing");
return Err(StartConcourError::AlreadyOnGoing);
}
match cron_scheduler
.add_concour_cron_job(server_id, channel_id, "*/1 * * * * *".to_string())
.await
{
Ok(_) => {}
Err(_) => {
tracing::error!("Error starting cron job");
return Err(StartConcourError::UnknownError(
"Error starting cron job".to_string(),
));
}
}
concour.status = ConcourStatus::OnGoing;
// // Update status to started match concour.update().await {
Ok(_) => {}
// match concour.update().await { Err(err) => {
// Ok(_) => {} tracing::error!(error = err.to_string(), "Error updating concour");
// Err(err) => { return Err(StartConcourError::UnknownError(
// tracing::error!(error = err.to_string(), "Error updating concour"); "Error updating concour".to_string(),
// return Err(StartConcourError::UnknownError( ));
// "Error updating concour".to_string(), }
// )); }
// } Ok(Some(concour))
// } }
// Ok(Some(concour))
#[instrument(level = "info")]
pub async fn start_concour_step2(
server_id: u64,
channel_id: u64,
message_id: u64,
) -> Result<(), StartConcourError> {
let concour = match Concour::find_by_server_id_channel_id(&server_id, &channel_id).await {
Ok(list_concour) => list_concour,
Err(err) => {
tracing::error!(error = err.to_string(), "Error finding concour");
return Err(StartConcourError::UnknownError(
"Error finding concour".to_string(),
));
}
};
if concour.is_none() {
info!("Concour doesn't exist");
return Err(StartConcourError::DoesntExist);
}
let mut concour = concour.unwrap();
concour.last_message_id = Some(message_id);
match concour.update().await {
Ok(_) => {}
Err(err) => {
tracing::error!(error = err.to_string(), "Error updating concour");
return Err(StartConcourError::UnknownError(
"Error updating concour".to_string(),
));
}
}
Ok(())
} }

View File

@ -1,19 +1,25 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{info, instrument}; use tracing::{info, instrument};
use crate::db::concour::Concour; use crate::{
db::concour::{Concour, ConcourStatus},
event::schedule_job::{ScheduleJob, StopScheduleJob},
};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum StopConcourError { pub enum StopConcourError {
DoesntExist, DoesntExist,
AlreadyStopped,
NotScheduled,
FindError(String), FindError(String),
UnknownError(String), UnknownError(String),
} }
#[instrument(level = "info")] #[instrument(level = "info", skip(cron_scheduler))]
pub async fn stop_concour( pub async fn stop_concour(
server_id: u64, server_id: u64,
channel_id: u64, channel_id: u64,
cron_scheduler: &mut ScheduleJob,
) -> Result<Option<Concour>, StopConcourError> { ) -> Result<Option<Concour>, StopConcourError> {
let concour = match Concour::find_by_server_id_channel_id(&server_id, &channel_id).await { let concour = match Concour::find_by_server_id_channel_id(&server_id, &channel_id).await {
Ok(list_concour) => list_concour, Ok(list_concour) => list_concour,
@ -29,19 +35,43 @@ pub async fn stop_concour(
info!("Concour doesn't exist"); info!("Concour doesn't exist");
return Err(StopConcourError::DoesntExist); return Err(StopConcourError::DoesntExist);
} }
todo!("Setup logic to stop the waiting period for the concour"); let mut concour = concour.unwrap();
// let mut concour = concour.unwrap();
// // Update status to Paused if concour.status == ConcourStatus::Paused {
return Err(StopConcourError::AlreadyStopped);
}
match cron_scheduler
.stop_scheduled_job(server_id, channel_id)
.await
{
Ok(_) => {}
Err(e) => {
tracing::error!(err = e.to_string(), "Error stopping cron job");
match e {
StopScheduleJob::JobNotFound => {
tracing::error!("Job not found");
return Err(StopConcourError::NotScheduled);
}
StopScheduleJob::RemoveFailed => {
tracing::error!("Remove failed");
return Err(StopConcourError::UnknownError(
"Error stopping cron job".to_string(),
));
}
}
}
}
concour.status = ConcourStatus::Paused;
concour.last_message_id = None;
// match concour.update().await { match concour.update().await {
// Ok(_) => {} Ok(_) => {}
// Err(err) => { Err(err) => {
// tracing::error!(error = err.to_string(), "Error updating concour"); tracing::error!(error = err.to_string(), "Error updating concour");
// return Err(StopConcourError::UnknownError( return Err(StopConcourError::UnknownError(
// "Error updating concour".to_string(), "Error updating concour".to_string(),
// )); ));
// } }
// } }
// Ok(Some(concour)) Ok(Some(concour))
} }

View File

@ -3,11 +3,13 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use surrealdb::opt::Resource; use surrealdb::opt::Resource;
use surrealdb::sql::Thing;
use tracing::instrument;
use utoipa::ToSchema; use utoipa::ToSchema;
const CONCOUR: &str = "concour"; const CONCOUR: &str = "concour";
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] #[derive(Debug, Serialize, Deserialize, Clone, ToSchema, PartialEq, Eq)]
pub enum ConcourStatus { pub enum ConcourStatus {
Created, Created,
Paused, Paused,
@ -21,6 +23,19 @@ impl fmt::Display for ConcourStatus {
} }
} }
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct ConcourRecord {
pub id: Thing,
pub server_id: u64,
pub channel_id: u64,
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct ConcourWinner {
pub user_id: u64,
pub date: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] #[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct Concour { pub struct Concour {
pub server_id: u64, pub server_id: u64,
@ -34,7 +49,8 @@ pub struct Concour {
pub banner: Option<String>, pub banner: Option<String>,
pub index_keyword: u64, pub index_keyword: u64,
pub status: ConcourStatus, pub status: ConcourStatus,
pub winner: HashMap<String, (u64, chrono::DateTime<chrono::Utc>)>, pub winner: HashMap<String, ConcourWinner>,
pub last_message_id: Option<u64>,
} }
impl Default for Concour { impl Default for Concour {
@ -52,6 +68,7 @@ impl Default for Concour {
index_keyword: 0, index_keyword: 0,
status: ConcourStatus::Created, status: ConcourStatus::Created,
winner: HashMap::new(), winner: HashMap::new(),
last_message_id: None,
} }
} }
} }
@ -71,8 +88,10 @@ impl Concour {
index_keyword: 0, index_keyword: 0,
status: ConcourStatus::Created, status: ConcourStatus::Created,
winner: HashMap::new(), winner: HashMap::new(),
last_message_id: None,
}) })
} }
#[instrument(level = "info")]
pub async fn create(&self) -> Result<(), surrealdb::Error> { pub async fn create(&self) -> Result<(), surrealdb::Error> {
match DB.create(Resource::from(CONCOUR)).content(&self).await { match DB.create(Resource::from(CONCOUR)).content(&self).await {
Ok(_) => {} Ok(_) => {}
@ -82,21 +101,61 @@ impl Concour {
}; };
Ok(()) Ok(())
} }
pub async fn update(&self) -> Result<(), surrealdb::Error> { #[instrument(level = "info")]
pub async fn update(&self) -> Result<bool, surrealdb::Error> {
let id = match Concour::get_records(&self.server_id, &self.channel_id).await {
Ok(Some(concour)) => concour.id,
Ok(None) => return Ok(false),
Err(e) => {
tracing::error!("Error getting concour id: {:?}", e);
return Err(e);
}
};
match DB
.update::<Option<Concour>>((CONCOUR.to_string(), id))
.merge(self)
.await
{
Ok(concour) => {
if concour.is_none() {
return Ok(false);
}
tracing::info!("Concour updated: {:?}", concour);
Ok(true)
}
Err(e) => {
tracing::error!("Error updating concour: {:?}", e);
Err(e)
}
}
}
#[instrument(level = "info")]
pub async fn get_records(
server_id: &u64,
channel_id: &u64,
) -> Result<Option<ConcourRecord>, surrealdb::Error> {
let sql = format!( let sql = format!(
"UPDATE {} SET title = '{}', description = '{}', start_date = '{}', periode = '{}', role_récompense = {}, keywords = '{:?}', banner = '{:?}', index_keyword = {}, status = '{}', winner = '{:?}' WHERE server_id = {} and channel_id = {}", "SELECT id,server_id,channel_id FROM {} WHERE server_id = {} AND channel_id = {}",
CONCOUR, self.title, self.description, self.start_date, self.periode, self.role_recompense, self.keywords, self.banner, self.index_keyword, self.status, self.winner, self.server_id, self.channel_id CONCOUR, server_id, channel_id
); );
match DB.query(sql).await { let mut results = match DB.query(&sql).await {
Ok(res) => { Ok(results) => results,
println!("{:?}", res); Err(e) => {
return Err(e);
}
};
let concour: ConcourRecord = match results.take(0) {
Ok(Some(concour)) => concour,
Ok(None) => {
return Ok(None);
} }
Err(e) => { Err(e) => {
return Err(e); return Err(e);
} }
}; };
Ok(()) Ok(Some(concour))
} }
#[instrument(level = "info")]
pub async fn find_by_server_id_channel_id( pub async fn find_by_server_id_channel_id(
server_id: &u64, server_id: &u64,
channel_id: &u64, channel_id: &u64,
@ -122,6 +181,7 @@ impl Concour {
}; };
Ok(Some(concour)) Ok(Some(concour))
} }
#[instrument(level = "info")]
pub async fn find_by_server_id(server_id: &u64) -> Result<Vec<Concour>, surrealdb::Error> { pub async fn find_by_server_id(server_id: &u64) -> Result<Vec<Concour>, surrealdb::Error> {
let sql = format!("SELECT * FROM {} WHERE server_id = {}", CONCOUR, server_id); let sql = format!("SELECT * FROM {} WHERE server_id = {}", CONCOUR, server_id);
let mut results = match DB.query(&sql).await { let mut results = match DB.query(&sql).await {
@ -136,7 +196,7 @@ impl Concour {
} }
Ok(concours) Ok(concours)
} }
#[instrument(level = "info")]
pub async fn find_by_status(status: &ConcourStatus) -> Result<Vec<Concour>, surrealdb::Error> { pub async fn find_by_status(status: &ConcourStatus) -> Result<Vec<Concour>, surrealdb::Error> {
let sql = format!("SELECT * FROM {} WHERE status = '{}'", CONCOUR, status); let sql = format!("SELECT * FROM {} WHERE status = '{}'", CONCOUR, status);
let mut results = match DB.query(&sql).await { let mut results = match DB.query(&sql).await {

View File

@ -2,6 +2,7 @@ use super::init::DB;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use surrealdb::opt::Resource; use surrealdb::opt::Resource;
use tracing::instrument;
use utoipa::ToSchema; use utoipa::ToSchema;
const SERVER_CONFIG: &str = "server_config"; const SERVER_CONFIG: &str = "server_config";
@ -37,6 +38,7 @@ impl ServerConfig {
admin_role: Vec::new(), admin_role: Vec::new(),
}) })
} }
#[instrument(level = "info")]
pub async fn create(&self) -> Result<(), surrealdb::Error> { pub async fn create(&self) -> Result<(), surrealdb::Error> {
match DB match DB
.create(Resource::from(SERVER_CONFIG)) .create(Resource::from(SERVER_CONFIG))
@ -47,6 +49,7 @@ impl ServerConfig {
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
#[instrument(level = "info")]
pub async fn update(&self) -> Result<(), surrealdb::Error> { pub async fn update(&self) -> Result<(), surrealdb::Error> {
let sql = format!( let sql = format!(
"UPDATE {} SET enable = {}, auto_meme = {}, auto_concour = {}, admin_role = {:?} WHERE server_id = {}", "UPDATE {} SET enable = {}, auto_meme = {}, auto_concour = {}, admin_role = {:?} WHERE server_id = {}",
@ -62,6 +65,7 @@ impl ServerConfig {
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
#[instrument(level = "info")]
pub async fn find_by_server_id( pub async fn find_by_server_id(
server_id: &u64, server_id: &u64,
) -> Result<Option<ServerConfig>, surrealdb::Error> { ) -> Result<Option<ServerConfig>, surrealdb::Error> {

View File

@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use surrealdb::opt::Resource; use surrealdb::opt::Resource;
use tracing::instrument;
use utoipa::ToSchema; use utoipa::ToSchema;
use super::init::DB; use super::init::DB;
@ -21,7 +22,7 @@ impl User {
enable, enable,
}) })
} }
#[instrument(level = "info")]
pub async fn create(&self) -> Result<(), surrealdb::Error> { pub async fn create(&self) -> Result<(), surrealdb::Error> {
match DB.create(Resource::from(USERIMAGE)).content(&self).await { match DB.create(Resource::from(USERIMAGE)).content(&self).await {
Ok(_) => {} Ok(_) => {}
@ -31,7 +32,7 @@ impl User {
}; };
Ok(()) Ok(())
} }
#[instrument(level = "info")]
pub async fn update(&self) -> Result<(), surrealdb::Error> { pub async fn update(&self) -> Result<(), surrealdb::Error> {
let sql = format!( let sql = format!(
"UPDATE {} SET enable = {} WHERE server_id = {} AND user_id = {}", "UPDATE {} SET enable = {} WHERE server_id = {} AND user_id = {}",
@ -47,7 +48,7 @@ impl User {
}; };
Ok(()) Ok(())
} }
#[instrument(level = "info")]
pub async fn find_by_server_id_user_id( pub async fn find_by_server_id_user_id(
server_id: &u64, server_id: &u64,
user_id: &u64, user_id: &u64,

View File

@ -1,16 +1,33 @@
use std::collections::HashMap; use std::{
collections::HashMap,
fmt::{self, Display},
sync::Arc,
};
use tokio::sync::RwLock;
use tokio_cron_scheduler::{Job, JobScheduler}; use tokio_cron_scheduler::{Job, JobScheduler};
use tracing::{error, info, instrument}; use tracing::{error, info, instrument};
use uuid::Uuid; use uuid::Uuid;
#[derive(Debug)]
pub enum StopScheduleJob {
JobNotFound,
RemoveFailed,
}
impl Display for StopScheduleJob {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct ScheduleJob { pub struct ScheduleJob {
pub job_id: HashMap<(u64, u64), Uuid>, pub job_id: Arc<RwLock<HashMap<(u64, u64), Uuid>>>,
pub scheduler: JobScheduler, pub scheduler: JobScheduler,
} }
impl ScheduleJob { impl ScheduleJob {
#[instrument()] #[instrument(level = "info")]
pub async fn start_cron_scheduler() -> Result<Self, ()> { pub async fn start_cron_scheduler() -> Result<Self, ()> {
let scheduler = JobScheduler::new().await; let scheduler = JobScheduler::new().await;
let mut future_self = match scheduler { let mut future_self = match scheduler {
@ -41,12 +58,14 @@ impl ScheduleJob {
} }
} }
#[instrument(skip(self))] #[instrument(skip(self), level = "info")]
pub async fn stop_cron_scheduler(&mut self) -> &mut Self { pub async fn stop_cron_scheduler(&mut self) -> &mut Self {
for (server_id, channel_id) in self.job_id.keys() { let job_id = self.job_id.write().await;
for (server_id, channel_id) in job_id.keys() {
match self match self
.scheduler .scheduler
.remove(self.job_id.get(&(*server_id, *channel_id)).unwrap()) .remove(job_id.get(&(*server_id, *channel_id)).unwrap())
.await .await
{ {
Ok(_) => { Ok(_) => {
@ -57,6 +76,8 @@ impl ScheduleJob {
} }
} }
} }
drop(job_id);
match self.scheduler.shutdown().await { match self.scheduler.shutdown().await {
Ok(_) => { Ok(_) => {
info!("Cron scheduler stopped"); info!("Cron scheduler stopped");
@ -69,8 +90,8 @@ impl ScheduleJob {
} }
} }
#[instrument(skip(self))] #[instrument(skip(self), level = "info")]
pub async fn add_cron_job( pub async fn add_concour_cron_job(
&mut self, &mut self,
server_id: u64, server_id: u64,
channel_id: u64, channel_id: u64,
@ -91,7 +112,10 @@ impl ScheduleJob {
match self.scheduler.add(job).await { match self.scheduler.add(job).await {
Ok(job_uid) => { Ok(job_uid) => {
info!("Cron job added"); info!("Cron job added");
self.job_id.insert((server_id, channel_id), job_uid); {
let mut job_id = self.job_id.write().await;
job_id.insert((server_id, channel_id), job_uid);
}
Ok(job_uid) Ok(job_uid)
} }
Err(e) => { Err(e) => {
@ -100,19 +124,30 @@ impl ScheduleJob {
} }
} }
} }
#[instrument(skip(self))] #[instrument(skip(self), level = "info")]
pub async fn stop_scheduled_job(&mut self, server_id: u64, channel_id: u64) { pub async fn stop_scheduled_job(
match self.job_id.remove(&(server_id, channel_id)) { &mut self,
server_id: u64,
channel_id: u64,
) -> Result<(), StopScheduleJob> {
let remove_job = {
let mut job_id = self.job_id.write().await;
job_id.remove(&(server_id, channel_id))
};
match remove_job {
Some(job_uid) => match self.scheduler.remove(&job_uid).await { Some(job_uid) => match self.scheduler.remove(&job_uid).await {
Ok(_) => { Ok(_) => {
info!("Cron job removed"); info!("Cron job removed");
Ok(())
} }
Err(e) => { Err(e) => {
error!("Error removing cron job: {:?}", e); error!("Error removing cron job: {:?}", e);
Err(StopScheduleJob::RemoveFailed)
} }
}, },
None => { None => {
error!("Cron job not found"); error!("Cron job not found");
Err(StopScheduleJob::JobNotFound)
} }
} }
} }

View File

@ -79,7 +79,6 @@ async fn main() -> std::io::Result<()> {
.bind(("0.0.0.0", port))? .bind(("0.0.0.0", port))?
.run() .run()
.await?; .await?;
cron_scheduler.stop_scheduled_job(123, 123).await;
info!("API Server stopped."); info!("API Server stopped.");
tx_bot.send(()).unwrap(); tx_bot.send(()).unwrap();
cron_scheduler.stop_cron_scheduler().await; cron_scheduler.stop_cron_scheduler().await;