feat: added rss to posts
58d87247
2 file(s) · +82 −0
| 3 | 3 | COOKIE_SECURE=false |
|
| 4 | 4 | HOST=127.0.0.1 |
|
| 5 | 5 | PORT=3000 |
|
| 6 | + | SITE_URL=http://localhost:3000 |
| 745 | 745 | Redirect::to("/admin/settings?success=true").into_response() |
|
| 746 | 746 | } |
|
| 747 | 747 | ||
| 748 | + | // --- RSS feed handler --- |
|
| 749 | + | ||
| 750 | + | fn xml_escape(s: &str) -> String { |
|
| 751 | + | s.replace('&', "&") |
|
| 752 | + | .replace('<', "<") |
|
| 753 | + | .replace('>', ">") |
|
| 754 | + | .replace('"', """) |
|
| 755 | + | .replace('\'', "'") |
|
| 756 | + | } |
|
| 757 | + | ||
| 758 | + | async fn rss_feed(State(state): State<Arc<AppState>>) -> Response { |
|
| 759 | + | let blog_title = get_blog_title(&state.db); |
|
| 760 | + | let blog_description = db::get_setting(&state.db, "blog_description") |
|
| 761 | + | .ok() |
|
| 762 | + | .flatten() |
|
| 763 | + | .unwrap_or_default(); |
|
| 764 | + | let site_url = std::env::var("SITE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string()); |
|
| 765 | + | let site_url = site_url.trim_end_matches('/'); |
|
| 766 | + | ||
| 767 | + | let posts = match db::get_published_posts(&state.db) { |
|
| 768 | + | Ok(posts) => posts, |
|
| 769 | + | Err(e) => { |
|
| 770 | + | tracing::error!("Failed to get posts for RSS: {}", e); |
|
| 771 | + | return (StatusCode::INTERNAL_SERVER_ERROR, "Server error").into_response(); |
|
| 772 | + | } |
|
| 773 | + | }; |
|
| 774 | + | ||
| 775 | + | let mut items = String::new(); |
|
| 776 | + | for post in &posts { |
|
| 777 | + | let link = format!("{}/posts/{}", site_url, xml_escape(&post.slug)); |
|
| 778 | + | let title = xml_escape(&post.title); |
|
| 779 | + | let description = match &post.meta_description { |
|
| 780 | + | Some(d) if !d.is_empty() => xml_escape(d), |
|
| 781 | + | _ => { |
|
| 782 | + | let plain: String = post.content.chars().take(200).collect(); |
|
| 783 | + | xml_escape(&plain) |
|
| 784 | + | } |
|
| 785 | + | }; |
|
| 786 | + | let pub_date = post.published_date.as_deref().unwrap_or(&post.created_at); |
|
| 787 | + | let guid = format!("{}/posts/{}", site_url, xml_escape(&post.slug)); |
|
| 788 | + | ||
| 789 | + | items.push_str(&format!( |
|
| 790 | + | " <item>\n <title>{title}</title>\n <link>{link}</link>\n <guid>{guid}</guid>\n <description>{description}</description>\n <pubDate>{pub_date}</pubDate>\n </item>\n" |
|
| 791 | + | )); |
|
| 792 | + | } |
|
| 793 | + | ||
| 794 | + | let last_build = posts |
|
| 795 | + | .first() |
|
| 796 | + | .and_then(|p| p.published_date.as_deref()) |
|
| 797 | + | .unwrap_or(""); |
|
| 798 | + | ||
| 799 | + | let xml = format!( |
|
| 800 | + | r#"<?xml version="1.0" encoding="UTF-8"?> |
|
| 801 | + | <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> |
|
| 802 | + | <channel> |
|
| 803 | + | <title>{title}</title> |
|
| 804 | + | <link>{site_url}</link> |
|
| 805 | + | <description>{desc}</description> |
|
| 806 | + | <lastBuildDate>{last_build}</lastBuildDate> |
|
| 807 | + | <atom:link href="{site_url}/feed.xml" rel="self" type="application/rss+xml"/> |
|
| 808 | + | {items} </channel> |
|
| 809 | + | </rss>"#, |
|
| 810 | + | title = xml_escape(&blog_title), |
|
| 811 | + | site_url = site_url, |
|
| 812 | + | desc = xml_escape(&blog_description), |
|
| 813 | + | last_build = last_build, |
|
| 814 | + | items = items, |
|
| 815 | + | ); |
|
| 816 | + | ||
| 817 | + | ( |
|
| 818 | + | StatusCode::OK, |
|
| 819 | + | [( |
|
| 820 | + | axum::http::header::CONTENT_TYPE, |
|
| 821 | + | HeaderValue::from_static("application/rss+xml; charset=utf-8"), |
|
| 822 | + | )], |
|
| 823 | + | xml, |
|
| 824 | + | ) |
|
| 825 | + | .into_response() |
|
| 826 | + | } |
|
| 827 | + | ||
| 748 | 828 | // --- Date helper --- |
|
| 749 | 829 | ||
| 750 | 830 | fn days_to_ymd(mut days: i64) -> (i64, i64, i64) { |
|
| 792 | 872 | .route("/", get(public_index)) |
|
| 793 | 873 | .route("/posts/{slug}", get(public_post)) |
|
| 794 | 874 | .route("/pages/{slug}", get(public_page)) |
|
| 875 | + | .route("/feed.xml", get(rss_feed)) |
|
| 795 | 876 | // Admin auth |
|
| 796 | 877 | .route("/admin/login", get(get_login).post(post_login)) |
|
| 797 | 878 | .route("/admin/logout", get(get_logout)) |
|