phonograph/interim-server/src/settings.rs
2025-10-09 08:01:01 +00:00

129 lines
3.4 KiB
Rust

//! Runtime application configuration values.
use anyhow::{Context as _, Result};
use axum::extract::FromRef;
use config::{Config, Environment};
use dotenvy::dotenv;
use serde::Deserialize;
use url::Url;
use crate::app::App;
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct Settings {
/// Prefix under which to nest all routes. If specified, include leading
/// slash but no trailing slash, for example "/app". For default behavior,
/// leave as empty string.
#[serde(default)]
pub(crate) root_path: String,
/// When set to 1, dev features such as the frontend reloader will be
/// enabled.
#[serde(default)]
pub(crate) dev: u8,
/// postgresql:// URL for Interim's application database.
pub(crate) database_url: Url,
/// postgresql:// URL to use for creating backing databases for new
/// workspaces.
pub(crate) new_workspace_db_url: Url,
#[serde(default = "default_app_db_max_connections")]
pub(crate) app_db_max_connections: u32,
/// When set to 1, embedded SQLx migrations will be run on startup.
#[serde(default)]
pub(crate) run_database_migrations: u8,
/// Address for server to bind to
#[serde(default = "default_host")]
pub(crate) host: String,
/// Port for server to bind to
#[serde(default = "default_port")]
pub(crate) port: u16,
/// Host visible to end users, for example "https://phono.dev"
pub(crate) frontend_host: String,
pub(crate) auth: AuthSettings,
/// String to prepend to user IDs in order to construct Postgres role names.
#[serde(default = "default_db_role_prefix")]
pub(crate) db_role_prefix: String,
/// Postgres schema in which to create managed backing tables.
#[serde(default = "default_phono_table_namespace")]
pub(crate) phono_table_namespace: String,
}
fn default_app_db_max_connections() -> u32 {
16
}
fn default_port() -> u16 {
8080
}
fn default_host() -> String {
"127.0.0.1".to_owned()
}
fn default_db_role_prefix() -> String {
"__phono__".to_owned()
}
fn default_phono_table_namespace() -> String {
"phono".to_owned()
}
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct AuthSettings {
pub(crate) client_id: String,
pub(crate) client_secret: String,
pub(crate) auth_url: String,
pub(crate) token_url: String,
pub(crate) userinfo_url: String,
pub(crate) logout_url: Option<String>,
#[serde(default = "default_cookie_name")]
pub(crate) cookie_name: String,
}
fn default_cookie_name() -> String {
"ITM_SESSION".to_string()
}
impl Settings {
pub(crate) fn load() -> Result<Self> {
match dotenv() {
Err(err) => {
if err.not_found() {
tracing::info!("no .env file found");
} else {
return Err(err).context("dotenvy error");
}
}
Ok(pathbuf) => {
tracing::info!(
"using env file {}",
pathbuf
.to_str()
.ok_or(anyhow::anyhow!("pathbuf is not valid unicode"))?
);
}
}
let s = Config::builder()
.add_source(Environment::default().separator("__"))
.build()
.context("config error")?;
s.try_deserialize().context("deserialize error")
}
}
impl FromRef<App> for Settings {
fn from_ref(state: &App) -> Self {
state.settings.clone()
}
}