diff --git a/Cargo.lock b/Cargo.lock index fc33486..b6baecb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1460,7 +1460,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sustenance" -version = "0.1.1" +version = "0.1.2" dependencies = [ "argon2", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 4060738..2d62373 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sustenance" -version = "0.1.1" +version = "0.1.2" edition = "2024" [dependencies] diff --git a/src/http.rs b/src/http.rs index 857b7aa..86c92d1 100644 --- a/src/http.rs +++ b/src/http.rs @@ -16,11 +16,7 @@ use axum::{ use futures_util::{SinkExt, StreamExt}; use serde::{Deserialize, de::DeserializeOwned}; use thiserror::Error; -use tower_http::{ - services::ServeDir, - set_header::SetResponseHeaderLayer, - trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}, -}; +use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; use tracing::{Level, error, warn}; use crate::domain::{DomainError, SessionUser}; @@ -29,6 +25,12 @@ use crate::services::{AuthService, InvitationService, ListService, MealService}; use crate::views; use crate::webauthn::WebAuthnService; +/// The contents of `static/`, embedded into the binary at compile time so the +/// app can be shipped as a single executable without a separate static directory. +const STYLE_CSS: &[u8] = include_bytes!("../static/style.css"); +const PASSKEY_LOGIN_JS: &[u8] = include_bytes!("../static/passkey-login.js"); +const PASSKEY_REGISTER_JS: &[u8] = include_bytes!("../static/passkey-register.js"); + #[derive(Clone)] pub struct AppState { pub auth: Arc, @@ -116,15 +118,7 @@ pub fn build_router(state: AppState) -> Router { .route("/lists/{list_id}/stream", get(list_stream)) .route("/invite/{token}", get(invitation_page)) .route("/invite/{token}/accept", post(accept_invitation)) - .nest_service( - "/static", - tower::ServiceBuilder::new() - .layer(SetResponseHeaderLayer::overriding( - header::CACHE_CONTROL, - HeaderValue::from_static("public, max-age=0, must-revalidate"), - )) - .service(ServeDir::new("static")), - ) + .route("/static/{path}", get(static_asset)) .layer( TraceLayer::new_for_http() .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) @@ -313,6 +307,17 @@ async fn home() -> Redirect { Redirect::to("/lists") } +/// Serves a file embedded in the binary from the `static/` folder. +async fn static_asset(Path(path): Path) -> Response { + let (mime, data) = match path.as_str() { + "style.css" => ("text/css", STYLE_CSS), + "passkey-login.js" => ("application/javascript", PASSKEY_LOGIN_JS), + "passkey-register.js" => ("application/javascript", PASSKEY_REGISTER_JS), + _ => return StatusCode::NOT_FOUND.into_response(), + }; + ([(header::CONTENT_TYPE, mime)], data).into_response() +} + async fn log_response_status(request: Request, next: Next) -> Response { let method = request.method().clone(); let uri = request.uri().clone();