diff --git a/sreez/Cargo.lock b/sreez/Cargo.lock index b162732..d3c6dd7 100644 --- a/sreez/Cargo.lock +++ b/sreez/Cargo.lock @@ -142,6 +142,27 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "actix-tls" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4cce60a2f2b477bc72e5cde0af1812a6e82d8fd85b5570a5dcf2a5bf2c5be5f" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "http 0.2.12", + "http 1.1.0", + "impl-more", + "pin-project-lite", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", + "webpki-roots", +] + [[package]] name = "actix-utils" version = "3.0.1" @@ -262,6 +283,21 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.82" @@ -315,6 +351,40 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "awc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c09cc97310b926f01621faee652f3d1b0962545a3cec6c9ac07def9ea36c2c" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "base64 0.21.7", + "bytes", + "cfg-if", + "cookie", + "derive_more", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "itoa", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "rustls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -459,6 +529,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.5", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -562,6 +646,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "cpufeatures" version = "0.2.12" @@ -985,6 +1075,29 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1001,6 +1114,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-more" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" + [[package]] name = "indexmap" version = "2.2.6" @@ -1452,6 +1571,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.32.2" @@ -1730,6 +1858,36 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "rstml" version = "0.11.2" @@ -1765,6 +1923,18 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + [[package]] name = "ryu" version = "1.0.17" @@ -1786,6 +1956,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + [[package]] name = "self_cell" version = "1.0.3" @@ -1809,9 +1989,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] @@ -1829,9 +2009,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", @@ -1840,9 +2020,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -2000,18 +2180,36 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "sreez" version = "0.1.0" dependencies = [ "actix-files", "actix-web", + "awc", + "chrono", "console_error_panic_hook", "http 0.2.12", "leptos", "leptos_actix", "leptos_meta", "leptos_router", + "regex", + "serde", + "serde_json", + "toml", "wasm-bindgen", ] @@ -2138,6 +2336,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-util" version = "0.7.10" @@ -2286,6 +2495,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -2429,6 +2650,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2460,6 +2700,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/sreez/Cargo.toml b/sreez/Cargo.toml index 46c792e..a9564fb 100644 --- a/sreez/Cargo.toml +++ b/sreez/Cargo.toml @@ -16,6 +16,12 @@ leptos_meta = { version = "0.6", features = ["nightly"] } leptos_actix = { version = "0.6", optional = true } leptos_router = { version = "0.6", features = ["nightly"] } wasm-bindgen = "=0.2.92" +toml = "0.8.12" +serde = "1.0.198" +chrono = "0.4.38" +awc = { version = "3.4.0", optional = true, features = ["rustls"] } +serde_json = { version = "1.0.116", optional = true } +regex = "1.10.4" [features] csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] @@ -27,7 +33,10 @@ ssr = [ "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", + "awc", + "serde_json", ] +awc = ["dep:awc"] # Defines a size-optimized profile for the WASM bundle in release mode [profile.wasm-release] diff --git a/sreez/config.toml b/sreez/config.toml new file mode 100644 index 0000000..e451612 --- /dev/null +++ b/sreez/config.toml @@ -0,0 +1 @@ +discord_webhook_url = "https://discord.com/api/webhooks/1230205994055762081/IUfZThaKpHCdlALdRr9orDBoJwlYepIKa2s8p_LvPk1hOBDYxpSyfBW98jGLLxGlOs2i" diff --git a/sreez/src/app.rs b/sreez/src/app.rs index 82ddd17..d4d7ad9 100644 --- a/sreez/src/app.rs +++ b/sreez/src/app.rs @@ -1,7 +1,10 @@ use leptos::*; use leptos_meta::*; use leptos_router::*; +use logging::log; +use chrono::Utc; +use crate::config::get_discord_webhook_url; use crate::pages::ShortLandingPage; use crate::pages::OkdInstallationOverview1; use crate::pages::HomePage; @@ -11,6 +14,7 @@ use crate::components::Matomo; #[component] pub fn App() -> impl IntoView { // Provides context that manages stylesheets, titles, meta tags, etc. + log!("Got a hit {}", Utc::now()); provide_meta_context(); view! { diff --git a/sreez/src/components/mod.rs b/sreez/src/components/mod.rs index 7fb9aa5..cc4e63a 100644 --- a/sreez/src/components/mod.rs +++ b/sreez/src/components/mod.rs @@ -1,6 +1,8 @@ mod book_a_demo; mod footer; mod matomo; +mod subscribe; +pub use subscribe::*; pub use book_a_demo::*; pub use footer::*; pub use matomo::*; diff --git a/sreez/src/components/subscribe.rs b/sreez/src/components/subscribe.rs new file mode 100644 index 0000000..0aec3c7 --- /dev/null +++ b/sreez/src/components/subscribe.rs @@ -0,0 +1,55 @@ +use leptos::*; +use leptos_router::use_location; +use leptos::logging::log; +#[cfg(feature = "ssr")] +use crate::infra::discord::send_discord_message; +use regex::Regex; + +fn is_valid_email(email: &str) -> bool { + let regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); + return regex.is_match(email); +} + +#[server(Subscribe, "/subscribe")] +pub async fn add_subscriber(message: String, email: String) -> Result<(), ServerFnError> { + let notification = &format!("New subscriber email : {} , message : {}", email, message); + log!("Sending new subscriber notification {} ", notification); + #[cfg(feature = "ssr")] + send_discord_message(notification).await?; + Ok(()) +} + +#[component] +pub fn SubscribeButton() -> impl IntoView { + let FORM_INITIAL = "INITIAL"; + let FORM_SUCCESS ="SUCCESS"; + let FORM_ERROR ="ERROR"; + + let (form_status, set_form_status) = create_signal(FORM_INITIAL); + + let email : NodeRef = create_node_ref(); + + let send = move |ev: leptos::ev::SubmitEvent | { + ev.prevent_default(); + let email_value = email.get().expect("Email input should be mounted").value(); + spawn_local(async move { + if is_valid_email(&email_value) { + let path = use_location().pathname.get(); + let _ = add_subscriber(path, email_value).await; + set_form_status(FORM_SUCCESS); + } else { + log!("Invalid email {}", email_value); + set_form_status(FORM_ERROR); + } + }); + }; + + view! { +
+ + + +

