129 lines
3.5 KiB
Rust
129 lines
3.5 KiB
Rust
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<axum::body::Body> {
|
|
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<T> = Result<T, ReqError>;
|
|
|
|
pub async fn router(
|
|
state: AppState,
|
|
with_watchers: bool,
|
|
) -> Result<(Router, Vec<Option<FileWatcher>>), NewRouterError> {
|
|
let live_reload_layer: Option<LiveReloadLayer> = 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<Html<String>> {
|
|
page("index", html! { "Index" })
|
|
}
|
|
|
|
async fn about() -> ReqResult<Html<String>> {
|
|
page("about", html! { "About" })
|
|
}
|
|
|
|
async fn dashboard() -> ReqResult<Html<String>> {
|
|
page("dashboard", html! { "Dashboard" })
|
|
}
|
|
|
|
async fn users(State(state): State<AppState>) -> ReqResult<String> {
|
|
let mut s = String::new();
|
|
let mut users = state.db.all::<IVec, User>(User::tree())?;
|
|
while let Some(Ok((_, user))) = users.next() {
|
|
s.push_str(&format!("{}: {:?}", user.username, user.registered_at))
|
|
}
|
|
Ok(s)
|
|
}
|