use axum::extract::State; use axum::response::IntoResponse; use axum::routing::get; use axum::{http::StatusCode, response::Html, serve, Router}; use minijinja::context; use state::State as AppState; pub use tracing::{debug, error, event, info, span, warn, Level}; use crate::live_reload::live_reload_layer; #[derive(Debug)] struct Berr(Box); type Besult = std::result::Result; mod file_watcher; mod live_reload; mod observe; mod state; mod static_files; mod tailwind; mod templates; #[tokio::main] async fn main() -> Besult<()> { // load configuration? let _setup_logging = observe::setup_logging(); let state = AppState::try_new().await?; // TODO: only start tailwind if in dev mode? tokio::spawn(async move { tailwind::start_watcher() }); let lr = live_reload_layer(); { // TODO: only start watcher for dev mode? let state = state.clone(); let lr = lr.reloader(); tokio::spawn(async move { state.templates.start_watcher(Some(lr)) }); } let (static_file_service, _static_file_watcher) = static_files::router(Some(lr.reloader()))?; let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); info!("Listening on {listener:?}"); let router = Router::new() .route("/", get(index)) .nest_service("/static", static_file_service) .with_state(state); Ok(serve(listener, router).await?) } async fn index(State(state): State) -> Besult> { Ok(Html( state .templates .render("pages/index.html.jinja", context!()) .await?, )) } impl IntoResponse for Berr { fn into_response(self) -> axum::http::Response { error!("webserver error: {:?}", self.0); ( StatusCode::INTERNAL_SERVER_ERROR, format!("internal server error: {}", self.0), ) .into_response() } } // This enables using `?` on functions that return `Result<_, anyhow::Error>` // to turn them into `Result<_, AppError>`. That way you don't need to do that // manually. impl From for Berr where E: Into>, { fn from(err: E) -> Self { Self(err.into()) } }