diff options
Diffstat (limited to 'src/server/mod.rs')
-rw-r--r-- | src/server/mod.rs | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..d9b3beb --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,160 @@ +mod admin; +mod config; +mod login; +mod nginx_check; +mod panel; +mod store; + +use std::{future::IntoFuture, path::PathBuf}; + +use axum::{ + body::Body, + extract::FromRef, + http::{header::CONTENT_TYPE, StatusCode}, + response::{IntoResponse, Redirect, Response}, + routing::get, + Router, +}; +use axum_extra::extract::CookieJar; +use eyre::Context; +use maud::{html, PreEscaped}; +use tap::Pipe; +use tokio::{net::TcpListener, select}; + +use crate::server::store::Store; + +use self::config::Config; + +pub async fn serve() -> eyre::Result<()> { + let config_path = std::env::args() + .skip(2) + .next() + .map(PathBuf::from) + .unwrap_or_else(|| std::env::current_dir().unwrap().join("dissociate.toml")); + let config = Config::load(&config_path)?; + + let store = Store::load(config.data).await?; + println!("admin listening on {:?}", config.admin_socket); + let admin_serve = admin::serve(&config.admin_socket, config.web_base.clone(), store.clone())?; + + let listener = TcpListener::bind(config.web_socket).await?; + + let app = Router::new() + .pipe(login::bind) + .pipe(nginx_check::bind) + .pipe(panel::bind) + .with_state(ApiState { + store, + cookie_domain: CookieDomain(config.cookie_domain), + web_base: WebBase(config.web_base), + }) + .fallback(get(|| async { + render_html( + html!(title { "not found" }), + html! { + h1 { "404 not found" } + p {"sowwy :("} + }, + ) + })); + + println!("web listening on {:?}", config.web_socket); + let web_serve = axum::serve(listener, app).into_future(); + + select! { + res = admin_serve => res.wrap_err("in admin"), + res = web_serve => res.wrap_err("in web"), + } +} + +#[derive(Clone, FromRef)] +struct ApiState { + pub store: Store, + pub cookie_domain: CookieDomain, + pub web_base: WebBase, +} + +#[derive(Clone)] +struct CookieDomain(Option<String>); + +#[derive(Clone)] +struct WebBase(String); + +fn render_html(head: PreEscaped<impl AsRef<str>>, body: PreEscaped<impl AsRef<str>>) -> Response { + let html = html! { + (PreEscaped("<!doctype html>")) + html { + head {(head)} + body {(body)} + } + } + .into_string(); + Response::builder() + .header(CONTENT_TYPE, "text/html; charset=utf-8") + .body(Body::new(html)) + .unwrap() +} + +trait MakeErrorMessage<T> { + fn error_message(self, status: StatusCode, message: impl ToString) -> Result<T, Response>; +} + +impl<T, E: ToString> MakeErrorMessage<T> for Result<T, E> { + fn error_message(self, status: StatusCode, message: impl ToString) -> Result<T, Response> { + self.map_err(|err| { + render_html( + html!(title { "error" }), + html! { + h1 { (status.canonical_reason().unwrap_or_else(|| status.as_str())) } + pre { (err.to_string()) ": " (message.to_string()) } + }, + ) + }) + } +} + +impl<T> MakeErrorMessage<T> for Option<T> { + fn error_message(self, status: StatusCode, message: impl ToString) -> Result<T, Response> { + self.ok_or_else(|| { + render_html( + html!(title { "error" }), + html! { + h1 { (status.canonical_reason().unwrap_or_else(|| status.as_str())) } + pre { (message.to_string()) } + }, + ) + }) + } +} + +trait MakeError<T> { + fn make_error(self) -> Result<T, Response>; +} + +impl<T> MakeError<T> for std::io::Result<T> { + fn make_error(self) -> Result<T, Response> { + self.error_message(StatusCode::INTERNAL_SERVER_ERROR, "internal io error") + } +} + +trait Nevermind<T> { + fn prompt_login(self) -> Result<T, Response>; + fn prompt_logout(self) -> Result<T, Response>; +} + +impl<T> Nevermind<T> for Option<T> { + fn prompt_login(self) -> Result<T, Response> { + self.ok_or_else(|| Redirect::to("/login").into_response()) + } + + fn prompt_logout(self) -> Result<T, Response> { + self.ok_or_else(|| Redirect::to("/login").into_response()) + } +} + +async fn account_auth(jar: &CookieJar, store: &Store) -> Option<String> { + let cookie = jar.get("dissociate-token")?; + let token = cookie.value(); + let name = store.check_token(token).await?; + Some(name) +} |