Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155//! Server runtime configuration.//!//! Configuration that varies per deployment (e.g. `DATABASE_URL`,//! `POLYMODEL_BASE_URL`) and is only needed by the server binary. These values//! are read from the process environment at startup, not baked into the WASM//! bundle.//!//! The server binary calls `dotenvy::dotenv()` in `main` before//! [`ServerConfig::load`]. Hydrant reads its own `HYDRANT_*` variables via its//! own `Config::from_env`; this struct holds only app-level server config and is//! the single place server-side environment variables are read.
use std::env;use std::path::PathBuf;
/// App-level server configuration loaded from the environment.#[derive(Debug, Clone)]pub struct ServerConfig { /// sqlx connect URL, e.g. `sqlite:./data/polymodel.db`. pub database_url: String, /// Public origin of this server, e.g. `https://polymodel.example.com`. /// /// The OAuth `client_id` is `{base_url}/oauth-client-metadata.json` and the /// redirect URI is `{base_url}/oauth/callback`. When unset, bootstrap /// defaults the OAuth client origin to `https://polymodel.space`, a functional /// hosted client; set `POLYMODEL_BASE_URL` for local or alternate origins. pub base_url: Option<String>, /// Optional DID/PDS resolver base used by deterministic local fixtures. /// Production deployments leave this unset and use the public resolver. pub resolver_base_url: Option<String>, /// Optional immutable official LDraw library root from `LDRAWDIR`. The /// directory must contain the standard `parts/` and `p/` subdirectories. It /// is never exposed to browser code and is only read by server-side compound /// routes. pub ldraw_library_dir: Option<PathBuf>, /// Whether to start the Hydrant firehose/indexing pipeline. Defaults to /// `on`; local browser/demo runs can set `POLYMODEL_INDEXING=off` to keep /// a seeded projection deterministic. pub indexing: bool, /// Local/demo sample-data mode. Defaults to `off`; `auto` seeds /// conservative local SQLite DBs only when empty; `force` seeds any SQLite /// DB when empty. pub sample_data: SampleDataMode,}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum SampleDataMode { Auto, Off, Force,}
impl SampleDataMode { fn from_env(value: Option<String>) -> Self { match value.as_deref().map(str::trim).map(str::to_ascii_lowercase) { Some(value) if value == "off" => Self::Off, Some(value) if value == "force" => Self::Force, Some(value) if value == "auto" => Self::Auto, Some(value) => { tracing::warn!(%value, "unknown POLYMODEL_SAMPLE_DATA value; falling back to off"); Self::Off } None => Self::Off, } }
pub fn should_seed(self, database_url: &str) -> bool { match self { Self::Off => false, Self::Force => database_url.starts_with("sqlite:"), Self::Auto => is_local_sqlite(database_url), } }}
fn is_local_sqlite(database_url: &str) -> bool { database_url == "sqlite::memory:" || database_url.starts_with("sqlite:./") || database_url.starts_with("sqlite:../") || database_url.starts_with("sqlite:/")}
impl ServerConfig { /// Read the server config from the process environment. /// /// Call `dotenvy::dotenv()` first so variables in `.env` are visible. This /// is the only place server-side env reads happen. pub fn load() -> Self { let database_url = env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite:./data/polymodel.db".to_string()); let base_url = env::var("POLYMODEL_BASE_URL") .ok() .map(|s| s.trim().to_owned()) .filter(|s| !s.is_empty()); let resolver_base_url = env::var("POLYMODEL_RESOLVER_BASE_URL") .ok() .map(|s| s.trim_end_matches('/').to_owned()) .filter(|s| !s.is_empty()); let ldraw_library_dir = env::var_os("LDRAWDIR") .map(PathBuf::from) .filter(|path| path.is_dir()); let indexing = !matches!( env::var("POLYMODEL_INDEXING") .ok() .as_deref() .map(str::trim) .map(str::to_ascii_lowercase) .as_deref(), Some("off" | "false" | "0") ); let sample_data = SampleDataMode::from_env(env::var("POLYMODEL_SAMPLE_DATA").ok()); Self { database_url, base_url, resolver_base_url, ldraw_library_dir, indexing, sample_data, } }}
#[cfg(test)]mod tests { use super::*;
#[test] fn sample_data_auto_is_limited_to_local_sqlite_urls() { assert!(SampleDataMode::Auto.should_seed("sqlite:./data/polymodel.db")); assert!(SampleDataMode::Auto.should_seed("sqlite:/tmp/polymodel.db")); assert!(!SampleDataMode::Auto.should_seed("postgres://example")); }
#[test] fn sample_data_env_parses_known_values() { assert_eq!( SampleDataMode::from_env(Some("off".into())), SampleDataMode::Off ); assert_eq!( SampleDataMode::from_env(Some("force".into())), SampleDataMode::Force ); assert_eq!( SampleDataMode::from_env(Some("auto".into())), SampleDataMode::Auto ); }
#[test] fn sample_data_defaults_to_off() { assert_eq!(SampleDataMode::from_env(None), SampleDataMode::Off); }}