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
+
{% if posts.len() == 0 %}
Nothing to see here!