mod cleaner; mod cli; mod config; mod db; mod helpers; mod locales; mod notify; mod server; use argon2::{ password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, Argon2, }; use clap::Parser; use log::warn; use std::{collections::HashMap, str::FromStr}; use unic_langid::LanguageIdentifier; #[tokio::main] async fn main() { let opt = cli::MainOpt::parse(); match opt.cmd { cli::MainSubcommand::Init => { init_all(opt.opt, cli::StartOpt { tmp: false }); } cli::MainSubcommand::Start(subopt) => { let (config, dbs, templates) = init_all(opt.opt, subopt); // These will never be dropped nor mutated let templates = Box::leak(Box::new(templates)); let config = Box::leak(Box::new(config)); let locales = Box::leak(Box::new(locales::Locales::new(config))); templates.tera.register_function( "tr", Box::new( |args: &HashMap| -> tera::Result { let langs = if let Some(tera::Value::Array(langs)) = args.get("l") { langs .iter() .filter_map(|lang| lang.as_str()) .filter_map(|lang| LanguageIdentifier::from_str(lang).ok()) .collect() } else { vec![locales.default_lang.clone()] }; let key = args .get("k") .ok_or_else(|| tera::Error::from("Missing argument `k`"))? .as_str() .ok_or_else(|| tera::Error::from("Argument `k` must be string"))?; let args_iter = fluent_bundle::FluentArgs::from_iter( args.iter().map(|(k, v)| (k, locales::tera_to_fluent(v))), ); let res = locales.tr(&langs, key, Some(&args_iter)); if res.is_none() { warn!("(calling `tr` in template) translation key `{key}` not found"); } Ok(res.into()) }, ), ); tokio::spawn(cleaner::run_cleaner(config, dbs.clone())); server::run_server(config, dbs, templates, locales).await; } cli::MainSubcommand::Psw => { let mut config = config::read_config(&opt.opt.dir.0); let password = rpassword::prompt_password("Additional admin password: ").unwrap(); let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let password_hash = argon2 .hash_password(password.as_bytes(), &salt) .unwrap() .to_string(); config.admin_passwords.push(password_hash); config::write_config(&opt.opt.dir.0, &config); } } } fn init_all( opt: cli::MainCommonOpt, subopt: cli::StartOpt, ) -> (config::Config, db::Dbs, server::page::templates::Templates) { std::fs::create_dir_all(&opt.dir.0).expect("Cannot create dir"); let config = config::read_config(&opt.dir.0); let dbs = db::load_dbs((!subopt.tmp).then_some(&opt.dir.0)); let templates = server::page::templates::Templates::new(&opt.dir.0, &config); (config, dbs, templates) }