Skip to content

Commit 1f4b40c

Browse files
committed
split state into phases, impl command and message websocket listener tasks, impl processing each message in new task
1 parent 811d3c1 commit 1f4b40c

4 files changed

Lines changed: 83 additions & 40 deletions

File tree

backend/src/axum.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::commands::command_controller::{delete_by_id, get_all_commands, get_al
22
use crate::panel_frontend::embedded_panel_server::{embedded_panel_service, to_panel_redirect, SERVER_PANEL_PATH};
33
use crate::service_oauth::oauth_endpoint::{list_oauth, receive_oauth};
44
use crate::webserver_authentication::auth_mod;
5-
use crate::AppState;
5+
use crate::{WebserverState};
66
use axum::middleware::from_fn_with_state;
77
use axum::routing::{any, delete, get, post};
88
use axum::Router;
@@ -12,7 +12,7 @@ use std::sync::Arc;
1212
use tower_http::cors::CorsLayer;
1313
use url::{form_urlencoded, Url};
1414

15-
pub type AxumState = Arc<AppState>;
15+
pub type AxumState = Arc<WebserverState>;
1616

1717
pub async fn axum(on_port: u16, state: AxumState) {
1818
let webserver_config = {

backend/src/commands/command_executor_service.rs

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

1816
#[allow(dead_code)]
@@ -66,7 +64,7 @@ pub struct ChatMessage {
6664

6765
pub type TriggerId = str;
6866
// pub type TriggerCallback = fn(&AppState, &TriggerId, &ChatMessage) ;
69-
pub type TriggerCallback = fn(Arc<AppState>, Box<TriggerId>, ChatMessage) -> Pin<Box<dyn Future<Output=()>>>;
67+
pub type TriggerCallback = fn(Arc<FullState>, Box<TriggerId>, ChatMessage) -> Pin<Box<dyn Future<Output=()>>>;
7068

7169
#[derive(Deserialize, Serialize)]
7270
pub enum ChatCooldown {
@@ -84,7 +82,7 @@ pub struct CommandTrigger {
8482
}
8583

8684
static TEXT_COMMAND_CALLBACK: TriggerCallback = |app_state, trigger_id, _chat_message| Box::pin(async move {
87-
match TemplateService::get_template_by_trigger_id(&app_state.prod_db, trigger_id.as_ref()).await {
85+
match TemplateService::get_template_by_trigger_id(&app_state.l1.prod_db, trigger_id.as_ref()).await {
8886
Err(_e) => {
8987
// log
9088
// logger.debug("Executing text command {}", commandId);
@@ -93,7 +91,7 @@ static TEXT_COMMAND_CALLBACK: TriggerCallback = |app_state, trigger_id, _chat_me
9391
// log
9492
// logger.error("Could not find template id for command id {}", commandId);
9593
}
96-
Ok(Some(t)) => app_state.twitch_service.send_raw_template(t.template.as_ref(), HashMap::new())
94+
Ok(Some(t)) => app_state.l2.twitch_service.send_raw_template(t.template.as_ref(), HashMap::new())
9795
}
9896
});
9997

@@ -151,24 +149,13 @@ impl CommandExecutorService {
151149
.collect()
152150
}
153151

154-
#[allow(dead_code)]
155-
pub async fn receive_commands(&self, app_state: Arc<AppState>, mut receiver: Receiver<ChatMessage>){
156-
loop {
157-
let message = match receiver.recv().await {
158-
Ok(m) => m,
159-
Err(RecvError::Closed) => return,
160-
Err(RecvError::Lagged(_s)) => {
161-
// log skip
162-
continue;
163-
}
164-
};
165-
for trigger in self.triggers.iter() {
166-
self.execute_trigger_if_matching(app_state.clone(), trigger, message.clone()).await
167-
}
152+
pub async fn process_chat_message(&self, app_state: Arc<FullState>, message: ChatMessage) {
153+
for trigger in self.triggers.iter() {
154+
self.execute_trigger_if_matching(app_state.clone(), trigger, message.clone()).await
168155
}
169156
}
170157

171-
async fn execute_trigger_if_matching(&self, app_state: Arc<AppState>, trigger: &CommandTrigger, chat_message: ChatMessage) {
158+
async fn execute_trigger_if_matching(&self, app_state: Arc<FullState>, trigger: &CommandTrigger, chat_message: ChatMessage) {
172159
if chat_message.user.permission < trigger.permission {
173160
// logger.debug("User {} with {}, missing {} permission for command {}", message.user().name(), message.user().permission(), trigger.permission(), trigger.id());
174161
return;

backend/src/lib.rs

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
use crate::commands::command_executor_service::CommandExecutorService;
1+
use crate::commands::command_executor_service::{ChatMessage, CommandExecutorService};
22
use crate::commands::twitch_service::TwitchService;
33
use crate::db::ProdDB;
4-
use service_oauth::oauth_service::OAuthService;
54
use crate::session_service::SessionService;
6-
use rand::distr::Alphanumeric;
7-
use rand::Rng;
85
use serde::{Deserialize, Serialize};
6+
use service_oauth::oauth_service::OAuthService;
97
use sqlx::MySqlPool;
108
use std::str::FromStr;
11-
use std::sync::{Arc, RwLock};
9+
use std::sync::{Arc, OnceLock, RwLock};
1210
use tokio::runtime::Handle;
11+
use tokio::sync::broadcast::error::RecvError;
1312
use url::Url;
1413

1514
mod webserver_authentication;
@@ -26,27 +25,71 @@ pub async fn start() {
2625
//check if all mandatory configuration values are set
2726
//start mini webserver
2827

28+
// L0, mini-webserver stage
2929
let db_connection = MySqlPool::connect(std::env::var("DATABASE_URL").unwrap().as_str()).await.unwrap();
3030
let prod_db = ProdDB::new(db_connection);
3131
println!("established db connection");
3232

33-
let app_state = Arc::new(AppState {
34-
session_service: SessionService::default(),
35-
oauth_service: OAuthService::default(),
36-
twitch_service: TwitchService::new(),
37-
prod_db,
33+
// 1st Stage
34+
let l1 = Arc::new(L1State {
3835
webserver_config: RwLock::new(WebserverConfig {
3936
panel_base_url: Url::from_str("http://localhost:4771/panel").unwrap(),
4037
server_base_url: Url::from_str("http://localhost:4771").unwrap(),
4138
panel_auth_twitch_client_id: "zmxjjn3xmncg8ewew6tjk08tub26bb".to_string()
4239
}),
43-
command_executor_service: CommandExecutorService::default(),
40+
oauth_service: Default::default(),
41+
session_service: Default::default(),
42+
prod_db,
4443
});
45-
let a2 = app_state.clone();
44+
45+
// Start Prod Webserver
46+
let webserver_state = Arc::new(WebserverState {
47+
l1: l1.clone(),
48+
l2: OnceLock::new(),
49+
});
50+
let a2 = webserver_state.clone();
4651
Handle::current().spawn(async { axum::axum(4771, a2).await });
4752

48-
let oauth = app_state.oauth_service.new_oauth_request("twitch".to_string(), "account".to_string(), "".to_string(), OAuthService::random_state());
53+
// 2nc Stage
54+
let service = TwitchService::new(l1.clone(), ()).await.unwrap();
55+
let l2 = Arc::new(L2State {
56+
twitch_service: service,
57+
command_executor_service: Default::default(),
58+
});
59+
60+
// 3rd. (Full) Stage
61+
let _ = webserver_state.l2.set(l2.clone());
62+
let full = Arc::new(FullState {
63+
l1,
64+
l2,
65+
});
66+
67+
let oauth = full.l1.oauth_service.new_oauth_request("twitch".to_string(), "account".to_string(), "".to_string(), OAuthService::random_state());
4968
println!("oauth: {:?}", oauth);
69+
70+
let (command_channel, _) = tokio::sync::broadcast::channel::<Box<ChatMessage>>(20);
71+
// fanout of messages
72+
let full2 = full.clone();
73+
let sender2 = command_channel.clone();
74+
Handle::current().spawn(async { TwitchService::start_websocket(full2, sender2) });
75+
let mut receiver = command_channel.subscribe();
76+
let full2 = full.clone();
77+
Handle::current().spawn(async {
78+
loop {
79+
let message = match receiver.recv().await {
80+
Ok(m) => *m,
81+
Err(RecvError::Closed) => return,
82+
Err(RecvError::Lagged(_s)) => {
83+
// log skip
84+
continue;
85+
}
86+
};
87+
let full = full2.clone();
88+
Handle::current().spawn(async move {
89+
CommandExecutorService::process_chat_message(&full.l2.command_executor_service, full, message).await;
90+
});
91+
}
92+
});
5093
}
5194

5295
#[derive(Clone, Deserialize, Serialize)]
@@ -68,15 +111,28 @@ struct WebserverConfig {
68111
// maybe we need to add stuff like cors and disable auth here
69112
}
70113

71-
struct AppState {
114+
struct L1State {
72115
pub prod_db: ProdDB,
73116
pub session_service: SessionService,
74117
pub oauth_service: OAuthService,
75118
pub webserver_config: RwLock<WebserverConfig>,
119+
}
120+
121+
struct L2State {
76122
pub twitch_service: TwitchService,
77123
pub command_executor_service: CommandExecutorService
78124
}
79125

126+
struct WebserverState {
127+
pub l1: Arc<L1State>,
128+
pub l2: OnceLock<Arc<L2State>>,
129+
}
130+
131+
struct FullState {
132+
pub l1: Arc<L1State>,
133+
pub l2: Arc<L2State>,
134+
}
135+
80136
#[allow(dead_code)]
81137
mod _services {
82138
struct SetupWebserver;

backend/src/service_oauth/oauth_endpoint.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub async fn receive_oauth(
3434
Path(service): Path<String>,
3535
Query(query): Query<ReceiveOAuthQuery>,
3636
) -> AxResult<impl IntoResponse> {
37-
let panel_base_url = if let Ok(v) = state.webserver_config.read() {
37+
let panel_base_url = if let Ok(v) = state.l1.webserver_config.read() {
3838
v.panel_base_url.as_str().to_string()
3939
} else {
4040
// log lock poisoned
@@ -50,7 +50,7 @@ pub async fn receive_oauth(
5050
if query.code.is_none() {
5151
return Ok(Body::new(format!("{}?success=false&error={}", panel_base_url, url_encode("Query param code is required for non error Oauth response"))))
5252
}
53-
Ok(Body::new(format!("{}{}", panel_base_url, match state.oauth_service.return_oauth(service, query.state, query.scope.unwrap(), query.code.unwrap()) {
53+
Ok(Body::new(format!("{}{}", panel_base_url, match state.l1.oauth_service.return_oauth(service, query.state, query.scope.unwrap(), query.code.unwrap()) {
5454
Ok(()) => format!("{}?success=true", panel_base_url),
5555
Err(OauthReturnError::NotRequested) => format!("{}?success=false&error={}", panel_base_url, url_encode("This oauth was never requested from the bot")),
5656
Err(OauthReturnError::ReturnChannelClosed) => {
@@ -61,5 +61,5 @@ pub async fn receive_oauth(
6161
}
6262

6363
pub async fn list_oauth(State(state): State<AxumState>) -> impl IntoResponse {
64-
Json(state.oauth_service.get_active_requests())
64+
Json(state.l1.oauth_service.get_active_requests())
6565
}

0 commit comments

Comments
 (0)