pub mod database_operations; use std::io; use std::rc::Rc; use std::fs; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Arc; use mtgott::dirsearch::{search_dir, get_root_html}; use mtgott::runtime::{MTGOTT, Value}; use std::error::Error; use axum; use axum::http; use axum::http::HeaderValue; use axum::http::header::ACCEPT_LANGUAGE; use axum::extract::{Form, State}; use axum::response::{Html, IntoResponse, Redirect}; use chrono::{Datelike, TimeZone, Utc}; macro_rules! valarr { ($($el:expr),* $(,)?) => { mtgott::runtime::Value::Arr(std::rc::Rc::new( vec![$($el),*] )) }; } macro_rules! valdict { ($($el:literal => $val:expr),* $(,)?) => {{ let mut hashmap: std::collections::HashMap = std::collections::HashMap::new(); $(hashmap.insert($el.to_string(), $val);)* mtgott::runtime::Value::Dict(std::rc::Rc::new(hashmap)) }} } fn years_between(start: T, end: T) -> i32 { let mut years = end.year() - start.year(); if (end.month(), end.day()) < (start.month(), start.day()) { years -= 1; } years } struct YyyiConfig { birthday: chrono::DateTime, } impl YyyiConfig { fn for_me() -> YyyiConfig { Self { birthday: Utc.with_ymd_and_hms(2005, 06, 21, 0, 0, 0).single().unwrap(), } } } struct LanguageConfig { available: Vec, default: String, } struct StaticAsset { content_type: HeaderValue, content: Vec } struct AssetsCache { static_assets: HashMap, pages: MTGOTT, l_conf: LanguageConfig, misc_cfg: YyyiConfig, } struct ExtensionContentTypeCorr{ extension: &'static str, content_type: HeaderValue, } fn load_static_assets(p: &str, need: &[ExtensionContentTypeCorr]) -> Result, Box> { let e: Vec<&'static str> = need.iter().map(|corr: &ExtensionContentTypeCorr| corr.extension).collect(); let content = search_dir(p, &e, &(|_| true))?; let mut st: HashMap = HashMap::new(); for i in 0..need.len() { let extension: &str = need[i].extension; let content_type: &HeaderValue = &need[i].content_type; for virtual_path in &content[i] { let path_org = format!("{p}/{virtual_path}{extension}"); st.insert(format!("{virtual_path}{extension}"), StaticAsset{ content_type: content_type.clone(), content: fs::read(&path_org)?, }); } } Ok(st) } fn load_needed_static_assets(p: &str) -> Result, Box> { load_static_assets(p, &[ ExtensionContentTypeCorr{extension: ".css", content_type: HeaderValue::from_str("text/css").unwrap()}, ExtensionContentTypeCorr{extension: ".jpeg", content_type: HeaderValue::from_str("image/jpeg").unwrap()}, ExtensionContentTypeCorr{extension: ".jpg", content_type: HeaderValue::from_str("image/jpeg").unwrap()}, ExtensionContentTypeCorr{extension: ".png", content_type: HeaderValue::from_str("image/png").unwrap()}, ]) } impl AssetsCache { fn load_assets(assets_dir: &str) -> Result> { Ok(AssetsCache { static_assets: load_needed_static_assets(assets_dir)?, pages: get_root_html(&format!("{assets_dir}/HypertextPages"))?, l_conf: LanguageConfig{ available: vec!["ru-RU".into(), "en-US".into()], default: "ru-RU".into(), }, misc_cfg: YyyiConfig::for_me(), }) } } async fn static_assets( axum::extract::Path(path): axum::extract::Path, axum::extract::State(assets): axum::extract::State>, ) -> Result<([(axum::http::HeaderName, axum::http::HeaderValue); 1], Vec), axum::http::StatusCode> { if let Some(file) = assets.static_assets.get(&path) { return Ok(( [(http::header::CONTENT_TYPE, file.content_type.clone())], file.content.clone() )) } Err(axum::http::StatusCode::NOT_FOUND) } async fn page_index( axum::extract::State(assets): axum::extract::State>, ) -> axum::response::Html { let now = chrono::Utc::now(); let age = years_between(assets.misc_cfg.birthday, now); let gr = valdict![ "age" => Value::Int(age as i64), "lang" => Value::Str(Rc::new(assets.l_conf.default.clone())) ]; axum::response::Html(assets.pages.render(gr, "index.main", 500).unwrap()) } async fn page_blog( axum::extract::State(assets): axum::extract::State>, ) -> impl axum::response::IntoResponse { let gr = valdict![ "lang" => Value::Str(Rc::new(assets.l_conf.default.clone())) ]; axum::response::Html(assets.pages.render(gr, "blog.main", 500).unwrap()) } async fn fallback_page() -> axum::http::StatusCode { axum::http::StatusCode::NOT_FOUND } pub async fn run_yyyi_ru() { let assets_dir = format!("{}/assets", env!("CARGO_MANIFEST_DIR")); let address = "0.0.0.0:3000"; println!("Assets dir: {assets_dir:?}"); let assets = Arc::new(AssetsCache::load_assets(&assets_dir).unwrap()); let app = axum::Router::new() .without_v07_checks() .route("/", axum::routing::MethodRouter::new().get(page_index)) .route("/blog", axum::routing::get(page_blog)) .route("/assets/{*path}", axum::routing::get(static_assets)) .fallback(fallback_page).with_state(assets); // run our app with hyper let listener = tokio::net::TcpListener::bind(address).await.unwrap(); println!("Running on http://{address}"); axum::serve(listener, app).await.unwrap(); }