use atom_syndication::{
Category, Content as AtomContent, Entry as AtomEntry, Feed, Generator, Link, Person, Text,
TextType,
};
use chrono::{DateTime, Utc};
use crate::posts::Post;
pub struct Context {
/// the page for which the feed is being generated for
pub page_title: String,
/// the human-readable page the feed is for
pub page_url: String,
/// the url to the atom xml document
pub self_url: String,
/// current site language
pub lang: String,
}
pub async fn atom<P: AsRef<dyn Post + Send + Sync>>(ctx: Context, entries: Vec<P>) -> Feed {
let mut authors = Vec::new();
let me = Person {
name: "cel".to_owned(),
email: Some("cel@blos.sm".to_owned()),
uri: Some("/contact".to_owned()),
};
authors.push(me.clone());
let page_link = Link {
href: ctx.page_url.clone(),
rel: "alternate".into(),
hreflang: Some(ctx.lang.clone()),
mime_type: Some("text/html".into()),
title: Some(ctx.page_title.clone()),
length: None,
};
let home_link = Link {
// TODO?: localisation
href: "/".into(),
rel: "via".into(),
hreflang: Some(ctx.lang.clone()),
mime_type: Some("text/html".into()),
// TODO: localisation
title: Some("cel's garden".into()),
length: None,
};
let self_link = Link {
href: ctx.self_url.into(),
rel: "self".into(),
hreflang: Some(ctx.lang.clone()),
mime_type: Some("application/atom+xml".into()),
// TODO: localisation
title: Some("atom feed".into()),
length: None,
};
let mut links = Vec::new();
links.push(page_link);
links.push(home_link);
links.push(self_link);
let mut feed = Feed {
title: Text {
value: ctx.page_title,
base: None,
lang: Some(ctx.lang.clone()),
r#type: TextType::Text,
},
id: ctx.page_url,
updated: entries
.iter()
.fold(DateTime::<Utc>::MIN_UTC, |acc, entry| {
if let Some(updated_at) = entry.as_ref().updated_at() {
if *updated_at > acc {
*updated_at
} else {
acc
}
} else {
let published_at = entry.as_ref().published_at();
if *published_at > acc {
*published_at
} else {
acc
}
}
})
.into(),
authors: authors.clone(),
categories: Vec::new(),
contributors: authors.clone(),
generator: Some(Generator {
value: "blossom".into(),
uri: Some("https://bunny.garden/cel/blossom".into()),
version: None,
}),
icon: Some("/static/favicon.png".into()),
links: links.clone(),
logo: Some("/logo.png".into()),
entries: Vec::new(),
// TODO: determine base url from lang
base: Some("https://en.blos.sm/".into()),
lang: Some(ctx.lang),
..Default::default()
};
for entry in entries {
// TODO: localisation: url from lang + filtering entries translated or not
let categories = entry
.as_ref()
.tags()
.into_iter()
.map(|category| Category {
term: category.to_string(),
scheme: None,
label: Some(category.to_string()),
})
.collect();
let published = Some(entry.as_ref().published_at().to_owned().into());
let title = if let Some(title) = entry.as_ref().subject() {
title.to_owned()
} else {
entry.as_ref().content()[..30].to_owned() + "..."
};
let entry = AtomEntry {
title: Text {
value: title,
base: None,
lang: Some(entry.as_ref().lang().to_owned()),
r#type: TextType::Text,
},
id: entry.as_ref().link(),
updated: (*entry
.as_ref()
.updated_at()
.unwrap_or_else(|| entry.as_ref().published_at()))
.into(),
authors: authors.clone(),
categories,
contributors: authors.clone(),
links: links.clone(),
published,
rights: None,
source: None,
summary: None,
content: Some(AtomContent {
base: None,
lang: Some(entry.as_ref().lang().to_owned()),
value: Some(entry.as_ref().content().to_owned()),
src: Some(entry.as_ref().link()),
content_type: Some("html".to_string()),
}),
..Default::default()
};
feed.entries.push(entry);
}
feed
}