diff --git a/Cargo.lock b/Cargo.lock index 35989ca..da659c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -220,6 +229,12 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" + [[package]] name = "byteorder" version = "1.5.0" @@ -270,6 +285,7 @@ dependencies = [ "axum-htmx", "axum-sqlx-tx", "chrono", + "figment", "minijinja", "minijinja-embed", "notify", @@ -446,6 +462,20 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + [[package]] name = "filetime" version = "0.2.25" @@ -942,6 +972,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + [[package]] name = "inotify" version = "0.11.0" @@ -1291,6 +1327,29 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "pear" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1363,6 +1422,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + [[package]] name = "quote" version = "1.0.38" @@ -1569,6 +1641,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2057,6 +2138,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.5.2" @@ -2192,6 +2307,15 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unicase" version = "2.8.1" @@ -2553,6 +2677,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen-rt" version = "0.33.0" @@ -2574,6 +2707,12 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.7.5" diff --git a/Cargo.toml b/Cargo.toml index 3867079..f963547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ axum = { version = "0.8.1", features = ["macros"] } axum-htmx = { version = "0.7.0", features = ["auto-vary", "serde", "guards"] } axum-sqlx-tx = "0.10.0" chrono = { version = "0.4.39", features = ["serde"] } +figment = { version = "0.10.19", features = ["env", "toml"] } minijinja = { version = "2.7.0", features = ["loader"] } minijinja-embed = "2.7.0" notify = "8.0.0" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..d2de8e9 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,98 @@ +use std::fmt::Display; +use std::net::SocketAddr; +use std::path::PathBuf; + +use crate::Result; +use figment::providers::{Env, Format, Serialized, Toml}; +use serde::{Deserialize, Serialize}; + +use figment::Figment; + +#[derive(Debug, Deserialize, Serialize)] +pub struct DBConfig { + user: String, + password: Option, + host: String, + name: String, + string: Option, +} + +impl Display for DBConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.generate_db_string(true)) + } +} + +impl Default for DBConfig { + fn default() -> Self { + DBConfig { + user: "postgres".to_owned(), + password: None, + host: "localhost".to_owned(), + name: "compendium".to_owned(), + string: None, + } + } +} + +impl DBConfig { + pub fn generate_db_string(&self, ofuscate: bool) -> String { + if let Some(value) = &self.string { + return value.clone(); + } + + let db_password = if ofuscate { + let mut db_password = self.password.clone().unwrap(); + let password_length = db_password.len(); + + let visible_characters = 4; + + if password_length <= visible_characters { + // Hide all the password + let filler = "*".repeat(password_length); + db_password.replace_range(0..password_length, &filler.to_owned()); + } else { + // Hide only a portion of the password + let filler = "*".repeat(password_length - visible_characters); + db_password.replace_range(visible_characters..password_length, &filler.to_owned()); + }; + + db_password + } else { + self.password.clone().unwrap() + }; + + let db_string = format!( + "postgres://{}:{}@{}/{}", + self.user, db_password, self.host, self.name, + ); + + db_string.to_owned() + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Config { + pub db: DBConfig, + pub addr: SocketAddr, +} + +impl Default for Config { + fn default() -> Self { + Config { + db: DBConfig::default(), + addr: "0.0.0.0:3000".parse().unwrap(), + } + } +} + +impl Config { + pub fn new(path: PathBuf) -> Result { + let config: Config = Figment::from(Serialized::defaults(Config::default())) + .merge(Toml::file(path)) + .merge(Env::prefixed("CPD_").split("_")) + .extract()?; + + Ok(config) + } +} diff --git a/src/error.rs b/src/error.rs index 935bdd9..0ae3903 100644 --- a/src/error.rs +++ b/src/error.rs @@ -30,6 +30,9 @@ pub enum Error { #[error(transparent)] DatabaseOperation(#[from] sqlx::Error), + + #[error(transparent)] + Config(#[from] figment::Error), } impl IntoResponse for Error { diff --git a/src/lib.rs b/src/lib.rs index 8ff7559..109a915 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod config; mod error; pub mod router; diff --git a/src/main.rs b/src/main.rs index 8d0c12a..62d55ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,8 @@ #![allow(dead_code)] use axum_htmx::AutoVaryLayer; -use compendium::{router, AppState, Error, Link, Result, Tx}; +use compendium::config::Config; +use compendium::{config, router, AppState, Error, Link, Result, Tx}; use minijinja::{Environment, Value}; use sqlx::postgres::PgPoolOptions; use sqlx::PgPool; @@ -41,25 +42,10 @@ fn load_templates() -> Result> { Ok(tmpl_env) } -async fn init_db() -> Result { - let db_string = format!( - "postgres://{}:{}@{}/{}", - env::var("CPD_DB_USER")?, - env::var("CPD_DB_PASSWORD")?, - env::var("CPD_DB_HOST")?, - env::var("CPD_DB_NAME")? - ); - - info!("Connecting to database {}", db_string); - - Ok(PgPoolOptions::new() - .max_connections(5) - .connect(&db_string) - .await?) -} - #[tokio::main] async fn main() -> Result<()> { + let config = Config::new("./config.toml".into())?; + // Logs tracing_subscriber::registry() .with( @@ -72,7 +58,12 @@ async fn main() -> Result<()> { let mut tmpl_env = load_templates()?; - let pool = init_db().await?; + info!("Connecting to database {}", config.db); + + let pool = PgPoolOptions::new() + .max_connections(5) + .connect(&config.db.generate_db_string(false)) + .await?; let (tx_state, tx_layer) = Tx::setup(pool); @@ -86,7 +77,7 @@ async fn main() -> Result<()> { #[cfg(debug_assertions)] let app = app.layer(tower_livereload::LiveReloadLayer::new()); - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; + let listener = tokio::net::TcpListener::bind(config.addr).await?; info!("listening on {}", listener.local_addr()?); axum::serve(listener, app).await?;