use crate::partials::page; use crate::user::User; use crate::{db, user}; use crate::{ file_watcher::FileWatcher, prelude::*, service::{auth, static_files}, state::State as AppState, }; use axum::extract::State; use axum::{ http::StatusCode, response::{Html, IntoResponse}, routing::get, Router, }; use axum_login::{login_required, AuthManagerLayerBuilder}; use maud::html; use sled::IVec; use thiserror::Error; use tower_http::trace::TraceLayer; use tower_livereload::LiveReloadLayer; use tower_sessions::SessionManagerLayer; #[derive(Error, Debug)] pub enum NewRouterError { #[error("watcher error: {0}")] Watcher(#[from] notify::Error), #[error("database error: {0}")] Database(#[from] db::Error), } #[derive(Error, Debug)] pub enum ReqError { #[error("argon2 error: {0}")] Argon2(#[from] argon2::password_hash::Error), #[error("bincode error: {0}")] Bincode(#[from] bincode::Error), #[error("database error: {0}")] Database(#[from] db::Error), #[error("user error: {0}")] User(#[from] user::Error), } impl IntoResponse for ReqError { fn into_response(self) -> axum::http::Response { error!("webserver error: {:?}", self); ( StatusCode::INTERNAL_SERVER_ERROR, // TODO: don't expose raw errors over the internet? format!("internal server error: {}", self), ) .into_response() } } pub type ReqResult = Result; pub async fn router( state: AppState, with_watchers: bool, ) -> Result<(Router, Vec>), NewRouterError> { let live_reload_layer: Option = if with_watchers { Some(LiveReloadLayer::new()) } else { None }; let orl = || { if let Some(lr) = &live_reload_layer { Some(lr.reloader()) } else { None } }; let (static_file_service, static_file_watcher) = static_files::router(orl())?; let auth_service = auth::router(state.clone()).unwrap(); let session_store = tower_sessions_sled_store::SledStore::new(state.db.tree("session")?); let session_layer = SessionManagerLayer::new(session_store); let auth_layer = AuthManagerLayerBuilder::new(state.clone(), session_layer).build(); let mut result = Router::new() .route("/dashboard", get(dashboard)) .route_layer(login_required!(AppState, login_url = "/auth/login")) .route("/", get(index)) .route("/about", get(about)) .route("/users", get(users)) .nest_service("/auth", auth_service) .nest_service("/static", static_file_service) .layer(TraceLayer::new_for_http()) .layer(auth_layer) .with_state(state.clone()); if let Some(lr) = live_reload_layer { result = result.clone().layer(lr); } let watchers = vec![static_file_watcher]; Ok((result, watchers)) } async fn index() -> ReqResult> { page("index", html! { "Index" }) } async fn about() -> ReqResult> { page("about", html! { "About" }) } async fn dashboard() -> ReqResult> { page("dashboard", html! { "Dashboard" }) } async fn users(State(state): State) -> ReqResult { let mut s = String::new(); let mut users = state.db.all::(User::tree())?; while let Some(Ok((_, user))) = users.next() { s.push_str(&format!("{}: {:?}", user.username, user.registered_at)) } Ok(s) }