use actix_web::http::header::HeaderValue;
use std::env;
use std::path::Path;
pub const DEFAULT_CACHE_CONTROL: &str = "max-age=3600";
pub const DEFAULT_PORT: u16 = 8090;
pub const DEFAULT_ADMIN_PIN: &str = "123456";
pub const DEFAULT_DUCKDB_PATH: &str = "pokedex.duckdb";
pub const DEFAULT_SQLITE_PATH: &str = "battles.db";
#[derive(Clone, Debug)]
pub struct Config {
pub port: u16,
pub cache_control: Option<HeaderValue>,
pub duckdb_path: String,
pub sqlite_path: String,
pub admin_pin: String,
}
impl Config {
#[must_use]
pub fn new(
port: u16,
cache_control: Option<HeaderValue>,
duckdb_path: String,
sqlite_path: String,
admin_pin: String,
) -> Self {
Self {
port,
cache_control,
duckdb_path,
sqlite_path,
admin_pin,
}
}
#[must_use]
pub fn is_valid_pin(&self, candidate: &str) -> bool {
let trimmed = candidate.trim();
trimmed.len() == 6
&& trimmed.chars().all(|c| c.is_ascii_digit())
&& trimmed == self.admin_pin
}
pub fn maybe_reset_databases(&self) {
let should_reset = env::var("RESET_DB")
.is_ok_and(|val| matches!(val.to_ascii_lowercase().as_str(), "true" | "1" | "yes"));
if should_reset {
log::info!("RESET_DB is set; resetting database files...");
for path in [&self.duckdb_path, &self.sqlite_path] {
if Path::new(path).exists() {
let _ = std::fs::remove_file(path);
let _ = std::fs::remove_file(format!("{path}.wal"));
let _ = std::fs::remove_file(format!("{path}-shm"));
let _ = std::fs::remove_file(format!("{path}-wal"));
log::info!("Removed {path}");
}
}
}
}
}
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("no-cache")
|| trimmed.eq_ignore_ascii_case("true")
|| trimmed == "1"
{
Some(HeaderValue::from_static("no-cache"))
} else {
HeaderValue::from_str(trimmed).ok()
}
})
}
#[must_use]
pub fn from_env() -> Config {
let port = env::var("PORT")
.ok()
.and_then(|p| p.parse::<u16>().ok())
.unwrap_or(DEFAULT_PORT);
let cache_control = parse_cache_control(env::var("CACHE_CONTROL").ok().as_deref());
let duckdb_path = env::var("DUCKDB_PATH").unwrap_or_else(|_| DEFAULT_DUCKDB_PATH.to_string());
let sqlite_path = env::var("SQLITE_PATH").unwrap_or_else(|_| DEFAULT_SQLITE_PATH.to_string());
let admin_pin = env::var("ADMIN_PIN")
.or_else(|_| env::var("PIN"))
.unwrap_or_else(|_| DEFAULT_ADMIN_PIN.to_string());
Config::new(port, cache_control, duckdb_path, sqlite_path, admin_pin)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_pin_validation() {
let cfg = Config::new(
8090,
None,
"test.duckdb".into(),
"test.db".into(),
"654321".into(),
);
assert!(cfg.is_valid_pin("654321"));
assert!(cfg.is_valid_pin(" 654321 \n"));
assert!(!cfg.is_valid_pin("1234"));
assert!(!cfg.is_valid_pin("12345"));
assert!(!cfg.is_valid_pin("1234567"));
assert!(!cfg.is_valid_pin("abcdef"));
}
#[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("public, max-age=3600")),
Some(HeaderValue::from_static("public, max-age=3600"))
);
assert_eq!(parse_cache_control(Some("")), None);
assert_eq!(parse_cache_control(None), None);
}
}