diff --git a/Cargo.lock b/Cargo.lock index cb1f757..abcc312 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" dependencies = [ "cc", "cxxbridge-flags", @@ -405,9 +405,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" dependencies = [ "cc", "codespan-reporting", @@ -420,15 +420,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" [[package]] name = "cxxbridge-macro" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", @@ -486,6 +486,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "eyre" version = "0.6.8" @@ -699,6 +720,18 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.9" @@ -844,6 +877,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys", +] + [[package]] name = "iri-string" version = "0.7.0" @@ -905,6 +949,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -1008,7 +1058,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -1213,6 +1263,42 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "procfs" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de8dacb0873f77e6aefc6d71e044761fcc68060290f5b1089fcdf84626bb69" +dependencies = [ + "bitflags", + "byteorder", + "hex", + "lazy_static", + "rustix", +] + +[[package]] +name = "prometheus" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "libc", + "memchr", + "parking_lot", + "procfs", + "protobuf", + "thiserror", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + [[package]] name = "pulldown-cmark" version = "0.9.2" @@ -1296,6 +1382,20 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b" +[[package]] +name = "rustix" +version = "0.36.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" version = "1.0.12" @@ -1993,6 +2093,7 @@ dependencies = [ "glob", "hyper", "lazy_static", + "prometheus", "pulldown-cmark", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index 2c9346b..caf4133 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ glob = "0.3.0" #grass = { version = "0.12.3", features = ["random"] } # not really needed yet hyper = { version = "0.14.19", features = ["full"] } lazy_static = "1.4.0" +prometheus = { version = "0.13.3", features = ["process"] } pulldown-cmark = "0.9.2" regex = "1.7.2" serde = "1.0.144" diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 095dd29..a5f2d57 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,21 +1,32 @@ -use hyper::StatusCode; -use std::sync::Arc; -use tracing::{instrument, log::*}; - use axum::{ response::{Html, IntoResponse, Response}, Extension, }; +use hyper::StatusCode; +use lazy_static::lazy_static; +use prometheus::{opts, IntCounterVec}; +use std::sync::Arc; +use tracing::{instrument, log::*}; use crate::{State, WebsiteError}; +pub mod posts; + +lazy_static! { + pub static ref HIT_COUNTER: IntCounterVec = + prometheus::register_int_counter_vec!(opts!("hits", "page hits"), &["page"]).unwrap(); +} + #[instrument(skip(state))] -pub async fn index(Extension(state): Extension>) -> std::result::Result>, WebsiteError> { +pub async fn index( + Extension(state): Extension>, +) -> std::result::Result>, WebsiteError> { let ctx = tera::Context::new(); let res = state.tera.render("index.html", &ctx).map_err(|e| { error!("Failed rendering index: {}", e); WebsiteError::NotFound })?; + HIT_COUNTER.with_label_values(&["/"]); Ok(Html(res.into())) } diff --git a/src/handlers/posts.rs b/src/handlers/posts.rs new file mode 100644 index 0000000..0efaf78 --- /dev/null +++ b/src/handlers/posts.rs @@ -0,0 +1,66 @@ +use std::sync::Arc; + +use axum::{extract::Path, response::Html, routing::get, Extension, Router}; +use serde_derive::Serialize; +use tracing::{instrument, log::*}; + +use crate::{ + post::{render_post, Post}, + State, WebsiteError, +}; + +use super::HIT_COUNTER; + +pub fn router() -> Router { + Router::new() + .route("/", get(index)) + .route("/:slug/", get(view)) + .fallback_service(tower_http::services::ServeDir::new("./posts")) +} + +#[instrument(skip(state))] +pub async fn view( + Path(slug): Path, + Extension(state): Extension>, +) -> Result, WebsiteError> { + debug!("viewing post: {slug}"); + let post = state.posts.get(&slug).ok_or(WebsiteError::NotFound)?; + + let res = render_post(&state.tera, post).await?; + + HIT_COUNTER.with_label_values(&[&format!("/posts/{}/", slug)]); + Ok(Html(res)) +} + +#[derive(Serialize)] +struct IndexContext<'a> { + title: &'a str, + posts: Vec<&'a Post>, +} + +#[instrument(skip(state))] +pub async fn index(Extension(state): Extension>) -> Result, WebsiteError> { + let mut posts = state.posts.values().collect::>(); + + posts.sort_by_key(|p| &p.date); + posts.reverse(); + + let ctx = IndexContext { + title: "Posts", + posts, + }; + + let res = match state + .tera + .render("posts_index.html", &tera::Context::from_serialize(ctx)?) + { + Ok(r) => r, + Err(e) => { + error!("failed to render posts index: {}", e); + return Err(e.into()); + } + }; + + HIT_COUNTER.with_label_values(&["/posts/"]); + Ok(Html(res)) +} diff --git a/src/main.rs b/src/main.rs index 426ef26..32e1e02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,10 +46,7 @@ pub async fn init_app() -> Result { let app = Router::new() .route("/", get(handlers::index)) - .nest( - "/posts", - post::router(), - ) + .nest("/posts", handlers::posts::router()) .nest_service("/static", tower_http::services::ServeDir::new("./static")) .route("/.healthcheck", get(healthcheck)) .layer(middleware); diff --git a/src/post.rs b/src/post.rs index 7b9dbf4..f4bf996 100644 --- a/src/post.rs +++ b/src/post.rs @@ -1,6 +1,5 @@ -use std::{collections::HashMap, path::Path, sync::Arc}; +use std::{collections::HashMap, path::Path}; -use axum::{response::Html, routing::get, Extension, Router, extract}; use chrono::{DateTime, FixedOffset}; use glob::glob; @@ -12,7 +11,7 @@ use tokio::fs; use tracing::{instrument, log::*}; -use crate::{State, WebsiteError}; +use crate::WebsiteError; #[derive(Deserialize, Debug, Default)] pub struct TomlFrontMatter { @@ -41,7 +40,9 @@ impl Post { slug, content, title: fm.title, - date: fm.date.map(|d| DateTime::parse_from_rfc3339(&d.to_string()).expect("bad toml datetime")), + date: fm + .date + .map(|d| DateTime::parse_from_rfc3339(&d.to_string()).expect("bad toml datetime")), aliases: fm.aliases.unwrap_or_default(), tags: fm.tags.unwrap_or_default(), } @@ -96,7 +97,11 @@ pub async fn load_post(slug: &str) -> color_eyre::eyre::Result { content_html }); - Ok(Post::new(slug.to_string(), content.unwrap_or_default(), tomlfm)) + Ok(Post::new( + slug.to_string(), + content.unwrap_or_default(), + tomlfm, + )) } #[instrument] @@ -121,62 +126,13 @@ fn parse_frontmatter( } #[instrument(skip(tera, post))] -async fn render_post(tera: &tera::Tera, post: &Post) -> Result { +pub async fn render_post(tera: &tera::Tera, post: &Post) -> Result { let mut ctx = tera::Context::new(); ctx.insert("page", &post); tera.render("post.html", &ctx).map_err(|e| e.into()) } -pub fn router() -> Router -{ - Router::new() - .route("/", get(index)) - .route("/:slug/", get(view)) - .fallback_service(tower_http::services::ServeDir::new("./posts")) -} - -#[instrument(skip(state))] -pub async fn view( - extract::Path(slug): extract::Path, - Extension(state): Extension>, -) -> Result, WebsiteError> { - debug!("viewing post: {slug}"); - let post = state - .posts - .get(&slug) - .ok_or(WebsiteError::NotFound)?; - - let res = render_post(&state.tera, post).await?; - - Ok(Html(res)) -} - -#[instrument(skip(state))] -pub async fn index( - Extension(state): Extension>, -) -> Result, WebsiteError> { - let mut ctx = tera::Context::new(); - - let mut posts = state.posts.values().collect::>(); - - posts.sort_by_key(|p| &p.date); - posts.reverse(); - - ctx.insert("page.title", "Posts"); - ctx.insert("posts", &posts); - - let res = match state.tera.render("posts_index.html", &ctx) { - Ok(r) => r, - Err(e) => { - error!("failed to render posts index: {}", e); - return Err(e.into()); - } - }; - - Ok(Html(res)) -} - #[cfg(test)] mod tests { use tera::Tera;