Skip to content

Commit 23bd0fd

Browse files
committed
fix command_repo transactions, update command_executor
1 parent 9156647 commit 23bd0fd

4 files changed

Lines changed: 108 additions & 32 deletions

File tree

backend/src/commands/command_controller.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use num_derive::FromPrimitive;
1111
use serde::{Deserialize, Serialize};
1212
use sqlx::Type;
1313
use std::str::FromStr;
14+
use crate::commands::command_repo::SaveCommandError;
1415

1516
#[derive(Deserialize, Serialize)]
1617
pub struct MessagePattern {
@@ -38,12 +39,11 @@ pub struct Command {
3839
#[derive(Deserialize, Serialize, Type, FromPrimitive)]
3940
#[repr(u8)]
4041
pub enum CooldownType {
41-
Seconds = 0,
42-
Messages = 1,
42+
SECONDS = 0,
43+
MESSAGES = 1,
4344
}
4445

4546
//TODO use new return type that only returns enough information to render commands table, request the entire object on edit open
46-
//TODO update command_executor
4747
pub async fn get_all_user_commands(
4848
State(state): State<AxumState>,
4949
Query(search): Query<String>,
@@ -89,7 +89,7 @@ pub async fn set_enabled(
8989
) -> AxResult<impl IntoResponse> {
9090
//TODO move into query parameter
9191
let enabled = bool::from_str(body.as_ref()).map_err(|_| StatusCode::BAD_REQUEST)?;
92-
command_repo::set_enabled(&state.prod_db, trigger_id.as_ref(), enabled)
92+
command_repo::set_enabled(state, trigger_id.as_ref(), enabled)
9393
.await
9494
.map_err(|_| {
9595
// log
@@ -104,8 +104,8 @@ pub async fn set_visible(
104104
Query(trigger_id): Query<String>,
105105
body: String
106106
) -> AxResult<impl IntoResponse> {
107-
let visible = bool::from_str(body.as_ref()).map_err(|_| StatusCode::BAD_REQUEST)?;
108-
command_repo::set_visible(&state.prod_db, trigger_id.as_ref(), visible)
107+
let visible = bool::from_str(body.as_str()).map_err(|_| StatusCode::BAD_REQUEST)?;
108+
command_repo::set_visible(&state.prod_db, trigger_id.as_str(), visible)
109109
.await
110110
.map_err(|_| {
111111
// log
@@ -118,19 +118,26 @@ pub async fn set_visible(
118118
pub async fn save(
119119
State(state): State<AxumState>,
120120
Json(to_save): Json<Command>,
121-
) -> AxResult<impl IntoResponse> {
122-
command_repo::save(&state.prod_db, &to_save).await.map_err(|_| {
123-
// log
124-
StatusCode::INTERNAL_SERVER_ERROR
125-
})?;
126-
Ok(())
121+
) -> impl IntoResponse {
122+
match command_repo::save(state, &to_save).await {
123+
Ok(()) => StatusCode::OK,
124+
Err(SaveCommandError::DbError(db_error)) => {
125+
// log
126+
StatusCode::INTERNAL_SERVER_ERROR
127+
}
128+
Err(SaveCommandError::RegexError(r)) => {
129+
// log, but actually more return. This error needs to reach the user in the panel
130+
//TODO figure out how to return this error to the user
131+
StatusCode::BAD_REQUEST
132+
}
133+
}
127134
}
128135

129136
pub async fn delete_by_id(
130137
State(state): State<AxumState>,
131138
Path(trigger_id): Path<String>,
132139
) -> AxResult<impl IntoResponse> {
133-
command_repo::delete_by_id(&state.prod_db, trigger_id.as_ref()).await.map_err(|_| {
140+
command_repo::delete_by_id(state, trigger_id.as_ref()).await.map_err(|_| {
134141
// log
135142
StatusCode::INTERNAL_SERVER_ERROR
136143
})?;

backend/src/commands/command_executor_service.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::cooldown_service::CooldownService;
22
use crate::commands::template_service::TemplateService;
33
use crate::AppState;
44
use num_derive::FromPrimitive;
5-
use regex::Regex;
5+
use regex::{Regex, RegexBuilder, RegexSet};
66
use serde::{Deserialize, Serialize};
77
use std::collections::HashMap;
88
use std::pin::Pin;
@@ -11,7 +11,8 @@ use std::time::Instant;
1111
use sqlx::Type;
1212
use tokio::sync::broadcast::error::RecvError;
1313
use tokio::sync::broadcast::Receiver;
14-
14+
use crate::commands::command_controller::{Command, CooldownType, MessagePattern};
15+
use crate::db::ProdDB;
1516
// ChatMessage
1617

1718
#[allow(dead_code)]
@@ -98,13 +99,56 @@ static TEXT_COMMAND_CALLBACK: TriggerCallback = |app_state, trigger_id, _chat_me
9899

99100
// Service
100101

102+
#[derive(Default)]
101103
pub struct CommandExecutorService {
102104
triggers: Vec<CommandTrigger>,
103105
cooldown_service: CooldownService,
104106
}
105107

106-
107108
impl CommandExecutorService {
109+
pub(crate) fn remove_command(&self, command_id: &TriggerId) {
110+
// self.triggers.retain(|c| c.id.as_ref() != command_id);
111+
}
112+
113+
pub(crate) fn upsert_command(&self, command: &Command) -> Result<(), regex::Error> {
114+
self.remove_command(command.id.as_ref());
115+
let global_cooldown = match command.global_cooldown_type {
116+
CooldownType::SECONDS => ChatCooldown::SECONDS(command.global_cooldown_amount),
117+
CooldownType::MESSAGES => ChatCooldown::MESSAGES(command.global_cooldown_amount)
118+
};
119+
let user_cooldown = match command.user_cooldown_type {
120+
CooldownType::SECONDS => ChatCooldown::SECONDS(command.user_cooldown_amount),
121+
CooldownType::MESSAGES => ChatCooldown::MESSAGES(command.user_cooldown_amount)
122+
};
123+
// self.triggers.push(CommandTrigger {
124+
// id: command.id.clone().into_boxed_str(),
125+
// global_cooldown,
126+
// user_cooldown,
127+
// permission: command.permission,
128+
// patterns: Self::convert_patterns(command.patterns.as_ref())?,
129+
// callback: TEXT_COMMAND_CALLBACK
130+
// });
131+
Ok(())
132+
}
133+
134+
pub(crate) fn refresh_patterns(&self, prod_db: &ProdDB, trigger_id: &TriggerId) {
135+
todo!()
136+
}
137+
138+
fn convert_patterns(patterns: &[MessagePattern]) -> Result<Vec<Regex>,regex::Error> {
139+
patterns.iter()
140+
.filter(|x| x.is_enabled)
141+
.map(|x1| {
142+
match x1.is_regex {
143+
true => Regex::new(x1.pattern.as_str()),
144+
false => RegexBuilder::new(format!("^{}(?: |$).*", &x1.pattern).as_str())
145+
.case_insensitive(true)
146+
.build(),
147+
}
148+
})
149+
.collect()
150+
}
151+
108152
#[allow(dead_code)]
109153
pub async fn receive_commands(&self, app_state: Arc<AppState>, mut receiver: Receiver<ChatMessage>){
110154
loop {

backend/src/commands/command_repo.rs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
use crate::axum::AxumState;
12
use crate::commands::command_controller::{Command, CooldownType, MessagePattern};
23
use crate::commands::command_executor_service::{TriggerId, TwitchUserPermission};
34
use crate::commands::template_service::StringTemplate;
45
use crate::db::ProdDB;
5-
use anyhow::{Context, Error};
6+
use anyhow::Context;
67
use num_traits::FromPrimitive;
7-
use sqlx::{query, query_as, AnyExecutor, Execute, MySql, MySqlConnection, MySqlExecutor, Transaction};
8-
use std::ops::{Deref, DerefMut};
98
use sqlx::mysql::MySqlQueryResult;
9+
use sqlx::{query, query_as, MySql, MySqlExecutor, Transaction};
10+
use std::ops::{Deref, DerefMut};
1011

1112
pub struct CommandTable {
1213
id: String,
@@ -195,15 +196,18 @@ async fn get_patterns(prod_db: &ProdDB, d: Vec<CommandTable>) -> anyhow::Result<
195196
Ok(commands)
196197
}
197198

198-
pub(crate) async fn set_enabled(prod_db: &ProdDB, trigger_id: &TriggerId, enabled: bool) -> anyhow::Result<Option<()>> {
199+
pub(crate) async fn set_enabled(state: AxumState, trigger_id: &TriggerId, enabled: bool) -> anyhow::Result<Option<()>> {
200+
let mut transaction = state.prod_db.begin().await?;
199201
let affected = query!("UPDATE `sys-chat_trigger-patterns` SET is_enabled = ? WHERE parent_trigger_id = ?", enabled, trigger_id)
200-
.execute(prod_db.deref())
202+
.execute(transaction.deref_mut())
201203
.await
202204
.context("failed to set enabled on command patterns")?
203205
.rows_affected();
204206
if affected == 0 {
205207
return Ok(None)
206208
}
209+
state.command_executor_service.refresh_patterns(&state.prod_db, trigger_id);
210+
transaction.commit().await?;
207211
Ok(Some(()))
208212
}
209213

@@ -219,8 +223,25 @@ pub(crate) async fn set_visible(prod_db: &ProdDB, trigger_id: &TriggerId, visibl
219223
Ok(Some(()))
220224
}
221225

222-
pub(crate) async fn save(prod_db: &ProdDB, command: &Command) -> anyhow::Result<()> {
223-
let mut transaction = prod_db.begin().await?;
226+
pub(crate) enum SaveCommandError {
227+
DbError(anyhow::Error),
228+
RegexError(regex::Error),
229+
}
230+
231+
pub(crate) async fn save(state: AxumState, command: &Command) -> Result<(), SaveCommandError> {
232+
let mut transaction = state.prod_db.begin().await.context("failed to start save command transaction")
233+
.map_err(|e| SaveCommandError::DbError(e))?;
234+
save_command(&command, &mut transaction).await
235+
.map_err(|e| SaveCommandError::DbError(e))?;
236+
237+
state.command_executor_service.upsert_command(&command)
238+
.map_err(|e| SaveCommandError::RegexError(e))?;
239+
transaction.commit().await.context("failed to commit save command transaction")
240+
.map_err(|e| SaveCommandError::DbError(e))?;
241+
Ok(())
242+
}
243+
244+
async fn save_command<'a>(command: &Command, transaction: &mut Transaction<'a, MySql>) -> anyhow::Result<()> {
224245
if let Some(template) = &command.template {
225246
save_template(transaction.deref_mut(), template).await?;
226247
}
@@ -233,14 +254,13 @@ pub(crate) async fn save(prod_db: &ProdDB, command: &Command) -> anyhow::Result<
233254
id = ?, is_auto_generated = ?, description = ?, global_cooldown_amount = ?, global_cooldown_type = ?, permission = ?, user_cooldown_amount = ?, user_cooldown_type = ?, template_id = ?
234255
"#, command.id, command.is_auto_generated, command.description, command.global_cooldown_amount, command.global_cooldown_type, command.permission, command.user_cooldown_amount, command.user_cooldown_type, template_id,
235256
command.id, command.is_auto_generated, command.description, command.global_cooldown_amount, command.global_cooldown_type, command.permission, command.user_cooldown_amount, command.user_cooldown_type, template_id,
236-
)
237-
.execute(prod_db.deref())
257+
).execute(transaction.deref_mut())
238258
.await
239259
.context("failed to set visible on command patterns")?;
240260
Ok(())
241261
}
242262

243-
async fn save_pattern<'a, E: MySqlExecutor<'a>>(prod_db: E, pattern: &MessagePattern, command_id: &str) -> Result<MySqlQueryResult, Error> {
263+
async fn save_pattern<'a, E: MySqlExecutor<'a>>(prod_db: E, pattern: &MessagePattern, command_id: &str) -> Result<MySqlQueryResult, anyhow::Error> {
244264
query!(r#"INSERT `sys-chat_trigger-patterns` (pattern, is_enabled, is_regex, is_visible, parent_trigger_id)
245265
VALUE (?, ?, ?, ? , ?) ON DUPLICATE KEY UPDATE
246266
pattern = ?, is_enabled = ?, is_regex = ?, is_visible = ?, parent_trigger_id = ?
@@ -251,23 +271,25 @@ async fn save_pattern<'a, E: MySqlExecutor<'a>>(prod_db: E, pattern: &MessagePat
251271
.context("failed to save command template")
252272
}
253273

254-
async fn save_template<'a, E: MySqlExecutor<'a>>(prod_db: E, template: &StringTemplate) -> Result<MySqlQueryResult, Error> {
274+
async fn save_template<'a, E: MySqlExecutor<'a>>(prod_db: E, template: &StringTemplate) -> Result<MySqlQueryResult, anyhow::Error> {
255275
query!("INSERT `sys-string_templates` (id, message_color, template) VALUE (?, ?, ?) ON DUPLICATE KEY UPDATE template = ?, message_color = ?",
256276
template.id, template.template, template.message_color, template.template, template.message_color)
257277
.execute(prod_db)
258278
.await
259279
.context("failed to save command template")
260280
}
261281

262-
pub(crate) async fn delete_by_id(prod_db: &ProdDB, trigger_id: &TriggerId) -> anyhow::Result<()> {
263-
query!("DELETE FROM `sys-string_templates` WHERE id = (SELECT template_id FROM `sys-chat_trigger-trigger` WHERE id = ?)", trigger_id)
264-
.execute(prod_db.deref())
282+
pub(crate) async fn delete_by_id(state: AxumState, trigger_id: &TriggerId) -> anyhow::Result<()> {
283+
let pool = state.prod_db.deref();
284+
query!("DELETE FROM `sys-string_templates` WHERE id = (SELECT template_id FROM `sys-chat_trigger-trigger` WHERE id = ?)", trigger_id)
285+
.execute(pool)
265286
.await
266287
.context("failed to delete string template")?;
267288
query!("DELETE FROM `sys-chat_trigger-trigger` WHERE id = ?", trigger_id)
268-
.execute(prod_db.deref())
289+
.execute(pool)
269290
.await
270291
.context("failed to delete command trigger")?;
292+
state.command_executor_service.remove_command(&trigger_id);
271293
Ok(())
272294
}
273295

backend/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::str::FromStr;
1111
use std::sync::{Arc, RwLock};
1212
use tokio::runtime::Handle;
1313
use url::Url;
14+
use crate::commands::command_executor_service::CommandExecutorService;
1415

1516
mod oauth_service;
1617
mod webserver_authentication;
@@ -40,7 +41,8 @@ pub async fn start() {
4041
webserver_config: RwLock::new(WebserverConfig {
4142
panel_base_url: Url::from_str("http://localhost:5173").unwrap(),
4243
server_base_url: Url::from_str("http://localhost:4771").unwrap(),
43-
})
44+
}),
45+
command_executor_service: CommandExecutorService::default(),
4446
});
4547
let a2 = app_state.clone();
4648
Handle::current().spawn(async { axum::axum(5000, a2).await });
@@ -73,6 +75,7 @@ struct AppState {
7375
pub oauth_service: OAuthService,
7476
pub webserver_config: RwLock<WebserverConfig>,
7577
pub twitch_service: TwitchService,
78+
pub command_executor_service: CommandExecutorService
7679
}
7780

7881
#[allow(dead_code)]

0 commit comments

Comments
 (0)