use actix_web::http::header::HeaderValue;
pub const DEFAULT_CACHE_CONTROL: &str = "public, max-age=31536000, immutable";
#[derive(Debug, Clone)]
pub struct Config {
pub port: u16,
pub cache_control: Option<HeaderValue>,
pub db_path: String,
pub reset_db: bool,
pub pin: String,
}
impl Config {
pub fn new(
port: u16,
cache_control: impl Into<Option<HeaderValue>>,
db_path: String,
reset_db: bool,
pin: String,
) -> Self {
Self {
port,
cache_control: cache_control.into(),
db_path,
reset_db,
pin,
}
}
pub fn is_valid_pin(&self, input: &str) -> bool {
let trimmed = input.trim();
trimmed.len() == 6 && trimmed.chars().all(|c| c.is_ascii_digit()) && trimmed == self.pin
}
/// Deletes the database file if `RESET_DB` is set to true.
/// Logs warnings and results appropriately.
pub fn maybe_reset_database(&self) {
if !self.reset_db {
return;
}
log::warn!(
"RESET_DB is set to true, deleting database file: {}",
self.db_path
);
if std::path::Path::new(&self.db_path).exists() {
if let Err(e) = std::fs::remove_file(&self.db_path) {
log::error!("Failed to delete database file: {e}");
} else {
log::info!("Database file deleted successfully");
}
}
}
}
pub fn parse_cache_control(val: Option<&str>) -> Option<HeaderValue> {
val.and_then(|v| {
let trimmed = v.trim();
if trimmed.is_empty() {
None
} else if trimmed.eq_ignore_ascii_case("true") || trimmed == "1" {
Some(HeaderValue::from_static("no-cache"))
} else {
HeaderValue::from_str(trimmed).ok()
}
})
}
pub fn from_env() -> Config {
let port = std::env::var("PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse()
.unwrap_or(8080);
let cache_control = parse_cache_control(std::env::var("CACHE_CONTROL").ok().as_deref());
let db_path = std::env::var("DB_PATH").unwrap_or_else(|_| "notify.db".to_string());
let reset_db = std::env::var("RESET_DB").unwrap_or_default() == "true";
let pin = std::env::var("PIN").unwrap_or_else(|_| "123456".to_string());
Config::new(port, cache_control, db_path, reset_db, pin)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_new() {
let config = Config::new(
8080,
HeaderValue::from_static(DEFAULT_CACHE_CONTROL),
"notify.db".to_string(),
false,
"123456".to_string(),
);
assert_eq!(config.port, 8080);
assert!(config.is_valid_pin("123456"));
assert!(config.is_valid_pin(" 123456 "));
assert!(!config.is_valid_pin("000000"));
assert!(!config.is_valid_pin("12345"));
assert!(!config.is_valid_pin("1234567"));
assert!(!config.is_valid_pin(""));
assert_eq!(
config.cache_control,
Some(HeaderValue::from_static(DEFAULT_CACHE_CONTROL))
);
}
#[test]
fn test_config_new_with_none() {
let config = Config::new(
8080,
None,
"notify.db".to_string(),
false,
"123456".to_string(),
);
assert_eq!(config.cache_control, None);
}
#[test]
fn test_parse_cache_control() {
assert_eq!(
parse_cache_control(Some("no-cache")),
Some(HeaderValue::from_static("no-cache"))
);
assert_eq!(
parse_cache_control(Some("no-store")),
Some(HeaderValue::from_static("no-store"))
);
assert_eq!(
parse_cache_control(Some("true")),
Some(HeaderValue::from_static("no-cache"))
);
assert_eq!(
parse_cache_control(Some("1")),
Some(HeaderValue::from_static("no-cache"))
);
assert_eq!(parse_cache_control(Some("")), None);
assert_eq!(parse_cache_control(Some(" ")), None);
assert_eq!(parse_cache_control(None), None);
}
#[test]
fn test_maybe_reset_database_no_flag() {
let path = format!(
"/tmp/test_notify_no_reset_{}.db",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
);
std::fs::write(&path, b"test").unwrap();
let config = Config::new(
8080,
HeaderValue::from_static(DEFAULT_CACHE_CONTROL),
path.clone(),
false,
"123456".to_string(),
);
config.maybe_reset_database();
assert!(std::path::Path::new(&path).exists());
let _ = std::fs::remove_file(&path);
}
#[test]
fn test_maybe_reset_database_with_flag() {
let path = format!(
"/tmp/test_notify_reset_{}.db",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
);
std::fs::write(&path, b"test").unwrap();
let config = Config::new(
8080,
HeaderValue::from_static(DEFAULT_CACHE_CONTROL),
path.clone(),
true,
"123456".to_string(),
);
config.maybe_reset_database();
assert!(!std::path::Path::new(&path).exists());
}
}