150 lines
4.2 KiB
Rust
150 lines
4.2 KiB
Rust
mod autocomplete;
|
|
mod image_proxy;
|
|
mod index;
|
|
mod opensearch;
|
|
mod search;
|
|
mod settings;
|
|
|
|
use std::{convert::Infallible, net::SocketAddr, sync::Arc};
|
|
|
|
use axum::{
|
|
extract::{Request, State},
|
|
http::{header, StatusCode},
|
|
middleware::{self, Next},
|
|
response::Response,
|
|
routing::{get, post, MethodRouter},
|
|
Router,
|
|
};
|
|
use axum_extra::extract::CookieJar;
|
|
use maud::{html, Markup, PreEscaped};
|
|
use tracing::info;
|
|
|
|
use crate::config::Config;
|
|
|
|
macro_rules! register_static_routes {
|
|
( $app:ident, $( $x:expr ),* ) => {
|
|
{
|
|
$(
|
|
let $app = $app.route(
|
|
concat!("/", $x),
|
|
static_route(
|
|
include_str!(concat!("assets/", $x)),
|
|
guess_mime_type($x)
|
|
),
|
|
);
|
|
)*
|
|
|
|
$app
|
|
}
|
|
};
|
|
}
|
|
|
|
pub async fn run(config: Config) {
|
|
let bind_addr = config.bind;
|
|
|
|
let config = Arc::new(config);
|
|
|
|
fn static_route<S>(
|
|
content: &'static str,
|
|
content_type: &'static str,
|
|
) -> MethodRouter<S, Infallible>
|
|
where
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
let response = ([(header::CONTENT_TYPE, content_type)], content);
|
|
get(|| async { response })
|
|
}
|
|
|
|
let app = Router::new()
|
|
.route("/", get(index::get))
|
|
.route("/search", get(search::get))
|
|
.route("/settings", get(settings::get))
|
|
.route("/settings", post(settings::post))
|
|
.route("/opensearch.xml", get(opensearch::route))
|
|
.route("/autocomplete", get(autocomplete::route))
|
|
.route("/image-proxy", get(image_proxy::route))
|
|
.layer(middleware::from_fn_with_state(
|
|
config.clone(),
|
|
config_middleware,
|
|
))
|
|
.with_state(config);
|
|
let app = register_static_routes![
|
|
app,
|
|
"style.css",
|
|
"script.js",
|
|
"robots.txt",
|
|
"scripts/colorpicker.js",
|
|
"themes/catppuccin-mocha.css",
|
|
"themes/catppuccin-macchiato.css",
|
|
"themes/catppuccin-latte.css",
|
|
"themes/nord-bluish.css",
|
|
"themes/discord.css"
|
|
];
|
|
|
|
info!("Listening on http://{bind_addr}");
|
|
|
|
let listener = tokio::net::TcpListener::bind(bind_addr).await.unwrap();
|
|
axum::serve(
|
|
listener,
|
|
app.into_make_service_with_connect_info::<SocketAddr>(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
fn guess_mime_type(path: &str) -> &'static str {
|
|
match path.rsplit('.').next() {
|
|
Some("css") => "text/css; charset=utf-8",
|
|
Some("js") => "text/javascript; charset=utf-8",
|
|
Some("txt") => "text/plain; charset=utf-8",
|
|
_ => "text/plain; charset=utf-8",
|
|
}
|
|
}
|
|
|
|
async fn config_middleware(
|
|
State(config): State<Arc<Config>>,
|
|
cookies: CookieJar,
|
|
mut req: Request,
|
|
next: Next,
|
|
) -> Result<Response, StatusCode> {
|
|
let mut config = config.clone().as_ref().clone();
|
|
|
|
let settings_cookie = cookies.get("settings");
|
|
if let Some(settings_cookie) = settings_cookie {
|
|
if let Ok(settings) = serde_json::from_str::<settings::Settings>(settings_cookie.value()) {
|
|
config.ui.stylesheet_url = settings.stylesheet_url;
|
|
config.ui.stylesheet_str = settings.stylesheet_str;
|
|
}
|
|
}
|
|
|
|
// modify the state
|
|
req.extensions_mut().insert(config);
|
|
|
|
Ok(next.run(req).await)
|
|
}
|
|
|
|
pub fn head_html(title: Option<&str>, config: &Config) -> Markup {
|
|
html! {
|
|
head {
|
|
meta charset="UTF-8";
|
|
meta name="viewport" content="width=device-width, initial-scale=1.0";
|
|
title {
|
|
@if let Some(title) = title {
|
|
{ (title) }
|
|
{ " - " }
|
|
}
|
|
{(config.ui.site_name)}
|
|
}
|
|
link rel="stylesheet" href="/style.css";
|
|
@if !config.ui.stylesheet_url.is_empty() {
|
|
link rel="stylesheet" href=(config.ui.stylesheet_url);
|
|
}
|
|
@if !config.ui.stylesheet_str.is_empty() {
|
|
style { (PreEscaped(html_escape::encode_style(&config.ui.stylesheet_str))) }
|
|
}
|
|
script src="/script.js" defer {}
|
|
link rel="search" type="application/opensearchdescription+xml" title="metasearch" href="/opensearch.xml";
|
|
}
|
|
}
|
|
}
|