use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration}; use axum::extract::MatchedPath; use color_eyre::eyre::{Error, Result}; use hyper::{Body, Request, Response}; use post::Post; use tag::Tag; use tera::Tera; use tower_http::{compression::CompressionLayer, cors::CorsLayer}; use tracing::{info_span, log::*, Span}; use tracing_subscriber::{prelude::*, EnvFilter}; mod feed; mod handlers; mod post; mod tag; #[derive(Default)] pub struct AppState { base_url: String, posts: HashMap, tags: HashMap, tera: Tera, } #[tokio::main] async fn main() -> Result<()> { color_eyre::install()?; init_tracing(); info!("Starting server..."); let base_url = option_env!("SITE_BASE_URL") .unwrap_or("http://localhost:8180") .to_string(); let tera = Tera::new("templates/**/*")?; let posts = post::load_all().await?; let tags = tag::get_tags(posts.values()); let state = Arc::new(AppState { base_url, tera, posts, tags, }); let app = handlers::routes(&state) .layer(CorsLayer::permissive()) .layer(CompressionLayer::new()) .layer( tower_http::trace::TraceLayer::new_for_http() .make_span_with(make_span) .on_response(on_response), ) .with_state(state.clone()); info!("Now listening at {}", state.base_url); axum::Server::bind(&"0.0.0.0:8180".parse().unwrap()) .serve(app.into_make_service()) .await?; Ok(()) } fn init_tracing() { let filter = EnvFilter::builder() .with_default_directive("into".parse().unwrap()) .from_env_lossy(); tracing_subscriber::registry() .with(filter) .with(tracing_subscriber::fmt::layer()) .init(); } fn make_span(request: &Request) -> Span { let uri = request.uri(); let route = request .extensions() .get::() .map(|mp| mp.as_str()) .unwrap_or_default(); let method = request.method().as_str(); let target = uri.path_and_query().map(|p| p.as_str()).unwrap_or_default(); let name = format!("{method} {route}"); use tracing::field::Empty; info_span!( "request", otel.name = %name, http.route = %route, http.method = %method, http.target = %target, http.status_code = Empty ) } fn on_response(response: &Response, _latency: Duration, span: &Span) { span.record("http.status_code", response.status().as_str()); } #[derive(Debug)] pub enum WebsiteError { NotFound, InternalError(Error), } impl std::error::Error for WebsiteError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { WebsiteError::NotFound => None, WebsiteError::InternalError(e) => Some(e.as_ref()), } } } impl Display for WebsiteError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { WebsiteError::NotFound => write!(f, "Not found"), _ => write!(f, "Internal error"), } } } impl From for WebsiteError { fn from(value: tera::Error) -> Self { WebsiteError::InternalError(value.into()) } } impl From for WebsiteError { fn from(value: color_eyre::Report) -> Self { WebsiteError::InternalError(value) } }