Basic RSS impl
This commit is contained in:
parent
0ce8ddd96e
commit
78dde7bba3
12 changed files with 277 additions and 4 deletions
3
.idea/.gitignore
generated
vendored
3
.idea/.gitignore
generated
vendored
|
@ -6,3 +6,6 @@
|
||||||
# Datasource local storage ignored files
|
# Datasource local storage ignored files
|
||||||
/dataSources/
|
/dataSources/
|
||||||
/dataSources.local.xml
|
/dataSources.local.xml
|
||||||
|
|
||||||
|
# Plugins
|
||||||
|
discord.xml
|
||||||
|
|
30
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
30
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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
132
Cargo.lock
generated
|
@ -247,6 +247,19 @@ dependencies = [
|
||||||
"num-traits",
|
"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]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -685,6 +698,72 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "deunicode"
|
name = "deunicode"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
@ -702,6 +781,15 @@ dependencies = [
|
||||||
"subtle",
|
"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]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
|
@ -734,6 +822,15 @@ version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
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]]
|
[[package]]
|
||||||
name = "entities"
|
name = "entities"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -1101,6 +1198,12 @@ dependencies = [
|
||||||
"cxx-build",
|
"cxx-build",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -1174,6 +1277,7 @@ dependencies = [
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"log",
|
"log",
|
||||||
"migration",
|
"migration",
|
||||||
|
"rss",
|
||||||
"sea-orm 0.11.0-rc.1",
|
"sea-orm 0.11.0-rc.1",
|
||||||
"sea-orm-migration",
|
"sea-orm-migration",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -1301,6 +1405,12 @@ dependencies = [
|
||||||
"windows-sys 0.42.0",
|
"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]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
|
@ -1635,6 +1745,16 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.23"
|
version = "1.0.23"
|
||||||
|
@ -1769,6 +1889,18 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "rust_decimal"
|
name = "rust_decimal"
|
||||||
version = "1.28.0"
|
version = "1.28.0"
|
||||||
|
|
|
@ -25,6 +25,9 @@ askama = { features = ["with-axum", "markdown"], git = "https://github.com/djc/a
|
||||||
askama_axum = { git = "https://github.com/djc/askama.git" }
|
askama_axum = { git = "https://github.com/djc/askama.git" }
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.91"
|
||||||
|
|
||||||
|
# RSS
|
||||||
|
rss = "2.0.2"
|
||||||
|
|
||||||
# Development
|
# Development
|
||||||
dotenvy = "0.15.6"
|
dotenvy = "0.15.6"
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ mod m20230204_201059_create_menu_entries;
|
||||||
mod m20230205_022300_create_settings;
|
mod m20230205_022300_create_settings;
|
||||||
mod m20230207_115847_create_images;
|
mod m20230207_115847_create_images;
|
||||||
mod m20230207_115849_add_featured_image_to_posts;
|
mod m20230207_115849_add_featured_image_to_posts;
|
||||||
|
mod m20230209_172329_add_website_url_and_description_to_settings;
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
@ -24,6 +25,7 @@ impl MigratorTrait for Migrator {
|
||||||
Box::new(m20230205_022300_create_settings::Migration),
|
Box::new(m20230205_022300_create_settings::Migration),
|
||||||
Box::new(m20230207_115847_create_images::Migration),
|
Box::new(m20230207_115847_create_images::Migration),
|
||||||
Box::new(m20230207_115849_add_featured_image_to_posts::Migration),
|
Box::new(m20230207_115849_add_featured_image_to_posts::Migration),
|
||||||
|
Box::new(m20230209_172329_add_website_url_and_description_to_settings::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ impl MigrationTrait for Migration {
|
||||||
.table(Settings::Table)
|
.table(Settings::Table)
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(
|
.col(
|
||||||
ColumnDef::new(Settings::Id)
|
ColumnDef::new(Settings::Id)
|
||||||
.string()
|
.string()
|
||||||
.not_null()
|
.not_null()
|
||||||
.primary_key(),
|
.primary_key(),
|
||||||
|
@ -35,11 +35,11 @@ impl MigrationTrait for Migration {
|
||||||
|
|
||||||
/// Learn more at https://docs.rs/sea-query#iden
|
/// Learn more at https://docs.rs/sea-query#iden
|
||||||
#[derive(Iden)]
|
#[derive(Iden)]
|
||||||
enum Settings {
|
pub enum Settings {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id,
|
||||||
SiteName,
|
SiteName,
|
||||||
SiteHeader,
|
SiteHeader,
|
||||||
SiteOwner,
|
SiteOwner,
|
||||||
SiteAdditionalHead
|
SiteAdditionalHead,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,10 @@ pub struct Model {
|
||||||
pub site_owner: String,
|
pub site_owner: String,
|
||||||
#[sea_orm(column_type = "Text", nullable)]
|
#[sea_orm(column_type = "Text", nullable)]
|
||||||
pub site_additional_head: Option<String>,
|
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)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
@ -54,8 +54,13 @@ async fn main() {
|
||||||
let app_state = AppState { db_conn };
|
let app_state = AppState { db_conn };
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
// Home page
|
||||||
.route("/", get(routes::root))
|
.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))
|
.route("/posts", get(routes::posts::posts))
|
||||||
|
// Resolve page/post
|
||||||
.route("/post/*slug", get(routes::post_resolver::resolver))
|
.route("/post/*slug", get(routes::post_resolver::resolver))
|
||||||
.route("/*slug", get(routes::page_resolver::resolver))
|
.route("/*slug", get(routes::page_resolver::resolver))
|
||||||
.with_state(app_state);
|
.with_state(app_state);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub(crate) mod page_resolver;
|
pub(crate) mod page_resolver;
|
||||||
pub(crate) mod post_resolver;
|
pub(crate) mod post_resolver;
|
||||||
pub(crate) mod posts;
|
pub(crate) mod posts;
|
||||||
|
pub(crate) mod rss_builder;
|
||||||
|
|
||||||
use crate::{block_types::BlockTypes, AppState};
|
use crate::{block_types::BlockTypes, AppState};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
|
57
src/routes/rss_builder.rs
Normal file
57
src/routes/rss_builder.rs
Normal 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(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -11,7 +11,10 @@
|
||||||
{% block description %}Listing of all posts{% endblock %}
|
{% block description %}Listing of all posts{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Posts</h1>
|
<div class="posts-header">
|
||||||
|
<h1>Posts</h1>
|
||||||
|
<a href="/posts/rss.xml" class="rss-button">RSS</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if posts.len() == 0 %}
|
{% if posts.len() == 0 %}
|
||||||
<i>Nothing to see here!</i>
|
<i>Nothing to see here!</i>
|
||||||
|
|
Reference in a new issue