implement atom syndication

This commit is contained in:
cel 🌸 2023-06-22 21:37:10 +01:00
parent bc5ac494c6
commit ab52991784
Signed by: cel
GPG Key ID: 48E29AF13B5F1349
14 changed files with 268 additions and 8 deletions

117
Cargo.lock generated
View File

@ -94,6 +94,19 @@ dependencies = [
"syn 2.0.18", "syn 2.0.18",
] ]
[[package]]
name = "atom_syndication"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca96cb38e3d8236f1573a84bbc55e130bd1ae07df770e36d0cf221ea7a50e36c"
dependencies = [
"chrono",
"derive_builder",
"diligent-date-parser",
"never",
"quick-xml",
]
[[package]] [[package]]
name = "atomic" name = "atomic"
version = "0.5.1" version = "0.5.1"
@ -437,6 +450,72 @@ dependencies = [
"syn 1.0.107", "syn 1.0.107",
] ]
[[package]]
name = "darling"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 1.0.107",
]
[[package]]
name = "darling_macro"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
dependencies = [
"darling_core",
"quote",
"syn 1.0.107",
]
[[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 1.0.107",
]
[[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 1.0.107",
]
[[package]] [[package]]
name = "derive_deref" name = "derive_deref"
version = "1.1.1" version = "1.1.1"
@ -498,6 +577,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 = "doc-comment" name = "doc-comment"
version = "0.3.3" version = "0.3.3"
@ -972,6 +1060,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"
@ -1377,6 +1471,12 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "never"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
[[package]] [[package]]
name = "normpath" name = "normpath"
version = "0.3.2" version = "0.3.2"
@ -1749,6 +1849,16 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "quick-xml"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1"
dependencies = [
"encoding_rs",
"memchr",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.28" version = "1.0.28"
@ -2179,6 +2289,7 @@ name = "site"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"atom_syndication",
"chrono", "chrono",
"chrono-humanize", "chrono-humanize",
"listenbrainz", "listenbrainz",
@ -2274,6 +2385,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.4.1" version = "2.4.1"

View File

@ -23,3 +23,4 @@ markdown = "1.0.0-alpha.10"
async-trait = "0.1.68" async-trait = "0.1.68"
toml = "0.7.4" toml = "0.7.4"
tokio-stream = { version = "0.1.14", features = ["fs"] } tokio-stream = { version = "0.1.14", features = ["fs"] }
atom_syndication = "0.12.1"

View File

@ -1,8 +1,17 @@
# TODO:
[x] make sure posts are organised in date order [x] make sure posts are organised in date order
[x] atom feed
[x] tags [x] tags
[ ] multiple tag UI [ ] multiple tag UI
[ ] more badges
[ ] site translations
[ ] comments [ ] comments
[ ] clean up spaghetti [ ] clean up spaghetti
[ ] brush font PLZ
[ ] deploy database yum
[ ] visitor counter
[ ] guestbook
badges to steal: badges to steal:
[x] keith [x] keith

View File

@ -1,3 +1,5 @@
use std::string::FromUtf8Error;
use rocket::{http::Status, Responder}; use rocket::{http::Status, Responder};
#[derive(Responder, Debug)] #[derive(Responder, Debug)]
@ -8,6 +10,8 @@ pub enum BlossomError {
Chrono(Status, #[response(ignore)] chrono::ParseError), Chrono(Status, #[response(ignore)] chrono::ParseError),
Io(Status, #[response(ignore)] std::io::Error), Io(Status, #[response(ignore)] std::io::Error),
Deserialization(Status, #[response(ignore)] toml::de::Error), Deserialization(Status, #[response(ignore)] toml::de::Error),
Syndicator(Status, #[response(ignore)] atom_syndication::Error),
Utf8Conversion(Status, #[response(ignore)] FromUtf8Error),
NotFound(Status), NotFound(Status),
NoMetadata(Status), NoMetadata(Status),
Unimplemented(Status), Unimplemented(Status),
@ -48,3 +52,15 @@ impl From<toml::de::Error> for BlossomError {
BlossomError::Deserialization(Status::new(500), e) BlossomError::Deserialization(Status::new(500), e)
} }
} }
impl From<atom_syndication::Error> for BlossomError {
fn from(e: atom_syndication::Error) -> Self {
BlossomError::Syndicator(Status::new(500), e)
}
}
impl From<FromUtf8Error> for BlossomError {
fn from(e: FromUtf8Error) -> Self {
BlossomError::Utf8Conversion(Status::new(500), e)
}
}

View File

@ -7,8 +7,9 @@ mod skweets;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet; use std::collections::HashSet;
use atom_syndication::Feed;
use rocket::fs::{relative, FileServer}; use rocket::fs::{relative, FileServer};
use rocket::http::Status; use rocket::http::{ContentType, Status};
use rocket::{Request, State}; use rocket::{Request, State};
use rocket_dyn_templates::{context, Template}; use rocket_dyn_templates::{context, Template};
@ -87,6 +88,17 @@ async fn blog(filter: Vec<String>) -> Result<Template> {
)) ))
} }
#[get("/feed")]
async fn feed() -> Result<(Status, (ContentType, String))> {
let posts = posts::get_blogposts().await?;
let feed = posts::syndication::atom(posts).await;
let feed: String = String::from_utf8(feed.write_to(Vec::new())?)?;
Ok((
Status::new(200),
(ContentType::new("application", "atom+xml"), feed),
))
}
#[get("/contact")] #[get("/contact")]
async fn contact() -> Template { async fn contact() -> Template {
Template::render("contact", context! {}) Template::render("contact", context! {})
@ -130,7 +142,7 @@ async fn main() -> std::result::Result<(), rocket::Error> {
.attach(Template::custom(|engines| { .attach(Template::custom(|engines| {
engines.tera.autoescape_on(vec![]); engines.tera.autoescape_on(vec![]);
})) }))
.mount("/", routes![home, contact, blog, blogpost, plants]) .mount("/", routes![home, contact, blog, blogpost, feed, plants])
.register("/", catchers![catcher]) .register("/", catchers![catcher])
.mount("/", FileServer::from(relative!("static"))) .mount("/", FileServer::from(relative!("static")))
.launch() .launch()

View File

@ -1,5 +1,6 @@
mod article; mod article;
mod note; mod note;
pub mod syndication;
use std::collections::HashSet; use std::collections::HashSet;

96
src/posts/syndication.rs Normal file
View File

@ -0,0 +1,96 @@
use atom_syndication::{Category, Content, Entry, Feed, Generator, Link, Person, Text, TextType};
use super::{Article, Post};
pub async fn atom(posts: Vec<Post<Article>>) -> Feed {
let me = Person {
name: "cel".into(),
email: Some("cel@blos.sm".into()),
uri: Some("https://blos.sm".into()),
};
let mut authors = Vec::new();
authors.push(me);
let link = Link {
href: "https://blos.sm/feed".into(),
rel: "self".into(),
hreflang: Some("en".into()),
mime_type: Some("application/atom+xml".into()),
title: Some("atom feed".into()),
length: None,
};
let mut links = Vec::new();
links.push(link);
let mut feed = Feed {
title: Text {
value: "cel's site".into(),
base: None,
lang: Some("en".into()),
r#type: TextType::Text,
},
id: "https://blos.sm".into(),
updated: posts[0].created_at.into(),
authors: authors.clone(),
categories: Vec::new(),
contributors: authors.clone(),
generator: Some(Generator {
value: "blos.sm".into(),
uri: Some("https://bunny.garden/cel/blos.sm".into()),
version: None,
}),
icon: Some("/icon.png".into()),
links: links.clone(),
logo: Some("/logo.png".into()),
rights: None,
subtitle: None,
entries: Vec::new(),
base: Some("https://blos.sm".into()),
lang: Some("en".into()),
..Default::default()
};
for mut post in posts {
post.render().await.unwrap_or_default();
let mut id = String::from("https://blos.sm/blog/");
id.push_str(&post.data.name);
let categories = post
.tags
.into_iter()
.map(|tag| Category {
term: tag.clone(),
scheme: None,
label: Some(tag.clone()),
})
.collect();
let entry = Entry {
title: Text {
value: post.subject.unwrap_or_default(),
base: None,
lang: Some("en".into()),
r#type: TextType::Text,
},
id: id.clone(),
updated: if let Some(updated_at) = post.updated_at {
updated_at.into()
} else {
post.created_at.into()
},
authors: authors.clone(),
categories,
contributors: authors.clone(),
links: links.clone(),
published: Some(post.created_at.into()),
rights: None,
source: None,
summary: None,
content: Some(Content {
base: None,
lang: Some("en".into()),
value: post.render,
src: Some(id),
content_type: Some("html".to_string()),
}),
..Default::default()
};
feed.entries.push(entry);
}
feed
}

BIN
static/atombadge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
static/atomfeed.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

View File

@ -166,6 +166,14 @@ footer {
background-color: #0000; background-color: #0000;
} }
.small-badge,
.small-badge * {
padding: 0;
border: 0;
margin: 0;
font-size: 0;
}
/* homepage css */ /* homepage css */
#title { #title {

View File

@ -40,7 +40,7 @@
<li><a class="{% block nav_blog %}{% endblock %}" style="font-family: Sligoil" href="/blog">girlblog</a></li> <li><a class="{% block nav_blog %}{% endblock %}" style="font-family: Sligoil" href="/blog">girlblog</a></li>
<li><a class="{% block nav_projects %}{% endblock %}" style="font-family: 'DeGerm LoCase';" href="/projects">projetos</a></li> <li><a class="{% block nav_projects %}{% endblock %}" style="font-family: 'DeGerm LoCase';" href="/projects">projetos</a></li>
<li><a class="{% block nav_sound %}{% endblock %}" style="font-family: 'kirieji'" href="/sound">音</a></li> <li><a class="{% block nav_sound %}{% endblock %}" style="font-family: 'kirieji'" href="/sound">音</a></li>
<li><a class="{% block nav_listens %}{% endblock %}" style="font-family: 'Almendra Display'; font-weight: 900;" href="https://listenbrainz.org/celblossom">écoute</a></li> <li><a class="{% block nav_listens %}{% endblock %}" style="font-family: 'Almendra Display'; font-weight: 900;" href="https://listenbrainz.org/user/celblossom">écoute</a></li>
<li><a href="https://bimbo.video/a/cel" style="font-family: 'Mon Hugo In'">video</a></li> <li><a href="https://bimbo.video/a/cel" style="font-family: 'Mon Hugo In'">video</a></li>
<li><a href="https://weirdstar.stream" style="font-family: id_kana018" >🔴ライブ</a></li> <li><a href="https://weirdstar.stream" style="font-family: id_kana018" >🔴ライブ</a></li>
<li><a class="{% block nav_pix %}{% endblock %}" style="font-family: Minecraftia" href="/pix">pix</a></li> <li><a class="{% block nav_pix %}{% endblock %}" style="font-family: Minecraftia" href="/pix">pix</a></li>
@ -70,6 +70,7 @@
</main> </main>
<footer class="panel"> <footer class="panel">
<a class="badge" href="https://blos.sm"><img src="https://blos.sm/badges/cel.png"></a>
<a class="badge" href="https://skinnyver.se"><img src="https://skinnyver.se/instance/skinnyversebadge.png"></a> <a class="badge" href="https://skinnyver.se"><img src="https://skinnyver.se/instance/skinnyversebadge.png"></a>
<img class="badge" src="/badges/mothracompat.gif"> <img class="badge" src="/badges/mothracompat.gif">
<img class="badge" src="/badges/flexbox.png"> <img class="badge" src="/badges/flexbox.png">

View File

@ -15,9 +15,8 @@
<div class="content panel h-card" rel="author"> <div class="content panel h-card" rel="author">
<h1 class="p-name">cel</h1> <h1 class="p-name">cel</h1>
<h3>pronouns: <a class="u-url" rel="me" href="https://en.pronouns.page/@celblossom"><span class="p-x-pronoun-nominative">she</span>/<span class="p-x-pronoun-accusative">her</span></a></h3>
<ul> <ul>
<li><h3>pronouns: <span class="p-x-pronoun-nominative">she</span>/<span class="p-x-pronoun-accusative">her</span></h3></li>
<li>email: <a class="u-email" href="mailto:cel@blos.sm">cel@blos.sm</a> <a href="cel.asc" class="u-key" rel="pgpkey">pgp key</a></li> <li>email: <a class="u-email" href="mailto:cel@blos.sm">cel@blos.sm</a> <a href="cel.asc" class="u-key" rel="pgpkey">pgp key</a></li>
<li>xmpp: <a class="u-impp" href="xmpp:cel@blos.sm?message">cel@blos.sm</a></li> <li>xmpp: <a class="u-impp" href="xmpp:cel@blos.sm?message">cel@blos.sm</a></li>
<li>fedi: <a class="u-url" rel="me" href="https://skinnyver.se/cel">cel@skinnyver.se</a></li> <li>fedi: <a class="u-url" rel="me" href="https://skinnyver.se/cel">cel@skinnyver.se</a></li>

View File

@ -30,10 +30,10 @@
<div class="panel content"> <div class="panel content">
<h2>hallo i am celeste welcome 2 my site 🌟</h2> <h2>hallo i am celeste welcome 2 my site 🌟</h2>
<p>this is where i dump everything</p> <p>this is where i do the posting</p>
<p>i wish u a wonderful day</p> <p>i wish u a wonderful day</p>
<br> <br>
<img src="https://s3.eu-west-1.wasabisys.com/skinnyverse/f28c467a-0ee1-4d49-a638-08a010fd9927/quinntyping.png"> <img src="/quinntyping.png">
<p>perpetually under construction</p> <p>perpetually under construction</p>
<img style="border: 0;" src="/barraconstruction.gif"> <img style="border: 0;" src="/barraconstruction.gif">
</div> </div>

View File

@ -1,5 +1,5 @@
<div class="panel" id="posts"> <div class="panel" id="posts">
<h2>latest posts <!--<a class="badge" href="/feed.xml"><img class="badge" src="/rssbadge.png" alt="rss newsfeed"></a>--></h2> <h2>latest posts <a class="small-badge" href="/feed"><img class="small-badge" src="/atombadge.png" alt="atom newsfeed"></a></h2>
<table id="post-list"> <table id="post-list">
{% for blogpost in blogposts %} {% for blogpost in blogposts %}
<tr> <tr>