diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 5ee006e..804cc16 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -634,6 +634,7 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "url", "uuid", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 5812992..e400df5 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -43,6 +43,7 @@ percent-encoding = "2.3" base64 = "0.21" quick-xml = "0.32" futures-util = "0.3" +url = "2.5" # Error handling thiserror = "1.0" diff --git a/backend/src/bin/maintenance.rs b/backend/src/bin/maintenance.rs index 679f12e..27e51bf 100644 --- a/backend/src/bin/maintenance.rs +++ b/backend/src/bin/maintenance.rs @@ -32,7 +32,14 @@ async fn main() -> Result<()> { async fn delete_all_assets() -> Result<()> { let config = AppConfig::from_env()?; - let pool = db::init_pool(&config.database_url)?; + tracing::info!( + component = "maintenance", + database_url = %config.redacted_database_url(), + pool_size = config.database_max_pool_size, + s3_bucket = %config.s3_bucket, + "loaded backend configuration" + ); + let pool = db::init_pool_with_size(&config.database_url, config.database_max_pool_size)?; let s3_client = s3::build_client(&config).await?; let storage = S3Storage::new(s3_client, config.s3_bucket.clone()); diff --git a/backend/src/bin/webdav.rs b/backend/src/bin/webdav.rs index 0240b45..7364592 100644 --- a/backend/src/bin/webdav.rs +++ b/backend/src/bin/webdav.rs @@ -19,7 +19,19 @@ async fn main() -> anyhow::Result<()> { init_tracing(); let config = AppConfig::from_env()?; - let pool = db::init_pool(&config.database_url)?; + tracing::info!( + component = "webdav", + database_url = %config.redacted_database_url(), + pool_size = config.database_max_pool_size, + server_host = %config.server_host, + server_port = config.server_port, + webdav_host = %config.webdav_host, + webdav_port = config.webdav_port, + quickwit_enabled = config.quickwit_endpoint.is_some(), + s3_bucket = %config.s3_bucket, + "loaded backend configuration" + ); + let pool = db::init_pool_with_size(&config.database_url, config.database_max_pool_size)?; let s3_client = build_client(&config).await?; let storage = Arc::new(S3Storage::new(s3_client, config.s3_bucket.clone())); let jwt = JwtService::from_config(&config)?; diff --git a/backend/src/bin/worker.rs b/backend/src/bin/worker.rs index b22522e..ce75d22 100644 --- a/backend/src/bin/worker.rs +++ b/backend/src/bin/worker.rs @@ -14,6 +14,14 @@ async fn main() -> anyhow::Result<()> { init_tracing(); let config = AppConfig::from_env()?; + tracing::info!( + component = "worker", + database_url = %config.redacted_database_url(), + pool_size = 1, + quickwit_enabled = config.quickwit_endpoint.is_some(), + s3_bucket = %config.s3_bucket, + "loaded backend configuration" + ); let pool = db::init_pool_with_size(&config.database_url, 1)?; let s3_client = build_client(&config).await?; let storage = Arc::new(S3Storage::new(s3_client, config.s3_bucket.clone())); diff --git a/backend/src/config.rs b/backend/src/config.rs index 6da9d74..2981bc3 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -1,10 +1,14 @@ use std::env; use anyhow::{Context, Result}; +use url::Url; + +use crate::db::DEFAULT_MAX_POOL_SIZE; #[derive(Clone, Debug)] pub struct AppConfig { pub database_url: String, + pub database_max_pool_size: u32, pub server_host: String, pub server_port: u16, pub webdav_host: String, @@ -31,6 +35,10 @@ pub struct AppConfig { impl AppConfig { pub fn from_env() -> Result { let database_url = env::var("DATABASE_URL").context("DATABASE_URL must be set")?; + let database_max_pool_size = env::var("DATABASE_MAX_POOL_SIZE") + .ok() + .and_then(|value| value.parse().ok()) + .unwrap_or(DEFAULT_MAX_POOL_SIZE); let server_host = env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); let server_port = env::var("SERVER_PORT") .unwrap_or_else(|_| "3000".to_string()) @@ -74,6 +82,7 @@ impl AppConfig { Ok(Self { database_url, + database_max_pool_size, server_host, server_port, webdav_host, @@ -97,4 +106,42 @@ impl AppConfig { quickwit_index, }) } + + pub fn redacted_database_url(&self) -> String { + redact_database_url(&self.database_url) + } +} + +fn redact_database_url(raw: &str) -> String { + match Url::parse(raw) { + Ok(mut parsed) => { + let _ = parsed.set_password(Some("*****")); + parsed.to_string() + } + Err(_) => "***".to_string(), + } +} + +#[cfg(test)] +mod tests { + use super::redact_database_url; + + #[test] + fn redacts_password_in_database_url() { + let redacted = redact_database_url("postgres://user:secret@localhost/db"); + assert!(redacted.contains("postgres://user:*****@")); + assert!(!redacted.contains("secret")); + } + + #[test] + fn handles_url_without_password() { + let redacted = redact_database_url("postgres://localhost/db"); + assert_eq!(redacted, "postgres://localhost/db"); + } + + #[test] + fn falls_back_when_parse_fails() { + let redacted = redact_database_url("not a url"); + assert_eq!(redacted, "***"); + } } diff --git a/backend/src/db.rs b/backend/src/db.rs index ccd84ca..a1cd981 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -5,7 +5,7 @@ use diesel::r2d2::{ConnectionManager, Pool}; pub type PgPool = Pool>; -const DEFAULT_MAX_POOL_SIZE: u32 = 2; +pub const DEFAULT_MAX_POOL_SIZE: u32 = 2; pub fn init_pool(database_url: &str) -> anyhow::Result { init_pool_with_size(database_url, DEFAULT_MAX_POOL_SIZE) diff --git a/backend/src/main.rs b/backend/src/main.rs index eb60155..d2c06af 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -19,7 +19,17 @@ async fn main() -> anyhow::Result<()> { init_tracing(); let config = AppConfig::from_env()?; - let pool = db::init_pool(&config.database_url)?; + tracing::info!( + component = "api", + database_url = %config.redacted_database_url(), + pool_size = config.database_max_pool_size, + server_host = %config.server_host, + server_port = config.server_port, + quickwit_enabled = config.quickwit_endpoint.is_some(), + s3_bucket = %config.s3_bucket, + "loaded backend configuration" + ); + let pool = db::init_pool_with_size(&config.database_url, config.database_max_pool_size)?; let s3_client = build_client(&config).await?; let storage = Arc::new(S3Storage::new(s3_client, config.s3_bucket.clone())); let jwt = JwtService::from_config(&config)?; diff --git a/backend/tests/common/mod.rs b/backend/tests/common/mod.rs index e861821..14fef39 100644 --- a/backend/tests/common/mod.rs +++ b/backend/tests/common/mod.rs @@ -116,8 +116,11 @@ impl TestApp { let config = AppConfig { database_url: database_url.clone(), + database_max_pool_size: db::DEFAULT_MAX_POOL_SIZE, server_host: "127.0.0.1".to_string(), server_port: 0, + webdav_host: "127.0.0.1".to_string(), + webdav_port: 0, jwt_secret: "test-secret".to_string(), jwt_issuer: "test-issuer".to_string(), jwt_audience: "test-audience".to_string(), @@ -137,7 +140,7 @@ impl TestApp { quickwit_index: None, }; - let pool = db::init_pool(&config.database_url)?; + let pool = db::init_pool_with_size(&config.database_url, config.database_max_pool_size)?; prepare_database(&pool).await?; let storage = Arc::new(FakeStorage::default());