Feature gating refactor: modular compilation with minimal feature set
This commit is contained in:
parent
3db87c029d
commit
66abce913f
23 changed files with 273 additions and 102 deletions
16
Cargo.toml
16
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 }
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::config::ConfigManager;
|
||||
|
||||
use crate::shared::models::UserSession;
|
||||
use crate::shared::state::AppState;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::config::ConfigManager;
|
||||
|
||||
use crate::shared::models::UserSession;
|
||||
use crate::shared::state::AppState;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
|
|
|||
|
|
@ -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<Arc<dyn LLMProvider>> = Some(state_clone.llm_provider.clone());
|
||||
let llm = Some(state_clone.llm_provider.clone());
|
||||
#[cfg(not(feature = "llm"))]
|
||||
let llm: Option<Arc<dyn LLMProvider>> = 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<std::sync::Arc<aws_sdk_s3::Client>>,
|
||||
|
|
@ -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<std::sync::Arc<aws_sdk_s3::Client>>,
|
||||
bucket: String,
|
||||
bot_id: String,
|
||||
_llm: Option<()>,
|
||||
alias: Dynamic,
|
||||
template_dir: Dynamic,
|
||||
prompt: Dynamic,
|
||||
) -> Result<String, Box<dyn Error + Send + Sync>> {
|
||||
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<String, Box<dyn Error + Send + Sync>> {
|
||||
let mut combined_content = String::new();
|
||||
|
||||
|
|
@ -129,6 +176,7 @@ fn load_templates(template_path: &std::path::Path) -> Result<String, Box<dyn Err
|
|||
Ok(combined_content)
|
||||
}
|
||||
|
||||
#[cfg(feature = "llm")]
|
||||
async fn generate_html_from_prompt(
|
||||
llm: Option<Arc<dyn LLMProvider>>,
|
||||
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<String, Box<dyn Error + Send + Sync>> {
|
||||
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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<BotResponse>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -19,3 +19,5 @@ diesel::table! {
|
|||
completed_at -> Nullable<Timestamptz>,
|
||||
}
|
||||
}
|
||||
|
||||
pub use self::tasks::*;
|
||||
|
|
|
|||
|
|
@ -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<std::sync::RwLock<HashMap<String, TaskManifest>>>,
|
||||
#[cfg(feature = "project")]
|
||||
pub project_service: Arc<RwLock<ProjectService>>,
|
||||
#[cfg(feature = "compliance")]
|
||||
pub legal_service: Arc<RwLock<LegalService>>,
|
||||
pub jwt_manager: Option<Arc<JwtManager>>,
|
||||
pub auth_provider_registry: Option<Arc<AuthProviderRegistry>>,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const BOOTSTRAP_SECRET_ENV: &str = "GB_BOOTSTRAP_SECRET";
|
|||
pub struct LoginRequest {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub remember: Option<bool>,
|
||||
pub remember: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
@ -111,13 +111,14 @@ pub struct BootstrapResponse {
|
|||
|
||||
pub fn configure() -> Router<Arc<AppState>> {
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,8 @@ async fn serve_embedded_file(req: Request<Body>) -> Response<Body> {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
79
src/main.rs
79
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<dyn crate::llm::LLMProvider> = 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<dyn crate::llm::cache::EmbeddingService>);
|
||||
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -950,6 +950,7 @@ pub fn build_default_route_permissions() -> Vec<RoutePermission> {
|
|||
|
||||
// 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),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue