yyyi_ru/src/lib.rs

176 lines
5.7 KiB
Rust

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<String, mtgott::runtime::Value> = std::collections::HashMap::new();
$(hashmap.insert($el.to_string(), $val);)*
mtgott::runtime::Value::Dict(std::rc::Rc::new(hashmap))
}}
}
fn years_between<T: chrono::Datelike>(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<Utc>,
}
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<String>,
default: String,
}
struct StaticAsset {
content_type: HeaderValue,
content: Vec<u8>
}
struct AssetsCache {
static_assets: HashMap<String, StaticAsset>,
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<HashMap<String, StaticAsset>, Box<dyn Error>> {
let e: Vec<&'static str> = need.iter().map(|corr: &ExtensionContentTypeCorr| corr.extension).collect();
let content = search_dir(p, &e, &(|_| true))?;
let mut st: HashMap<String, StaticAsset> = 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<HashMap<String, StaticAsset>, Box<dyn Error>> {
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<AssetsCache, Box<dyn Error>> {
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<String>,
axum::extract::State(assets): axum::extract::State<Arc<AssetsCache>>,
) -> Result<([(axum::http::HeaderName, axum::http::HeaderValue); 1], Vec<u8>), 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<Arc<AssetsCache>>,
) -> axum::response::Html<String> {
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<Arc<AssetsCache>>,
) -> 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();
}