use std::path::PathBuf; #[derive(Clone, Debug)] pub struct AuthConfig { pub cookie_secure: bool, pub cookie_domain: Option, pub session_ttl_days: i64, } impl Default for AuthConfig { fn default() -> Self { Self { cookie_secure: true, cookie_domain: None, session_ttl_days: 30, } } } #[derive(Clone, Debug)] pub struct UploadConfig { /// Total request size cap, enforced by axum's DefaultBodyLimit on the /// upload routes. Rejected requests get a 413. pub max_request_bytes: usize, /// Per-image-part size cap, enforced after the part is read. Lets us /// reject a single oversized cover/page without failing the whole /// request just because the total happens to fit. pub max_file_bytes: usize, } impl Default for UploadConfig { fn default() -> Self { Self { max_request_bytes: 200 * 1024 * 1024, // 200 MiB max_file_bytes: 20 * 1024 * 1024, // 20 MiB } } } #[derive(Clone, Debug)] pub struct Config { pub database_url: String, pub bind_address: String, pub storage_dir: PathBuf, pub auth: AuthConfig, pub upload: UploadConfig, pub cors_allowed_origins: Vec, } impl Config { pub fn from_env() -> anyhow::Result { Ok(Self { database_url: std::env::var("DATABASE_URL") .map_err(|_| anyhow::anyhow!("DATABASE_URL must be set"))?, bind_address: std::env::var("BIND_ADDRESS") .unwrap_or_else(|_| "0.0.0.0:8080".to_string()), storage_dir: std::env::var("STORAGE_DIR") .unwrap_or_else(|_| "./data/storage".to_string()) .into(), auth: AuthConfig { cookie_secure: env_bool("COOKIE_SECURE", true), cookie_domain: std::env::var("COOKIE_DOMAIN") .ok() .filter(|s| !s.is_empty()), session_ttl_days: env_i64("SESSION_TTL_DAYS", 30), }, upload: UploadConfig { max_request_bytes: env_usize("MAX_REQUEST_BYTES", 200 * 1024 * 1024), max_file_bytes: env_usize("MAX_FILE_BYTES", 20 * 1024 * 1024), }, cors_allowed_origins: std::env::var("CORS_ALLOWED_ORIGINS") .ok() .map(|s| { s.split(',') .map(|o| o.trim().to_string()) .filter(|o| !o.is_empty()) .collect() }) .unwrap_or_default(), }) } } fn env_bool(name: &str, default: bool) -> bool { match std::env::var(name).ok().as_deref() { Some("1") | Some("true") | Some("TRUE") | Some("yes") => true, Some("0") | Some("false") | Some("FALSE") | Some("no") => false, _ => default, } } fn env_i64(name: &str, default: i64) -> i64 { std::env::var(name) .ok() .and_then(|s| s.parse().ok()) .unwrap_or(default) } fn env_usize(name: &str, default: usize) -> usize { std::env::var(name) .ok() .and_then(|s| s.parse().ok()) .unwrap_or(default) }