diff --git a/Cargo.lock b/Cargo.lock index da659c4..27b8e6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,6 +286,7 @@ dependencies = [ "axum-sqlx-tx", "chrono", "figment", + "mime_guess", "minijinja", "minijinja-embed", "notify", diff --git a/Cargo.toml b/Cargo.toml index f963547..96ec578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ axum-htmx = { version = "0.7.0", features = ["auto-vary", "serde", "guards"] } axum-sqlx-tx = "0.10.0" chrono = { version = "0.4.39", features = ["serde"] } figment = { version = "0.10.19", features = ["env", "toml"] } +mime_guess = "2.0.5" minijinja = { version = "2.7.0", features = ["loader"] } minijinja-embed = "2.7.0" notify = "8.0.0" diff --git a/src/error.rs b/src/error.rs index 0ae3903..afdbddf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,7 +6,7 @@ use tracing::debug; pub type Result = std::result::Result; -pub type ResultTemplate = std::result::Result, Error>; +pub type ResultTemplate = Result>; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -33,15 +33,35 @@ pub enum Error { #[error(transparent)] Config(#[from] figment::Error), + + #[error("")] + HTTP(StatusCode), +} + +impl From for Error { + fn from(value: StatusCode) -> Self { + Self::HTTP(value) + } } impl IntoResponse for Error { fn into_response(self) -> axum::response::Response { + let catch_all = ( + StatusCode::INTERNAL_SERVER_ERROR, + "An unexpected error has ocurred!", + ); let (status, message) = match self { - _ => ( - StatusCode::INTERNAL_SERVER_ERROR, - "An unexpected error has ocurred!", - ), + Error::Template(ref error) => { + if let minijinja::ErrorKind::TemplateNotFound = error.kind() { + ( + StatusCode::NOT_FOUND, + "The requested resource was not found.", + ) + } else { + catch_all + } + } + _ => catch_all, }; debug!(error = ?self); diff --git a/src/router.rs b/src/router.rs index 9fd870f..1846bf5 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,17 +1,39 @@ -use axum::{extract::State, response::Html, routing::get, Router}; +use axum::{ + extract::{Path, State}, + http::{header, HeaderMap, HeaderValue}, + response::Html, + routing::get, + Router, +}; use axum_htmx::HxRequest; use chrono::Utc; use minijinja::context; use serde::Serialize; use sqlx::prelude::FromRow; -use tower_http::services::ServeDir; -use crate::{AppState, ResultTemplate, Tx}; +use crate::{AppState, Result, ResultTemplate, Tx}; pub fn new() -> Router { Router::new() .route("/", get(handler_home)) - .nest_service("/assets", ServeDir::new("dist/assets")) + .route("/assets/{*asset}", get(handle_assets)) +} + +async fn handle_assets( + State(state): State, + Path(asset): Path, +) -> Result<(HeaderMap, String)> { + let mut headers = HeaderMap::new(); + + let mime = mime_guess::from_path(&asset).first_raw(); + + if let Some(mime) = mime { + headers.insert(header::CONTENT_TYPE, HeaderValue::from_static(mime)); + } + + let template = state.tmpl_env.get_template(&format!("assets/{}", asset))?; + + Ok((headers, template.render(context!())?)) } #[derive(FromRow, Debug, Serialize)]