aboutsummaryrefslogtreecommitdiff
path: root/src/modules
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-14 21:28:21 -0800
committerFuwn <[email protected]>2026-02-14 21:28:21 -0800
commit7493510908dfc9c348c40a90e0cde96f0434b3af (patch)
tree84f792e8b1f69dbe70f0a15012fd5f784ee9791c /src/modules
parentfix(xml): Implement full XML 1.0 escaping and RSS-safe CDATA handling (diff)
downloadlocus-7493510908dfc9c348c40a90e0cde96f0434b3af.tar.xz
locus-7493510908dfc9c348c40a90e0cde96f0434b3af.zip
feat(blog): Treat Notion Hidden posts as private across site and RSS
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/blog/config.rs1
-rw-r--r--src/modules/blog/module.rs152
2 files changed, 144 insertions, 9 deletions
diff --git a/src/modules/blog/config.rs b/src/modules/blog/config.rs
index a061b7f..04c4e23 100644
--- a/src/modules/blog/config.rs
+++ b/src/modules/blog/config.rs
@@ -15,4 +15,5 @@ pub struct BlogPost {
pub last_modified: Option<String>,
pub content: String,
pub blog_id: String,
+ pub hidden: bool,
}
diff --git a/src/modules/blog/module.rs b/src/modules/blog/module.rs
index 51fa06b..af20488 100644
--- a/src/modules/blog/module.rs
+++ b/src/modules/blog/module.rs
@@ -72,10 +72,7 @@ pub fn module(router: &mut windmark::router::Router) {
"This blog could not be found.",
);
};
- let category_posts: Vec<_> = posts
- .iter()
- .filter(|post| post.blog_id == category.notion_id)
- .collect();
+ let category_posts = visible_posts_for_blog(&posts, &category.notion_id);
let post_listing = category_posts
.iter()
.map(|post| {
@@ -131,9 +128,8 @@ pub fn module(router: &mut windmark::router::Router) {
"This blog could not be found.",
);
};
- let matched_post = posts.iter().find(|post| {
- post.blog_id == category.notion_id && slugify(&post.title) == post_slug
- });
+ let matched_post =
+ find_visible_post_by_slug(&posts, &category.notion_id, &post_slug);
let Some(post) = matched_post else {
return windmark::response::Response::not_found(
"This post could not be found.",
@@ -184,6 +180,26 @@ fn construct_header(post: &BlogPost) -> String {
fn slugify(text: &str) -> String { text.replace(' ', "_").to_lowercase() }
+fn visible_posts_for_blog<'a>(
+ posts: &'a [BlogPost],
+ blog_identifier: &str,
+) -> Vec<&'a BlogPost> {
+ posts
+ .iter()
+ .filter(|post| post.blog_id == blog_identifier && !post.hidden)
+ .collect()
+}
+
+fn find_visible_post_by_slug<'a>(
+ posts: &'a [BlogPost],
+ blog_identifier: &str,
+ post_slug: &str,
+) -> Option<&'a BlogPost> {
+ visible_posts_for_blog(posts, blog_identifier)
+ .into_iter()
+ .find(|post| slugify(&post.title) == post_slug)
+}
+
fn build_rss_feed(blog_slug: &str) -> windmark::response::Response {
let categories = BLOG_CATEGORIES.read().unwrap();
let posts = BLOG_POSTS.read().unwrap();
@@ -207,7 +223,7 @@ fn build_rss_feed(blog_slug: &str) -> windmark::response::Response {
xml.add_field("lastBuildDate", &chrono::Local::now().to_rfc2822());
xml.add_link(&format!("{ROOT_GEMINI_URL}/blog/{blog_slug}.xml"));
- for post in posts.iter().filter(|post| post.blog_id == category.notion_id) {
+ for post in visible_posts_for_blog(&posts, &category.notion_id) {
let post_slug = slugify(&post.title);
xml.add_item(&{
@@ -314,6 +330,7 @@ fn fetch_from_notion() {
},
content: page_content,
blog_id: blog_identifier,
+ hidden: notion::extract_checkbox(&page.properties, "Hidden"),
}
})
.collect();
@@ -323,7 +340,7 @@ fn fetch_from_notion() {
global_posts.clear();
- for post in &fetched_posts {
+ for post in fetched_posts.iter().filter(|post| !post.hidden) {
let blog_title = categories
.iter()
.find(|category| category.notion_id == post.blog_id)
@@ -361,3 +378,120 @@ pub fn refresh_loop() {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ build_rss_feed, find_visible_post_by_slug, slugify, visible_posts_for_blog,
+ BLOG_CATEGORIES, BLOG_POSTS,
+ };
+ use crate::modules::blog::config::{BlogCategory, BlogPost};
+
+ #[test]
+ fn hidden_posts_are_excluded_from_rss_feed() {
+ let original_categories = BLOG_CATEGORIES.read().unwrap().clone();
+ let original_posts = BLOG_POSTS.read().unwrap().clone();
+ let category = BlogCategory {
+ title: "Test Blog".to_string(),
+ description: None,
+ priority: 1,
+ notion_id: "blog-id".to_string(),
+ };
+ let visible_post = BlogPost {
+ title: "Visible".to_string(),
+ description: None,
+ author: None,
+ created: None,
+ last_modified: None,
+ content: "visible content".to_string(),
+ blog_id: "blog-id".to_string(),
+ hidden: false,
+ };
+ let hidden_post = BlogPost {
+ title: "Hidden".to_string(),
+ description: None,
+ author: None,
+ created: None,
+ last_modified: None,
+ content: "hidden content".to_string(),
+ blog_id: "blog-id".to_string(),
+ hidden: true,
+ };
+
+ *BLOG_CATEGORIES.write().unwrap() = vec![category];
+ *BLOG_POSTS.write().unwrap() = vec![visible_post, hidden_post];
+
+ let feed = build_rss_feed(&slugify("Test Blog"));
+ let feed_xml = feed.content;
+
+ assert!(feed_xml.contains("<title>Visible</title>"));
+ assert!(!feed_xml.contains("<title>Hidden</title>"));
+
+ *BLOG_CATEGORIES.write().unwrap() = original_categories;
+ *BLOG_POSTS.write().unwrap() = original_posts;
+ }
+
+ #[test]
+ fn visible_posts_for_blog_excludes_hidden_posts() {
+ let visible_post = BlogPost {
+ title: "Visible".to_string(),
+ description: None,
+ author: None,
+ created: None,
+ last_modified: None,
+ content: "visible content".to_string(),
+ blog_id: "blog-id".to_string(),
+ hidden: false,
+ };
+ let hidden_post = BlogPost {
+ title: "Hidden".to_string(),
+ description: None,
+ author: None,
+ created: None,
+ last_modified: None,
+ content: "hidden content".to_string(),
+ blog_id: "blog-id".to_string(),
+ hidden: true,
+ };
+
+ let posts = vec![visible_post, hidden_post];
+ let visible_titles = visible_posts_for_blog(&posts, "blog-id")
+ .into_iter()
+ .map(|post| post.title.clone())
+ .collect::<Vec<_>>();
+
+ assert_eq!(visible_titles, vec!["Visible".to_string()]);
+ }
+
+ #[test]
+ fn find_visible_post_by_slug_skips_hidden_posts() {
+ let visible_post = BlogPost {
+ title: "Visible Post".to_string(),
+ description: None,
+ author: None,
+ created: None,
+ last_modified: None,
+ content: "visible content".to_string(),
+ blog_id: "blog-id".to_string(),
+ hidden: false,
+ };
+ let hidden_post = BlogPost {
+ title: "Hidden Post".to_string(),
+ description: None,
+ author: None,
+ created: None,
+ last_modified: None,
+ content: "hidden content".to_string(),
+ blog_id: "blog-id".to_string(),
+ hidden: true,
+ };
+ let posts = vec![visible_post, hidden_post];
+
+ assert!(
+ find_visible_post_by_slug(&posts, "blog-id", "visible_post").is_some()
+ );
+ assert!(
+ find_visible_post_by_slug(&posts, "blog-id", "hidden_post").is_none()
+ );
+ }
+}