#![doc = include_str!("../README.md")] mod config; mod database; mod markdown; mod model; mod routes; use crate::database::DataManager; use axum::{Extension, Router}; use config::Config; use nanoneo::core::element::Render; use std::{collections::HashMap, env::var, net::SocketAddr, process::exit, sync::Arc}; use tera::{Tera, Value}; use tetratto_core::html; use tetratto_shared::hash::salt; use tokio::sync::RwLock; use tower_http::{ catch_panic::CatchPanicLayer, trace::{self, TraceLayer}, }; use tracing::{Level, info}; pub(crate) type InnerState = (DataManager, Tera, String); pub(crate) type State = Arc>; fn render_markdown(value: &Value, _: &HashMap) -> tera::Result { Ok(markdown::render_markdown(value.as_str().unwrap()) .replace("\\@", "@") .replace("%5C@", "@") .into()) } fn remove_script_tags(value: &Value, _: &HashMap) -> tera::Result { Ok(value .as_str() .unwrap() .replace("", "</script>") .into()) } #[macro_export] macro_rules! create_dir_if_not_exists { ($dir_path:expr) => { if !std::fs::exists(&$dir_path).unwrap() { std::fs::create_dir($dir_path).unwrap(); } }; } #[tokio::main(flavor = "multi_thread")] async fn main() { dotenv::dotenv().ok(); tracing_subscriber::fmt() .with_target(false) .compact() .init(); let port = match var("PORT") { Ok(port) => port.parse::().expect("port should be a u16"), Err(_) => 9119, }; // ... let database = DataManager::new(Config::read()) .await .expect("failed to connect to database"); database.init().await.expect("failed to init database"); // build lisp create_dir_if_not_exists!("./templates_build"); create_dir_if_not_exists!("./icons"); for x in glob::glob("./templates_src/**/*").expect("failed to read pattern") { match x { Ok(x) => std::fs::write( x.to_str() .unwrap() .replace("templates_src/", "templates_build/"), html::pull_icons( nanoneo::parse(&std::fs::read_to_string(x).expect("failed to read template")) .render(&mut HashMap::new()), "./icons", ) .await, ) .expect("failed to write template"), Err(e) => panic!("{e}"), } } // create docs dir create_dir_if_not_exists!("./docs"); // ... let mut tera = match Tera::new(&format!("./templates_build/**/*")) { Ok(t) => t, Err(e) => { println!("{e}"); exit(1); } }; tera.register_filter("markdown", render_markdown); tera.register_filter("remove_script_tags", remove_script_tags); // create app let app = Router::new() .merge(routes::routes()) .layer(Extension(Arc::new(RwLock::new((database, tera, salt()))))) .layer(axum::extract::DefaultBodyLimit::max( var("BODY_LIMIT") .unwrap_or("8388608".to_string()) .parse::() .unwrap(), )) .layer( TraceLayer::new_for_http() .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO)) .on_response(trace::DefaultOnResponse::new().level(Level::INFO)), ) .layer(CatchPanicLayer::new()); // ... let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)) .await .unwrap(); info!("🪨 malachite."); info!("listening on http://0.0.0.0:{}", port); axum::serve( listener, app.into_make_service_with_connect_info::(), ) .await .unwrap(); }