cargagep-v2/src/utils/config.rs
2026-08-24 11:47:21 +02:00

114 lines
3.6 KiB
Rust

use std::{path::Path, sync::OnceLock};
use config::Config;
use serde::Deserialize;
#[derive(Deserialize, Clone)]
pub struct ServerConfig {
pub address: Option<String>,
pub port: Option<u16>,
/// Public origin the browser reaches the app on. The OIDC redirect uri is
/// built from it, so it must match what is registered on the provider.
/// In development this is the vite dev server, not the backend.
pub base_url: Option<String>,
}
#[derive(Deserialize, Clone)]
pub struct PostgresConfig {
pub host: Option<String>,
pub port: Option<u16>,
pub user: String,
pub password: String,
pub name: String,
}
/// Whiskey, the AGEPoly OIDC provider
#[derive(Deserialize, Clone)]
pub struct OidcConfig {
pub issuer_url: String,
pub client_id: String,
pub client_secret: String,
pub session_lifetime: Option<i64>,
}
/// A user that can be logged in without going through the provider.
/// Only usable in debug builds, see `POST /api/login`.
#[derive(Deserialize, Clone)]
pub struct DevUserConfig {
pub firstname: String,
pub name: String,
pub email: String,
pub external_id: Option<String>,
#[serde(default)]
pub units: Vec<String>,
#[serde(default)]
pub admin: bool,
}
#[derive(Deserialize, Clone)]
pub struct AppConfig {
pub server: ServerConfig,
pub postgres: PostgresConfig,
pub oidc: OidcConfig,
#[serde(default)]
pub dev_users: Vec<DevUserConfig>,
/// Directory containing the built frontend
pub frontend_dir: String,
}
impl AppConfig {
pub fn get_bind_address(&self) -> String {
let address = self.server.address.clone().unwrap_or("0.0.0.0".to_owned());
let port = self.server.port.unwrap_or(3000);
format!("{}:{}", address, port)
}
/// Origin used to build the OIDC redirect uri. Defaults to the vite dev
/// server, which is what a developer reaches the app on.
pub fn get_base_url(&self) -> String {
self.server
.base_url
.clone()
.unwrap_or("http://localhost:5000".to_owned())
}
/// Session lifetime in minutes
pub fn get_session_lifetime(&self) -> i64 {
self.oidc.session_lifetime.unwrap_or(60)
}
pub fn get_postgresql_url(&self) -> String {
let host = self.postgres.host.clone().unwrap_or("localhost".to_owned());
let port = self.postgres.port.unwrap_or(5432);
let user = self.postgres.user.clone();
let password = self.postgres.password.clone();
let name = self.postgres.name.clone();
format!("postgres://{user}:{password}@{host}:{port}/{name}")
}
}
/// Loads the configuration, by decreasing priority:
/// - `APP__SERVER__PORT=3000` style environment variables
/// - `./config.yml`
/// - the file pointed by `$APP_CONFIG` (`/etc/cargagep/config.yml` by default)
pub fn get() -> &'static AppConfig {
static CONFIG: OnceLock<AppConfig> = OnceLock::new();
CONFIG.get_or_init(|| {
let config_path =
std::env::var("APP_CONFIG").unwrap_or("/etc/cargagep/config.yml".to_owned());
let config_path = Path::new(&config_path);
let raw = Config::builder()
.add_source(config::File::from(config_path).required(false))
.add_source(config::File::with_name("config").required(false))
.add_source(
config::Environment::with_prefix("APP")
.separator("__")
.ignore_empty(true),
)
.build()
.expect("Invalid config source");
raw.try_deserialize::<AppConfig>()
.expect("Invalid config format")
})
}