debt-pirate/api/src/models/environment.rs

200 lines
5.8 KiB
Rust

use std::{
path::{Path, PathBuf},
sync::mpsc::Sender,
};
use pasetors::{keys::SymmetricKey, version4::V4};
use crate::services::UserConfirmationMessage;
use super::AppError;
#[derive(Clone)]
pub struct Environment {
assets_dir: PathBuf,
database_url: String,
email_sender: Sender<UserConfirmationMessage>,
hostname: String,
port: u32,
rust_log: String,
send_verification_email: bool,
token_key: SymmetricKey<V4>,
}
impl Environment {
pub fn init(email_sender: Sender<UserConfirmationMessage>) -> Result<Self, AppError> {
let mut builder = EnvironmentObjectBuilder::new(email_sender);
dotenvy::dotenv_iter()
.expect("Missing .env file")
.filter_map(|item| item.ok())
.for_each(|(key, value)| match key.as_str() {
"ASSETS_DIR" => builder.with_assets_dir(value),
"DATABASE_URL" => builder.with_database_url(value),
"HOSTNAME" => builder.with_hostname(value),
"PORT" => builder.with_port(value),
"RUST_LOG" => builder.with_rust_log(value),
"SEND_VERIFICATION_EMAIL" => builder.with_send_verification_email(value),
"TOKEN_KEY" => builder.with_token_key(value),
_ => {}
});
let missing_vars = builder.uninitialized_variables();
if let Some(missing_vars) = missing_vars {
Err(AppError::missing_environment_variables(missing_vars))
} else {
Ok(Environment::from(builder))
}
}
pub fn hostname(&self) -> &str {
self.hostname.as_str()
}
pub fn port(&self) -> u32 {
self.port
}
pub fn token_key(&self) -> &SymmetricKey<V4> {
&self.token_key
}
pub fn db_connection_uri(&self) -> &str {
self.database_url.as_str()
}
pub fn assets_dir(&self) -> &Path {
self.assets_dir.as_path()
}
pub fn email_sender(&self) -> &Sender<UserConfirmationMessage> {
&self.email_sender
}
pub fn rust_log(&self) -> &str {
self.rust_log.as_str()
}
pub fn send_verification_email(&self) -> bool {
self.send_verification_email
}
}
impl From<EnvironmentObjectBuilder> for Environment {
fn from(builder: EnvironmentObjectBuilder) -> Self {
let EnvironmentObjectBuilder {
assets_dir,
database_url,
email_sender,
hostname,
port,
rust_log,
send_verification,
token_key,
} = builder;
Self {
assets_dir: assets_dir.unwrap(),
database_url: database_url.unwrap(),
email_sender: email_sender.unwrap(),
hostname: hostname.unwrap(),
port: port.unwrap(),
rust_log: rust_log.unwrap(),
send_verification_email: send_verification.unwrap_or(true),
token_key: token_key.unwrap(),
}
}
}
#[derive(Debug, Default)]
pub struct EnvironmentObjectBuilder {
pub assets_dir: Option<PathBuf>,
pub database_url: Option<String>,
pub email_sender: Option<Sender<UserConfirmationMessage>>,
pub hostname: Option<String>,
pub port: Option<u32>,
pub rust_log: Option<String>,
pub send_verification: Option<bool>,
pub token_key: Option<SymmetricKey<V4>>,
}
impl EnvironmentObjectBuilder {
pub fn new(email_sender: Sender<UserConfirmationMessage>) -> Self {
Self {
email_sender: Some(email_sender),
..Default::default()
}
}
pub fn uninitialized_variables(&self) -> Option<Vec<&'static str>> {
let mut missing_vars = [
("HOSTNAME", self.hostname.as_deref()),
("DATABASE_URL", self.database_url.as_deref()),
("RUST_LOG", self.rust_log.as_deref()),
]
.into_iter()
.filter_map(|(key, value)| value.map(|_| key).xor(Some(key)))
.collect::<Vec<&'static str>>();
if self.token_key.is_none() {
missing_vars.push("TOKEN_KEY");
}
if self.assets_dir.is_none() {
missing_vars.push("ASSETS_DIR");
}
if missing_vars.is_empty() {
None
} else {
Some(missing_vars)
}
}
pub fn with_hostname(&mut self, hostname: String) {
self.hostname = Some(hostname);
}
pub fn with_port(&mut self, port: String) {
let port = port
.parse::<u32>()
.inspect_err(|err| eprintln!("Not a valid port, defaulting to '42069': {err}"))
.ok();
self.port = port;
}
pub fn with_token_key(&mut self, key: String) {
match SymmetricKey::<V4>::try_from(key.as_str()).map_err(|_| AppError::token_key()) {
Ok(key) => self.token_key = Some(key),
Err(err) => panic!("{err}"),
};
}
pub fn with_database_url(&mut self, url: String) {
self.database_url = Some(url);
}
pub fn with_assets_dir(&mut self, assets_dir_path: String) {
let assets_dir = PathBuf::from(assets_dir_path);
if !assets_dir.try_exists().unwrap_or_default() {
panic!(
"The 'ASSETS_DIR' environment variable points to a directory that doesn't exist."
);
}
if assets_dir.is_file() {
panic!("The 'ASSETS_DIR' environment variable must be set to a directory, not a file.");
}
self.assets_dir = Some(assets_dir);
}
pub fn with_rust_log(&mut self, rust_log: String) {
self.rust_log = Some(rust_log);
}
pub fn with_send_verification_email(&mut self, send_verification_email: String) {
let send_verification_email = send_verification_email.to_lowercase() == "true";
self.send_verification = Some(send_verification_email);
}
}