diff --git a/.dockerignore b/.dockerignore index c4db73f..38f1a0f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,3 +4,4 @@ !Cargo.lock !src/** !templates/** +!static/** diff --git a/Cargo.lock b/Cargo.lock index 65794d1..3ab3d68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -568,6 +568,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + [[package]] name = "httparse" version = "1.9.5" @@ -742,6 +748,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.2" @@ -1373,13 +1389,21 @@ dependencies = [ "bitflags", "bytes", "futures-core", + "futures-util", "http", "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", "pin-project-lite", "tokio", "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -1514,6 +1538,12 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.14" diff --git a/Cargo.toml b/Cargo.toml index 18a921c..b4079ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,6 @@ rusqlite = { version = "0.32.1", features = ["chrono", "bundled"] } serde = { version = "1.0.217", features = ["derive"] } tera = "1.20.0" tokio = { version = "1.42.0", features = ["full"] } -tower-http = { version = "0.6.2", features = ["compression-br", "compression-gzip", "set-header"] } +tower-http = { version = "0.6.2", features = ["compression-br", "compression-gzip", "set-header", "fs"] } tracing = "0.1.41" tracing-subscriber = "0.3.19" diff --git a/Dockerfile b/Dockerfile index 281ed54..611b658 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,7 @@ FROM alpine:3.21 COPY --from=builder /app/target/release/calathea /app/calathea COPY --from=builder /app/dumb-init /app/dumb-init COPY templates /app/templates +COPY static /app/static # Create a non-root user & make sure it can write to the data directory RUN set -x && \ @@ -36,7 +37,8 @@ RUN set -x && \ WORKDIR /data -ENV TEMPLATE_DIR=/app/templates +ENV TEMPLATE_DIR=/app/templates \ + STATIC_DIR=/app/static USER www-data:www-data diff --git a/src/main.rs b/src/main.rs index 72a7da0..62fda66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,10 +13,6 @@ const MIGRATIONS: [&str; 4] = [ include_str!("migrations/002_comments.sql"), include_str!("migrations/003_events.sql"), ]; -const STATIC_FILES: [(&str, &'static str); 1] = [( - "htmx_2.0.4.min.js", - include_str!("static/htmx_2.0.4.min.js"), -)]; #[derive(Clone)] pub struct Context { @@ -35,11 +31,13 @@ async fn main() { let template_dir = std::env::var("TEMPLATE_DIR").unwrap_or(String::from("./templates")); let tera = Tera::new(&format!("{template_dir}/**/*")).unwrap(); + let static_dir = std::env::var("STATIC_DIR").unwrap_or(String::from("./static")); + let ctx = Context { pool, tera: Arc::new(tera), }; - let app = server::app(ctx).layer(CompressionLayer::new().br(true).gzip(true)); + let app = server::app(ctx, &static_dir).layer(CompressionLayer::new().br(true).gzip(true)); let address = "0.0.0.0:8000"; diff --git a/src/server/mod.rs b/src/server/mod.rs index 16c31de..56e156e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -11,7 +11,7 @@ use axum::{ Router, }; use tera::Context; -use tower_http::set_header::SetResponseHeaderLayer; +use tower_http::{services::ServeDir, set_header::SetResponseHeaderLayer}; use crate::db::Plant; @@ -48,18 +48,15 @@ pub fn render_view( } } -pub fn app(ctx: crate::Context) -> axum::Router { - let mut router = Router::new() +pub fn app(ctx: crate::Context, static_dir: &str) -> axum::Router { + let router = Router::new() .route("/", get(get_index)) .nest("/plants", plants::app()) .nest("/comments", comments::app()) .nest("/events", events::app()) + .nest_service("/static", ServeDir::new(static_dir)) .with_state(ctx.clone()); - for (name, content) in crate::STATIC_FILES { - router = router.route(&format!("/static/{}", name), get(content)) - } - // Routes return either partial or full pages depending on whether the request is done using // HTMX or just as a plain HTTP request. Adding the Vary header ensures caches don't mix // partial and full responses. diff --git a/src/static/htmx_2.0.4.min.js b/static/htmx_2.0.4.min.js similarity index 100% rename from src/static/htmx_2.0.4.min.js rename to static/htmx_2.0.4.min.js