lyrs/src/main.rs

135 lines
3.9 KiB
Rust
Raw Normal View History

2024-05-07 21:13:46 -05:00
use axum::extract::State;
use axum::response::IntoResponse;
use axum::routing::get;
use axum::{http::StatusCode, response::Html, serve, Router};
use minijinja::context;
2024-05-06 15:39:21 -05:00
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
2024-05-07 19:49:04 -05:00
use std::{path::PathBuf, str::FromStr, sync::OnceLock};
use templates::Templates;
use tokio::sync::mpsc::channel;
2024-05-06 15:39:21 -05:00
use tower_http::services::ServeDir;
use tower_livereload::{LiveReloadLayer, Reloader};
2024-05-12 20:27:13 -05:00
pub use tracing::{debug, error, event, info, span, warn, Level};
2024-05-07 21:13:46 -05:00
#[derive(Debug)]
struct Berr(Box<dyn std::error::Error>);
type Besult<T> = std::result::Result<T, Berr>;
2024-05-06 13:05:04 -05:00
mod observe;
mod tailwind;
mod templates;
2024-05-06 13:05:04 -05:00
2024-05-06 15:39:21 -05:00
fn static_file_dir() -> &'static PathBuf {
static STATIC_FILE_DIR: OnceLock<PathBuf> = OnceLock::new();
STATIC_FILE_DIR.get_or_init(|| PathBuf::from_str("static").unwrap())
2024-05-06 13:05:04 -05:00
}
2024-05-07 21:13:46 -05:00
#[derive(Clone)]
struct AppState {
templates: Templates,
2024-05-07 21:13:46 -05:00
}
2024-05-06 15:39:21 -05:00
#[tokio::main]
async fn main() -> Besult<()> {
// load configuration?
let _setup_logging = observe::setup_logging();
// TODO: reload templates when they change? separate watcher?
let templates = Templates::try_load().await?;
let mut tt = templates.clone();
let templates_watcher = tt.start_watcher();
2024-05-07 21:13:46 -05:00
// TODO: only start tailwind if in dev mode?
tokio::spawn(async { tailwind::start_watcher() });
tokio::spawn(async move { templates_watcher.await });
2024-05-07 21:13:46 -05:00
2024-05-07 19:49:04 -05:00
// pulling the watcher into main's scope lets it live until the program dies
let (rl_layer, _watcher) = live_reload_layer()?;
2024-05-06 15:39:21 -05:00
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
info!("Listening on {listener:#?}");
2024-05-07 19:49:04 -05:00
let router = Router::new()
2024-05-07 21:13:46 -05:00
.route("/", get(index))
2024-05-07 19:49:04 -05:00
.nest_service("/static", ServeDir::new(static_file_dir()))
2024-05-07 21:13:46 -05:00
.layer(rl_layer)
.with_state(AppState { templates });
2024-05-07 19:49:04 -05:00
Ok(serve(listener, router).await?)
}
2024-05-07 21:13:46 -05:00
async fn index(State(state): State<AppState>) -> Besult<Html<String>> {
Ok(Html(
state
.templates
.render("pages/index.html.jinja", context!())
.await?,
2024-05-07 21:13:46 -05:00
))
}
2024-05-07 19:49:04 -05:00
fn live_reload_layer() -> Besult<(LiveReloadLayer, RecommendedWatcher)> {
2024-05-06 15:39:21 -05:00
let rl_layer = LiveReloadLayer::new();
let rl = rl_layer.reloader();
2024-05-07 19:49:04 -05:00
let watcher = static_file_watcher(rl)?;
Ok((rl_layer, watcher))
2024-05-06 15:39:21 -05:00
}
2024-05-07 19:49:04 -05:00
fn static_file_watcher(
reloader: Reloader,
) -> Result<RecommendedWatcher, Box<dyn std::error::Error>> {
info!("Creating async watcher...");
let (tx, mut rx) = channel(1);
info!("Creating watcher...");
2024-05-07 19:49:04 -05:00
// watcher needs to move out of scope to live long enough to watch
let mut watcher = RecommendedWatcher::new(
move |res| {
info!("Res from watcher: {res:#?}");
futures::executor::block_on(async {
tx.send(res).await.unwrap();
})
},
Config::default(),
)?;
info!("Created watcher");
watcher.watch(static_file_dir(), RecursiveMode::Recursive)?;
tokio::spawn(async move {
while let Some(res) = rx.recv().await {
match res {
Ok(event) => {
info!("fs event: {event:#?}");
2024-05-06 15:39:21 -05:00
reloader.reload()
}
Err(e) => println!("watch error: {:?}", e),
}
}
});
2024-05-07 19:49:04 -05:00
Ok(watcher)
2024-05-05 09:47:13 -05:00
}
2024-05-07 21:13:46 -05:00
impl IntoResponse for Berr {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
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<E> From<E> for Berr
where
E: Into<Box<dyn std::error::Error>>,
{
fn from(err: E) -> Self {
Self(err.into())
}
}