use std::{path::Path, sync::OnceLock}; use config::Config; use serde::Deserialize; #[derive(Deserialize, Clone)] pub struct ServerConfig { pub address: Option, pub port: Option, } #[derive(Deserialize, Clone)] pub struct PostgresConfig { pub host: Option, pub port: Option, pub user: String, pub password: String, pub name: String, } /// Rendering of the lecture notes to pdf #[derive(Deserialize, Clone)] pub struct NotesConfig { /// Directory of `template.tex`, `box-filter.lua` and `assets/` pub template_dir: String, /// Logo shown at the top right, relative to `template_dir` pub logo: Option, /// Shown in the header of the pdf pub student_name: Option, pub student_id: Option, } /// TimeTagger account the focus sessions are counted in #[derive(Deserialize, Clone)] pub struct TimetaggerConfig { /// Api of the TimeTagger server, `https://timetagger.app/api/v2` by default pub url: Option, /// Api token, from the account page of TimeTagger pub token: String, } #[derive(Deserialize, Clone)] pub struct AppConfig { pub server: ServerConfig, pub postgres: PostgresConfig, /// Directory containing the built frontend pub frontend_dir: String, /// Where the files of the courses and associations are stored (lecture notes, /// pdf...), one folder each. A local directory standing in for the NAS for now. pub storage_dir: String, pub notes: NotesConfig, /// Optional: without it the pomodoro only counts the time in the app pub timetagger: Option, } 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) } 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/studium/config.yml` by default) pub fn get() -> &'static AppConfig { static CONFIG: OnceLock = OnceLock::new(); CONFIG.get_or_init(|| { let config_path = std::env::var("APP_CONFIG").unwrap_or("/etc/studium/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::() .expect("Invalid config format") }) }