Basic RSS impl

This commit is contained in:
Evie Viau-Chow-Stuart 2023-02-10 07:10:21 -08:00
parent 0ce8ddd96e
commit 78dde7bba3
Signed by: evie
GPG key ID: 928652CDFCEC8099
12 changed files with 277 additions and 4 deletions

3
.idea/.gitignore generated vendored
View file

@ -6,3 +6,6 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Plugins
discord.xml

View file

@ -0,0 +1,30 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="CssUnknownProperty" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myCustomPropertiesEnabled" value="false" />
<option name="myIgnoreVendorSpecificProperties" value="true" />
<option name="myCustomPropertiesList">
<value>
<list size="0" />
</value>
</option>
</inspection_tool>
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="7">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="rss-button" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
</profile>
</component>

132
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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),
]
}
}

View file

@ -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,
}

View file

@ -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
}
}

View file

@ -15,6 +15,10 @@ pub struct Model {
pub site_owner: String,
#[sea_orm(column_type = "Text", nullable)]
pub site_additional_head: Option<String>,
#[sea_orm(column_type = "Text")]
pub site_url: String,
#[sea_orm(column_type = "Text", nullable)]
pub site_description: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View file

@ -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);

View file

@ -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;

57
src/routes/rss_builder.rs Normal file
View file

@ -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<AppState>) -> 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::Model> = 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::<Vec<rss::Item>>(),
)
.build();
(
StatusCode::OK,
[(header::CONTENT_TYPE, "application/rss+xml")],
channel.to_string(),
)
}

View file

@ -11,7 +11,10 @@
{% block description %}Listing of all posts{% endblock %}
{% block content %}
<div class="posts-header">
<h1>Posts</h1>
<a href="/posts/rss.xml" class="rss-button">RSS</a>
</div>
{% if posts.len() == 0 %}
<i>Nothing to see here!</i>