This commit is contained in:
Cadey Ratio 2020-07-14 15:16:52 -04:00
parent 900b6d7858
commit 4aa0069e0d
6 changed files with 286 additions and 21 deletions

136
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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