rss/atom
This commit is contained in:
parent
900b6d7858
commit
4aa0069e0d
|
@ -78,6 +78,19 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||
|
||||
[[package]]
|
||||
name = "atom_syndication"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d0b2fa7aedc48c4fbe1d38b25c1462a6e7b962397f27a3a8d7cbb1c08f0008b"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive_builder",
|
||||
"diligent-date-parser",
|
||||
"quick-xml 0.18.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
|
@ -227,6 +240,7 @@ checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6"
|
|||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
|
||||
|
@ -239,7 +253,7 @@ dependencies = [
|
|||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"strsim 0.8.0",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
|
@ -296,6 +310,66 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.9.3",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"derive_builder_core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dhall"
|
||||
version = "0.5.3"
|
||||
|
@ -344,6 +418,15 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diligent-date-parser"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28caca0eb64b9b22bdcab47424e0f7716af92d33ad035f765e5ec2b08cf14fcc"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
|
@ -723,6 +806,12 @@ dependencies = [
|
|||
"tokio-tls",
|
||||
]
|
||||
|
||||
[[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.2.0"
|
||||
|
@ -1348,6 +1437,26 @@ version = "1.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cc440ee4802a86e357165021e3e255a9143724da31db1e2ea540214c96a0f82"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.7"
|
||||
|
@ -1586,6 +1695,16 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
|
||||
|
||||
[[package]]
|
||||
name = "rss"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99979205510c60f80a119dedbabd0b8426517384edf205322f8bcd51796bcef9"
|
||||
dependencies = [
|
||||
"derive_builder",
|
||||
"quick-xml 0.17.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ructe"
|
||||
version = "0.11.4"
|
||||
|
@ -1817,6 +1936,12 @@ version = "0.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.34"
|
||||
|
@ -2371,6 +2496,7 @@ name = "xesite"
|
|||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atom_syndication",
|
||||
"chrono",
|
||||
"comrak",
|
||||
"envy",
|
||||
|
@ -2383,6 +2509,7 @@ dependencies = [
|
|||
"pretty_env_logger",
|
||||
"prometheus",
|
||||
"rand 0.7.3",
|
||||
"rss",
|
||||
"ructe",
|
||||
"serde",
|
||||
"serde_dhall",
|
||||
|
@ -2390,8 +2517,15 @@ dependencies = [
|
|||
"thiserror",
|
||||
"tokio",
|
||||
"warp",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.4"
|
||||
|
|
|
@ -9,6 +9,7 @@ build = "src/build.rs"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
atom_syndication = { version = "0.9", features = ["with-serde"] }
|
||||
chrono = "0.4"
|
||||
comrak = "0.8"
|
||||
envy = "0.4"
|
||||
|
@ -20,6 +21,7 @@ mime = "0.3.0"
|
|||
pretty_env_logger = "0"
|
||||
prometheus = { version = "0.9", default-features = false, features = ["process"] }
|
||||
rand = "0"
|
||||
rss = "1"
|
||||
ructe = "0.11"
|
||||
serde_dhall = "0.5.3"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
@ -27,6 +29,7 @@ serde_yaml = "0.8"
|
|||
thiserror = "1"
|
||||
tokio = { version = "0.2", features = ["macros"] }
|
||||
warp = "0.2"
|
||||
xml-rs = "0.8"
|
||||
|
||||
# workspace dependencies
|
||||
jsonfeed = { path = "./lib/jsonfeed" }
|
||||
|
|
39
src/app.rs
39
src/app.rs
|
@ -1,5 +1,6 @@
|
|||
use crate::{post::Post, signalboost::Person};
|
||||
use anyhow::Result;
|
||||
use atom_syndication as atom;
|
||||
use comrak::{markdown_to_html, ComrakOptions};
|
||||
use serde::Deserialize;
|
||||
use std::{fs, path::PathBuf};
|
||||
|
@ -40,6 +41,8 @@ pub struct State {
|
|||
pub talks: Vec<Post>,
|
||||
pub everything: Vec<Post>,
|
||||
pub jf: jsonfeed::Feed,
|
||||
pub rf: rss::Channel,
|
||||
pub af: atom::Feed,
|
||||
}
|
||||
|
||||
pub fn init(cfg: PathBuf) -> Result<State> {
|
||||
|
@ -64,6 +67,9 @@ pub fn init(cfg: PathBuf) -> Result<State> {
|
|||
everything.sort();
|
||||
everything.reverse();
|
||||
|
||||
let mut ri: Vec<rss::Item> = vec![];
|
||||
let mut ai: Vec<atom::Entry> = vec![];
|
||||
|
||||
let mut jfb = jsonfeed::Feed::builder()
|
||||
.title("Christine Dodrill's Blog")
|
||||
.description("My blog posts and rants about various technology things.")
|
||||
|
@ -80,9 +86,38 @@ pub fn init(cfg: PathBuf) -> Result<State> {
|
|||
|
||||
for post in &everything {
|
||||
let post = post.clone();
|
||||
jfb = jfb.item(post.into());
|
||||
jfb = jfb.item(post.clone().into());
|
||||
ri.push(post.clone().into());
|
||||
ai.push(post.clone().into());
|
||||
}
|
||||
|
||||
let af = {
|
||||
let mut af = atom::FeedBuilder::default();
|
||||
af.title("Christine Dodrill's Blog");
|
||||
af.id("https://christine.website/blog");
|
||||
af.generator({
|
||||
let mut generator = atom::Generator::default();
|
||||
generator.set_value(env!("CARGO_PKG_NAME"));
|
||||
generator.set_version(env!("CARGO_PKG_VERSION").to_string());
|
||||
generator.set_uri("https://github.com/Xe/site".to_string());
|
||||
|
||||
generator
|
||||
});
|
||||
af.entries(ai);
|
||||
|
||||
af.build().unwrap()
|
||||
};
|
||||
|
||||
let rf = {
|
||||
let mut rf = rss::ChannelBuilder::default();
|
||||
rf.title("Christine Dodrill's Blog");
|
||||
rf.link("https://christine.website/blog");
|
||||
rf.generator(crate::APPLICATION_NAME.to_string());
|
||||
rf.items(ri);
|
||||
|
||||
rf.build().unwrap()
|
||||
};
|
||||
|
||||
Ok(State {
|
||||
cfg: cfg,
|
||||
signalboost: sb,
|
||||
|
@ -92,6 +127,8 @@ pub fn init(cfg: PathBuf) -> Result<State> {
|
|||
talks: talks,
|
||||
everything: everything,
|
||||
jf: jfb.build(),
|
||||
af: af,
|
||||
rf: rf,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,63 @@
|
|||
use crate::{
|
||||
app::State,
|
||||
};
|
||||
use crate::app::State;
|
||||
use lazy_static::lazy_static;
|
||||
use prometheus::{IntCounterVec, register_int_counter_vec, opts};
|
||||
use std::{sync::Arc};
|
||||
use warp::{
|
||||
Reply,
|
||||
};
|
||||
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
||||
use std::sync::Arc;
|
||||
use warp::{http::Response, Rejection, Reply};
|
||||
|
||||
lazy_static! {
|
||||
static ref HIT_COUNTER: IntCounterVec =
|
||||
register_int_counter_vec!(opts!("feed_hits", "Number of hits to various feeds"), &["kind"])
|
||||
.unwrap();
|
||||
static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
|
||||
opts!("feed_hits", "Number of hits to various feeds"),
|
||||
&["kind"]
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn jsonfeed(state: Arc<State>) -> impl Reply {
|
||||
pub async fn jsonfeed(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
||||
HIT_COUNTER.with_label_values(&["json"]).inc();
|
||||
let state = state.clone();
|
||||
warp::reply::json(&state.jf)
|
||||
Ok(warp::reply::json(&state.jf))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RenderError {
|
||||
WriteAtom(atom_syndication::Error),
|
||||
WriteRss(rss::Error),
|
||||
Build(warp::http::Error),
|
||||
}
|
||||
|
||||
impl warp::reject::Reject for RenderError {}
|
||||
|
||||
pub async fn atom(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
||||
HIT_COUNTER.with_label_values(&["atom"]).inc();
|
||||
let state = state.clone();
|
||||
let mut buf = Vec::new();
|
||||
state
|
||||
.af
|
||||
.write_to(&mut buf)
|
||||
.map_err(RenderError::WriteAtom)
|
||||
.map_err(warp::reject::custom)?;
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("Content-Type", "application/atom+xml")
|
||||
.body(buf)
|
||||
.map_err(RenderError::Build)
|
||||
.map_err(warp::reject::custom)
|
||||
}
|
||||
|
||||
|
||||
pub async fn rss(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
||||
HIT_COUNTER.with_label_values(&["rss"]).inc();
|
||||
let state = state.clone();
|
||||
let mut buf = Vec::new();
|
||||
state
|
||||
.rf
|
||||
.write_to(&mut buf)
|
||||
.map_err(RenderError::WriteRss)
|
||||
.map_err(warp::reject::custom)?;
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("Content-Type", "application/rss+xml")
|
||||
.body(buf)
|
||||
.map_err(RenderError::Build)
|
||||
.map_err(warp::reject::custom)
|
||||
}
|
||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -92,10 +92,17 @@ async fn main() -> Result<()> {
|
|||
let css = warp::path("css").and(warp::fs::dir("./css"));
|
||||
let sw = warp::path("sw.js").and(warp::fs::file("./static/js/sw.js"));
|
||||
let robots = warp::path("robots.txt").and(warp::fs::file("./static/robots.txt"));
|
||||
let favicon = warp::path("favicon.ico").and(warp::fs::file("./static/favicon/favicon.ico"));
|
||||
|
||||
let jsonfeed = warp::path("blog.json")
|
||||
.and(with_state(state.clone()))
|
||||
.map(handlers::feeds::jsonfeed);
|
||||
.and_then(handlers::feeds::jsonfeed);
|
||||
let atom = warp::path("blog.atom")
|
||||
.and(with_state(state.clone()))
|
||||
.and_then(handlers::feeds::atom);
|
||||
let rss = warp::path("blog.rss")
|
||||
.and(with_state(state.clone()))
|
||||
.and_then(handlers::feeds::rss);
|
||||
|
||||
let metrics_endpoint = warp::path("metrics").and(warp::path::end()).map(move || {
|
||||
let encoder = TextEncoder::new();
|
||||
|
@ -110,14 +117,12 @@ async fn main() -> Result<()> {
|
|||
});
|
||||
|
||||
let site = index
|
||||
.or(contact.or(feeds))
|
||||
.or(resume.or(signalboost))
|
||||
.or(contact.or(feeds).or(resume.or(signalboost)))
|
||||
.or(blog_index.or(series.or(series_view).or(post_view)))
|
||||
.or(gallery_index.or(gallery_post_view))
|
||||
.or(talk_index.or(talk_post_view))
|
||||
.or(jsonfeed)
|
||||
.or(files.or(css))
|
||||
.or(sw.or(robots))
|
||||
.or(jsonfeed.or(atom).or(rss))
|
||||
.or(files.or(css).or(favicon).or(sw.or(robots)))
|
||||
.or(healthcheck.or(metrics_endpoint))
|
||||
.map(|reply| {
|
||||
warp::reply::with_header(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use atom_syndication as atom;
|
||||
use chrono::prelude::*;
|
||||
use glob::glob;
|
||||
use std::{cmp::Ordering, fs};
|
||||
|
@ -45,6 +46,49 @@ impl Into<jsonfeed::Item> for Post {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<atom::Entry> for Post {
|
||||
fn into(self) -> atom::Entry {
|
||||
let mut content = atom::ContentBuilder::default();
|
||||
|
||||
content.src(format!("https://christine.website/{}", self.link));
|
||||
content.content_type(Some("html".into()));
|
||||
content.value(Some(xml::escape::escape_str_pcdata(&self.body_html).into()));
|
||||
|
||||
let content = content.build().unwrap();
|
||||
|
||||
let mut result = atom::EntryBuilder::default();
|
||||
result.title(self.front_matter.title);
|
||||
let mut link = atom::Link::default();
|
||||
link.href = format!("https://christine.website/{}", self.link);
|
||||
result.links(vec![link]);
|
||||
result.content(content);
|
||||
// result.published(Some(
|
||||
// DateTime::<Utc>::from_utc(
|
||||
// NaiveDateTime::new(self.date, NaiveTime::from_hms(0, 0, 0)),
|
||||
// Utc,
|
||||
// )
|
||||
// .with_timezone(&Utc),
|
||||
// ));
|
||||
|
||||
result.build().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<rss::Item> for Post {
|
||||
fn into(self) -> rss::Item {
|
||||
let mut guid = rss::Guid::default();
|
||||
guid.set_value(format!("https://christine.website/{}", self.link));
|
||||
let mut result = rss::ItemBuilder::default();
|
||||
result.title(Some(self.front_matter.title));
|
||||
result.link(format!("https://christine.website/{}", self.link));
|
||||
result.guid(guid);
|
||||
result.author(Some("me@christine.website".to_string()));
|
||||
result.content(self.body_html);
|
||||
|
||||
result.build().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Post {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.partial_cmp(&other).unwrap()
|
||||
|
|
Loading…
Reference in New Issue