"Status is "{form_status}

+
+ } +} diff --git a/sreez/src/config.rs b/sreez/src/config.rs new file mode 100644 index 0000000..6f7d8fe --- /dev/null +++ b/sreez/src/config.rs @@ -0,0 +1,28 @@ +use std::env; +use toml; +use std::fs; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +struct Config { + discord_webhook_url: String, +} + +fn get_full_config() -> Option { + if let Ok(config) = fs::read_to_string("config.toml") { + return toml::from_str(&config).unwrap_or(None); + } + None +} + +pub fn get_discord_webhook_url() -> Option { + if let Ok(url) = env::var("DISCORD_WEBHOOK_URL") { + return Some(url); + } + + if let Some(config) = get_full_config() { + return Some(config.discord_webhook_url); + } + + return None; +} diff --git a/sreez/src/infra/discord.rs b/sreez/src/infra/discord.rs new file mode 100644 index 0000000..45580d8 --- /dev/null +++ b/sreez/src/infra/discord.rs @@ -0,0 +1,25 @@ +use crate::config::get_discord_webhook_url; + +#[cfg(feature = "ssr")] +use awc::{error::SendRequestError, Client}; +#[cfg(feature = "ssr")] +use serde_json::json; +#[cfg(feature = "ssr")] +pub async fn send_discord_message(content: &str) -> Result<(), SendRequestError> { + + let webhook_url = get_discord_webhook_url().expect("Webhook url configuration is available"); + let client = Client::default(); + + let mut request = client.post(webhook_url); + request = request.insert_header(("User-Agent", "Actix-web")); + + let body = json!({ + "content": content, + }); + + let response = request.send_json(&body).await?; + + println!("Response: {:?}", response); + + Ok(()) +} diff --git a/sreez/src/infra/mod.rs b/sreez/src/infra/mod.rs new file mode 100644 index 0000000..3228a62 --- /dev/null +++ b/sreez/src/infra/mod.rs @@ -0,0 +1 @@ +pub mod discord; diff --git a/sreez/src/lib.rs b/sreez/src/lib.rs index 7a33594..38a9fad 100644 --- a/sreez/src/lib.rs +++ b/sreez/src/lib.rs @@ -1,6 +1,8 @@ pub mod app; mod pages; mod components; +mod config; +mod infra; #[cfg(feature = "hydrate")] #[wasm_bindgen::prelude::wasm_bindgen] diff --git a/sreez/src/pages/okd_installation_overview_1.rs b/sreez/src/pages/okd_installation_overview_1.rs index 79d9314..6b5dd74 100644 --- a/sreez/src/pages/okd_installation_overview_1.rs +++ b/sreez/src/pages/okd_installation_overview_1.rs @@ -1,4 +1,5 @@ use leptos::*; +use crate::components::SubscribeButton; /// Renders the home page of your application. #[component] pub fn OkdInstallationOverview1() -> impl IntoView { @@ -95,6 +96,7 @@ pub fn OkdInstallationOverview1() -> impl IntoView { + } } diff --git a/sreez/style/main.scss b/sreez/style/main.scss index 10d7b13..8307cf2 100644 --- a/sreez/style/main.scss +++ b/sreez/style/main.scss @@ -177,6 +177,11 @@ ol { margin-bottom: 8rem; } +.margin-y-3 { + margin-top: 3rem; + margin-bottom: 3rem; +} + .margin-y-4 { margin-top: 4rem; margin-bottom: 4rem; @@ -236,6 +241,13 @@ ol { margin: 3rem; } +.margin-right-1 { + margin-right: 1rem; +} + +.margin-right-3 { + margin-right: 3rem; +} .max-width-300 { max-width: 300px; @@ -454,14 +466,25 @@ ol.huge-list-markers li{ font-weight: 700; font-size: inherit; cursor: pointer; - background-color: #e72235; - color: white; + background-color: #e72235; + color: white; &:hover { - // background-color: #eee; - // color: black; - box-shadow: 5px 5px 27px 10px rgba(231,34,53,0.21); - //background-color: #66d500; - //background-color: white; + box-shadow: 5px 5px 17px 5px rgba(231,34,53,0.21); + } +} + +.input { + border: solid 2px #eee; + padding: 1rem 1.5rem; + border-radius: 0.75rem; + font-weight: 400; + font-size: inherit; + cursor: pointer; + background-color: white; + color: black; + &:focus { + box-shadow: 5px 5px 17px -5px rgba(0,0,0,0.21); + outline: none; } } @@ -496,6 +519,11 @@ ol.huge-list-markers li{ } } +.form-message { + display: none; + // TODO handle dynamic class +} + @media screen and (min-width: 960px) { .lg-margin-x-auto {