diff --git a/Cargo.toml b/Cargo.toml index 55c01b15a..772d96cfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ features = ["database", "i18n"] [features] # ===== SINGLE DEFAULT FEATURE SET ===== -default = ["chat", "drive", "tasks", "automation", "cache"] +default = ["chat", "drive", "tasks", "automation", "cache", "directory"] # ===== COMMUNICATION APPS ===== chat = [] @@ -30,14 +30,17 @@ tasks = ["dep:cron", "automation"] project=["quick-xml"] goals = [] workspace = [] -productivity = ["calendar", "tasks", "project", "goals", "workspace", "cache"] +workspaces = ["workspace"] +tickets = [] +billing = [] +productivity = ["calendar", "tasks", "project", "goals", "workspaces", "cache"] # ===== DOCUMENT APPS ===== paper = ["docs", "dep:pdf-extract"] docs = ["docx-rs", "ooxmlsdk"] sheet = ["calamine", "spreadsheet-ods"] slides = ["ooxmlsdk"] -drive = ["dep:aws-config", "dep:aws-sdk-s3", "dep:pdf-extract"] +drive = ["dep:aws-config", "dep:aws-sdk-s3", "dep:aws-smithy-async", "dep:pdf-extract"] documents = ["paper", "docs", "sheet", "slides", "drive"] # ===== MEDIA APPS ===== @@ -127,7 +130,7 @@ num-format = "0.4" once_cell = "1.18.0" rand = "0.9.2" regex = "1.11" -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "multipart", "stream"] } +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "multipart", "stream", "json"] } serde = { version = "1.0", default-features = false, features = ["derive", "std"] } serde_json = "1.0" toml = "0.8" @@ -176,8 +179,9 @@ calamine = { version = "0.26", optional = true } spreadsheet-ods = { version = "1.0", optional = true } # File Storage & Drive (drive feature) -aws-config = { version = "1.8.8", default-features = false, optional = true } -aws-sdk-s3 = { version = "1.109.0", default-features = false, optional = true } +aws-config = { version = "1.8.8", default-features = false, features = ["behavior-version-latest", "rt-tokio", "rustls"], optional = true } +aws-sdk-s3 = { version = "1.109.0", default-features = false, features = ["rt-tokio", "rustls"], optional = true } +aws-smithy-async = { version = "1.2", features = ["rt-tokio"], optional = true } pdf-extract = { version = "0.10.0", optional = true } quick-xml = { version = "0.37", optional=true, features = ["serialize"] } flate2 = { version = "1.0", optional = false } diff --git a/src/auto_task/app_generator.rs b/src/auto_task/app_generator.rs index 8403af644..0d265171c 100644 --- a/src/auto_task/app_generator.rs +++ b/src/auto_task/app_generator.rs @@ -9,7 +9,7 @@ use crate::auto_task::task_manifest::{ use crate::basic::keywords::table_definition::{ generate_create_table_sql, FieldDefinition, TableDefinition, }; -use crate::core::config::ConfigManager; + use crate::core::shared::get_content_type; use crate::core::shared::models::UserSession; use crate::core::shared::state::{AgentActivity, AppState}; @@ -20,7 +20,7 @@ use diesel::sql_query; use log::{error, info, trace, warn}; use serde::{Deserialize, Serialize}; use std::sync::Arc; -use tokio::sync::mpsc; + use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/auto_task/designer_ai.rs b/src/auto_task/designer_ai.rs index cd8403e14..7ae266390 100644 --- a/src/auto_task/designer_ai.rs +++ b/src/auto_task/designer_ai.rs @@ -1,4 +1,4 @@ -use crate::core::config::ConfigManager; + use crate::shared::models::UserSession; use crate::shared::state::AppState; use chrono::{DateTime, Utc}; diff --git a/src/auto_task/intent_classifier.rs b/src/auto_task/intent_classifier.rs index 8db5bd931..a3fb59118 100644 --- a/src/auto_task/intent_classifier.rs +++ b/src/auto_task/intent_classifier.rs @@ -1,7 +1,7 @@ use crate::auto_task::app_generator::AppGenerator; use crate::auto_task::intent_compiler::IntentCompiler; use crate::basic::ScriptService; -use crate::core::config::ConfigManager; + use crate::shared::models::UserSession; use crate::shared::state::AppState; use chrono::{DateTime, Utc}; diff --git a/src/auto_task/intent_compiler.rs b/src/auto_task/intent_compiler.rs index 24ef7bd60..708c2bd8a 100644 --- a/src/auto_task/intent_compiler.rs +++ b/src/auto_task/intent_compiler.rs @@ -1,4 +1,4 @@ -use crate::core::config::ConfigManager; + use crate::shared::models::UserSession; use crate::shared::state::AppState; use chrono::{DateTime, Utc}; diff --git a/src/basic/keywords/create_site.rs b/src/basic/keywords/create_site.rs index 73e9aabfc..413ae3a09 100644 --- a/src/basic/keywords/create_site.rs +++ b/src/basic/keywords/create_site.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "llm")] use crate::llm::LLMProvider; use crate::shared::models::UserSession; use crate::shared::state::AppState; @@ -11,6 +12,10 @@ use std::io::Read; use std::path::PathBuf; use std::sync::Arc; +// When llm feature is disabled, create a dummy trait for type compatibility +#[cfg(not(feature = "llm"))] +trait LLMProvider: Send + Sync {} + pub fn create_site_keyword(state: &AppState, user: UserSession, engine: &mut Engine) { let state_clone = state.clone(); let user_clone = user; @@ -42,9 +47,9 @@ pub fn create_site_keyword(state: &AppState, user: UserSession, engine: &mut Eng let bot_id = user_clone.bot_id.to_string(); #[cfg(feature = "llm")] - let llm: Option> = Some(state_clone.llm_provider.clone()); + let llm = Some(state_clone.llm_provider.clone()); #[cfg(not(feature = "llm"))] - let llm: Option> = None; + let llm: Option<()> = None; let fut = create_site(config, s3, bucket, bot_id, llm, alias, template_dir, prompt); let result = @@ -56,6 +61,7 @@ pub fn create_site_keyword(state: &AppState, user: UserSession, engine: &mut Eng .expect("valid syntax registration"); } +#[cfg(feature = "llm")] async fn create_site( config: crate::core::config::AppConfig, s3: Option>, @@ -96,6 +102,47 @@ async fn create_site( Ok(format!("/apps/{}", alias_str)) } +#[cfg(not(feature = "llm"))] +async fn create_site( + config: crate::core::config::AppConfig, + s3: Option>, + bucket: String, + bot_id: String, + _llm: Option<()>, + alias: Dynamic, + template_dir: Dynamic, + prompt: Dynamic, +) -> Result> { + let alias_str = alias.to_string(); + let template_dir_str = template_dir.to_string(); + let prompt_str = prompt.to_string(); + + info!( + "CREATE SITE: {} from template {}", + alias_str, template_dir_str + ); + + let base_path = PathBuf::from(&config.site_path); + let template_path = base_path.join(&template_dir_str); + + let combined_content = load_templates(&template_path)?; + + let generated_html = generate_html_from_prompt(_llm, &combined_content, &prompt_str).await?; + + let drive_path = format!("apps/{}", alias_str); + store_to_drive(s3.as_ref(), &bucket, &bot_id, &drive_path, &generated_html).await?; + + let serve_path = base_path.join(&alias_str); + sync_to_serve_path(&serve_path, &generated_html, &template_path)?; + + info!( + "CREATE SITE: {} completed, available at /apps/{}", + alias_str, alias_str + ); + + Ok(format!("/apps/{}", alias_str)) +} + fn load_templates(template_path: &std::path::Path) -> Result> { let mut combined_content = String::new(); @@ -129,6 +176,7 @@ fn load_templates(template_path: &std::path::Path) -> Result>, templates: &str, @@ -196,6 +244,16 @@ OUTPUT: Complete index.html file only, no explanations."#, Ok(html) } +#[cfg(not(feature = "llm"))] +async fn generate_html_from_prompt( + _llm: Option<()>, + _templates: &str, + prompt: &str, +) -> Result> { + debug!("LLM feature not enabled, using placeholder HTML"); + Ok(generate_placeholder_html(prompt)) +} + fn extract_html_from_response(response: &str) -> String { let trimmed = response.trim(); diff --git a/src/basic/keywords/mod.rs b/src/basic/keywords/mod.rs index 9fd7b6b32..a0fbdde53 100644 --- a/src/basic/keywords/mod.rs +++ b/src/basic/keywords/mod.rs @@ -27,6 +27,7 @@ pub mod hear_talk; pub mod http_operations; pub mod human_approval; pub mod last; +#[cfg(feature = "llm")] pub mod llm_keyword; #[cfg(feature = "llm")] pub mod llm_macros; diff --git a/src/basic/mod.rs b/src/basic/mod.rs index e0d8ba20f..583847284 100644 --- a/src/basic/mod.rs +++ b/src/basic/mod.rs @@ -45,6 +45,7 @@ use self::keywords::use_tool::use_tool_keyword; use self::keywords::use_website::{clear_websites_keyword, use_website_keyword}; use self::keywords::web_data::register_web_data_keywords; use self::keywords::webhook::webhook_keyword; +#[cfg(feature = "llm")] use self::keywords::llm_keyword::llm_keyword; use self::keywords::on::on_keyword; use self::keywords::print::print_keyword; @@ -132,6 +133,7 @@ impl ScriptService { first_keyword(&mut engine); last_keyword(&mut engine); format_keyword(&mut engine); + #[cfg(feature = "llm")] llm_keyword(state.clone(), user.clone(), &mut engine); get_keyword(state.clone(), user.clone(), &mut engine); set_keyword(&state, user.clone(), &mut engine); diff --git a/src/core/bootstrap/mod.rs b/src/core/bootstrap/mod.rs index 777b8f0fb..83134d433 100644 --- a/src/core/bootstrap/mod.rs +++ b/src/core/bootstrap/mod.rs @@ -1847,26 +1847,17 @@ VAULT_CACHE_TTL=300 std::env::set_var("SSL_CERT_FILE", ca_cert_path); } - let timeout_config = aws_config::timeout::TimeoutConfig::builder() - .connect_timeout(std::time::Duration::from_secs(5)) - .read_timeout(std::time::Duration::from_secs(30)) - .operation_timeout(std::time::Duration::from_secs(30)) - .operation_attempt_timeout(std::time::Duration::from_secs(15)) - .build(); - - let retry_config = aws_config::retry::RetryConfig::standard() - .with_max_attempts(2); - - let base_config = aws_config::defaults(BehaviorVersion::latest()) + // Provide TokioSleep for retry/timeout configs + let base_config = aws_config::from_env() .endpoint_url(endpoint) - .region("auto") + .region(aws_config::Region::new("auto")) .credentials_provider(aws_sdk_s3::config::Credentials::new( access_key, secret_key, None, None, "static", )) - .timeout_config(timeout_config) - .retry_config(retry_config) + .sleep_impl(std::sync::Arc::new(aws_smithy_async::rt::sleep::TokioSleep::new())) .load() .await; + let s3_config = aws_sdk_s3::config::Builder::from(&base_config) .force_path_style(true) .build(); diff --git a/src/core/bot/mod.rs b/src/core/bot/mod.rs index 31581e68b..3f1e99364 100644 --- a/src/core/bot/mod.rs +++ b/src/core/bot/mod.rs @@ -3,7 +3,9 @@ use crate::core::config::ConfigManager; #[cfg(feature = "drive")] use crate::drive::drive_monitor::DriveMonitor; +#[cfg(feature = "llm")] use crate::llm::llm_models; +#[cfg(feature = "llm")] use crate::llm::OpenAIClient; #[cfg(feature = "nvidia")] use crate::nvidia::get_system_metrics; @@ -70,6 +72,7 @@ impl BotOrchestrator { Ok(()) } + #[cfg(feature = "llm")] pub async fn stream_response( &self, message: UserMessage, @@ -305,6 +308,33 @@ impl BotOrchestrator { Ok(()) } + #[cfg(not(feature = "llm"))] + pub async fn stream_response( + &self, + message: UserMessage, + response_tx: mpsc::Sender, + ) -> Result<(), Box> { + warn!("LLM feature not enabled, cannot stream response"); + + let error_response = BotResponse { + bot_id: message.bot_id, + user_id: message.user_id, + session_id: message.session_id, + channel: message.channel, + content: "LLM feature is not enabled in this build".to_string(), + message_type: MessageType::BOT_RESPONSE, + stream_token: None, + is_complete: true, + suggestions: Vec::new(), + context_name: None, + context_length: 0, + context_max_length: 0, + }; + + response_tx.send(error_response).await?; + Ok(()) + } + pub async fn get_user_sessions( &self, user_id: Uuid, diff --git a/src/core/directory/provisioning.rs b/src/core/directory/provisioning.rs index 1f440f91d..b369c7d2e 100644 --- a/src/core/directory/provisioning.rs +++ b/src/core/directory/provisioning.rs @@ -179,6 +179,7 @@ impl UserProvisioningService { Ok(()) } + #[cfg(feature = "mail")] fn setup_email_account(&self, account: &UserAccount) -> Result<()> { use crate::shared::models::schema::user_email_accounts; use diesel::prelude::*; @@ -207,6 +208,12 @@ impl UserProvisioningService { Ok(()) } + #[cfg(not(feature = "mail"))] + fn setup_email_account(&self, _account: &UserAccount) -> Result<()> { + log::debug!("Email feature not enabled, skipping email account setup"); + Ok(()) + } + fn setup_oauth_config(&self, _user_id: &str, account: &UserAccount) -> Result<()> { use crate::shared::models::schema::bot_configuration; use diesel::prelude::*; @@ -305,6 +312,7 @@ impl UserProvisioningService { Ok(()) } + #[cfg(feature = "mail")] fn remove_email_config(&self, username: &str) -> Result<()> { use crate::shared::models::schema::user_email_accounts; use diesel::prelude::*; @@ -320,4 +328,10 @@ impl UserProvisioningService { Ok(()) } + + #[cfg(not(feature = "mail"))] + fn remove_email_config(&self, _username: &str) -> Result<()> { + log::debug!("Email feature not enabled, skipping email config removal"); + Ok(()) + } } diff --git a/src/core/shared/admin.rs b/src/core/shared/admin.rs index 3602ecb9c..21c36e1ac 100644 --- a/src/core/shared/admin.rs +++ b/src/core/shared/admin.rs @@ -1440,6 +1440,7 @@ pub async fn create_invitation( let invite_message = payload.message.clone(); let invite_id = new_id; + #[cfg(feature = "mail")] tokio::spawn(async move { if let Err(e) = send_invitation_email(&email_to, &invite_role, invite_message.as_deref(), invite_id).await { warn!("Failed to send invitation email to {}: {}", email_to, e); @@ -1648,12 +1649,15 @@ pub async fn resend_invitation( match result { Ok(rows) if rows > 0 => { // Trigger email resend - let resend_id = id; - tokio::spawn(async move { - if let Err(e) = send_invitation_email_by_id(resend_id).await { - warn!("Failed to resend invitation email for {}: {}", resend_id, e); - } - }); + #[cfg(feature = "mail")] + { + let resend_id = id; + tokio::spawn(async move { + if let Err(e) = send_invitation_email_by_id(resend_id).await { + warn!("Failed to resend invitation email for {}: {}", resend_id, e); + } + }); + } (StatusCode::OK, Json(serde_json::json!({ "success": true, diff --git a/src/core/shared/models/mod.rs b/src/core/shared/models/mod.rs index fc748e103..68c1b956f 100644 --- a/src/core/shared/models/mod.rs +++ b/src/core/shared/models/mod.rs @@ -1,7 +1,3 @@ -use chrono::{DateTime, Utc}; -use diesel::prelude::*; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; pub mod core; pub use self::core::*; @@ -10,23 +6,45 @@ pub mod rbac; pub use self::rbac::*; #[cfg(feature = "tasks")] -pub mod tasks; +pub mod task_models; #[cfg(feature = "tasks")] -pub use self::tasks::*; +pub use self::task_models::*; pub use super::schema; -// Re-export schema tables for convenience, as they were before +// Re-export core schema tables pub use super::schema::{ - basic_tools, bot_configuration, bot_memories, bots, clicks, distribution_lists, - email_auto_responders, email_drafts, email_folders, email_label_assignments, email_labels, - email_rules, email_signatures, email_templates, global_email_signatures, kb_collections, - kb_documents, message_history, organizations, rbac_group_roles, rbac_groups, + basic_tools, bot_configuration, bot_memories, bots, clicks, + message_history, organizations, rbac_group_roles, rbac_groups, rbac_permissions, rbac_role_permissions, rbac_roles, rbac_user_groups, rbac_user_roles, - scheduled_emails, session_tool_associations, shared_mailbox_members, shared_mailboxes, - system_automations, tasks, user_email_accounts, user_kb_associations, user_login_tokens, + session_tool_associations, system_automations, user_login_tokens, user_preferences, user_sessions, users, }; +// Re-export feature-gated schema tables +#[cfg(feature = "tasks")] +pub use super::schema::tasks; + +#[cfg(feature = "mail")] +pub use super::schema::{ + distribution_lists, email_auto_responders, email_drafts, email_folders, + email_label_assignments, email_labels, email_rules, email_signatures, + email_templates, global_email_signatures, scheduled_emails, + shared_mailbox_members, shared_mailboxes, user_email_accounts, +}; + +#[cfg(feature = "people")] +pub use super::schema::{ + crm_accounts, crm_activities, crm_contacts, crm_leads, crm_notes, + crm_opportunities, crm_pipeline_stages, people, people_departments, + people_org_chart, people_person_skills, people_skills, people_team_members, + people_teams, people_time_off, +}; + +#[cfg(feature = "vectordb")] +pub use super::schema::{ + kb_collections, kb_documents, user_kb_associations, +}; + pub use botlib::message_types::MessageType; pub use botlib::models::{ApiResponse, Attachment, BotResponse, Session, Suggestion, UserMessage}; diff --git a/src/core/shared/models/tasks.rs b/src/core/shared/models/task_models.rs similarity index 100% rename from src/core/shared/models/tasks.rs rename to src/core/shared/models/task_models.rs diff --git a/src/core/shared/schema/core.rs b/src/core/shared/schema/core.rs index 4530386e6..78aa21967 100644 --- a/src/core/shared/schema/core.rs +++ b/src/core/shared/schema/core.rs @@ -251,3 +251,14 @@ diesel::table! { is_active -> Bool, } } + +diesel::allow_tables_to_appear_in_same_query!( + rbac_roles, + rbac_groups, + rbac_permissions, + rbac_role_permissions, + rbac_user_roles, + rbac_user_groups, + rbac_group_roles, + users, +); diff --git a/src/core/shared/schema/mod.rs b/src/core/shared/schema/mod.rs index 09ff7831d..4999bf52d 100644 --- a/src/core/shared/schema/mod.rs +++ b/src/core/shared/schema/mod.rs @@ -1,88 +1,86 @@ // Core (Always available) -mod core; +pub mod core; pub use self::core::*; #[cfg(feature = "tasks")] -mod tasks; -#[cfg(feature = "tasks")] -pub use self::tasks::*; +pub mod tasks; #[cfg(feature = "mail")] -mod mail; +pub mod mail; #[cfg(feature = "mail")] pub use self::mail::*; #[cfg(feature = "people")] -mod people; +pub mod people; #[cfg(feature = "people")] pub use self::people::*; #[cfg(feature = "tickets")] -mod tickets; +pub mod tickets; #[cfg(feature = "tickets")] pub use self::tickets::*; #[cfg(feature = "billing")] -mod billing; +pub mod billing; #[cfg(feature = "billing")] pub use self::billing::*; #[cfg(feature = "attendant")] -mod attendant; +pub mod attendant; #[cfg(feature = "attendant")] pub use self::attendant::*; #[cfg(feature = "calendar")] -mod calendar; +pub mod calendar; #[cfg(feature = "calendar")] pub use self::calendar::*; #[cfg(feature = "goals")] -mod goals; +pub mod goals; #[cfg(feature = "goals")] pub use self::goals::*; #[cfg(feature = "canvas")] -mod canvas; +pub mod canvas; #[cfg(feature = "canvas")] pub use self::canvas::*; #[cfg(feature = "workspaces")] -mod workspaces; +pub mod workspaces; #[cfg(feature = "workspaces")] pub use self::workspaces::*; #[cfg(feature = "social")] -mod social; +pub mod social; #[cfg(feature = "social")] pub use self::social::*; #[cfg(feature = "analytics")] -mod analytics; +pub mod analytics; #[cfg(feature = "analytics")] pub use self::analytics::*; #[cfg(feature = "compliance")] -mod compliance; +pub mod compliance; #[cfg(feature = "compliance")] pub use self::compliance::*; #[cfg(feature = "meet")] -mod meet; +pub mod meet; #[cfg(feature = "meet")] pub use self::meet::*; #[cfg(feature = "research")] -mod research; +pub mod research; #[cfg(feature = "research")] pub use self::research::*; #[cfg(feature = "learn")] -mod learn; +pub mod learn; #[cfg(feature = "learn")] pub use self::learn::*; #[cfg(feature = "project")] -mod project; +pub mod project; #[cfg(feature = "project")] pub use self::project::*; diff --git a/src/core/shared/schema/tasks.rs b/src/core/shared/schema/tasks.rs index 9c2d10c86..e84135fd4 100644 --- a/src/core/shared/schema/tasks.rs +++ b/src/core/shared/schema/tasks.rs @@ -19,3 +19,5 @@ diesel::table! { completed_at -> Nullable, } } + +pub use self::tasks::*; diff --git a/src/core/shared/state.rs b/src/core/shared/state.rs index 7bb6536ee..c10c94168 100644 --- a/src/core/shared/state.rs +++ b/src/core/shared/state.rs @@ -7,6 +7,7 @@ use crate::core::session::SessionManager; use crate::core::shared::analytics::MetricsCollector; #[cfg(feature = "project")] use crate::project::ProjectService; +#[cfg(feature = "compliance")] use crate::legal::LegalService; use crate::security::auth_provider::AuthProviderRegistry; use crate::security::jwt::JwtManager; @@ -16,7 +17,7 @@ use crate::core::shared::test_utils::create_mock_auth_service; #[cfg(all(test, feature = "llm"))] use crate::core::shared::test_utils::MockLLMProvider; #[cfg(feature = "directory")] -use crate::core::directory::AuthService; +use crate::directory::AuthService; #[cfg(feature = "llm")] use crate::llm::LLMProvider; use crate::shared::models::BotResponse; @@ -374,6 +375,7 @@ pub struct AppState { pub task_manifests: Arc>>, #[cfg(feature = "project")] pub project_service: Arc>, + #[cfg(feature = "compliance")] pub legal_service: Arc>, pub jwt_manager: Option>, pub auth_provider_registry: Option>, @@ -416,6 +418,7 @@ impl Clone for AppState { task_manifests: Arc::clone(&self.task_manifests), #[cfg(feature = "project")] project_service: Arc::clone(&self.project_service), + #[cfg(feature = "compliance")] legal_service: Arc::clone(&self.legal_service), jwt_manager: self.jwt_manager.clone(), auth_provider_registry: self.auth_provider_registry.clone(), @@ -623,6 +626,7 @@ impl Default for AppState { task_manifests: Arc::new(std::sync::RwLock::new(HashMap::new())), #[cfg(feature = "project")] project_service: Arc::new(RwLock::new(crate::project::ProjectService::new())), + #[cfg(feature = "compliance")] legal_service: Arc::new(RwLock::new(crate::legal::LegalService::new())), jwt_manager: None, auth_provider_registry: None, diff --git a/src/directory/auth_routes.rs b/src/directory/auth_routes.rs index b81a3be1e..273d1fbd9 100644 --- a/src/directory/auth_routes.rs +++ b/src/directory/auth_routes.rs @@ -36,7 +36,7 @@ const BOOTSTRAP_SECRET_ENV: &str = "GB_BOOTSTRAP_SECRET"; pub struct LoginRequest { pub email: String, pub password: String, - pub remember: Option, + pub remember: Option, } #[derive(Debug, Serialize)] @@ -111,13 +111,14 @@ pub struct BootstrapResponse { pub fn configure() -> Router> { Router::new() - .route("/api/auth/login", post(login)) - .route("/api/auth/logout", post(logout)) - .route("/api/auth/me", get(get_current_user)) - .route("/api/auth/refresh", post(refresh_token)) - .route("/api/auth/2fa/verify", post(verify_2fa)) - .route("/api/auth/2fa/resend", post(resend_2fa)) - .route("/api/auth/bootstrap", post(bootstrap_admin)) + .route("/", get(crate::directory::auth_handler)) + .route("/login", post(login)) + .route("/logout", post(logout)) + .route("/me", get(get_current_user)) + .route("/refresh", post(refresh_token)) + .route("/2fa/verify", post(verify_2fa)) + .route("/2fa/resend", post(resend_2fa)) + .route("/bootstrap", post(bootstrap_admin)) } pub async fn login( diff --git a/src/drive/drive_monitor/mod.rs b/src/drive/drive_monitor/mod.rs index d4dff361c..5d078a4de 100644 --- a/src/drive/drive_monitor/mod.rs +++ b/src/drive/drive_monitor/mod.rs @@ -397,7 +397,9 @@ impl DriveMonitor { if llm_lines.is_empty() { let _ = config_manager.sync_gbot_config(&self.bot_id, &csv_content); } else { + #[cfg(feature = "llm")] use crate::llm::local::ensure_llama_servers_running; + #[cfg(feature = "llm")] use crate::llm::DynamicLLMProvider; let mut restart_needed = false; let mut llm_url_changed = false; @@ -437,6 +439,7 @@ impl DriveMonitor { } } let _ = config_manager.sync_gbot_config(&self.bot_id, &csv_content); + #[cfg(feature = "llm")] if restart_needed { if let Err(e) = ensure_llama_servers_running(Arc::clone(&self.state)).await @@ -444,6 +447,7 @@ impl DriveMonitor { log::error!("Failed to restart LLaMA servers after llm- config change: {}", e); } } + #[cfg(feature = "llm")] if llm_url_changed { info!("check_gbot: LLM config changed, updating provider..."); let effective_url = if new_llm_url.is_empty() { diff --git a/src/embedded_ui.rs b/src/embedded_ui.rs index 6bacfaba0..eb9d62208 100644 --- a/src/embedded_ui.rs +++ b/src/embedded_ui.rs @@ -99,7 +99,8 @@ async fn serve_embedded_file(req: Request) -> Response { } pub fn embedded_ui_router() -> Router { - Router::new().fallback(get(serve_embedded_file)) + use axum::routing::any; + Router::new().fallback(any(serve_embedded_file)) } pub fn has_embedded_ui() -> bool { diff --git a/src/main.rs b/src/main.rs index fb24ba88e..562703b41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,10 +12,12 @@ static GLOBAL: Jemalloc = Jemalloc; #[cfg(feature = "automation")] pub mod auto_task; pub mod basic; +#[cfg(feature = "billing")] pub mod billing; #[cfg(feature = "canvas")] pub mod canvas; pub mod channels; +#[cfg(feature = "people")] pub mod contacts; pub mod core; #[cfg(feature = "dashboards")] @@ -27,9 +29,11 @@ pub mod multimodal; pub mod player; #[cfg(feature = "people")] pub mod people; +#[cfg(feature = "billing")] pub mod products; pub mod search; pub mod security; +#[cfg(feature = "tickets")] pub mod tickets; #[cfg(feature = "attendant")] pub mod attendant; @@ -59,8 +63,10 @@ pub mod video; pub mod monitoring; #[cfg(feature = "project")] pub mod project; +#[cfg(feature = "workspaces")] pub mod workspaces; pub mod botmodels; +#[cfg(feature = "compliance")] pub mod legal; pub mod settings; @@ -224,7 +230,7 @@ use crate::core::bot_database::BotDatabaseManager; use crate::core::config::AppConfig; #[cfg(feature = "directory")] -use crate::core::directory::auth_handler; +use crate::directory::auth_handler; use package_manager::InstallMode; use session::{create_session, get_session_history, get_sessions, start_session}; @@ -437,10 +443,9 @@ async fn run_axum_server( #[cfg(feature = "directory")] { api_router = api_router - .route(ApiUrls::AUTH, get(auth_handler)) .merge(crate::core::directory::api::configure_user_routes()) .merge(crate::directory::router::configure()) - .merge(crate::directory::auth_routes::configure()); + .nest(ApiUrls::AUTH, crate::directory::auth_routes::configure()); } #[cfg(feature = "meet")] @@ -520,8 +525,11 @@ api_router = api_router.merge(crate::slides::configure_slides_routes()); api_router = api_router.merge(crate::dashboards::configure_dashboards_routes()); api_router = api_router.merge(crate::dashboards::ui::configure_dashboards_ui_routes()); } - api_router = api_router.merge(crate::legal::configure_legal_routes()); - api_router = api_router.merge(crate::legal::ui::configure_legal_ui_routes()); + #[cfg(feature = "compliance")] + { + api_router = api_router.merge(crate::legal::configure_legal_routes()); + api_router = api_router.merge(crate::legal::ui::configure_legal_ui_routes()); + } #[cfg(feature = "compliance")] { api_router = api_router.merge(crate::compliance::configure_compliance_routes()); @@ -540,8 +548,11 @@ api_router = api_router.merge(crate::slides::configure_slides_routes()); api_router = api_router.merge(crate::auto_task::configure_autotask_routes()); } api_router = api_router.merge(crate::core::shared::admin::configure()); - api_router = api_router.merge(crate::workspaces::configure_workspaces_routes()); - api_router = api_router.merge(crate::workspaces::ui::configure_workspaces_ui_routes()); + #[cfg(feature = "workspaces")] + { + api_router = api_router.merge(crate::workspaces::configure_workspaces_routes()); + api_router = api_router.merge(crate::workspaces::ui::configure_workspaces_ui_routes()); + } #[cfg(feature = "project")] { api_router = api_router.merge(crate::project::configure()); @@ -577,14 +588,23 @@ api_router = api_router.merge(crate::email::ui::configure_email_ui_routes()); { api_router = api_router.merge(crate::meet::ui::configure_meet_ui_routes()); } -api_router = api_router.merge(crate::contacts::crm_ui::configure_crm_routes()); - api_router = api_router.merge(crate::contacts::crm::configure_crm_api_routes()); - api_router = api_router.merge(crate::billing::billing_ui::configure_billing_routes()); - api_router = api_router.merge(crate::billing::api::configure_billing_api_routes()); - api_router = api_router.merge(crate::products::configure_products_routes()); - api_router = api_router.merge(crate::products::api::configure_products_api_routes()); - api_router = api_router.merge(crate::tickets::configure_tickets_routes()); - api_router = api_router.merge(crate::tickets::ui::configure_tickets_ui_routes()); + #[cfg(feature = "people")] + { + api_router = api_router.merge(crate::contacts::crm_ui::configure_crm_routes()); + api_router = api_router.merge(crate::contacts::crm::configure_crm_api_routes()); + } + #[cfg(feature = "billing")] + { + api_router = api_router.merge(crate::billing::billing_ui::configure_billing_routes()); + api_router = api_router.merge(crate::billing::api::configure_billing_api_routes()); + api_router = api_router.merge(crate::products::configure_products_routes()); + api_router = api_router.merge(crate::products::api::configure_products_api_routes()); + } + #[cfg(feature = "tickets")] + { + api_router = api_router.merge(crate::tickets::configure_tickets_routes()); + api_router = api_router.merge(crate::tickets::ui::configure_tickets_ui_routes()); + } #[cfg(feature = "people")] { api_router = api_router.merge(crate::people::configure_people_routes()); @@ -1155,7 +1175,7 @@ use crate::core::config::ConfigManager; info!("Loaded Zitadel config from {}: url={}", config_path, base_url); - crate::core::directory::client::ZitadelConfig { + crate::directory::ZitadelConfig { issuer_url: base_url.to_string(), issuer: base_url.to_string(), client_id: client_id.to_string(), @@ -1167,7 +1187,7 @@ use crate::core::config::ConfigManager; } } else { warn!("Failed to parse directory_config.json, using defaults"); - crate::core::directory::client::ZitadelConfig { + crate::directory::ZitadelConfig { issuer_url: "http://localhost:8300".to_string(), issuer: "http://localhost:8300".to_string(), client_id: String::new(), @@ -1180,7 +1200,7 @@ use crate::core::config::ConfigManager; } } else { warn!("directory_config.json not found, using default Zitadel config"); - crate::core::directory::client::ZitadelConfig { + crate::directory::ZitadelConfig { issuer_url: "http://localhost:8300".to_string(), issuer: "http://localhost:8300".to_string(), client_id: String::new(), @@ -1194,7 +1214,7 @@ use crate::core::config::ConfigManager; }; #[cfg(feature = "directory")] let auth_service = Arc::new(tokio::sync::Mutex::new( - crate::core::directory::AuthService::new(zitadel_config.clone()).map_err(|e| std::io::Error::other(format!("Failed to create auth service: {}", e)))?, + crate::directory::AuthService::new(zitadel_config.clone()).map_err(|e| std::io::Error::other(format!("Failed to create auth service: {}", e)))?, )); #[cfg(feature = "directory")] @@ -1205,22 +1225,22 @@ use crate::core::config::ConfigManager; Ok(pat_token) => { let pat_token = pat_token.trim().to_string(); info!("Using admin PAT token for bootstrap authentication"); - crate::core::directory::client::ZitadelClient::with_pat_token(zitadel_config, pat_token) + crate::directory::ZitadelClient::with_pat_token(zitadel_config, pat_token) .map_err(|e| std::io::Error::other(format!("Failed to create bootstrap client with PAT: {}", e)))? } Err(e) => { warn!("Failed to read admin PAT token: {}, falling back to OAuth2", e); - crate::core::directory::client::ZitadelClient::new(zitadel_config) + crate::directory::ZitadelClient::new(zitadel_config) .map_err(|e| std::io::Error::other(format!("Failed to create bootstrap client: {}", e)))? } } } else { info!("Admin PAT not found, using OAuth2 client credentials for bootstrap"); - crate::core::directory::client::ZitadelClient::new(zitadel_config) + crate::directory::ZitadelClient::new(zitadel_config) .map_err(|e| std::io::Error::other(format!("Failed to create bootstrap client: {}", e)))? }; - match crate::core::directory::bootstrap::check_and_bootstrap_admin(&bootstrap_client).await { + match crate::directory::bootstrap::check_and_bootstrap_admin(&bootstrap_client).await { Ok(Some(_)) => { info!("Bootstrap completed - admin credentials displayed in console"); } @@ -1257,13 +1277,16 @@ use crate::core::config::ConfigManager; .get_config(&default_bot_id, "llm-key", Some("")) .unwrap_or_default(); - let base_llm_provider =crate::llm::create_llm_provider_from_url( + #[cfg(feature = "llm")] + let base_llm_provider = crate::llm::create_llm_provider_from_url( &llm_url, if llm_model.is_empty() { None } else { Some(llm_model.clone()) }, ); + #[cfg(feature = "llm")] let dynamic_llm_provider = Arc::new(crate::llm::DynamicLLMProvider::new(base_llm_provider)); + #[cfg(feature = "llm")] let llm_provider: Arc = if let Some(ref cache) = redis_client { let embedding_url = config_manager .get_config( @@ -1284,7 +1307,7 @@ use crate::core::config::ConfigManager; )) as Arc); - let cache_config =crate::llm::cache::CacheConfig { + let cache_config = crate::llm::cache::CacheConfig { ttl: 3600, semantic_matching: true, similarity_threshold: 0.85, @@ -1354,6 +1377,7 @@ use crate::core::config::ConfigManager; session_manager: session_manager.clone(), metrics_collector, task_scheduler, + #[cfg(feature = "llm")] llm_provider: llm_provider.clone(), #[cfg(feature = "directory")] auth_service: auth_service.clone(), @@ -1371,7 +1395,8 @@ use crate::core::config::ConfigManager; kb_manager: Some(kb_manager.clone()), task_engine, extensions: { - let ext =crate::core::shared::state::Extensions::new(); + let ext = crate::core::shared::state::Extensions::new(); + #[cfg(feature = "llm")] ext.insert_blocking(Arc::clone(&dynamic_llm_provider)); ext }, @@ -1379,7 +1404,9 @@ use crate::core::config::ConfigManager; task_progress_broadcast: Some(task_progress_tx), billing_alert_broadcast: None, task_manifests: Arc::new(std::sync::RwLock::new(HashMap::new())), + #[cfg(feature = "project")] project_service: Arc::new(tokio::sync::RwLock::new(crate::project::ProjectService::new())), + #[cfg(feature = "compliance")] legal_service: Arc::new(tokio::sync::RwLock::new(crate::legal::LegalService::new())), jwt_manager: None, auth_provider_registry: None, diff --git a/src/security/rbac_middleware.rs b/src/security/rbac_middleware.rs index 7d4b29f67..afc410b80 100644 --- a/src/security/rbac_middleware.rs +++ b/src/security/rbac_middleware.rs @@ -950,6 +950,7 @@ pub fn build_default_route_permissions() -> Vec { // Auth routes - login must be anonymous RoutePermission::new("/api/auth", "GET", "").with_anonymous(true), + RoutePermission::new("/api/auth/login", "POST", "").with_anonymous(true), RoutePermission::new("/api/auth/bootstrap", "POST", "").with_anonymous(true), RoutePermission::new("/api/auth/refresh", "POST", "").with_anonymous(true),