fluffle/
main.rs

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