From 2d548e83bafb90ff4a05aef79d1d5d0a01af2a6c Mon Sep 17 00:00:00 2001 From: Michal Vanko Date: Tue, 30 Jan 2024 22:19:36 +0100 Subject: [PATCH] RSS feed --- axum_server/Cargo.toml | 1 + axum_server/src/feed.rs | 50 +++++++++++++++++++++++++++++++++++++++ axum_server/src/main.rs | 1 + axum_server/src/router.rs | 6 ++++- 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 axum_server/src/feed.rs diff --git a/axum_server/Cargo.toml b/axum_server/Cargo.toml index 6280dab..8e85b38 100644 --- a/axum_server/Cargo.toml +++ b/axum_server/Cargo.toml @@ -12,6 +12,7 @@ axum = "0.7.3" chrono = { version = "0.4.31", features = ["serde"] } gray_matter = "0.2.6" markdown = "1.0.0-alpha.16" +rss = "2.0.7" serde = "1.0.195" serde_json = "1.0.111" tokio = { version = "1.35.1", features = ["full"] } diff --git a/axum_server/src/feed.rs b/axum_server/src/feed.rs new file mode 100644 index 0000000..9a81575 --- /dev/null +++ b/axum_server/src/feed.rs @@ -0,0 +1,50 @@ +use axum::http::{header, StatusCode}; +use axum::response::IntoResponse; +use chrono::Utc; +use rss::{ChannelBuilder, GuidBuilder, Item, ItemBuilder}; + +use crate::{pages::post::PostMetadata, post_list::get_post_list}; + +pub async fn render_rss_feed() -> Result { + let mut post_list = get_post_list::().await.unwrap_or(vec![]); + post_list.sort_by_key(|post| post.metadata.date); + post_list.reverse(); + + let last_build_date = Utc::now().to_rfc2822(); + let publish_date = post_list.last().map_or_else( + || last_build_date.clone(), + |post| post.metadata.date.to_rfc2822(), + ); + + let post_items = post_list + .into_iter() + .map(|post| { + ItemBuilder::default() + .title(Some(post.metadata.title)) + .link(Some(format!("https://michalvanko.dev/blog/{}", post.slug))) + // TODO Description should be just a preview + .description(None) + .guid(Some( + GuidBuilder::default() + .value(format!("https://michalvanko.dev/blog/{}", post.slug)) + .build(), + )) + .pub_date(Some(post.metadata.date.to_rfc2822())) + .build() + }) + .collect::>(); + + let feed_builder = ChannelBuilder::default() + .title("michalvanko.dev latest posts".to_string()) + .link("https://michalvanko.dev".to_string()) + .description("Latest posts published on michalvanko.dev blog site".to_string()) + .language(Some("en".to_string())) + .webmaster(Some("michalvankosk@gmail.com".to_string())) + .pub_date(Some(publish_date)) + .last_build_date(Some(last_build_date)) + .items(post_items) + .build(); + + let response = feed_builder.to_string(); + return Ok(([(header::CONTENT_TYPE, "application/xml")], response)); +} diff --git a/axum_server/src/main.rs b/axum_server/src/main.rs index e1c8d5d..b30e175 100644 --- a/axum_server/src/main.rs +++ b/axum_server/src/main.rs @@ -2,6 +2,7 @@ use axum; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod components; +mod feed; mod pages; mod post_list; mod post_parser; diff --git a/axum_server/src/router.rs b/axum_server/src/router.rs index cf87917..77c7f68 100644 --- a/axum_server/src/router.rs +++ b/axum_server/src/router.rs @@ -1,4 +1,7 @@ -use crate::pages::{index::render_index, post::render_post, post_list::render_post_list}; +use crate::{ + feed::render_rss_feed, + pages::{index::render_index, post::render_post, post_list::render_post_list}, +}; use axum::{extract::MatchedPath, http::Request, routing::get, Router}; use tower_http::trace::TraceLayer; use tracing::info_span; @@ -9,6 +12,7 @@ pub fn get_router() -> Router { .route("/blog", get(render_post_list)) .route("/blog/tags/:tag", get(render_post_list)) .route("/blog/:post_id", get(render_post)) + .route("/feed.xml", get(render_rss_feed)) .layer( TraceLayer::new_for_http().make_span_with(|request: &Request<_>| { // Log the matched route's path (with placeholders not filled in).