use axum::{ Router, Server as AxumServer, extract::Path, http::{StatusCode, header::*}, response::{AppendHeaders, Html}, routing::get, }; use std::{net::SocketAddr, sync::LazyLock, time::SystemTime}; const COMPILED_AT: LazyLock = LazyLock::new(SystemTime::now); // LazyLock::new(|| edg::r! { || -> std::time::SystemTime { std::time::SystemTime::now() }}); static COMPILED: LazyLock = LazyLock::new(|| httpdate::fmt_http_date(*COMPILED_AT)); fn no_bytes(map: HeaderMap) -> (StatusCode, Option<&'static [u8]>) { if let Some(x) = map.get("if-modified-since") && let Ok(x) = x.to_str() && let Ok(x) = httpdate::parse_http_date(x) && x < *COMPILED_AT { (StatusCode::NOT_MODIFIED, Some(&[])) } else { (StatusCode::OK, None) } } macro_rules! html { ($file:expr) => { get(|_map: HeaderMap| async { println!("a wild visitor approaches"); #[cfg(debug_assertions)] return Html(std::fs::read(concat!("html-src/", stringify!($file), ".html")).unwrap()); #[cfg(not(debug_assertions))] { let (code, bytes) = no_bytes(_map); ( code, Html(bytes.unwrap_or(include_bytes!(concat!( "../html/", stringify!($file), ".html" )))), ) } }) }; } macro_rules! png { ($file:expr) => { get(|map: HeaderMap| async { let (code, bytes) = no_bytes(map); let bytes = bytes.unwrap_or(include_bytes!(concat!( "../html/", stringify!($file), ".png" ))); ( code, ( AppendHeaders([(CONTENT_TYPE, "image/png"), (LAST_MODIFIED, &*COMPILED)]), bytes, ), ) }) }; } pub struct Server; impl Server { pub async fn spawn(addr: SocketAddr) { let router = Router::new() .route("/", html!(index)) .route("/fail.png", png!(fail)) .route("/bg.png", png!(bg)) .route("/border.png", png!(border)) .route("/border-active.png", png!(border_active)) .route("/border-hover.png", png!(border_hover)) .route("/favicon.ico", png!(favicon)) .route( "/default.woff", get(|| async { ( [(CONTENT_TYPE, "font/woff")], include_bytes!("../html-src/default.woff"), ) }), ) .route( "/index.js", get(|| async { ( [(CONTENT_TYPE, "application/javascript")], if cfg!(debug_assertions) { std::fs::read_to_string("html-src/index.js").unwrap().leak() } else { include_str!("../html-src/index.js") }, ) }), ) .route( "/files", get(|| async { serde_json::to_string( &crate::bot::search::files() .map(|(x, _)| { x.with_extension("") .file_name() .unwrap() .to_string_lossy() .into_owned() }) .collect::>(), ) .unwrap() }), ) .route( "/blame/:file", get(|Path(file): Path| async move { ( StatusCode::OK, crate::bot::ownership::whos( &crate::bot::repos::DESIGN_IT, match u64::from_str_radix(file.trim_end_matches(".msch"), 16) { Ok(x) => x, Err(_) => return (StatusCode::NOT_FOUND, "".into()), }, ) .await, ) }), ) .route( "/files/:file", get(|Path(file): Path| async move { match crate::bot::search::files().map(|(x, _)| x).find(|x| { x.with_extension("").file_name().unwrap().to_string_lossy() == file }) { Some(x) => (StatusCode::OK, std::fs::read(x).unwrap()), None => (StatusCode::NOT_FOUND, vec![]), } }), ); AxumServer::bind(&addr) .serve(router.into_make_service()) .await .unwrap() } }