diff --git a/posts/dungeon/index.md b/posts/dungeon/index.md index 1454952..989993e 100644 --- a/posts/dungeon/index.md +++ b/posts/dungeon/index.md @@ -3,8 +3,6 @@ date = 2018-01-10T17:50:00+01:00 draft = false title = "Yet another (traditional) roguelike written in c++" aliases = ["/blog/dungeon/", "/blog/dungeon.html"] - -[taxonomies] tags = ["cpp", "roguelike"] +++ diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 3423ec3..ddeddbd 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -15,6 +15,7 @@ use tracing::{instrument, log::*}; use crate::{AppState, WebsiteError}; pub mod posts; +pub mod tags; lazy_static! { pub static ref HIT_COUNTER: IntCounterVec = prometheus::register_int_counter_vec!( @@ -31,6 +32,7 @@ pub fn routes(state: &Arc) -> Router> { Router::new() .route("/", get(index)) .nest("/posts", posts::router()) + .nest("/tags", tags::router()) .merge(posts::alias_router(state.posts.values())) .route("/healthcheck", get(healthcheck)) .route("/metrics", get(metrics)) @@ -67,17 +69,22 @@ async fn metrics() -> impl IntoResponse { .unwrap() } -pub async fn not_found() -> Response { - (StatusCode::NOT_FOUND, ()).into_response() +pub async fn not_found() -> impl IntoResponse { + (StatusCode::NOT_FOUND, ()) } pub async fn metrics_middleware(request: Request, next: Next) -> Response { let path = request.uri().path().to_string(); let method = request.method().to_string(); + let response = next.run(request).await; - HIT_COUNTER - .with_label_values(&[&path, &method, response.status().as_str()]) - .inc(); + + if !response.status().is_client_error() { + HIT_COUNTER + .with_label_values(&[&path, &method, response.status().as_str()]) + .inc(); + } + response } @@ -113,9 +120,11 @@ mod tests { async fn setup_routes() { // Load the actual posts, just to make this test fail if // aliases overlap with themselves or other routes + let posts = crate::post::load_all().await.unwrap(); let state = Arc::new(AppState { tera: tera::Tera::new("templates/**/*").unwrap(), - posts: crate::post::load_all().await.unwrap(), + tags: crate::tag::get_tags(posts.values()), + posts, }); super::routes(&state) diff --git a/src/handlers/tags.rs b/src/handlers/tags.rs new file mode 100644 index 0000000..d2ef15f --- /dev/null +++ b/src/handlers/tags.rs @@ -0,0 +1,53 @@ +use std::sync::Arc; + +use axum::{Router, response::Html, routing::get, extract::{State, Path}}; +use serde_derive::Serialize; +use tracing::instrument; + +use crate::{AppState, WebsiteError, post::Post}; + +pub fn router() -> Router> { + Router::new() + .route("/", get(index)) + .route("/:tag/", get(view)) +} + +#[derive(Serialize, Debug)] +struct PageContext<'a> { + title: &'a str, +} + +#[instrument(skip(state))] +pub async fn index(State(state): State>) -> Result, WebsiteError> { + + let tags: Vec<_> = state.tags.values().collect(); + let ctx = PageContext { title: "Tags" }; + + let mut c = tera::Context::new(); + c.insert("page", &ctx); + c.insert("tags", &tags); + + let res = state.tera.render("tags_index.html", &c)?; + + Ok(Html(res)) +} + +#[instrument(skip(state))] +pub async fn view(Path(tag): Path, State(state): State>) -> Result, WebsiteError> { + let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published() && p.tags.contains(&tag)).collect(); + + posts.sort_by_key(|p| &p.date); + posts.reverse(); + + let title = format!("Posts tagged with #{tag}"); + + let ctx = PageContext { title: &title }; + + let mut c = tera::Context::new(); + c.insert("page", &ctx); + c.insert("posts", &posts); + + let res = state.tera.render("tag.html", &c)?; + + Ok(Html(res)) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index ef3943c..869a6e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,15 +4,18 @@ use color_eyre::eyre::{Error, Result}; use post::Post; +use tag::Tag; use tera::Tera; -use tower_http::{compression::CompressionLayer, trace::TraceLayer}; +use tower_http::{compression::CompressionLayer, trace::TraceLayer, cors::CorsLayer}; use tracing::log::*; mod handlers; mod post; +mod tag; pub struct AppState { posts: HashMap, + tags: HashMap, tera: Tera, } @@ -24,9 +27,11 @@ async fn main() -> Result<()> { let tera = Tera::new("templates/**/*")?; let posts = post::load_all().await?; - let state = Arc::new(AppState { tera, posts }); + let tags = tag::get_tags(posts.values()); + let state = Arc::new(AppState { tera, posts, tags }); let app = handlers::routes(&state) + .layer(CorsLayer::permissive()) .layer(TraceLayer::new_for_http()) .layer(CompressionLayer::new()) .with_state(state); diff --git a/src/tag.rs b/src/tag.rs new file mode 100644 index 0000000..a5c711e --- /dev/null +++ b/src/tag.rs @@ -0,0 +1,33 @@ +use std::collections::HashMap; + +use serde_derive::Serialize; + +use crate::post::Post; + +#[derive(Serialize, Debug)] +pub struct Tag { + pub slug: String, + pub absolute_path: String, + pub posts: Vec, +} + +pub fn get_tags<'a>(posts: impl IntoIterator) -> HashMap { + let mut tags: HashMap = HashMap::new(); + + for post in posts.into_iter().filter(|p| p.is_published()) { + for key in &post.tags { + if let Some(tag) = tags.get_mut(key) { + tag.posts.push(post.slug.clone()); + } + else { + tags.insert(key.clone(), Tag { + slug: key.clone(), + absolute_path: format!("/tags/{key}/"), + posts: vec![post.slug.clone()] + }); + } + } + } + + tags +} diff --git a/static/site.css b/static/site.css index 73aad82..a6f3766 100644 --- a/static/site.css +++ b/static/site.css @@ -2,4 +2,13 @@ body { max-width: 800px; margin: auto; padding: 8px; -} \ No newline at end of file +} + +.tags { + list-style: none; +} + +.tags > li { + display: inline-block; + margin-right: 1em; +} diff --git a/templates/base.html b/templates/base.html index fd00488..a7b6766 100644 --- a/templates/base.html +++ b/templates/base.html @@ -14,4 +14,4 @@ {% include "partials/footer.html" -%} - \ No newline at end of file + diff --git a/templates/index.html b/templates/index.html index 127a21d..d461637 100644 --- a/templates/index.html +++ b/templates/index.html @@ -21,4 +21,4 @@
  • ⬜ image processing?? (resizing, conversion)
  • ⬜ opentelemetry?
  • -{% endblock main %} \ No newline at end of file +{% endblock main %} diff --git a/templates/post.html b/templates/post.html index 80c3a41..eaa5430 100644 --- a/templates/post.html +++ b/templates/post.html @@ -9,5 +9,12 @@ Posted on {% endif -%} {{ page.content | safe -}} + {% if page.tags -%} + +
      + {% for tag in page.tags %}
    • #{{ tag }}
    • {% endfor %} +
    +
    + {% endif -%} -{% endblock main -%} \ No newline at end of file +{% endblock main -%} diff --git a/templates/posts_index.html b/templates/posts_index.html index 110793d..4645358 100644 --- a/templates/posts_index.html +++ b/templates/posts_index.html @@ -12,4 +12,4 @@ {% endfor -%} -{% endblock main -%} \ No newline at end of file +{% endblock main -%} diff --git a/templates/tag.html b/templates/tag.html new file mode 100644 index 0000000..e957e83 --- /dev/null +++ b/templates/tag.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block main -%} + +{% endblock main -%} diff --git a/templates/tags_index.html b/templates/tags_index.html new file mode 100644 index 0000000..5e59ac3 --- /dev/null +++ b/templates/tags_index.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{% block main -%} +

    Tags

    + +{% endblock main -%}