fluffle/
main.rs

1#![doc = include_str!("../README.md")]
2mod config;
3mod database;
4mod markdown;
5mod model;
6mod routes;
7
8use crate::database::DataManager;
9use axum::{Extension, Router};
10use config::Config;
11use nanoneo::core::element::Render;
12use std::{collections::HashMap, env::var, net::SocketAddr, process::exit, sync::Arc};
13use tera::{Tera, Value};
14use tetratto_core::html;
15use tetratto_shared::hash::salt;
16use tokio::sync::RwLock;
17use tower_http::{
18    catch_panic::CatchPanicLayer,
19    trace::{self, TraceLayer},
20};
21use tracing::{Level, info};
22
23pub(crate) type InnerState = (DataManager, Tera, String);
24pub(crate) type State = Arc<RwLock<InnerState>>;
25
26fn render_markdown(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
27    Ok(markdown::render_markdown(value.as_str().unwrap())
28        .replace("\\@", "@")
29        .replace("%5C@", "@")
30        .into())
31}
32
33fn remove_script_tags(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
34    Ok(value
35        .as_str()
36        .unwrap()
37        .replace("</script>", "&lt;/script&gt;")
38        .into())
39}
40
41#[macro_export]
42macro_rules! create_dir_if_not_exists {
43    ($dir_path:expr) => {
44        if !std::fs::exists(&$dir_path).unwrap() {
45            std::fs::create_dir($dir_path).unwrap();
46        }
47    };
48}
49
50#[tokio::main(flavor = "multi_thread")]
51async fn main() {
52    dotenv::dotenv().ok();
53    tracing_subscriber::fmt()
54        .with_target(false)
55        .compact()
56        .init();
57
58    let port = match var("PORT") {
59        Ok(port) => port.parse::<u16>().expect("port should be a u16"),
60        Err(_) => 9119,
61    };
62
63    // ...
64    let database = DataManager::new(Config::read())
65        .await
66        .expect("failed to connect to database");
67    database.init().await.expect("failed to init database");
68
69    // build lisp
70    create_dir_if_not_exists!("./templates_build");
71    create_dir_if_not_exists!("./icons");
72
73    for x in glob::glob("./templates_src/**/*").expect("failed to read pattern") {
74        match x {
75            Ok(x) => std::fs::write(
76                x.to_str()
77                    .unwrap()
78                    .replace("templates_src/", "templates_build/"),
79                html::pull_icons(
80                    nanoneo::parse(&std::fs::read_to_string(x).expect("failed to read template"))
81                        .render(&mut HashMap::new()),
82                    "./icons",
83                )
84                .await,
85            )
86            .expect("failed to write template"),
87            Err(e) => panic!("{e}"),
88        }
89    }
90
91    // create docs dir
92    create_dir_if_not_exists!("./docs");
93
94    // ...
95    let mut tera = match Tera::new(&format!("./templates_build/**/*")) {
96        Ok(t) => t,
97        Err(e) => {
98            println!("{e}");
99            exit(1);
100        }
101    };
102
103    tera.register_filter("markdown", render_markdown);
104    tera.register_filter("remove_script_tags", remove_script_tags);
105
106    // create app
107    let app = Router::new()
108        .merge(routes::routes())
109        .layer(Extension(Arc::new(RwLock::new((database, tera, salt())))))
110        .layer(axum::extract::DefaultBodyLimit::max(
111            var("BODY_LIMIT")
112                .unwrap_or("8388608".to_string())
113                .parse::<usize>()
114                .unwrap(),
115        ))
116        .layer(
117            TraceLayer::new_for_http()
118                .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
119                .on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
120        )
121        .layer(CatchPanicLayer::new());
122
123    // ...
124    let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
125        .await
126        .unwrap();
127
128    info!("🐰 fluffle.");
129    info!("listening on http://0.0.0.0:{}", port);
130    axum::serve(
131        listener,
132        app.into_make_service_with_connect_info::<SocketAddr>(),
133    )
134    .await
135    .unwrap();
136}