diff --git a/.vscode/launch.json b/.vscode/launch.json index 741b23a5..27393a06 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ }, "args": [], "env": { - "RUST_LOG": "trace" + "RUST_LOG": "debug" }, "cwd": "${workspaceFolder}" }, diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 17d13bc4..1a0f2e63 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -1,11 +1,11 @@ -use actix_web::{web, HttpResponse, Result}; +use actix_web::{HttpRequest, HttpResponse, Result, web}; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; use diesel::pg::PgConnection; use diesel::prelude::*; -use log::{error, warn}; +use log::{error}; use redis::Client; use std::collections::HashMap; use std::sync::Arc; @@ -148,6 +148,7 @@ impl AuthService { #[actix_web::get("/api/auth")] async fn auth_handler( + req: HttpRequest, data: web::Data, web::Query(params): web::Query>, ) -> Result { @@ -166,45 +167,10 @@ async fn auth_handler( } }; - let bot_id = if let Ok(bot_guid) = std::env::var("BOT_GUID") { - match Uuid::parse_str(&bot_guid) { - Ok(uuid) => uuid, - Err(e) => { - warn!("Invalid BOT_GUID from env: {}", e); - return Ok(HttpResponse::BadRequest() - .json(serde_json::json!({"error": "Invalid BOT_GUID"}))); - } - } - } else { - // BOT_GUID not set, get first available bot from database - use crate::shared::models::schema::bots::dsl::*; - use diesel::prelude::*; - - let mut db_conn = data.conn.lock().unwrap(); - match bots - .filter(is_active.eq(true)) - .select(id) - .first::(&mut *db_conn) - .optional() - { - Ok(Some(first_bot_id)) => { - log::info!( - "BOT_GUID not set, using first available bot: {}", - first_bot_id - ); - first_bot_id - } - Ok(None) => { - error!("No active bots found in database"); - return Ok(HttpResponse::ServiceUnavailable() - .json(serde_json::json!({"error": "No bots available"}))); - } - Err(e) => { - error!("Failed to query bots: {}", e); - return Ok(HttpResponse::InternalServerError() - .json(serde_json::json!({"error": "Failed to query bots"}))); - } - } + let mut db_conn = data.conn.lock().unwrap(); + let (bot_id, bot_name) = match crate::bot::bot_from_url(&mut *db_conn, req.path()) { + Ok((id, name)) => (id, name), + Err(res) => return Ok(res), }; let session = { @@ -224,35 +190,40 @@ async fn auth_handler( } }; - let session_id_clone = session.id.clone(); - let auth_script_path = "./templates/annoucements.gbai/annoucements.gbdialog/auth.bas"; - let auth_script = match std::fs::read_to_string(auth_script_path) { - Ok(content) => content, - Err(_) => r#"SET_USER "00000000-0000-0000-0000-000000000001""#.to_string(), - }; - - let script_service = crate::basic::ScriptService::new(Arc::clone(&data), session.clone()); - match script_service - .compile(&auth_script) - .and_then(|ast| script_service.run(&ast)) - { - Ok(result) => { - if result.to_string() == "false" { - error!("Auth script returned false, authentication failed"); - return Ok(HttpResponse::Unauthorized() - .json(serde_json::json!({"error": "Authentication failed"}))); + let auth_script_path = format!("./work/{}.gbai/{}.gbdialog/auth.ast", bot_name, bot_name); + if std::path::Path::new(&auth_script_path).exists() { + let auth_script = match std::fs::read_to_string(&auth_script_path) { + Ok(content) => content, + Err(e) => { + error!("Failed to read auth script: {}", e); + return Ok(HttpResponse::InternalServerError() + .json(serde_json::json!({"error": "Failed to read auth script"}))); + } + }; + + let script_service = crate::basic::ScriptService::new(Arc::clone(&data), session.clone()); + match script_service + .compile(&auth_script) + .and_then(|ast| script_service.run(&ast)) + { + Ok(result) => { + if result.to_string() == "false" { + error!("Auth script returned false, authentication failed"); + return Ok(HttpResponse::Unauthorized() + .json(serde_json::json!({"error": "Authentication failed"}))); + } + } + Err(e) => { + error!("Failed to run auth script: {}", e); + return Ok(HttpResponse::InternalServerError() + .json(serde_json::json!({"error": "Auth failed"}))); } - } - Err(e) => { - error!("Failed to run auth script: {}", e); - return Ok(HttpResponse::InternalServerError() - .json(serde_json::json!({"error": "Auth failed"}))); } } let session = { let mut sm = data.session_manager.lock().await; - match sm.get_session_by_id(session_id_clone) { + match sm.get_session_by_id(session.id) { Ok(Some(s)) => s, Ok(None) => { error!("Failed to retrieve session"); diff --git a/src/bot/mod.rs b/src/bot/mod.rs index f4b90e72..db54784e 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -3,6 +3,7 @@ use crate::shared::models::{BotResponse, UserMessage, UserSession}; use crate::shared::state::AppState; use actix_web::{web, HttpRequest, HttpResponse, Result}; use actix_ws::Message as WsMessage; +use diesel::PgConnection; use log::{debug, error, info, warn}; use chrono::Utc; use serde_json; @@ -826,6 +827,52 @@ impl BotOrchestrator { } } +pub fn bot_from_url( + db_conn: &mut PgConnection, + path: &str +) -> Result<(Uuid, String), HttpResponse> { + use crate::shared::models::schema::bots::dsl::*; + use diesel::prelude::*; + + // Extract bot name from first path segment + if let Some(bot_name) = path.split('/').nth(1).filter(|s| !s.is_empty()) { + match bots + .filter(name.eq(bot_name)) + .filter(is_active.eq(true)) + .select((id, name)) + .first::<(Uuid, String)>(db_conn) + .optional() + { + Ok(Some((bot_id, bot_name))) => return Ok((bot_id, bot_name)), + Ok(None) => warn!("No active bot found with name: {}", bot_name), + Err(e) => error!("Failed to query bot by name: {}", e), + } + } + + // Fall back to first available bot + match bots + .filter(is_active.eq(true)) + .select((id, name)) + .first::<(Uuid, String)>(db_conn) + .optional() + { + Ok(Some((first_bot_id, first_bot_name))) => { + log::info!("Using first available bot: {} ({})", first_bot_id, first_bot_name); + Ok((first_bot_id, first_bot_name)) + } + Ok(None) => { + error!("No active bots found in database"); + Err(HttpResponse::ServiceUnavailable() + .json(serde_json::json!({"error": "No bots available"}))) + } + Err(e) => { + error!("Failed to query bots: {}", e); + Err(HttpResponse::InternalServerError() + .json(serde_json::json!({"error": "Failed to query bots"}))) + } + } +} + impl Default for BotOrchestrator { fn default() -> Self { Self { @@ -1013,7 +1060,9 @@ async fn websocket_handler( error!("Error processing WebSocket message {}: {}", message_count, e); } } - WsMessage::Close(_) => { + WsMessage::Close(reason) => { + debug!("WebSocket closing for session {} - reason: {:?}", session_id_clone2, reason); + let bot_id = { use crate::shared::models::schema::bots::dsl::*; use diesel::prelude::*; @@ -1037,7 +1086,8 @@ async fn websocket_handler( } }; - orchestrator + debug!("Sending session_end event for {}", session_id_clone2); + if let Err(e) = orchestrator .send_event( &user_id_clone, &bot_id, @@ -1047,12 +1097,19 @@ async fn websocket_handler( serde_json::json!({}), ) .await - .ok(); + { + error!("Failed to send session_end event: {}", e); + } + debug!("Removing WebSocket connection for {}", session_id_clone2); web_adapter.remove_connection(&session_id_clone2).await; + + debug!("Unregistering response channel for {}", session_id_clone2); orchestrator .unregister_response_channel(&session_id_clone2) .await; + + info!("WebSocket fully closed for session {}", session_id_clone2); break; } _ => {} diff --git a/src/main.rs b/src/main.rs index 6e3f1322..a16bf34d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -285,25 +285,21 @@ async fn main() -> std::io::Result<()> { .wrap(cors) .wrap(Logger::default()) .wrap(Logger::new("HTTP REQUEST: %a %{User-Agent}i")) - .app_data(web::Data::from(app_state_clone)); - - app = app - .service(upload_file) - .service(index) - .service(static_files) - .service(websocket_handler) + .app_data(web::Data::from(app_state_clone)) .service(auth_handler) - .service(whatsapp_webhook_verify) + .service(chat_completions_local) + .service(create_session) + .service(embeddings_local) + .service(get_session_history) + .service(get_sessions) + .service(index) + .service(start_session) + .service(upload_file) .service(voice_start) .service(voice_stop) - .service(create_session) - .service(get_sessions) - .service(start_session) - .service(get_session_history) - .service(chat_completions_local) - .service(embeddings_local) - .service(bot_index); // Must be last - catches all remaining paths - + .service(whatsapp_webhook_verify) + .service(websocket_handler); + #[cfg(feature = "email")] { app = app @@ -313,9 +309,12 @@ async fn main() -> std::io::Result<()> { .service(send_email) .service(save_draft) .service(save_click); - } + } + app = app.service(static_files); + app = app.service(bot_index); app + }) .workers(worker_count) .bind((config.server.host.clone(), config.server.port))? diff --git a/src/web_server/mod.rs b/src/web_server/mod.rs index bcbebf37..a9a9fedf 100644 --- a/src/web_server/mod.rs +++ b/src/web_server/mod.rs @@ -26,7 +26,7 @@ async fn bot_index(req: HttpRequest) -> Result { } } -#[actix_web::get("/{filename:.*}")] +#[actix_web::get("/static/{filename:.*}")] async fn static_files(req: HttpRequest) -> Result { let filename = req.match_info().query("filename"); let path = format!("web/html/{}", filename);