1
0
Fork 0
This commit is contained in:
Adrian Hedqvist 2023-03-29 21:48:27 +02:00
parent f9cdffcd24
commit ba465f4bde
12 changed files with 157 additions and 15 deletions

View file

@ -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"]
+++

View file

@ -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<AppState>) -> Router<Arc<AppState>> {
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<B>(request: Request<B>, next: Next<B>) -> 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)

53
src/handlers/tags.rs Normal file
View file

@ -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<Arc<AppState>> {
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<Arc<AppState>>) -> Result<Html<String>, 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<String>, State(state): State<Arc<AppState>>) -> Result<Html<String>, 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))
}

View file

@ -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<String, Post>,
tags: HashMap<String, Tag>,
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);

33
src/tag.rs Normal file
View file

@ -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<String>,
}
pub fn get_tags<'a>(posts: impl IntoIterator<Item=&'a Post>) -> HashMap<String,Tag> {
let mut tags: HashMap<String,Tag> = 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
}

View file

@ -2,4 +2,13 @@ body {
max-width: 800px;
margin: auto;
padding: 8px;
}
}
.tags {
list-style: none;
}
.tags > li {
display: inline-block;
margin-right: 1em;
}

View file

@ -14,4 +14,4 @@
{% include "partials/footer.html" -%}
</footer>
</body>
</html>
</html>

View file

@ -21,4 +21,4 @@
<li>⬜ image processing?? (resizing, conversion)</li>
<li>⬜ opentelemetry?</li>
</ul>
{% endblock main %}
{% endblock main %}

View file

@ -9,5 +9,12 @@
<small>Posted on <time datetime="{{ page.date }}">{{ page.date | date(format="%Y-%m-%d %H:%M") }}</time></small>
{% endif -%}
{{ page.content | safe -}}
{% if page.tags -%}
<small>
<ul class="tags">
{% for tag in page.tags %}<li><a href="/tags/{{tag}}/">#{{ tag }}</a></li>{% endfor %}
</ul>
</small>
{% endif -%}
</article>
{% endblock main -%}
{% endblock main -%}

View file

@ -12,4 +12,4 @@
</a></li>
{% endfor -%}
</ul>
{% endblock main -%}
{% endblock main -%}

19
templates/tag.html Normal file
View file

@ -0,0 +1,19 @@
{% extends "base.html" %}
{% block main -%}
<article>
{% if page.title -%}
<h1>{{ page.title }}</h1>
{% endif -%}
<ul>
{% for post in posts -%}
<li><a href="{{post.absolute_path | safe}}">{% if post.date -%}
<time datetime="{{ post.date }}">{{ post.date | date(format="%Y-%m-%d") }}</time> - {{ post.title -}}
{% else -%}
{{ post.title -}}
{% endif -%}
</a></li>
{% endfor -%}
</ul>
</article>
{% endblock main -%}

View file

@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block main -%}
<h1>Tags</h1>
<ul>
{% for tag in tags -%}
<li><a href="{{tag.absolute_path | safe}}">#{{ tag.slug }}</a></li>
{% endfor -%}
</ul>
{% endblock main -%}