diff --git a/server/.env.example b/server/.env.example index 70679ee..3e93a22 100644 --- a/server/.env.example +++ b/server/.env.example @@ -28,6 +28,7 @@ LLM_URL=https://openai.ionos.com/openai LLM_API_KEY= LLM_MODEL= LLM_TEMPERATURE=0 +LLM_TIMEOUT_SECONDS=180 # Session SESSION_TIMEOUT_HOURS=8 diff --git a/server/src/config.rs b/server/src/config.rs index b91346b..bb317cc 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -52,6 +52,7 @@ pub struct Config { pub llm_api_key: String, pub llm_model: String, pub llm_temperature: f32, + pub llm_timeout_seconds: u64, pub session_timeout_hours: u32, /// Whether to set the `Secure` flag on the session cookie. Default `true`. /// Set `COOKIE_SECURE=false` only for plain-HTTP local development — @@ -85,10 +86,59 @@ impl Config { llm_api_key: optional_env("LLM_API_KEY", ""), llm_model: optional_env("LLM_MODEL", ""), llm_temperature: optional_env_parsed("LLM_TEMPERATURE", 0.0), + llm_timeout_seconds: optional_env_parsed("LLM_TIMEOUT_SECONDS", 180), session_timeout_hours: optional_env_parsed("SESSION_TIMEOUT_HOURS", 8), cookie_secure: optional_env_parsed("COOKIE_SECURE", true), } } + + /// True iff all three required fields for the external analysis LLM are + /// non-empty. Used to gate the "Fall abschließen" UI and handler — if + /// no LLM is configured, the close action must not be reachable. + pub fn llm_configured(&self) -> bool { + !self.llm_url.is_empty() && !self.llm_api_key.is_empty() && !self.llm_model.is_empty() + } + + /// Sane defaults for integration tests. Not a `Default` impl on purpose: + /// production code must go through `from_env()`, and `Default::default()` + /// carries an implicit "safe fallback" connotation this value does not + /// satisfy (empty users, `/tmp` paths). Override fields via + /// struct-update syntax: + /// + /// ```ignore + /// let cfg = Config { + /// data_path: tmp_dir, + /// users: vec![my_user], + /// ..Config::test_default() + /// }; + /// ``` + #[doc(hidden)] + pub fn test_default() -> Self { + Self { + server_port: 3000, + data_path: PathBuf::from("/tmp"), + log_level: "info".into(), + log_path: PathBuf::from("/tmp"), + log_max_days: 90, + users: Vec::new(), + api_keys: HashMap::new(), + retention_audio_days: 30, + retention_transcript_days: 30, + retention_document_days: 0, + whisper_url: "http://localhost:10300".into(), + whisper_timeout_seconds: 120, + ollama_url: "http://localhost:11434".into(), + ollama_model: "gemma3:4b".into(), + ollama_keep_alive: 0, + llm_url: String::new(), + llm_api_key: String::new(), + llm_model: String::new(), + llm_temperature: 0.0, + llm_timeout_seconds: 180, + session_timeout_hours: 8, + cookie_secure: false, + } + } } /// Load users from a TOML file and build the API key lookup map. diff --git a/server/tests/auth_test.rs b/server/tests/auth_test.rs index e8f83b7..e9996af 100644 --- a/server/tests/auth_test.rs +++ b/server/tests/auth_test.rs @@ -9,11 +9,8 @@ use doctate_server::config::{Config, User}; fn test_config() -> Arc { Arc::new(Config { - server_port: 3000, data_path: "/tmp/doctate-test".into(), - log_level: "info".into(), log_path: "/tmp/doctate-test/logs".into(), - log_max_days: 90, users: vec![User { slug: "dr_test".into(), api_key: "test-key-123".into(), @@ -22,20 +19,7 @@ fn test_config() -> Arc { whisper: Default::default(), }], api_keys: HashMap::from([("test-key-123".into(), "dr_test".into())]), - retention_audio_days: 30, - retention_transcript_days: 30, - retention_document_days: 0, - whisper_url: "http://localhost:10300".into(), - whisper_timeout_seconds: 120, - ollama_url: "http://localhost:11434".into(), - ollama_model: "gemma3:4b".into(), - ollama_keep_alive: 0, - llm_url: String::new(), - llm_api_key: String::new(), - llm_model: String::new(), - llm_temperature: 0.0, - session_timeout_hours: 8, - cookie_secure: false, + ..Config::test_default() }) } diff --git a/server/tests/health_test.rs b/server/tests/health_test.rs index c065e1d..abf0a87 100644 --- a/server/tests/health_test.rs +++ b/server/tests/health_test.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::sync::Arc; use axum::body::Body; @@ -9,27 +8,9 @@ use doctate_server::config::Config; fn test_config() -> Arc { Arc::new(Config { - server_port: 3000, data_path: "/tmp/doctate-test".into(), - log_level: "info".into(), log_path: "/tmp/doctate-test/logs".into(), - log_max_days: 90, - users: vec![], - api_keys: HashMap::new(), - retention_audio_days: 30, - retention_transcript_days: 30, - retention_document_days: 0, - whisper_url: "http://localhost:10300".into(), - whisper_timeout_seconds: 120, - ollama_url: "http://localhost:11434".into(), - ollama_model: "gemma3:4b".into(), - ollama_keep_alive: 0, - llm_url: String::new(), - llm_api_key: String::new(), - llm_model: String::new(), - llm_temperature: 0.0, - session_timeout_hours: 8, - cookie_secure: false, + ..Config::test_default() }) } diff --git a/server/tests/login_test.rs b/server/tests/login_test.rs index 49aa38c..4c81775 100644 --- a/server/tests/login_test.rs +++ b/server/tests/login_test.rs @@ -26,27 +26,10 @@ fn test_config_with_users(users: Vec) -> Arc { let api_keys: HashMap = users.iter().map(|u| (u.api_key.clone(), u.slug.clone())).collect(); Arc::new(Config { - server_port: 3000, data_path, - log_level: "info".into(), - log_path: "/tmp/doctate-test/logs".into(), - log_max_days: 90, users, api_keys, - retention_audio_days: 30, - retention_transcript_days: 30, - retention_document_days: 0, - whisper_url: "http://localhost:10300".into(), - whisper_timeout_seconds: 120, - ollama_url: "http://localhost:11434".into(), - ollama_model: "gemma3:4b".into(), - ollama_keep_alive: 0, - llm_url: String::new(), - llm_api_key: String::new(), - llm_model: String::new(), - llm_temperature: 0.0, - session_timeout_hours: 8, - cookie_secure: false, + ..Config::test_default() }) } diff --git a/server/tests/transcribe_test.rs b/server/tests/transcribe_test.rs index 23efa45..37cce96 100644 --- a/server/tests/transcribe_test.rs +++ b/server/tests/transcribe_test.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use std::time::Duration; -use std::collections::HashMap; use std::sync::Arc; use doctate_server::config::{Config, User, WhisperUserSettings}; @@ -249,11 +248,6 @@ async fn recovery_enqueues_only_pending_recordings() { fn test_config_with_whisper(whisper_url: String) -> Arc { Arc::new(Config { - server_port: 3000, - data_path: "/tmp".into(), - log_level: "info".into(), - log_path: "/tmp".into(), - log_max_days: 90, users: vec![User { slug: "dr_test".into(), api_key: "k".into(), @@ -261,22 +255,12 @@ fn test_config_with_whisper(whisper_url: String) -> Arc { role: "doctor".into(), whisper: Default::default(), }], - api_keys: HashMap::new(), - retention_audio_days: 30, - retention_transcript_days: 30, - retention_document_days: 0, whisper_url, whisper_timeout_seconds: 5, // Ollama URL is irrelevant — worker never reaches it in the failure path. ollama_url: "http://127.0.0.1:1".into(), ollama_model: "test".into(), - ollama_keep_alive: 0, - llm_url: String::new(), - llm_api_key: String::new(), - llm_model: String::new(), - llm_temperature: 0.0, - session_timeout_hours: 8, - cookie_secure: false, + ..Config::test_default() }) } diff --git a/server/tests/upload_test.rs b/server/tests/upload_test.rs index 78ecb19..c09f723 100644 --- a/server/tests/upload_test.rs +++ b/server/tests/upload_test.rs @@ -14,11 +14,7 @@ fn test_config() -> Arc { uuid::Uuid::new_v4() )); Arc::new(Config { - server_port: 3000, data_path, - log_level: "info".into(), - log_path: "/tmp/doctate-test/logs".into(), - log_max_days: 90, users: vec![User { slug: "dr_test".into(), api_key: "test-key-123".into(), @@ -27,20 +23,7 @@ fn test_config() -> Arc { whisper: Default::default(), }], api_keys: HashMap::from([("test-key-123".into(), "dr_test".into())]), - retention_audio_days: 30, - retention_transcript_days: 30, - retention_document_days: 0, - whisper_url: "http://localhost:10300".into(), - whisper_timeout_seconds: 120, - ollama_url: "http://localhost:11434".into(), - ollama_model: "gemma3:4b".into(), - ollama_keep_alive: 0, - llm_url: String::new(), - llm_api_key: String::new(), - llm_model: String::new(), - llm_temperature: 0.0, - session_timeout_hours: 8, - cookie_secure: false, + ..Config::test_default() }) } diff --git a/server/tests/web_test.rs b/server/tests/web_test.rs index 3c6d391..9123edc 100644 --- a/server/tests/web_test.rs +++ b/server/tests/web_test.rs @@ -14,11 +14,7 @@ fn test_config() -> Arc { uuid::Uuid::new_v4() )); Arc::new(Config { - server_port: 3000, data_path, - log_level: "info".into(), - log_path: "/tmp/doctate-test/logs".into(), - log_max_days: 90, users: vec![User { slug: "dr_test".into(), api_key: "test-key".into(), @@ -27,20 +23,7 @@ fn test_config() -> Arc { whisper: Default::default(), }], api_keys: HashMap::from([("test-key".into(), "dr_test".into())]), - retention_audio_days: 30, - retention_transcript_days: 30, - retention_document_days: 0, - whisper_url: "http://localhost:10300".into(), - whisper_timeout_seconds: 120, - ollama_url: "http://localhost:11434".into(), - ollama_model: "gemma3:4b".into(), - ollama_keep_alive: 0, - llm_url: String::new(), - llm_api_key: String::new(), - llm_model: String::new(), - llm_temperature: 0.0, - session_timeout_hours: 8, - cookie_secure: false, + ..Config::test_default() }) }