merge: Unify master into main - all commits unified
Some checks failed
BotServer CI / build (push) Failing after 6m9s

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-03-01 07:43:07 -03:00
commit 2c92a81302
16 changed files with 167 additions and 102 deletions

View file

@ -620,7 +620,7 @@ pub async fn create_objective(
let record = new_objective.clone(); let record = new_objective.clone();
let _result = tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let mut conn = pool.get().map_err(|e| GoalsError::Database(e.to_string()))?; let mut conn = pool.get().map_err(|e| GoalsError::Database(e.to_string()))?;
diesel::insert_into(okr_objectives::table) diesel::insert_into(okr_objectives::table)
.values(&new_objective) .values(&new_objective)
@ -718,7 +718,7 @@ pub async fn delete_objective(
) -> Result<Json<serde_json::Value>, GoalsError> { ) -> Result<Json<serde_json::Value>, GoalsError> {
let pool = state.conn.clone(); let pool = state.conn.clone();
let _result = tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let mut conn = pool.get().map_err(|e| GoalsError::Database(e.to_string()))?; let mut conn = pool.get().map_err(|e| GoalsError::Database(e.to_string()))?;
let deleted = diesel::delete(okr_objectives::table.find(objective_id)) let deleted = diesel::delete(okr_objectives::table.find(objective_id))
.execute(&mut conn) .execute(&mut conn)
@ -793,7 +793,7 @@ pub async fn create_key_result(
let record = new_kr.clone(); let record = new_kr.clone();
let _result = tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let mut conn = pool.get().map_err(|e| GoalsError::Database(e.to_string()))?; let mut conn = pool.get().map_err(|e| GoalsError::Database(e.to_string()))?;
diesel::insert_into(okr_key_results::table) diesel::insert_into(okr_key_results::table)
.values(&new_kr) .values(&new_kr)

View file

@ -884,8 +884,6 @@ END SCHEDULE
.clone() .clone()
.unwrap_or_else(|| "unspecified".to_string()); .unwrap_or_else(|| "unspecified".to_string());
let _goal_id = Uuid::new_v4();
// Goals are more complex - they create a monitoring + action loop // Goals are more complex - they create a monitoring + action loop
let basic_code = format!( let basic_code = format!(
r#"' Goal: {goal_name} r#"' Goal: {goal_name}

View file

@ -16,11 +16,6 @@ use std::path::PathBuf;
#[cfg(feature = "llm")] #[cfg(feature = "llm")]
use std::sync::Arc; use std::sync::Arc;
// When llm feature is disabled, create a dummy trait for type compatibility
#[cfg(not(feature = "llm"))]
#[allow(dead_code)]
trait LLMProvider: Send + Sync {}
pub fn create_site_keyword(state: &AppState, user: UserSession, engine: &mut Engine) { pub fn create_site_keyword(state: &AppState, user: UserSession, engine: &mut Engine) {
let state_clone = state.clone(); let state_clone = state.clone();
let user_clone = user; let user_clone = user;

View file

@ -1,7 +1,6 @@
use log::info; use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::path::Path;
use uuid::Uuid; use uuid::Uuid;
use crate::core::shared::state::AppState; use crate::core::shared::state::AppState;
@ -174,56 +173,56 @@ pub fn fetch_folder_changes(
Ok(events) Ok(events)
} }
#[allow(dead_code)]
fn apply_filters(events: Vec<FolderChangeEvent>, filters: &Option<FileFilters>) -> Vec<FolderChangeEvent> {
let Some(filters) = filters else {
return events;
};
events
.into_iter()
.filter(|event| {
if let Some(ref extensions) = filters.extensions {
let ext = Path::new(&event.path)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
if !extensions.iter().any(|e| e.eq_ignore_ascii_case(ext)) {
return false;
}
}
if let Some(min_size) = filters.min_size {
if event.size.unwrap_or(0) < min_size {
return false;
}
}
if let Some(max_size) = filters.max_size {
if event.size.unwrap_or(i64::MAX) > max_size {
return false;
}
}
if let Some(ref pattern) = filters.name_pattern {
let file_name = Path::new(&event.path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("");
if !file_name.contains(pattern) {
return false;
}
}
true
})
.collect()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
fn apply_filters(events: Vec<FolderChangeEvent>, filters: &Option<FileFilters>) -> Vec<FolderChangeEvent> {
let Some(ref filters) = filters else {
return events;
};
events
.into_iter()
.filter(|event| {
if let Some(ref extensions) = filters.extensions {
let ext = Path::new(&event.path)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
if !extensions.iter().any(|e| e.eq_ignore_ascii_case(ext)) {
return false;
}
}
if let Some(min_size) = filters.min_size {
if event.size.unwrap_or(0) < min_size {
return false;
}
}
if let Some(max_size) = filters.max_size {
if event.size.unwrap_or(i64::MAX) > max_size {
return false;
}
}
if let Some(ref pattern) = filters.name_pattern {
let file_name = Path::new(&event.path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("");
if !file_name.contains(pattern) {
return false;
}
}
true
})
.collect()
}
use super::*;
#[test] #[test]
fn test_folder_provider_from_str() { fn test_folder_provider_from_str() {
assert_eq!( assert_eq!(

View file

@ -139,18 +139,18 @@ pub struct CodeScanner {
} }
impl CodeScanner { impl CodeScanner {
pub fn new(base_path: impl AsRef<Path>) -> Self { pub fn new(base_path: impl AsRef<Path>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let patterns = Self::build_patterns(); let patterns = Self::build_patterns()?;
Self { Ok(Self {
patterns, patterns,
base_path: base_path.as_ref().to_path_buf(), base_path: base_path.as_ref().to_path_buf(),
} })
} }
fn build_patterns() -> Vec<ScanPattern> { fn build_patterns() -> Result<Vec<ScanPattern>, Box<dyn std::error::Error + Send + Sync>> {
vec![ Ok(vec![
ScanPattern { ScanPattern {
regex: Regex::new(r#"(?i)password\s*=\s*["'][^"']+["']"#).expect("valid regex"), regex: Regex::new(r#"(?i)password\s*=\s*["'][^"']+["']"#)?,
issue_type: IssueType::PasswordInConfig, issue_type: IssueType::PasswordInConfig,
severity: IssueSeverity::Critical, severity: IssueSeverity::Critical,
title: "Hardcoded Password".to_string(), title: "Hardcoded Password".to_string(),
@ -159,7 +159,7 @@ impl CodeScanner {
category: "Security".to_string(), category: "Security".to_string(),
}, },
ScanPattern { ScanPattern {
regex: Regex::new(r#"(?i)(api[_-]?key|apikey|secret[_-]?key|client[_-]?secret)\s*=\s*["'][^"']{8,}["']"#).expect("valid regex"), regex: Regex::new(r#"(?i)(api[_-]?key|apikey|secret[_-]?key|client[_-]?secret)\s*=\s*["'][^"']{8,}["']"#)?,
issue_type: IssueType::HardcodedSecret, issue_type: IssueType::HardcodedSecret,
severity: IssueSeverity::Critical, severity: IssueSeverity::Critical,
title: "Hardcoded API Key/Secret".to_string(), title: "Hardcoded API Key/Secret".to_string(),
@ -168,7 +168,7 @@ impl CodeScanner {
category: "Security".to_string(), category: "Security".to_string(),
}, },
ScanPattern { ScanPattern {
regex: Regex::new(r#"(?i)token\s*=\s*["'][a-zA-Z0-9_\-]{20,}["']"#).expect("valid regex"), regex: Regex::new(r#"(?i)token\s*=\s*["'][a-zA-Z0-9_\-]{20,}["']"#)?,
issue_type: IssueType::HardcodedSecret, issue_type: IssueType::HardcodedSecret,
severity: IssueSeverity::High, severity: IssueSeverity::High,
title: "Hardcoded Token".to_string(), title: "Hardcoded Token".to_string(),
@ -177,7 +177,7 @@ impl CodeScanner {
category: "Security".to_string(), category: "Security".to_string(),
}, },
ScanPattern { ScanPattern {
regex: Regex::new(r"(?i)IF\s+.*\binput\b").expect("valid regex"), regex: Regex::new(r"(?i)IF\s+.*\binput\b")?,
issue_type: IssueType::DeprecatedIfInput, issue_type: IssueType::DeprecatedIfInput,
severity: IssueSeverity::Medium, severity: IssueSeverity::Medium,
title: "Deprecated IF...input Pattern".to_string(), title: "Deprecated IF...input Pattern".to_string(),
@ -189,7 +189,7 @@ impl CodeScanner {
category: "Code Quality".to_string(), category: "Code Quality".to_string(),
}, },
ScanPattern { ScanPattern {
regex: Regex::new(r"(?i)\b(GET_BOT_MEMORY|SET_BOT_MEMORY|GET_USER_MEMORY|SET_USER_MEMORY|USE_KB|USE_TOOL|SEND_MAIL|CREATE_TASK)\b").expect("valid regex"), regex: Regex::new(r"(?i)\b(GET_BOT_MEMORY|SET_BOT_MEMORY|GET_USER_MEMORY|SET_USER_MEMORY|USE_KB|USE_TOOL|SEND_MAIL|CREATE_TASK)\b")?,
issue_type: IssueType::UnderscoreInKeyword, issue_type: IssueType::UnderscoreInKeyword,
severity: IssueSeverity::Low, severity: IssueSeverity::Low,
title: "Underscore in Keyword".to_string(), title: "Underscore in Keyword".to_string(),
@ -198,7 +198,7 @@ impl CodeScanner {
category: "Naming Convention".to_string(), category: "Naming Convention".to_string(),
}, },
ScanPattern { ScanPattern {
regex: Regex::new(r"(?i)POST\s+TO\s+INSTAGRAM\s+\w+\s*,\s*\w+").expect("valid regex"), regex: Regex::new(r"(?i)POST\s+TO\s+INSTAGRAM\s+\w+\s*,\s*\w+")?,
issue_type: IssueType::InsecurePattern, issue_type: IssueType::InsecurePattern,
severity: IssueSeverity::High, severity: IssueSeverity::High,
title: "Instagram Credentials in Code".to_string(), title: "Instagram Credentials in Code".to_string(),
@ -209,7 +209,7 @@ impl CodeScanner {
category: "Security".to_string(), category: "Security".to_string(),
}, },
ScanPattern { ScanPattern {
regex: Regex::new(r"(?i)(SELECT|INSERT|UPDATE|DELETE)\s+.*(FROM|INTO|SET)\s+").expect("valid regex"), regex: Regex::new(r"(?i)(SELECT|INSERT|UPDATE|DELETE)\s+.*(FROM|INTO|SET)\s+")?,
issue_type: IssueType::FragileCode, issue_type: IssueType::FragileCode,
severity: IssueSeverity::Medium, severity: IssueSeverity::Medium,
title: "Raw SQL Query".to_string(), title: "Raw SQL Query".to_string(),
@ -221,7 +221,7 @@ impl CodeScanner {
category: "Security".to_string(), category: "Security".to_string(),
}, },
ScanPattern { ScanPattern {
regex: Regex::new(r"(?i)\bEVAL\s*\(").expect("valid regex"), regex: Regex::new(r"(?i)\bEVAL\s*\(")?,
issue_type: IssueType::FragileCode, issue_type: IssueType::FragileCode,
severity: IssueSeverity::High, severity: IssueSeverity::High,
title: "Dynamic Code Execution".to_string(), title: "Dynamic Code Execution".to_string(),
@ -233,7 +233,7 @@ impl CodeScanner {
regex: Regex::new( regex: Regex::new(
r#"(?i)(password|secret|key|token)\s*=\s*["'][A-Za-z0-9+/=]{40,}["']"#, r#"(?i)(password|secret|key|token)\s*=\s*["'][A-Za-z0-9+/=]{40,}["']"#,
) )
.expect("valid regex"), ?,
issue_type: IssueType::HardcodedSecret, issue_type: IssueType::HardcodedSecret,
severity: IssueSeverity::High, severity: IssueSeverity::High,
title: "Potential Encoded Secret".to_string(), title: "Potential Encoded Secret".to_string(),
@ -243,7 +243,7 @@ impl CodeScanner {
category: "Security".to_string(), category: "Security".to_string(),
}, },
ScanPattern { ScanPattern {
regex: Regex::new(r"(?i)(AKIA[0-9A-Z]{16})").expect("valid regex"), regex: Regex::new(r"(?i)(AKIA[0-9A-Z]{16})")?,
issue_type: IssueType::HardcodedSecret, issue_type: IssueType::HardcodedSecret,
severity: IssueSeverity::Critical, severity: IssueSeverity::Critical,
title: "AWS Access Key".to_string(), title: "AWS Access Key".to_string(),
@ -253,7 +253,7 @@ impl CodeScanner {
category: "Security".to_string(), category: "Security".to_string(),
}, },
ScanPattern { ScanPattern {
regex: Regex::new(r"-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----").expect("valid regex"), regex: Regex::new(r"-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----")?,
issue_type: IssueType::HardcodedSecret, issue_type: IssueType::HardcodedSecret,
severity: IssueSeverity::Critical, severity: IssueSeverity::Critical,
title: "Private Key in Code".to_string(), title: "Private Key in Code".to_string(),
@ -263,7 +263,7 @@ impl CodeScanner {
category: "Security".to_string(), category: "Security".to_string(),
}, },
ScanPattern { ScanPattern {
regex: Regex::new(r"(?i)(postgres|mysql|mongodb|redis)://[^:]+:[^@]+@").expect("valid regex"), regex: Regex::new(r"(?i)(postgres|mysql|mongodb|redis)://[^:]+:[^@]+@")?,
issue_type: IssueType::HardcodedSecret, issue_type: IssueType::HardcodedSecret,
severity: IssueSeverity::Critical, severity: IssueSeverity::Critical,
title: "Database Credentials in Connection String".to_string(), title: "Database Credentials in Connection String".to_string(),
@ -450,12 +450,12 @@ impl CodeScanner {
fn redact_sensitive(line: &str) -> String { fn redact_sensitive(line: &str) -> String {
let mut result = line.to_string(); let mut result = line.to_string();
let secret_pattern = Regex::new(r#"(["'])[^"']{8,}(["'])"#).expect("valid regex"); let secret_pattern = Regex::new(r#"(["'])[^"']{8,}(["'])"#)?;
result = secret_pattern result = secret_pattern
.replace_all(&result, "$1***REDACTED***$2") .replace_all(&result, "$1***REDACTED***$2")
.to_string(); .to_string();
let aws_pattern = Regex::new(r"AKIA[0-9A-Z]{16}").expect("valid regex"); let aws_pattern = Regex::new(r"AKIA[0-9A-Z]{16}")?;
result = aws_pattern result = aws_pattern
.replace_all(&result, "AKIA***REDACTED***") .replace_all(&result, "AKIA***REDACTED***")
.to_string(); .to_string();

View file

@ -24,7 +24,9 @@ struct ThirdPartyConfig {
static THIRDPARTY_CONFIG: Lazy<ThirdPartyConfig> = Lazy::new(|| { static THIRDPARTY_CONFIG: Lazy<ThirdPartyConfig> = Lazy::new(|| {
let toml_str = include_str!("../../../3rdparty.toml"); let toml_str = include_str!("../../../3rdparty.toml");
toml::from_str(toml_str).expect("Failed to parse embedded 3rdparty.toml") toml::from_str(toml_str).unwrap_or_else(|e| {
panic!("Failed to parse embedded 3rdparty.toml: {e}")
})
}); });
fn get_component_url(name: &str) -> Option<String> { fn get_component_url(name: &str) -> Option<String> {

View file

@ -474,12 +474,12 @@ impl DirectorySetup {
.bearer_auth(&access_token) .bearer_auth(&access_token)
.json(&json!({ .json(&json!({
"name": app_name, "name": app_name,
"redirectUris": [redirect_uri, "http://localhost:3000/auth/callback", "http://localhost:8080/auth/callback"], "redirectUris": [redirect_uri, "http://localhost:3000/auth/callback", "http://localhost:8080/auth/callback", "http://localhost:9000/auth/callback"],
"responseTypes": ["OIDC_RESPONSE_TYPE_CODE"], "responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
"grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN", "OIDC_GRANT_TYPE_PASSWORD"], "grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN", "OIDC_GRANT_TYPE_PASSWORD"],
"appType": "OIDC_APP_TYPE_WEB", "appType": "OIDC_APP_TYPE_WEB",
"authMethodType": "OIDC_AUTH_METHOD_TYPE_POST", "authMethodType": "OIDC_AUTH_METHOD_TYPE_POST",
"postLogoutRedirectUris": ["http://localhost:8080", "http://localhost:3000", "http://localhost:8080"], "postLogoutRedirectUris": ["http://localhost:8080", "http://localhost:3000", "http://localhost:9000"],
"accessTokenType": "OIDC_TOKEN_TYPE_BEARER", "accessTokenType": "OIDC_TOKEN_TYPE_BEARER",
"devMode": true, "devMode": true,
})) }))

View file

@ -20,6 +20,7 @@ impl SecretPaths {
pub const EMAIL: &'static str = "gbo/email"; pub const EMAIL: &'static str = "gbo/email";
pub const LLM: &'static str = "gbo/llm"; pub const LLM: &'static str = "gbo/llm";
pub const ENCRYPTION: &'static str = "gbo/encryption"; pub const ENCRYPTION: &'static str = "gbo/encryption";
pub const JWT: &'static str = "gbo/jwt";
pub const MEET: &'static str = "gbo/meet"; pub const MEET: &'static str = "gbo/meet";
pub const ALM: &'static str = "gbo/alm"; pub const ALM: &'static str = "gbo/alm";
pub const VECTORDB: &'static str = "gbo/vectordb"; pub const VECTORDB: &'static str = "gbo/vectordb";
@ -270,6 +271,10 @@ impl SecretsManager {
self.get_value(SecretPaths::ENCRYPTION, "master_key").await self.get_value(SecretPaths::ENCRYPTION, "master_key").await
} }
pub async fn get_jwt_secret(&self) -> Result<String> {
self.get_value(SecretPaths::JWT, "secret").await
}
pub async fn put_secret(&self, path: &str, data: HashMap<String, String>) -> Result<()> { pub async fn put_secret(&self, path: &str, data: HashMap<String, String>) -> Result<()> {
let client = self let client = self
.client .client

View file

@ -1,3 +1,4 @@
use serde::Serialize;
use std::path::Path; use std::path::Path;
use std::fs; use std::fs;
@ -83,7 +84,7 @@ impl BasFileAnalyzer {
} }
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, Serialize)]
pub struct WorkflowMetadata { pub struct WorkflowMetadata {
pub name: String, pub name: String,
pub step_count: usize, pub step_count: usize,

View file

@ -242,7 +242,7 @@ pub async fn workflow_designer_page(
content = '<strong>Parallel</strong><br>Multiple branches'; content = '<strong>Parallel</strong><br>Multiple branches';
break; break;
case 'event': case 'event':
content = '<strong>Event</strong><br><input type="text" placeholder="Event Name " style="width:100px;margin:2px;">'; content = '<strong>Event</strong><br><input type="text" placeholder="Event Name" style="width:100px;margin:2px;">';
break; break;
} }

View file

@ -406,7 +406,7 @@ pub async fn get_current_user(
is_anonymous: true, is_anonymous: true,
}) })
} }
Some(token) if token.is_empty() => { Some("") => {
info!("get_current_user: empty authorization token - returning anonymous user"); info!("get_current_user: empty authorization token - returning anonymous user");
Json(CurrentUserResponse { Json(CurrentUserResponse {
id: None, id: None,

View file

@ -23,7 +23,6 @@ use serde::{Deserialize, Serialize};
use tokio::fs as tokio_fs; use tokio::fs as tokio_fs;
#[cfg(any(feature = "research", feature = "llm"))] #[cfg(any(feature = "research", feature = "llm"))]
#[allow(dead_code)]
const KB_INDEXING_TIMEOUT_SECS: u64 = 60; const KB_INDEXING_TIMEOUT_SECS: u64 = 60;
const MAX_BACKOFF_SECS: u64 = 300; const MAX_BACKOFF_SECS: u64 = 300;
const INITIAL_BACKOFF_SECS: u64 = 30; const INITIAL_BACKOFF_SECS: u64 = 30;
@ -43,7 +42,6 @@ pub struct DriveMonitor {
is_processing: Arc<AtomicBool>, is_processing: Arc<AtomicBool>,
consecutive_failures: Arc<AtomicU32>, consecutive_failures: Arc<AtomicU32>,
#[cfg(any(feature = "research", feature = "llm"))] #[cfg(any(feature = "research", feature = "llm"))]
#[allow(dead_code)]
kb_indexing_in_progress: Arc<TokioRwLock<HashSet<String>>>, kb_indexing_in_progress: Arc<TokioRwLock<HashSet<String>>>,
} }
impl DriveMonitor { impl DriveMonitor {

View file

@ -263,7 +263,23 @@ impl SafeCommand {
} }
cmd.env_clear(); cmd.env_clear();
cmd.env("PATH", "/usr/local/bin:/usr/bin:/bin");
// Build PATH with standard locations plus botserver-stack/bin/shared
let mut path_entries = vec![
"/usr/local/bin".to_string(),
"/usr/bin".to_string(),
"/bin".to_string(),
];
// Add botserver-stack/bin/shared to PATH if it exists
let stack_path = std::env::var("BOTSERVER_STACK_PATH")
.unwrap_or_else(|_| "./botserver-stack".to_string());
let shared_bin = format!("{}/bin/shared", stack_path);
if std::path::Path::new(&shared_bin).exists() {
path_entries.insert(0, shared_bin);
}
cmd.env("PATH", path_entries.join(":"));
cmd.env("HOME", dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp"))); cmd.env("HOME", dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp")));
cmd.env("LANG", "C.UTF-8"); cmd.env("LANG", "C.UTF-8");
@ -284,7 +300,23 @@ impl SafeCommand {
} }
cmd.env_clear(); cmd.env_clear();
cmd.env("PATH", "/usr/local/bin:/usr/bin:/bin");
// Build PATH with standard locations plus botserver-stack/bin/shared
let mut path_entries = vec![
"/usr/local/bin".to_string(),
"/usr/bin".to_string(),
"/bin".to_string(),
];
// Add botserver-stack/bin/shared to PATH if it exists
let stack_path = std::env::var("BOTSERVER_STACK_PATH")
.unwrap_or_else(|_| "./botserver-stack".to_string());
let shared_bin = format!("{}/bin/shared", stack_path);
if std::path::Path::new(&shared_bin).exists() {
path_entries.insert(0, shared_bin);
}
cmd.env("PATH", path_entries.join(":"));
cmd.env("HOME", dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp"))); cmd.env("HOME", dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp")));
cmd.env("LANG", "C.UTF-8"); cmd.env("LANG", "C.UTF-8");
@ -305,7 +337,23 @@ impl SafeCommand {
} }
cmd.env_clear(); cmd.env_clear();
cmd.env("PATH", "/usr/local/bin:/usr/bin:/bin");
// Build PATH with standard locations plus botserver-stack/bin/shared
let mut path_entries = vec![
"/usr/local/bin".to_string(),
"/usr/bin".to_string(),
"/bin".to_string(),
];
// Add botserver-stack/bin/shared to PATH if it exists
let stack_path = std::env::var("BOTSERVER_STACK_PATH")
.unwrap_or_else(|_| "./botserver-stack".to_string());
let shared_bin = format!("{}/bin/shared", stack_path);
if std::path::Path::new(&shared_bin).exists() {
path_entries.insert(0, shared_bin);
}
cmd.env("PATH", path_entries.join(":"));
cmd.env("HOME", dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp"))); cmd.env("HOME", dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp")));
cmd.env("LANG", "C.UTF-8"); cmd.env("LANG", "C.UTF-8");
@ -326,7 +374,23 @@ impl SafeCommand {
} }
cmd.env_clear(); cmd.env_clear();
cmd.env("PATH", "/usr/local/bin:/usr/bin:/bin");
// Build PATH with standard locations plus botserver-stack/bin/shared
let mut path_entries = vec![
"/usr/local/bin".to_string(),
"/usr/bin".to_string(),
"/bin".to_string(),
];
// Add botserver-stack/bin/shared to PATH if it exists
let stack_path = std::env::var("BOTSERVER_STACK_PATH")
.unwrap_or_else(|_| "./botserver-stack".to_string());
let shared_bin = format!("{}/bin/shared", stack_path);
if std::path::Path::new(&shared_bin).exists() {
path_entries.insert(0, shared_bin);
}
cmd.env("PATH", path_entries.join(":"));
cmd.env("HOME", dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp"))); cmd.env("HOME", dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp")));
cmd.env("LANG", "C.UTF-8"); cmd.env("LANG", "C.UTF-8");

View file

@ -337,8 +337,6 @@ mod tests {
fn test_catch_panic_failure() { fn test_catch_panic_failure() {
let result = catch_panic(|| { let result = catch_panic(|| {
panic!("test panic"); panic!("test panic");
#[allow(unreachable_code)]
42
}); });
assert!(result.is_err()); assert!(result.is_err());
assert!(result.unwrap_err().message.contains("test panic")); assert!(result.unwrap_err().message.contains("test panic"));

View file

@ -1,4 +1,3 @@
#![cfg_attr(feature = "mail", allow(unused_imports))]
pub mod audit_log; pub mod audit_log;
pub mod menu_config; pub mod menu_config;
pub mod permission_inheritance; pub mod permission_inheritance;
@ -147,13 +146,10 @@ r##"<div class="connections-empty">
</div>"## .to_string(), ) } </div>"## .to_string(), ) }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct SearchSettingsRequest { struct SearchSettingsRequest {
enable_fuzzy_search: Option<bool>, enable_fuzzy_search: Option<bool>,
search_result_limit: Option<i32>, search_result_limit: Option<i32>,
enable_ai_suggestions: Option<bool>, enable_ai_suggestions: Option<bool>,
index_attachments: Option<bool>,
search_sources: Option<Vec<String>>,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@ -183,8 +179,15 @@ Json(SearchSettingsResponse {
} }
#[derive(Debug, Serialize)]
struct SmtpTestResponse {
success: bool,
message: Option<String>,
error: Option<String>,
}
#[cfg(feature = "mail")]
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct SmtpTestRequest { struct SmtpTestRequest {
host: String, host: String,
port: i32, port: i32,
@ -193,11 +196,14 @@ password: Option<String>,
use_tls: Option<bool>, use_tls: Option<bool>,
} }
#[derive(Debug, Serialize)] #[cfg(not(feature = "mail"))]
struct SmtpTestResponse { #[derive(Debug, Deserialize)]
success: bool, struct SmtpTestRequest {
message: Option<String>, _host: String,
error: Option<String>, _port: i32,
_username: Option<String>,
_password: Option<String>,
_use_tls: Option<bool>,
} }
#[cfg(feature = "mail")] #[cfg(feature = "mail")]

View file

@ -344,7 +344,6 @@ async fn process_attendant_command(
} }
async fn check_is_attendant(state: &Arc<AppState>, phone: &str) -> bool { async fn check_is_attendant(state: &Arc<AppState>, phone: &str) -> bool {
let _conn = state.conn.clone();
let phone_clone = phone.to_string(); let phone_clone = phone.to_string();
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {