2023-03-29 21:48:27 +02:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
2023-04-02 15:26:20 +02:00
|
|
|
use axum::{
|
|
|
|
extract::{Path, State},
|
2023-04-03 23:33:25 +02:00
|
|
|
response::{Html, Redirect, IntoResponse},
|
2023-04-02 15:26:20 +02:00
|
|
|
routing::get,
|
|
|
|
Router,
|
|
|
|
};
|
2023-04-03 23:33:25 +02:00
|
|
|
use hyper::{header::CONTENT_TYPE, StatusCode};
|
2023-03-29 21:48:27 +02:00
|
|
|
use serde_derive::Serialize;
|
|
|
|
use tracing::instrument;
|
|
|
|
|
2023-04-02 15:26:20 +02:00
|
|
|
use crate::{post::Post, AppState, WebsiteError};
|
2023-03-29 21:48:27 +02:00
|
|
|
|
|
|
|
pub fn router() -> Router<Arc<AppState>> {
|
|
|
|
Router::new()
|
2023-04-02 15:26:20 +02:00
|
|
|
.route("/tags", get(|| async { Redirect::permanent("/") }))
|
|
|
|
.route("/tags/", get(index))
|
|
|
|
.route("/tags/:tag", get(redirect))
|
|
|
|
.route("/tags/:tag/", get(view))
|
2023-04-03 23:33:25 +02:00
|
|
|
.route("/tags/:tag/atom.xml", get(feed))
|
2023-03-29 21:48:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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))]
|
2023-04-02 15:26:20 +02:00
|
|
|
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();
|
2023-03-29 21:48:27 +02:00
|
|
|
|
|
|
|
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))
|
2023-04-02 15:26:20 +02:00
|
|
|
}
|
|
|
|
|
2023-04-03 23:33:25 +02:00
|
|
|
pub async fn feed(
|
|
|
|
Path(slug): Path<String>,
|
|
|
|
State(state): State<Arc<AppState>>,
|
|
|
|
) -> Result<impl IntoResponse, WebsiteError> {
|
|
|
|
let tag = state.tags.get(&slug).ok_or(WebsiteError::NotFound)?;
|
|
|
|
|
|
|
|
let mut posts: Vec<&Post> = state
|
|
|
|
.posts
|
|
|
|
.values()
|
|
|
|
.filter(|p| p.is_published() && p.tags.contains(&slug))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
posts.sort_by_key(|p| &p.date);
|
|
|
|
posts.reverse();
|
|
|
|
posts.truncate(10);
|
|
|
|
|
|
|
|
Ok((StatusCode::OK, [(CONTENT_TYPE, "application/atom+xml")], crate::feed::render_atom_tag_feed(tag, &posts, &state.tera)?))
|
|
|
|
}
|
|
|
|
|
2023-04-02 15:26:20 +02:00
|
|
|
#[instrument(skip(state))]
|
|
|
|
pub async fn redirect(
|
|
|
|
Path(slug): Path<String>,
|
|
|
|
State(state): State<Arc<AppState>>,
|
|
|
|
) -> Result<Redirect, WebsiteError> {
|
|
|
|
if state.tags.contains_key(&slug) {
|
|
|
|
Ok(Redirect::permanent(&format!("/tags/{slug}/")))
|
2023-04-02 15:27:06 +02:00
|
|
|
} else {
|
2023-04-02 15:26:20 +02:00
|
|
|
Err(WebsiteError::NotFound)
|
|
|
|
}
|
|
|
|
}
|