diff --git a/.gitignore b/.gitignore index ea8c4bf..3077a23 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +src/htmx.min.js \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 4574dac..6cda3c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -165,6 +171,7 @@ dependencies = [ "rand", "rayon", "regex", + "reqwest", "ring", "rocket", "rocket_cors", @@ -182,7 +189,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b1866ecef4f2d06a0bb77880015fdf2b89e25a1c2e5addacb87e459c86dc67e" dependencies = [ - "base64", + "base64 0.22.1", "blowfish", "getrandom", "subtle", @@ -195,6 +202,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -463,7 +476,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" dependencies = [ - "bitflags", + "bitflags 2.6.0", "proc-macro2", "proc-macro2-diagnostics", "quote", @@ -945,6 +958,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -1133,6 +1159,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + [[package]] name = "is-terminal" version = "0.4.13" @@ -1454,7 +1486,7 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -1735,7 +1767,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -1802,6 +1834,46 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "ring" version = "0.16.20" @@ -1948,13 +2020,22 @@ version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustversion" version = "1.0.18" @@ -2003,7 +2084,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2293,8 +2374,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", - "base64", - "bitflags", + "base64 0.22.1", + "bitflags 2.6.0", "byteorder", "bytes", "chrono", @@ -2337,8 +2418,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", - "base64", - "bitflags", + "base64 0.22.1", + "bitflags 2.6.0", "byteorder", "chrono", "crc", @@ -2447,6 +2528,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.13.1" @@ -2458,6 +2545,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.14.0" @@ -2595,6 +2703,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -2920,6 +3038,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.99" @@ -3175,6 +3306,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 2666f07..b627b77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,5 +26,8 @@ data-encoding = "2.6.0" bcrypt = "0.16.0" dashmap = "6.1.0" +[build-dependencies] +reqwest = { version = "0.11", features = ["blocking"] } + [features] cache = [] diff --git a/README.md b/README.md index 5855af6..994c265 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,19 @@ CREATE TABLE IF NOT EXISTS user_session ( FOREIGN KEY("user") REFERENCES users(username) ); ``` + +## HTMX +Based has a route for serving HTMX at `based::htmx::htmx_script_route` which you can include in your rocket `routes!`. The HTMX script will be available at `/assets/htmx.min.js`. + +Additionally you can check if a requests originates from HTMX: + +```rs +#[get("/")] +pub async fn index(ctx: RequestContext) -> StringRespopnse { + if ctx.is_htmx { + ... + } else { + ... + } +} +``` diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..c1dd6fd --- /dev/null +++ b/build.rs @@ -0,0 +1,19 @@ +use std::fs; +use std::path::Path; + +fn main() { + let url = "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"; + let dest_path = Path::new("src/htmx.min.js"); + + println!("Downloading htmx.min.js from {}", url); + let response = reqwest::blocking::get(url) + .expect("Failed to send HTTP request") + .error_for_status() + .expect("Received error response from server"); + + let content = response.bytes().expect("Failed to read response body"); + + fs::write(&dest_path, &content).expect("Failed to write htmx.min.js to destination"); + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/src/htmx.rs b/src/htmx.rs new file mode 100644 index 0000000..6c17189 --- /dev/null +++ b/src/htmx.rs @@ -0,0 +1,8 @@ +use rocket::get; + +use crate::request::{StringResponse, respond_script}; + +#[get("/assets/htmx.min.js")] +pub fn htmx_script_route() -> StringResponse { + respond_script(include_str!("htmx.min.js")) +} diff --git a/src/lib.rs b/src/lib.rs index a028ecb..00a0df5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ use tokio::sync::OnceCell; pub mod auth; pub mod format; +pub mod htmx; pub mod page; pub mod request; pub mod result; diff --git a/src/request/mod.rs b/src/request/mod.rs index d322842..1ecab66 100644 --- a/src/request/mod.rs +++ b/src/request/mod.rs @@ -12,5 +12,83 @@ pub use context::RequestContext; /// HTTP response containing a string payload. pub type StringResponse = (Status, (ContentType, String)); +pub trait RespondString { + fn respond(self) -> StringResponse; +} + +impl RespondString for String { + fn respond(self) -> StringResponse { + (Status::Ok, (ContentType::Text, self)) + } +} + /// HTTP response containing raw binary data. pub type RawResponse = (Status, (ContentType, Vec)); + +pub trait RespondRaw { + fn respond(self) -> RawResponse; +} + +impl RespondRaw for Vec { + fn respond(self) -> RawResponse { + (Status::Ok, (ContentType::Binary, self)) + } +} + +impl RespondRaw for StringResponse { + fn respond(self) -> RawResponse { + (self.0, (self.1.0, self.1.1.into_bytes())) + } +} + +/// Helper function to create a JSON HTTP response. +/// +/// # Parameters +/// - `json`: A reference to a `serde_json::Value` representing the JSON payload. +/// +/// # Returns +/// A `StringResponse` with status `200 OK`, content type `application/json`, and the JSON-encoded body. +pub fn respond_json(json: &serde_json::Value) -> StringResponse { + ( + Status::Ok, + ( + ContentType::JSON, + serde_json::to_string(json).expect("Failed to serialize JSON"), + ), + ) +} + +/// Helper function to create an HTML HTTP response. +/// +/// # Parameters +/// - `html`: A string slice (`&str`) containing the HTML content. +/// +/// # Returns +/// A `StringResponse` with status `200 OK`, content type `text/html`, and the HTML content as the body. +pub fn respond_html(html: &str) -> StringResponse { + (Status::Ok, (ContentType::HTML, html.to_string())) +} + +/// Helper function to create an JS HTTP response. +/// +/// # Parameters +/// - `html`: A string slice (`&str`) containing the JS content. +/// +/// # Returns +/// A `StringResponse` with status `200 OK`, content type `text/javascript`, and the JS content as the body. +pub fn respond_script(script: &str) -> StringResponse { + (Status::Ok, (ContentType::JavaScript, script.to_string())) +} + +/// Creates a custom HTTP response with the specified status, content type, and body. +/// +/// # Parameters +/// - `status`: The HTTP status code to include in the response. +/// - `content_type`: The content type of the response body (e.g., `ContentType::JSON` or `ContentType::HTML`). +/// - `body`: The response body as a `Vec`. +/// +/// # Returns +/// A `RawResponse` containing the provided status, content type, and body. +pub fn respond_with(status: Status, content_type: ContentType, body: Vec) -> RawResponse { + (status, (content_type, body)) +}