From 0e20b1d53cb467bb16ef689d5616650fc0b8f046 Mon Sep 17 00:00:00 2001 From: cbax Date: Thu, 24 Oct 2024 00:10:35 -0400 Subject: [PATCH] hot reload memcache --- Cargo.lock | 292 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 + deploy.playbook.yml | 2 +- posts/test_post.md | 17 +++ src/main.rs | 358 +++++++++++++++++++++++++++++++++++++++----- 5 files changed, 638 insertions(+), 35 deletions(-) create mode 100644 posts/test_post.md diff --git a/Cargo.lock b/Cargo.lock index 948d159..e31132b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -41,6 +53,18 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + [[package]] name = "askama" version = "0.12.1" @@ -225,6 +249,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -272,13 +305,17 @@ dependencies = [ "askama_axum", "axum", "axum-server-timing", + "gray_matter", "markdown", "notify", + "serde", + "syntect", "tokio", "tower 0.5.1", "tower-http", "tracing", "tracing-subscriber", + "walkdir", ] [[package]] @@ -321,6 +358,30 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "filetime" version = "0.2.25" @@ -424,6 +485,43 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "gray_matter" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ee6a6070bad7c953b0c8be9367e9372181fed69f3e026c4eb5160d8b3c0222" +dependencies = [ + "serde", + "serde_json", + "toml", + "yaml-rust2", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -527,6 +625,16 @@ dependencies = [ "tower-service", ] +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.0", +] + [[package]] name = "inotify" version = "0.9.6" @@ -621,6 +729,12 @@ dependencies = [ "redox_syscall 0.5.7", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "lock_api" version = "0.4.11" @@ -761,6 +875,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.17" @@ -785,6 +905,28 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "onig" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +dependencies = [ + "bitflags 1.3.2", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "overload" version = "0.1.1" @@ -858,6 +1000,25 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +[[package]] +name = "plist" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" +dependencies = [ + "base64", + "indexmap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.89" @@ -867,6 +1028,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.35" @@ -1090,6 +1260,48 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "syntect" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +dependencies = [ + "bincode", + "bitflags 1.3.2", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax 0.8.2", + "serde", + "serde_derive", + "serde_json", + "thiserror", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "thiserror" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.7" @@ -1100,6 +1312,37 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tokio" version = "1.41.0" @@ -1142,6 +1385,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower" version = "0.4.13" @@ -1516,6 +1768,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yaml-rust2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zstd" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index 3f0d954..13ea850 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,14 @@ askama = { version = "0.12.1", features = ["with-axum"] } askama_axum = "0.4.0" axum = "0.7.7" axum-server-timing = "1.0.1" +gray_matter = "0.2.8" markdown = "1.0.0-alpha.21" notify = "6.1.1" +serde = { version = "1.0.213", features = ["derive"] } +syntect = "5.2.0" tokio = { version = "1.41.0", features = ["full"] } tower = "0.5.1" tower-http = { version = "0.6.1", features = ["full"] } tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +walkdir = "2.5.0" diff --git a/deploy.playbook.yml b/deploy.playbook.yml index 9f063f8..1387e1a 100644 --- a/deploy.playbook.yml +++ b/deploy.playbook.yml @@ -1,6 +1,6 @@ - name: cbax dev deploy hosts: - containers: + '{{ lookup("env", "ANSIBLE_HOST") }}': ansible_user: '{{ lookup("env", "ANSIBLE_USER") }}' ansible_password: '{{ lookup("env", "ANSIBLE_PASSWORD") }}' tasks: diff --git a/posts/test_post.md b/posts/test_post.md new file mode 100644 index 0000000..fb3d9d3 --- /dev/null +++ b/posts/test_post.md @@ -0,0 +1,17 @@ +--- +title: "Test markdown post" +author: "cbax" +description: "Only a test post" +img_path: "/res/img/posts/testing.png" +canonical_url: "//cbax.dev/blog/testing" +--- + +# This is a test +## h2 + +testing testing testing + +> quote me! +```bash +cat lol | ripgrep "test" +``` diff --git a/src/main.rs b/src/main.rs index 65f9ee8..408ff03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ -use std::io::prelude::*; +use std::collections::HashMap; +use std::hash::Hash; use std::sync::{Arc, RwLock}; -use std::{collections::HashMap, fs::File}; use askama::Template; use axum::extract::{Path, State}; @@ -10,9 +10,13 @@ use axum::{ routing::get, Router, }; +use gray_matter::engine::YAML; +use gray_matter::Matter; +use notify::{RecursiveMode, Watcher}; use tower::ServiceBuilder; use tower_http::services::ServeDir; use tower_http::trace::TraceLayer; +use tracing::Instrument; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[derive(Clone)] @@ -77,7 +81,8 @@ struct BlogPostTemplate { #[derive(Clone)] struct AppState { - posts: Arc>>, + posts: HashMap, + styles: HashMap, } struct HtmlTemplate(T); @@ -98,14 +103,52 @@ where } } -async fn blog(State(state): State) -> Result { - let mut styles_file = File::open("static/css/styles.css").expect("Failed to open stylesheet."); - let mut styles = String::new(); - styles_file - .read_to_string(&mut styles) - .expect("Failed to read stylesheet."); +async fn blog(State(state): State>>) -> Result { + let mut styles: String = state + .read() + .unwrap() + .styles + .get("core.css") + .unwrap() + .clone(); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("nord.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("colors.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("components.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("font.css") + .unwrap() + .as_str(), + ); let mut posts: Vec<(String, Meta)> = vec![]; - for (slug, post) in state.posts.read().unwrap().clone().iter() { + for (slug, post) in state.read().unwrap().posts.clone().iter() { posts.push((slug.clone(), post.meta.clone())); } let meta = Meta { @@ -128,15 +171,53 @@ async fn blog(State(state): State) -> Result, + State(state): State>>, Path(post_slug): Path, ) -> Result { - let mut styles_file = File::open("static/css/styles.css").expect("Failed to open stylesheet."); - let mut styles = String::new(); - styles_file - .read_to_string(&mut styles) - .expect("Failed to read stylesheet."); - if let Some(post) = state.posts.read().unwrap().clone().get(&post_slug) { + let mut styles: String = state + .read() + .unwrap() + .styles + .get("core.css") + .unwrap() + .clone(); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("nord.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("colors.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("components.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("font.css") + .unwrap() + .as_str(), + ); + if let Some(post) = state.read().unwrap().posts.clone().get(&post_slug) { let meta = post.meta.clone(); let template = BlogPostTemplate { _parent: BaseTemplate { @@ -152,22 +233,102 @@ async fn blog_post( return Err(StatusCode::NOT_FOUND); } -async fn homelab() -> Result { - let mut styles_file = File::open("static/css/styles.css").expect("Failed to open stylesheet."); - let mut styles = String::new(); - styles_file - .read_to_string(&mut styles) - .expect("Failed to read stylesheet."); +async fn homelab( + State(state): State>>, +) -> Result { + let mut styles: String = state + .read() + .unwrap() + .styles + .get("core.css") + .unwrap() + .clone(); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("nord.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("colors.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("components.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("font.css") + .unwrap() + .as_str(), + ); let template = HomelabTemplate { styles }; return Ok(HtmlTemplate(template)); } -async fn index() -> Result { - let mut styles_file = File::open("static/css/styles.css").expect("Failed to open stylesheet."); - let mut styles = String::new(); - styles_file - .read_to_string(&mut styles) - .expect("Failed to read stylesheet."); +async fn index( + State(state): State>>, +) -> Result { + let mut styles: String = state + .read() + .unwrap() + .styles + .get("core.css") + .unwrap() + .clone(); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("nord.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("colors.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("components.css") + .unwrap() + .as_str(), + ); + styles.push_str( + state + .read() + .unwrap() + .styles + .get("font.css") + .unwrap() + .as_str(), + ); let template = IndexTemplate { styles }; return Ok(HtmlTemplate(template)); } @@ -185,10 +346,11 @@ async fn main() { ) .with(tracing_subscriber::fmt::layer()) .init(); - let state = AppState { - posts: Arc::new(RwLock::new(HashMap::new())), - }; - state.posts.write().unwrap().insert( + let state = Arc::new(RwLock::new(AppState { + posts: HashMap::new(), + styles: HashMap::new(), + })); + state.write().unwrap().posts.insert( "test_slug".into(), Post { meta: Meta { @@ -202,12 +364,140 @@ async fn main() { body: "this is a test post!".into(), }, ); + for entry in walkdir::WalkDir::new("./posts") { + let entry = entry.unwrap(); + if entry.file_type().is_file() { + let path = entry.into_path(); + let file: String = std::fs::read_to_string(&path).unwrap(); + let matter = Matter::::new(); + let matter_result = matter.parse(file.as_str()); + let meta = Meta { + title: matter_result.data.as_ref().unwrap()["title"] + .as_string() + .unwrap_or_default(), + author: matter_result.data.as_ref().unwrap()["author"] + .as_string() + .unwrap_or_default(), + description: matter_result.data.as_ref().unwrap()["description"] + .as_string() + .unwrap_or_default(), + img_path: matter_result.data.as_ref().unwrap()["img_path"] + .as_string() + .unwrap_or_default(), + canonical_url: matter_result.data.as_ref().unwrap()["canonical_url"] + .as_string() + .unwrap_or_default(), + }; + state.write().unwrap().posts.insert( + path.file_name().unwrap().to_string_lossy().to_string(), + Post { + meta, + cover_img_path: String::new(), + body: markdown::to_html_with_options( + matter_result.content.as_str(), + &markdown::Options::gfm(), + ) + .unwrap(), + }, + ); + } + } + for entry in walkdir::WalkDir::new("./static/css") { + let entry = entry.unwrap(); + if entry.file_type().is_file() { + let path = entry.into_path(); + let file: String = std::fs::read_to_string(&path).unwrap(); + state.write().unwrap().styles.insert( + path.file_name().unwrap().to_string_lossy().to_string(), + file, + ); + } + } + let notify_state1 = state.clone(); + let notify_state2 = state.clone(); + let mut css_watcher = + notify::recommended_watcher(move |res: Result| match res { + Ok(event) => { + println!("event triggered: {:?}", event); + if event.kind.is_create() || event.kind.is_modify() { + for path in event.paths.iter() { + println!("refreshing css: {}", path.to_string_lossy()); + let file: String = std::fs::read_to_string(&path).unwrap(); + notify_state1.clone().write().unwrap().styles.insert( + path.file_name().unwrap().to_string_lossy().to_string(), + file, + ); + } + } + } + Err(e) => eprintln!("watch error: {:?}", e), + }) + .unwrap(); + let mut post_watcher = + notify::recommended_watcher(move |res: Result| match res { + Ok(mut event) => { + if event.kind.is_create() || event.kind.is_modify() { + event.paths.dedup(); + for path in event.paths.iter().filter(|p| { + if p.extension().is_some() { + p.extension().unwrap() == "md" + } else { + false + } + }) { + println!("refreshing post: {}", path.to_string_lossy().to_string()); + std::thread::sleep(std::time::Duration::from_millis(1)); + let file: String = + std::fs::read_to_string(path.to_string_lossy().to_string()).unwrap(); + let matter = Matter::::new(); + let matter_result = matter.parse(file.as_str()); + let meta = Meta { + title: matter_result.data.as_ref().unwrap()["title"] + .as_string() + .unwrap_or_default(), + author: matter_result.data.as_ref().unwrap()["author"] + .as_string() + .unwrap_or_default(), + description: matter_result.data.as_ref().unwrap()["description"] + .as_string() + .unwrap_or_default(), + img_path: matter_result.data.as_ref().unwrap()["img_path"] + .as_string() + .unwrap_or_default(), + canonical_url: matter_result.data.as_ref().unwrap()["canonical_url"] + .as_string() + .unwrap_or_default(), + }; + notify_state2.clone().write().unwrap().posts.insert( + path.file_name().unwrap().to_string_lossy().to_string(), + Post { + meta, + cover_img_path: String::new(), + body: markdown::to_html_with_options( + matter_result.content.as_str(), + &markdown::Options::gfm(), + ) + .unwrap(), + }, + ); + } + } + } + Err(e) => eprintln!("watch error: {:?}", e), + }) + .unwrap(); + css_watcher + .watch(std::path::Path::new("static/css"), RecursiveMode::Recursive) + .unwrap(); + post_watcher + .watch(std::path::Path::new("posts"), RecursiveMode::Recursive) + .unwrap(); let app = Router::new() .route("/", get(index)) .route("/homelab", get(homelab)) .route("/blog", get(blog)) .route("/blog/:post_slug", get(blog_post)) - .with_state(state) + .with_state(state.clone()) .fallback_service(serve_dir()) .layer(axum_server_timing::ServerTimingLayer::new("Axum")) .layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()));