Spaces:
Paused
Paused
Download api/src/config.rs from stremio-db/a: direct link, hf CLI and curl.
- Browser
- Download file 13.7 kB
-
https://huggingface.co/spaces/stremio-db/a/resolve/main/api/src/config.rs
- Command line
-
hf download hf://spaces/stremio-db/a/api/src/config.rs
-
curl -L -o config.rs https://huggingface.co/spaces/stremio-db/a/resolve/main/api/src/config.rs
13.7 kB
| use std::env; | |
| pub struct Config { | |
| pub tmdb_api_key: String, | |
| pub omdb_api_key: Option<String>, | |
| pub cache_dir: String, | |
| pub db_dir: String, | |
| pub listen_addr: String, | |
| pub ratings_min_stale_secs: u64, | |
| pub ratings_max_age_secs: u64, | |
| pub image_stale_secs: u64, | |
| pub image_quality: u8, | |
| pub mdblist_api_key: Option<String>, | |
| pub image_mem_cache_mb: u64, | |
| pub static_dir: Option<String>, | |
| pub cors_origin: Option<String>, | |
| pub fanart_api_key: Option<String>, | |
| pub trakt_client_id: Option<String>, | |
| pub enable_cdn_redirects: bool, | |
| pub external_cache_only: bool, | |
| pub free_key_enabled: Option<bool>, | |
| pub disable_public_pages: bool, | |
| pub stremio_accounts: Vec<StremioAccount>, | |
| pub stremio_purge_exclude_addons: Vec<String>, | |
| } | |
| pub struct StremioAccount { | |
| pub label: String, | |
| pub auth_key: String, | |
| } | |
| impl std::fmt::Debug for Config { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| f.debug_struct("Config") | |
| .field("tmdb_api_key", &"[REDACTED]") | |
| .field("omdb_api_key", &self.omdb_api_key.as_ref().map(|_| "[REDACTED]")) | |
| .field("cache_dir", &self.cache_dir) | |
| .field("db_dir", &self.db_dir) | |
| .field("listen_addr", &self.listen_addr) | |
| .field("ratings_min_stale_secs", &self.ratings_min_stale_secs) | |
| .field("ratings_max_age_secs", &self.ratings_max_age_secs) | |
| .field("image_stale_secs", &self.image_stale_secs) | |
| .field("image_quality", &self.image_quality) | |
| .field("mdblist_api_key", &self.mdblist_api_key.as_ref().map(|_| "[REDACTED]")) | |
| .field("image_mem_cache_mb", &self.image_mem_cache_mb) | |
| .field("static_dir", &self.static_dir) | |
| .field("cors_origin", &self.cors_origin) | |
| .field("fanart_api_key", &self.fanart_api_key.as_ref().map(|_| "[REDACTED]")) | |
| .field("trakt_client_id", &self.trakt_client_id.as_ref().map(|_| "[REDACTED]")) | |
| .field("enable_cdn_redirects", &self.enable_cdn_redirects) | |
| .field("external_cache_only", &self.external_cache_only) | |
| .field("free_key_enabled", &self.free_key_enabled) | |
| .field("disable_public_pages", &self.disable_public_pages) | |
| .field( | |
| "stremio_accounts", | |
| &self.stremio_accounts.iter().map(|a| a.label.clone()).collect::<Vec<_>>(), | |
| ) | |
| .field("stremio_purge_exclude_addons", &self.stremio_purge_exclude_addons) | |
| .finish() | |
| } | |
| } | |
| /// Read an optional secret env var, trimming surrounding whitespace and treating | |
| /// an empty (or whitespace-only) value as absent. | |
| /// | |
| /// Without this a blank or whitespace/CR/quote-contaminated value (a common | |
| /// deployment foot-gun: CRLF `.env` files, mis-quoted shell) would still be | |
| /// `Some(_)` and construct a live client that sends a junk key on every request. | |
| /// For Trakt that surfaces as a 403 on *every* title rather than the provider | |
| /// simply being disabled. | |
| fn optional_secret(key: &str) -> Option<String> { | |
| env::var(key) | |
| .ok() | |
| .map(|s| s.trim().to_string()) | |
| .filter(|s| !s.is_empty()) | |
| } | |
| /// Parses up to 10 numbered Stremio account pairs: | |
| /// STREMIO_ACCOUNT_USER_{n} and STREMIO_ACCOUNT_AUTHKEY_{n} (n = 1..10). | |
| /// Both must be present (and non-empty) for slot n to count. | |
| fn parse_stremio_accounts() -> Vec<StremioAccount> { | |
| let mut accounts = Vec::new(); | |
| for i in 1..=10 { | |
| let label = optional_secret(&format!("STREMIO_ACCOUNT_USER_{i}")); | |
| let auth_key = optional_secret(&format!("STREMIO_ACCOUNT_AUTHKEY_{i}")); | |
| if let (Some(label), Some(auth_key)) = (label, auth_key) { | |
| accounts.push(StremioAccount { label, auth_key }); | |
| } | |
| } | |
| accounts | |
| } | |
| /// Parses a comma-separated list env var into trimmed, non-empty entries. | |
| fn parse_exclude_list(key: &str) -> Vec<String> { | |
| env::var(key) | |
| .ok() | |
| .map(|v| { | |
| v.split(',') | |
| .map(|s| s.trim().to_string()) | |
| .filter(|s| !s.is_empty()) | |
| .collect() | |
| }) | |
| .unwrap_or_default() | |
| } | |
| impl Config { | |
| pub fn from_env() -> Self { | |
| let config = Self { | |
| tmdb_api_key: env::var("TMDB_API_KEY").expect("TMDB_API_KEY must be set"), | |
| omdb_api_key: optional_secret("OMDB_API_KEY"), | |
| cache_dir: env::var("CACHE_DIR").unwrap_or_else(|_| "./cache".into()), | |
| db_dir: env::var("DB_DIR").unwrap_or_else(|_| "./db".into()), | |
| listen_addr: env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:3000".into()), | |
| ratings_min_stale_secs: env::var("RATINGS_STALE_SECS") | |
| .ok() | |
| .and_then(|v| v.parse().ok()) | |
| .unwrap_or(86400), | |
| ratings_max_age_secs: env::var("RATINGS_MAX_AGE_SECS") | |
| .ok() | |
| .and_then(|v| v.parse().ok()) | |
| .unwrap_or(31_536_000), | |
| image_stale_secs: env::var("IMAGE_STALE_SECS") | |
| .ok() | |
| .and_then(|v| v.parse().ok()) | |
| .unwrap_or(0), | |
| image_quality: env::var("IMAGE_QUALITY") | |
| .ok() | |
| .and_then(|v| v.parse().ok()) | |
| .unwrap_or(85), | |
| mdblist_api_key: optional_secret("MDBLIST_API_KEY"), | |
| image_mem_cache_mb: env::var("IMAGE_MEM_CACHE_MB") | |
| .ok() | |
| .and_then(|v| v.parse().ok()) | |
| .unwrap_or(512), | |
| static_dir: env::var("STATIC_DIR").ok(), | |
| cors_origin: env::var("CORS_ORIGIN").ok(), | |
| fanart_api_key: optional_secret("FANART_API_KEY"), | |
| trakt_client_id: optional_secret("TRAKT_CLIENT_ID"), | |
| enable_cdn_redirects: env::var("ENABLE_CDN_REDIRECTS") | |
| .map(|v| v == "true" || v == "1") | |
| .unwrap_or(false), | |
| external_cache_only: env::var("EXTERNAL_CACHE_ONLY") | |
| .map(|v| v == "true" || v == "1") | |
| .unwrap_or(false), | |
| free_key_enabled: env::var("FREE_KEY_ENABLED") | |
| .ok() | |
| .map(|v| v == "true" || v == "1"), | |
| disable_public_pages: env::var("DISABLE_PUBLIC_PAGES") | |
| .map(|v| v == "true" || v == "1") | |
| .unwrap_or(false), | |
| stremio_accounts: parse_stremio_accounts(), | |
| stremio_purge_exclude_addons: parse_exclude_list("STREMIO_PURGE_EXCLUDE_ADDONS"), | |
| }; | |
| if config.omdb_api_key.is_none() | |
| && config.mdblist_api_key.is_none() | |
| && config.trakt_client_id.is_none() | |
| { | |
| panic!( | |
| "at least one of OMDB_API_KEY, MDBLIST_API_KEY, or TRAKT_CLIENT_ID must be set" | |
| ); | |
| } | |
| config | |
| } | |
| } | |
| mod tests { | |
| use super::*; | |
| use serial_test::serial; | |
| use std::env; | |
| /// Helper: remove all env vars that Config::from_env reads. | |
| /// | |
| /// # Safety | |
| /// Tests using this must run serially (via `#[serial]`) to avoid races. | |
| unsafe fn clear_config_env() { | |
| for key in [ | |
| "TMDB_API_KEY", | |
| "OMDB_API_KEY", | |
| "MDBLIST_API_KEY", | |
| "CACHE_DIR", | |
| "DB_DIR", | |
| "LISTEN_ADDR", | |
| "RATINGS_STALE_SECS", | |
| "RATINGS_MAX_AGE_SECS", | |
| "IMAGE_STALE_SECS", | |
| "IMAGE_QUALITY", | |
| "IMAGE_MEM_CACHE_MB", | |
| "STATIC_DIR", | |
| "CORS_ORIGIN", | |
| "FANART_API_KEY", | |
| "TRAKT_CLIENT_ID", | |
| "ENABLE_CDN_REDIRECTS", | |
| "EXTERNAL_CACHE_ONLY", | |
| "FREE_KEY_ENABLED", | |
| "DISABLE_PUBLIC_PAGES", | |
| ] { | |
| unsafe { env::remove_var(key) }; | |
| } | |
| } | |
| fn test_valid_config_with_omdb() { | |
| unsafe { clear_config_env() }; | |
| unsafe { env::set_var("TMDB_API_KEY", "tmdb_test") }; | |
| unsafe { env::set_var("OMDB_API_KEY", "omdb_test") }; | |
| let cfg = Config::from_env(); | |
| assert_eq!(cfg.tmdb_api_key, "tmdb_test"); | |
| assert_eq!(cfg.omdb_api_key.as_deref(), Some("omdb_test")); | |
| assert!(cfg.mdblist_api_key.is_none()); | |
| assert_eq!(cfg.cache_dir, "./cache"); | |
| assert_eq!(cfg.db_dir, "./db"); | |
| assert_eq!(cfg.listen_addr, "0.0.0.0:3000"); | |
| assert_eq!(cfg.ratings_min_stale_secs, 86400); | |
| assert_eq!(cfg.ratings_max_age_secs, 31_536_000); | |
| assert_eq!(cfg.image_stale_secs, 0); | |
| assert_eq!(cfg.image_quality, 85); | |
| assert_eq!(cfg.image_mem_cache_mb, 512); | |
| assert!(!cfg.enable_cdn_redirects); | |
| assert!(!cfg.external_cache_only); | |
| assert!(cfg.free_key_enabled.is_none()); | |
| } | |
| fn test_valid_config_with_mdblist() { | |
| unsafe { clear_config_env() }; | |
| unsafe { env::set_var("TMDB_API_KEY", "tmdb_test") }; | |
| unsafe { env::set_var("MDBLIST_API_KEY", "mdblist_test") }; | |
| let cfg = Config::from_env(); | |
| assert!(cfg.omdb_api_key.is_none()); | |
| assert_eq!(cfg.mdblist_api_key.as_deref(), Some("mdblist_test")); | |
| } | |
| fn test_optional_secret_trims_and_drops_empty() { | |
| unsafe { clear_config_env() }; | |
| unsafe { env::set_var("TMDB_API_KEY", "tmdb_test") }; | |
| // A whitespace-padded value is trimmed (CRLF .env / mis-quoted shell). | |
| unsafe { env::set_var("OMDB_API_KEY", " omdb_test ") }; | |
| // Empty / whitespace-only optional keys are treated as absent so they | |
| // never build a client that 403s on every request (the Trakt symptom). | |
| unsafe { env::set_var("MDBLIST_API_KEY", "") }; | |
| unsafe { env::set_var("TRAKT_CLIENT_ID", " ") }; | |
| let cfg = Config::from_env(); | |
| assert_eq!(cfg.omdb_api_key.as_deref(), Some("omdb_test"), "value is trimmed"); | |
| assert!(cfg.mdblist_api_key.is_none(), "empty key treated as absent"); | |
| assert!(cfg.trakt_client_id.is_none(), "whitespace-only key treated as absent"); | |
| } | |
| fn test_custom_numeric_overrides() { | |
| unsafe { clear_config_env() }; | |
| unsafe { env::set_var("TMDB_API_KEY", "tmdb_test") }; | |
| unsafe { env::set_var("OMDB_API_KEY", "omdb_test") }; | |
| unsafe { env::set_var("RATINGS_STALE_SECS", "100") }; | |
| unsafe { env::set_var("RATINGS_MAX_AGE_SECS", "200") }; | |
| unsafe { env::set_var("IMAGE_STALE_SECS", "300") }; | |
| unsafe { env::set_var("IMAGE_QUALITY", "50") }; | |
| unsafe { env::set_var("IMAGE_MEM_CACHE_MB", "1024") }; | |
| let cfg = Config::from_env(); | |
| assert_eq!(cfg.ratings_min_stale_secs, 100); | |
| assert_eq!(cfg.ratings_max_age_secs, 200); | |
| assert_eq!(cfg.image_stale_secs, 300); | |
| assert_eq!(cfg.image_quality, 50); | |
| assert_eq!(cfg.image_mem_cache_mb, 1024); | |
| } | |
| fn test_invalid_numeric_falls_back_to_default() { | |
| unsafe { clear_config_env() }; | |
| unsafe { env::set_var("TMDB_API_KEY", "tmdb_test") }; | |
| unsafe { env::set_var("OMDB_API_KEY", "omdb_test") }; | |
| unsafe { env::set_var("IMAGE_QUALITY", "notanumber") }; | |
| unsafe { env::set_var("RATINGS_STALE_SECS", "abc") }; | |
| let cfg = Config::from_env(); | |
| assert_eq!(cfg.image_quality, 85); | |
| assert_eq!(cfg.ratings_min_stale_secs, 86400); | |
| } | |
| fn test_panics_without_tmdb_key() { | |
| unsafe { clear_config_env() }; | |
| Config::from_env(); | |
| } | |
| fn test_panics_without_ratings_provider() { | |
| unsafe { clear_config_env() }; | |
| unsafe { env::set_var("TMDB_API_KEY", "tmdb_test") }; | |
| Config::from_env(); | |
| } | |
| fn test_boolean_env_true() { | |
| unsafe { clear_config_env() }; | |
| unsafe { env::set_var("TMDB_API_KEY", "tmdb_test") }; | |
| unsafe { env::set_var("OMDB_API_KEY", "omdb_test") }; | |
| unsafe { env::set_var("ENABLE_CDN_REDIRECTS", "true") }; | |
| unsafe { env::set_var("EXTERNAL_CACHE_ONLY", "1") }; | |
| unsafe { env::set_var("DISABLE_PUBLIC_PAGES", "true") }; | |
| let cfg = Config::from_env(); | |
| assert!(cfg.enable_cdn_redirects); | |
| assert!(cfg.external_cache_only); | |
| assert!(cfg.disable_public_pages); | |
| } | |
| fn test_boolean_env_defaults_false() { | |
| unsafe { clear_config_env() }; | |
| unsafe { env::set_var("TMDB_API_KEY", "tmdb_test") }; | |
| unsafe { env::set_var("OMDB_API_KEY", "omdb_test") }; | |
| let cfg = Config::from_env(); | |
| assert!(!cfg.enable_cdn_redirects); | |
| assert!(!cfg.external_cache_only); | |
| assert!(!cfg.disable_public_pages); | |
| } | |
| fn test_free_key_enabled_some() { | |
| unsafe { clear_config_env() }; | |
| unsafe { env::set_var("TMDB_API_KEY", "tmdb_test") }; | |
| unsafe { env::set_var("OMDB_API_KEY", "omdb_test") }; | |
| unsafe { env::set_var("FREE_KEY_ENABLED", "true") }; | |
| let cfg = Config::from_env(); | |
| assert_eq!(cfg.free_key_enabled, Some(true)); | |
| } | |
| fn test_free_key_enabled_none() { | |
| unsafe { clear_config_env() }; | |
| unsafe { env::set_var("TMDB_API_KEY", "tmdb_test") }; | |
| unsafe { env::set_var("OMDB_API_KEY", "omdb_test") }; | |
| let cfg = Config::from_env(); | |
| assert!(cfg.free_key_enabled.is_none()); | |
| } | |
| } | |