From 78dde7bba33b023276ad6d89afdf9c1453f69e72 Mon Sep 17 00:00:00 2001 From: Evie Viau Date: Fri, 10 Feb 2023 07:10:21 -0800 Subject: [PATCH] Basic RSS impl --- .idea/.gitignore | 3 + .idea/inspectionProfiles/Project_Default.xml | 30 ++++ Cargo.lock | 132 ++++++++++++++++++ Cargo.toml | 3 + migration/src/lib.rs | 2 + .../src/m20230205_022300_create_settings.rs | 6 +- ...website_url_and_description_to_settings.rs | 33 +++++ src/entities/settings.rs | 4 + src/main.rs | 5 + src/routes/mod.rs | 1 + src/routes/rss_builder.rs | 57 ++++++++ templates/posts_listing.html | 5 +- 12 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 migration/src/m20230209_172329_add_website_url_and_description_to_settings.rs create mode 100644 src/routes/rss_builder.rs diff --git a/.idea/.gitignore b/.idea/.gitignore index 13566b8..873f27e 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -6,3 +6,6 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml + +# Plugins +discord.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..12e9f0a --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,30 @@ + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 4ddbdea..231710b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -247,6 +247,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atom_syndication" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91a85f2ee28cbd1ecf91288460f6dc74661fd99b4e9a559836a667ccf63aa38c" +dependencies = [ + "chrono", + "derive_builder", + "diligent-date-parser", + "never", + "quick-xml", +] + [[package]] name = "atomic-waker" version = "1.1.0" @@ -685,6 +698,72 @@ dependencies = [ "syn", ] +[[package]] +name = "darling" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0808e1bd8671fb44a113a14e13497557533369847788fa2ae912b6ebfce9fa8" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "001d80444f28e193f30c2f293455da62dcf9a6b29918a4253152ae2b1de592cb" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b36230598a2d5de7ec1c6f51f72d8a99a9208daff41de2084d06e3fd3ea56685" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn", +] + [[package]] name = "deunicode" version = "0.4.3" @@ -702,6 +781,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "diligent-date-parser" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6cf7fe294274a222363f84bcb63cdea762979a0443b4cf1f4f8fd17c86b1182" +dependencies = [ + "chrono", +] + [[package]] name = "dirs" version = "4.0.0" @@ -734,6 +822,15 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + [[package]] name = "entities" version = "1.0.1" @@ -1101,6 +1198,12 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.3.0" @@ -1174,6 +1277,7 @@ dependencies = [ "dotenvy", "log", "migration", + "rss", "sea-orm 0.11.0-rc.1", "sea-orm-migration", "serde", @@ -1301,6 +1405,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "never" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" + [[package]] name = "nom" version = "7.1.3" @@ -1635,6 +1745,16 @@ dependencies = [ "syn", ] +[[package]] +name = "quick-xml" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc053f057dd768a56f62cd7e434c42c831d296968997e9ac1f76ea7c2d14c41" +dependencies = [ + "encoding_rs", + "memchr", +] + [[package]] name = "quote" version = "1.0.23" @@ -1769,6 +1889,18 @@ dependencies = [ "syn", ] +[[package]] +name = "rss" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14816ec8b4e58b34a36adba66dcadd3e1e221dcb0fb2fd83e7c5129ea1a72458" +dependencies = [ + "atom_syndication", + "derive_builder", + "never", + "quick-xml", +] + [[package]] name = "rust_decimal" version = "1.28.0" diff --git a/Cargo.toml b/Cargo.toml index d95954a..d727bdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,9 @@ askama = { features = ["with-axum", "markdown"], git = "https://github.com/djc/a askama_axum = { git = "https://github.com/djc/askama.git" } serde_json = "1.0.91" +# RSS +rss = "2.0.2" + # Development dotenvy = "0.15.6" diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 06f996a..fc347b9 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -9,6 +9,7 @@ mod m20230204_201059_create_menu_entries; mod m20230205_022300_create_settings; mod m20230207_115847_create_images; mod m20230207_115849_add_featured_image_to_posts; +mod m20230209_172329_add_website_url_and_description_to_settings; pub struct Migrator; #[async_trait::async_trait] @@ -24,6 +25,7 @@ impl MigratorTrait for Migrator { Box::new(m20230205_022300_create_settings::Migration), Box::new(m20230207_115847_create_images::Migration), Box::new(m20230207_115849_add_featured_image_to_posts::Migration), + Box::new(m20230209_172329_add_website_url_and_description_to_settings::Migration), ] } } diff --git a/migration/src/m20230205_022300_create_settings.rs b/migration/src/m20230205_022300_create_settings.rs index 109113f..bdb4e8b 100644 --- a/migration/src/m20230205_022300_create_settings.rs +++ b/migration/src/m20230205_022300_create_settings.rs @@ -12,7 +12,7 @@ impl MigrationTrait for Migration { .table(Settings::Table) .if_not_exists() .col( - ColumnDef::new(Settings::Id) + ColumnDef::new(Settings::Id) .string() .not_null() .primary_key(), @@ -35,11 +35,11 @@ impl MigrationTrait for Migration { /// Learn more at https://docs.rs/sea-query#iden #[derive(Iden)] -enum Settings { +pub enum Settings { Table, Id, SiteName, SiteHeader, SiteOwner, - SiteAdditionalHead + SiteAdditionalHead, } diff --git a/migration/src/m20230209_172329_add_website_url_and_description_to_settings.rs b/migration/src/m20230209_172329_add_website_url_and_description_to_settings.rs new file mode 100644 index 0000000..a841218 --- /dev/null +++ b/migration/src/m20230209_172329_add_website_url_and_description_to_settings.rs @@ -0,0 +1,33 @@ +use sea_orm_migration::prelude::*; + +use crate::m20230205_022300_create_settings::Settings; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Settings::Table) + .add_column(ColumnDef::new(Alias::new("site_url")).text().not_null()) + .add_column(ColumnDef::new(Alias::new("site_description")).text()) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Settings::Table) + .drop_column(Alias::new("site_url")) + .drop_column(Alias::new("site_description")) + .to_owned(), + ) + .await + } +} diff --git a/src/entities/settings.rs b/src/entities/settings.rs index 0bbb455..8c474c2 100644 --- a/src/entities/settings.rs +++ b/src/entities/settings.rs @@ -15,6 +15,10 @@ pub struct Model { pub site_owner: String, #[sea_orm(column_type = "Text", nullable)] pub site_additional_head: Option, + #[sea_orm(column_type = "Text")] + pub site_url: String, + #[sea_orm(column_type = "Text", nullable)] + pub site_description: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/src/main.rs b/src/main.rs index f9451f2..dafaa4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,8 +54,13 @@ async fn main() { let app_state = AppState { db_conn }; let app = Router::new() + // Home page .route("/", get(routes::root)) + // Generated blog RSS feed + .route("/posts/rss.xml", get(routes::rss_builder::build_rss)) + // Special "posts" page .route("/posts", get(routes::posts::posts)) + // Resolve page/post .route("/post/*slug", get(routes::post_resolver::resolver)) .route("/*slug", get(routes::page_resolver::resolver)) .with_state(app_state); diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 58c8284..5c9d3e3 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,6 +1,7 @@ pub(crate) mod page_resolver; pub(crate) mod post_resolver; pub(crate) mod posts; +pub(crate) mod rss_builder; use crate::{block_types::BlockTypes, AppState}; use askama::Template; diff --git a/src/routes/rss_builder.rs b/src/routes/rss_builder.rs new file mode 100644 index 0000000..5c8d234 --- /dev/null +++ b/src/routes/rss_builder.rs @@ -0,0 +1,57 @@ +use crate::entities::{prelude::*, *}; +use crate::AppState; +use sea_orm::*; + +use axum::extract::State; +use axum::http::{header, StatusCode}; +use axum::response::IntoResponse; +use rss::{ChannelBuilder, ItemBuilder}; + +pub(crate) async fn build_rss(state: State) -> impl IntoResponse { + let settings: settings::Model = match Settings::find() + .filter(settings::Column::Id.eq("current")) + .one(&state.db_conn) + .await + .expect("Failed to get site settings!") + { + Some(settings) => settings, + None => panic!("Missing \"current\" settings in database!"), + }; + + let posts: Vec = Post::find() + .filter(post::Column::Draft.eq(false)) + .order_by_desc(post::Column::Published) + .all(&state.db_conn) + .await + .expect("Failed to load posts!"); + + let channel = ChannelBuilder::default() + .generator("Kyanite".to_owned()) + .title(settings.site_name) + .link(&settings.site_url) + .description(settings.site_description.unwrap_or(settings.site_header)) + .items( + posts + .into_iter() + .map(|f| { + ItemBuilder::default() + .guid(rss::Guid { + value: f.id, + permalink: false, + }) + .title(f.title) + .description(f.summary) + .pub_date(f.published.format("%a, %d %b %Y %H:%M:%S GMT").to_string()) + .link(format!("{}/post/{}", settings.site_url, f.slug)) + .build() + }) + .collect::>(), + ) + .build(); + + ( + StatusCode::OK, + [(header::CONTENT_TYPE, "application/rss+xml")], + channel.to_string(), + ) +} diff --git a/templates/posts_listing.html b/templates/posts_listing.html index 20e2f85..b6ded27 100644 --- a/templates/posts_listing.html +++ b/templates/posts_listing.html @@ -11,7 +11,10 @@ {% block description %}Listing of all posts{% endblock %} {% block content %} -

Posts

+
+

Posts

+ RSS +
{% if posts.len() == 0 %} Nothing to see here!