From 493afa9c543727fb01603ab33e460f8002b527b9 Mon Sep 17 00:00:00 2001 From: Jose Storopoli Date: Sun, 25 Feb 2024 14:25:49 -0300 Subject: [PATCH 1/2] feat(wasm): doesn't work --- Cargo.toml | 24 ++++++---- build.rs | 5 +++ src/data.rs | 15 ++----- src/{app.rs => lib.rs} | 57 ++++++++++++++--------- src/main.rs | 43 +++--------------- src/pages.rs | 100 +++++++++++++++++++---------------------- templates/index.html | 86 ----------------------------------- templates/quote.html | 4 -- 8 files changed, 111 insertions(+), 223 deletions(-) create mode 100644 build.rs rename src/{app.rs => lib.rs} (88%) delete mode 100644 templates/index.html delete mode 100644 templates/quote.html diff --git a/Cargo.toml b/Cargo.toml index 3e2feed..27ce370 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,26 +3,32 @@ name = "stoic-quotes" version = "0.3.5" edition = "2021" authors = ["Jose Storopoli "] -description = "Stoic quotes API backend" +description = "Stoic Quotes PWA" license = "MIT" readme = "README.md" [dependencies] -anyhow = "1" -askama = "0.12" -axum = "0.7" lazy_static = "1" +prest = "0.2" rand = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" -tokio = { version = "1", features = ["full"] } -tower-http = { version = "0.5", features = ["tracing", "trace", "fs"] } -tracing = "0.1" -tracing-subscriber = "0.3" +wasm-bindgen = "0.2" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.2", features = ["js"] } [dev-dependencies] http-body-util = "0.1" +tokio = { version = "1", features = ["rt", "macros"] } tower = { version = "0.4", features = ["util"] } +[build-dependencies] +prest-build = "0.2" + [profile.release] -lto = true +opt-level = "z" # Optimized for size, use 3 for speed +lto = true # Enable Link Time Optimization +codegen-units = 1 # Reduced to increase optimizations +panic = "abort" # Abort on panic +strip = "symbols" # Strip symbols from binary diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..ec97084 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +use prest_build::*; + +fn main() { + build_pwa(PWAOptions::default()).unwrap(); +} diff --git a/src/data.rs b/src/data.rs index c1169d7..ff21b2a 100644 --- a/src/data.rs +++ b/src/data.rs @@ -18,16 +18,7 @@ lazy_static! { /// /// * `text` - A [`String`] that contains the quote text /// * `author` - A [`String`] that contains the quote author -/// -/// # Examples -/// -/// ``` -/// let quote = Quote { -/// text: "Hello, world!".to_string(), -/// author: "Anonymous".to_string(), -/// }; -/// ``` -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct Quote { pub text: String, pub author: String, @@ -45,8 +36,8 @@ pub fn read_data() -> Result> { /// Currently, this function uses [`rand::thread_rng()`] to generate /// a random number between 0 and the length of [`struct@QUOTES`]. /// Then, it clones the [`struct@QUOTES`] at the random index and returns it. -pub fn random_quote() -> Quote { +pub fn random_quote() -> &'static Quote { let mut rng = thread_rng(); let index = rng.gen_range(0..QUOTES.len()); - QUOTES[index].clone() + "ES[index] } diff --git a/src/app.rs b/src/lib.rs similarity index 88% rename from src/app.rs rename to src/lib.rs index 22fc187..cec55db 100644 --- a/src/app.rs +++ b/src/lib.rs @@ -1,16 +1,24 @@ -//! Module that has functions that handles the Axum [`Router`]. - -use crate::pages::{plain_quote, quote, root}; -use axum::{http::header::USER_AGENT, http::Request, response::Response, routing::get, Router}; -use std::{env::current_dir, path::PathBuf}; -use tower_http::{services::ServeDir, trace::TraceLayer}; -use tracing::info; +//! # Stoic Quotes +//! +//! `stoic-quotes` is a collection of stoic quotes in an Axum web server +//! that serves stoic quotes with reactivity by the all-mighty +//! [htmx](https://htmx.org) (no YAVASCRIPT). +//! +//! It also has plain-text API GET endpoints at `/` that returns a stoic quote +//! for terminal users with `curl` and `wget`. + +pub mod data; +pub mod pages; + +use crate::pages::{home, plain_quote, quote}; +use prest::header::USER_AGENT; +use prest::*; /// Handles the User Agent header /// If the user agent is `curl` or `wget`, /// return a plain quote. /// Otherwise, return the root page. -async fn handle_user_agent(req: Request) -> Response { +async fn handle_user_agent(req: Request) -> Markup { let header = Request::headers(&req); let user_agent: String = if let Some(user_agent) = header.get(USER_AGENT) { user_agent.clone().to_str().unwrap().to_string() @@ -21,37 +29,46 @@ async fn handle_user_agent(req: Request) -> Response { info!("got user agent: {user_agent}"); if user_agent.contains("curl") || user_agent.contains("Wget") { - plain_quote().await + html! {(plain_quote().await)} } else { - root().await + home().await } } +pub async fn into_page(content: Markup) -> Markup { + let title = "Stoic Quotes"; + let css = "/assets/main.css"; + html! {(DOCTYPE) { + (Head::default().title(title).css(css)) + (content) + (Scripts::default()) + }} +} + /// Creates an Axum [`Router`] that only handles GET requests to /// `/` and `/quote`. pub fn app() -> Router { - let assets_path: PathBuf = current_dir().unwrap(); // Create a router info!("initializing router..."); Router::new() .route("/", get(handle_user_agent)) .route("/quote", get(quote)) - .nest_service( - "/assets", - ServeDir::new(format!("{}/assets", assets_path.to_str().unwrap())), - ) - // We can still add middleware - .layer(TraceLayer::new_for_http()) +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(start)] +pub fn main() { + app().handle_fetch_events() } #[cfg(test)] mod tests { use super::*; - use axum::{ - body::Body, + use http_body_util::BodyExt; // for `collect` + use prest::{ http::{Request, StatusCode}, + Body, }; - use http_body_util::BodyExt; // for `collect` use std::str::from_utf8; use tower::{Service, ServiceExt}; // for `call`, `oneshot`, and `ready` diff --git a/src/main.rs b/src/main.rs index 36c9761..22bda53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,42 +1,9 @@ -//! # Stoic Quotes -//! -//! `stoic-quotes` is a collection of stoic quotes in an Axum web server -//! that serves stoic quotes with reactivity by the all-mighty -//! [htmx](https://htmx.org) (no YAVASCRIPT). -//! -//! It also has plain-text API GET endpoints at `/` that returns a stoic quote -//! for terminal users with `curl` and `wget`. +use prest::*; -use anyhow::{Context, Result}; -use axum::serve; -use tokio::net::TcpListener; -use tracing::{info, Level}; -use tracing_subscriber::FmtSubscriber; +use stoic_quotes::{app, into_page}; -mod app; -mod data; -mod pages; +embed_build_output_as!(BuiltAssets); -use app::app; - -#[tokio::main] -async fn main() -> Result<()> { - // Set up logging - let subscriber = FmtSubscriber::builder() - // all spans/events with a level higher than TRACE (e.g, debug, info, warn, etc.) - // will be written to stdout. - .with_max_level(Level::TRACE) - .finish(); - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - - let app = app(); - - // run our app with hyper, listening globally on port 3000 - let port = 3000_u16; - let addr = TcpListener::bind(format!("0.0.0.0:{port}")).await.unwrap(); - info!("router initialized, listening on port {:?}", port); - serve(addr, app) - .await - .context("error while starting server")?; - Ok(()) +fn main() { + app().wrap_non_htmx(into_page).embed(BuiltAssets).run(); } diff --git a/src/pages.rs b/src/pages.rs index 5ca6bb8..8b9bc38 100644 --- a/src/pages.rs +++ b/src/pages.rs @@ -1,69 +1,61 @@ //! Module that has functions and structs that handles the Askama templates //! to be rendered as HTML responses by Axum. -use crate::data::{random_quote, Quote}; -use askama::Template; -use axum::{ - http::StatusCode, - response::{Html, IntoResponse, Response}, +use crate::{ + data::{random_quote, Quote}, + into_page, }; - -/// A wrapper type that we'll use to encapsulate HTML parsed by askama -/// into valid HTML for axum to serve. -pub struct HtmlTemplate(T); - -/// Allows us to convert Askama HTML templates into valid HTML for axum -/// to serve in the response. -impl IntoResponse for HtmlTemplate -where - T: Template, -{ - fn into_response(self) -> Response { - // Attempt to render the template with askama - match self.0.render() { - // If we're able to successfully parse and aggregate the template, serve it - Ok(html) => Html(html).into_response(), - // If we're not, return an error or some bit of fallback HTML - Err(err) => ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to render template. Error: {}", err), - ) - .into_response(), +use prest::{html, Markup}; + +pub async fn quote() -> Markup { + let quote = random_quote(); + let text = "e.text; + let author = "e.author; + + html! { + blockquote #quote .text-center { + p ."text-2xl font-semibold md:text-4xl" { + (text) + } + footer ."mt-4 text-xl text-right italic" { + (author) + } } } } -/// An askama template that we'll use to render the root HTML element. -#[derive(Template)] -#[template(path = "index.html")] -struct IndexTemplate {} - -/// An askama template that we'll use to render the quote HTML elements. -/// It has htmx attributes that allow us to refresh the quote without -/// refreshing the entire page. -#[derive(Template)] -#[template(path = "quote.html")] -struct QuoteTemplate { - text: String, - author: String, -} - /// Returns the rendered askama template for the root HTML element. -pub async fn root() -> Response { - let template = IndexTemplate {}; - HtmlTemplate(template).into_response() -} - -/// Returns the rendered askama template for a random quote HTML element. -pub async fn quote() -> Response { - let Quote { text, author } = random_quote(); - let template = QuoteTemplate { text, author }; - HtmlTemplate(template).into_response() +pub async fn home() -> Markup { + into_page(html!( + div."min-h-screen flex flex-col items-center justify-center text-white" { + div."card card-bordered border-accent bg-base-300 shadow-2xl mx-4 p-10 rounded-lg" { + blockquote hx-get="/quote" hx-swap="outerHTML" hx-trigger="load" #quote ."text-center" { + "Please Enable JavaScript (I know, it sucks...)" + } + } + button."btn btn-accent bg-base-300 mt-10" hx-get="/quote" hx-trigger="click" hx-target="#quote" hx-swap="outerHTML transition:true" + aria-label="Refresh" { + svg."w-6 h-6 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" + stroke-width="1.5" stroke="currentColor" { + path stroke-linecap="round" stroke-linejoin="round" + d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" {} + } + } + a."mt-10 flex hover:text-accent" aria-label="Go to the GitHub repository with the code" + href="https://github.com/storopoli/stoic-quotes" target="_blank" rel="noopener noreferrer" { + svg ."w-8 h-8" fill="currentColor" viewBox="0 0 24 24" { + path fill="evenodd" clip="evenodd" + d="M12 2C6.477 2 2 6.477 2 12c0 4.418 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.009-.866-.014-1.7-2.782.603-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.003.07 1.532 1.03 1.532 1.03.891 1.529 2.341 1.088 2.912.833.091-.646.349-1.086.635-1.337-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.03-2.682-.103-.253-.447-1.27.098-2.646 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 7.07c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.026 2.747-1.026.547 1.376.203 2.394.1 2.646.64.699 1.028 1.591 1.028 2.682 0 3.841-2.337 4.687-4.565 4.934.359.31.678.92.678 1.852 0 1.336-.012 2.415-.012 2.741 0 .267.18.578.688.48A10.017 10.017 0 0022 12C22 6.477 17.523 2 12 2z" {} + } + "storopoli/stoic-quotes" + } + } + )).await } /// Returns a plain text random quote without any HTML. -pub async fn plain_quote() -> Response { +pub async fn plain_quote() -> String { let Quote { text, author } = random_quote(); let formatted_quote: String = format!("{text}\n\n - {author}\n"); - Html(formatted_quote).into_response() + formatted_quote } diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index cff6710..0000000 --- a/templates/index.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - Stoic Quotes - - - - - - - - - -
-
-
- Please Enable JavaScript (I know, it sucks...) -
-
- - - - - - - storopoli/stoic-quotes - -
- - - diff --git a/templates/quote.html b/templates/quote.html deleted file mode 100644 index 100fdc3..0000000 --- a/templates/quote.html +++ /dev/null @@ -1,4 +0,0 @@ -
-

{{ text }}

-
{{ author }}
-
\ No newline at end of file From c6c646a610d573e0cd027ab2238e658dd1699d1a Mon Sep 17 00:00:00 2001 From: Jose Storopoli Date: Sun, 25 Feb 2024 16:41:30 -0300 Subject: [PATCH 2/2] doc: fix doc --- src/data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data.rs b/src/data.rs index ff21b2a..65661a5 100644 --- a/src/data.rs +++ b/src/data.rs @@ -9,7 +9,7 @@ use std::clone::Clone; lazy_static! { /// A vector of [`Quote`]s - static ref QUOTES: Vec = read_data().expect("failed to read data"); + pub static ref QUOTES: Vec = read_data().expect("failed to read data"); } /// A quote with text and author