114 lines
3.6 KiB
Rust
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")
|
|
})
|
|
}
|