about summary refs log tree commit diff
path: root/src/frontend/mod.rs
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2025-03-13 11:06:23 +0300
committerVika <vika@fireburn.ru>2025-03-13 11:06:23 +0300
commit4ca3c83d447fbf86a4f21d841a107cb28a64658e (patch)
treee196c4a83d602bdfeb84f5f5bb1973009e17cf22 /src/frontend/mod.rs
parentab6614c720064a5630100c1ba600942dcab2632f (diff)
downloadkittybox-4ca3c83d447fbf86a4f21d841a107cb28a64658e.tar.zst
WIP: RSS feed generator feature/rss
Some people are old-fashioned, and RSS feeds can be consumed even by
laypeople unfamiliar with microformats2. This does violate DRY at
first glance, but since the feeds are dynamically generated it's not
repetition but rather format conversion, and as such does not violate
DRY per se.
Diffstat (limited to 'src/frontend/mod.rs')
-rw-r--r--src/frontend/mod.rs53
1 files changed, 39 insertions, 14 deletions
diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs
index 9ba1a69..478a48e 100644
--- a/src/frontend/mod.rs
+++ b/src/frontend/mod.rs
@@ -7,11 +7,14 @@ use axum::{
 use axum_extra::{extract::Host, headers::HeaderMapExt};
 use futures_util::FutureExt;
 use serde::Deserialize;
-use std::convert::TryInto;
+use std::{convert::TryInto, str::FromStr};
 use tracing::{debug, error};
 //pub mod login;
 pub mod onboarding;
 
+mod rss_conversion;
+use rss_conversion::feed_to_rss;
+
 use kittybox_frontend_renderer::{
     Entry, Feed, VCard,
     ErrorPage, Template, MainPage,
@@ -22,6 +25,8 @@ pub use kittybox_frontend_renderer::assets::statics;
 #[derive(Debug, Deserialize)]
 pub struct QueryParams {
     after: Option<String>,
+    #[serde(default)]
+    rss: bool,
 }
 
 #[derive(Debug)]
@@ -297,7 +302,8 @@ pub async fn homepage<D: Storage>(
                         cursor: cursor.as_deref(),
                         webring: crate::database::settings::Setting::into_inner(webring)
                     }
-                    .to_string(),
+                        .to_string(),
+                    rss_link: Some(&(feed_path + "?rss=true"))
                 }
                 .to_string(),
             ).into_response()
@@ -333,6 +339,7 @@ pub async fn homepage<D: Storage>(
                             msg: Some(err.msg().to_string()),
                         }
                         .to_string(),
+                        rss_link: None,
                     }
                     .to_string(),
                 ).into_response()
@@ -357,7 +364,7 @@ pub async fn catchall<D: Storage>(
         .unwrap();
 
     match get_post_from_database(&db, path.as_str(), query.after, user).await {
-        Ok((post, cursor)) => {
+        Ok((mut post, cursor)) => {
             let (blogname, channels) = tokio::join!(
                 db.get_setting::<crate::database::settings::SiteName>(&host)
                 .map(Result::unwrap_or_default),
@@ -365,22 +372,18 @@ pub async fn catchall<D: Storage>(
                 db.get_channels(&host).map(|i| i.unwrap_or_default())
             );
             let mut headers = axum::http::HeaderMap::new();
-            headers.insert(
-                axum::http::header::CONTENT_TYPE,
-                axum::http::HeaderValue::from_static(r#"text/html; charset="utf-8""#),
-            );
+            headers.typed_insert(axum_extra::headers::ContentType::html());
             headers.insert(
                 axum::http::header::X_CONTENT_TYPE_OPTIONS,
                 axum::http::HeaderValue::from_static("nosniff")
             );
             if user.is_some() {
-                headers.insert(
-                    axum::http::header::CACHE_CONTROL,
-                    axum::http::HeaderValue::from_static("private")
-                );
+                headers.typed_insert(axum_extra::headers::CacheControl::new().with_private());
             }
 
-            if post["type"][0].as_str() == Some("h-entry") {
+            let post_type = post.pointer("/type/0").and_then(|i| i.as_str());
+
+            if post_type == Some("h-entry") {
                 let last_modified = post["properties"]["updated"]
                     .as_array()
                     .and_then(|v| v.last())
@@ -400,6 +403,26 @@ pub async fn catchall<D: Storage>(
                 }
             }
 
+            if query.rss {
+                match post_type {
+                    Some("h-feed") => {
+                        headers.typed_insert(axum_extra::headers::ContentType::from_str("application/rss+xml").unwrap());
+                        return (
+                            StatusCode::OK,
+                            headers,
+                            feed_to_rss(post).to_string()
+                        ).into_response()
+                    },
+                    _ => {
+                        headers.typed_insert(axum_extra::headers::ContentType::text_utf8());
+                        return (StatusCode::NOT_FOUND, headers, "RSS feeds cannot be generated for this document.").into_response()
+                    }
+                }
+            }
+            let rss_link: Option<String> = match post_type {
+                Some("h-feed") => Some(post["properties"]["uid"][0].as_str().unwrap().to_owned() + "?rss=true"),
+                _ => None
+            };
             // Render the homepage
             (
                 StatusCode::OK,
@@ -409,7 +432,7 @@ pub async fn catchall<D: Storage>(
                     blog_name: blogname.as_ref(),
                     feeds: channels,
                     user: session.as_deref(),
-                    content: match post.pointer("/type/0").and_then(|i| i.as_str()) {
+                    content: match post_type {
                         Some("h-entry") => Entry { post: &post, from_feed: false, }.to_string(),
                         Some("h-feed") => Feed { feed: &post, cursor: cursor.as_deref() }.to_string(),
                         Some("h-card") => VCard { card: &post }.to_string(),
@@ -417,6 +440,7 @@ pub async fn catchall<D: Storage>(
                             unimplemented!("Template for MF2-JSON type {:?}", unknown)
                         }
                     },
+                    rss_link: rss_link.as_deref()
                 }
                 .to_string(),
             ).into_response()
@@ -443,7 +467,8 @@ pub async fn catchall<D: Storage>(
                         code: err.code(),
                         msg: Some(err.msg().to_owned()),
                     }
-                    .to_string(),
+                        .to_string(),
+                    rss_link: None,
                 }
                 .to_string(),
             ).into_response()