jsonfeed support

This commit is contained in:
Cadey Ratio 2020-07-14 13:27:52 -04:00
parent d8a16e0d51
commit 900b6d7858
9 changed files with 257 additions and 147 deletions

101
Cargo.lock generated
View File

@ -203,12 +203,9 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "0.5.5" version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
dependencies = [
"loom",
]
[[package]] [[package]]
name = "cc" name = "cc"
@ -404,11 +401,12 @@ dependencies = [
[[package]] [[package]]
name = "error-chain" name = "error-chain"
version = "0.10.0" version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"version_check 0.9.2",
] ]
[[package]] [[package]]
@ -555,19 +553,6 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "generator"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68"
dependencies = [
"cc",
"libc",
"log 0.4.8",
"rustc_version",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.12.3" version = "0.12.3"
@ -602,9 +587,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.2.5" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" checksum = "993f9e0baeed60001cf565546b0d3dbe6a6ad23f2bd31644a133c641eccf6d53"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -613,10 +598,10 @@ dependencies = [
"futures-util", "futures-util",
"http", "http",
"indexmap", "indexmap",
"log 0.4.8",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tracing",
] ]
[[package]] [[package]]
@ -703,9 +688,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.13.6" version = "0.13.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f" checksum = "3e68a8dd9716185d9e64ea473ea6ef63529252e3e27623295a0378a19665d5eb"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@ -716,12 +701,12 @@ dependencies = [
"http-body", "http-body",
"httparse", "httparse",
"itoa", "itoa",
"log 0.4.8",
"pin-project", "pin-project",
"socket2", "socket2",
"time", "time",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing",
"want", "want",
] ]
@ -887,17 +872,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "loom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7"
dependencies = [
"cfg-if",
"generator",
"scoped-tls 0.1.2",
]
[[package]] [[package]]
name = "maplit" name = "maplit"
version = "1.0.2" version = "1.0.2"
@ -1632,15 +1606,6 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"
@ -1672,12 +1637,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "scoped-tls"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.0" version = "1.0.0"
@ -1707,21 +1666,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.114" version = "1.0.114"
@ -2034,6 +1978,26 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
[[package]]
name = "tracing"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e2a2de6b0d5cbb13fc21193a2296888eaab62b6044479aafb3c54c01c29fcd"
dependencies = [
"cfg-if",
"log 0.4.8",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94ae75f0d28ae10786f3b1895c55fe72e79928fd5ccdebb5438c75e93fec178f"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.3" version = "0.2.3"
@ -2246,7 +2210,7 @@ dependencies = [
"mime_guess 2.0.3", "mime_guess 2.0.3",
"multipart", "multipart",
"pin-project", "pin-project",
"scoped-tls 1.0.0", "scoped-tls",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
@ -2412,6 +2376,7 @@ dependencies = [
"envy", "envy",
"glob", "glob",
"hyper", "hyper",
"jsonfeed",
"lazy_static", "lazy_static",
"log 0.4.8", "log 0.4.8",
"mime 0.3.16", "mime 0.3.16",

View File

@ -28,6 +28,9 @@ thiserror = "1"
tokio = { version = "0.2", features = ["macros"] } tokio = { version = "0.2", features = ["macros"] }
warp = "0.2" warp = "0.2"
# workspace dependencies
jsonfeed = { path = "./lib/jsonfeed" }
[build-dependencies] [build-dependencies]
ructe = { version = "0.11", features = ["warp02"] } ructe = { version = "0.11", features = ["warp02"] }

View File

@ -9,7 +9,7 @@ readme = "README.adoc"
version = "0.2.0" version = "0.2.0"
[dependencies] [dependencies]
error-chain = "0.10.0" error-chain = "0.12"
serde = "1" serde = "1"
serde_derive = "1" serde_derive = "1"
serde_json = "1" serde_json = "1"

View File

@ -20,6 +20,51 @@ impl Builder {
self self
} }
pub fn home_page_url<I: Into<String>>(mut self, url: I) -> Builder {
self.0.home_page_url = Some(url.into());
self
}
pub fn feed_url<I: Into<String>>(mut self, url: I) -> Builder {
self.0.feed_url = Some(url.into());
self
}
pub fn description<I: Into<String>>(mut self, desc: I) -> Builder {
self.0.description = Some(desc.into());
self
}
pub fn user_comment<I: Into<String>>(mut self, cmt: I) -> Builder {
self.0.user_comment = Some(cmt.into());
self
}
pub fn next_url<I: Into<String>>(mut self, url: I) -> Builder {
self.0.next_url = Some(url.into());
self
}
pub fn icon<I: Into<String>>(mut self, url: I) -> Builder {
self.0.icon = Some(url.into());
self
}
pub fn favicon<I: Into<String>>(mut self, url: I) -> Builder {
self.0.favicon = Some(url.into());
self
}
pub fn author(mut self, author: Author) -> Builder {
self.0.author = Some(author);
self
}
pub fn expired(mut self) -> Builder {
self.0.expired = Some(true);
self
}
pub fn item(mut self, item: Item) -> Builder { pub fn item(mut self, item: Item) -> Builder {
self.0.items.push(item); self.0.items.push(item);
self self
@ -30,7 +75,6 @@ impl Builder {
} }
} }
/// Builder object for an item in a feed /// Builder object for an item in a feed
pub struct ItemBuilder { pub struct ItemBuilder {
pub id: Option<String>, pub id: Option<String>,
@ -72,6 +116,41 @@ impl ItemBuilder {
self self
} }
pub fn image<I: Into<String>>(mut self, i: I) -> ItemBuilder {
self.image = Some(i.into());
self
}
pub fn id<I: Into<String>>(mut self, i: I) -> ItemBuilder {
self.id = Some(i.into());
self
}
pub fn url<I: Into<String>>(mut self, i: I) -> ItemBuilder {
self.url = Some(i.into());
self
}
pub fn external_url<I: Into<String>>(mut self, i: I) -> ItemBuilder {
self.external_url = Some(i.into());
self
}
pub fn date_modified<I: Into<String>>(mut self, i: I) -> ItemBuilder {
self.date_modified = Some(i.into());
self
}
pub fn date_published<I: Into<String>>(mut self, i: I) -> ItemBuilder {
self.date_published = Some(i.into());
self
}
pub fn tags(mut self, tags: Vec<String>) -> ItemBuilder {
self.tags = Some(tags);
self
}
pub fn content_html<I: Into<String>>(mut self, i: I) -> ItemBuilder { pub fn content_html<I: Into<String>>(mut self, i: I) -> ItemBuilder {
match self.content { match self.content {
Some(Content::Text(t)) => { Some(Content::Text(t)) => {

View File

@ -29,6 +29,8 @@ pub fn markdown(inp: &str) -> String {
markdown_to_html(inp, &options) markdown_to_html(inp, &options)
} }
pub const ICON: &'static str = "https://christine.website/static/img/avatar.png";
pub struct State { pub struct State {
pub cfg: Config, pub cfg: Config,
pub signalboost: Vec<Person>, pub signalboost: Vec<Person>,
@ -37,6 +39,7 @@ pub struct State {
pub gallery: Vec<Post>, pub gallery: Vec<Post>,
pub talks: Vec<Post>, pub talks: Vec<Post>,
pub everything: Vec<Post>, pub everything: Vec<Post>,
pub jf: jsonfeed::Feed,
} }
pub fn init(cfg: PathBuf) -> Result<State> { pub fn init(cfg: PathBuf) -> Result<State> {
@ -61,6 +64,25 @@ pub fn init(cfg: PathBuf) -> Result<State> {
everything.sort(); everything.sort();
everything.reverse(); everything.reverse();
let mut jfb = jsonfeed::Feed::builder()
.title("Christine Dodrill's Blog")
.description("My blog posts and rants about various technology things.")
.author(
jsonfeed::Author::new()
.name("Christine Dodrill")
.url("https://christine.website")
.avatar(ICON),
)
.feed_url("https://christine.website/blog.json")
.user_comment("This is a JSON feed of my blogposts. For more information read: https://jsonfeed.org/version/1")
.icon(ICON)
.favicon(ICON);
for post in &everything {
let post = post.clone();
jfb = jfb.item(post.into());
}
Ok(State { Ok(State {
cfg: cfg, cfg: cfg,
signalboost: sb, signalboost: sb,
@ -69,6 +91,7 @@ pub fn init(cfg: PathBuf) -> Result<State> {
gallery: gallery, gallery: gallery,
talks: talks, talks: talks,
everything: everything, everything: everything,
jf: jfb.build(),
}) })
} }

21
src/handlers/feeds.rs Normal file
View File

@ -0,0 +1,21 @@
use crate::{
app::State,
};
use lazy_static::lazy_static;
use prometheus::{IntCounterVec, register_int_counter_vec, opts};
use std::{sync::Arc};
use warp::{
Reply,
};
lazy_static! {
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 {
HIT_COUNTER.with_label_values(&["json"]).inc();
let state = state.clone();
warp::reply::json(&state.jf)
}

View File

@ -49,6 +49,7 @@ pub async fn not_found() -> Result<impl Reply, Rejection> {
} }
pub mod blog; pub mod blog;
pub mod feeds;
pub mod gallery; pub mod gallery;
pub mod talks; pub mod talks;

View File

@ -33,84 +33,69 @@ async fn main() -> Result<()> {
let healthcheck = warp::get().and(warp::path(".within").and(warp::path("health")).map(|| "OK")); let healthcheck = warp::get().and(warp::path(".within").and(warp::path("health")).map(|| "OK"));
let blog = { let base = warp::path!("blog" / ..);
let base = warp::path!("blog" / ..); let blog_index = base
let index = base .and(warp::path::end())
.and(warp::path::end()) .and(with_state(state.clone()))
.and_then(handlers::blog::index);
let series = base
.and(warp::path!("series").and(with_state(state.clone()).and_then(handlers::blog::series)));
let series_view = base.and(
warp::path!("series" / String)
.and(with_state(state.clone())) .and(with_state(state.clone()))
.and_then(handlers::blog::index); .and(warp::get())
let series = base.and( .and_then(handlers::blog::series_view),
warp::path!("series").and(with_state(state.clone()).and_then(handlers::blog::series)), );
); let post_view = base.and(
let series_view = base.and( warp::path!(String)
warp::path!("series" / String)
.and(with_state(state.clone()))
.and(warp::get())
.and_then(handlers::blog::series_view),
);
let post_view = base.and(
warp::path!(String)
.and(with_state(state.clone()))
.and(warp::get())
.and_then(handlers::blog::post_view),
);
index.or(series.or(series_view)).or(post_view)
};
let gallery = {
let base = warp::path!("gallery" / ..);
let index = base
.and(warp::path::end())
.and(with_state(state.clone())) .and(with_state(state.clone()))
.and_then(handlers::gallery::index); .and(warp::get())
let post_view = base.and( .and_then(handlers::blog::post_view),
warp::path!(String) );
.and(with_state(state.clone()))
.and(warp::get())
.and_then(handlers::gallery::post_view),
);
index.or(post_view) let gallery_base = warp::path!("gallery" / ..);
}; let gallery_index = gallery_base
.and(warp::path::end())
let talks = { .and(with_state(state.clone()))
let base = warp::path!("talks" / ..); .and_then(handlers::gallery::index);
let index = base let gallery_post_view = gallery_base.and(
.and(warp::path::end()) warp::path!(String)
.and(with_state(state.clone())) .and(with_state(state.clone()))
.and_then(handlers::talks::index); .and(warp::get())
let post_view = base.and( .and_then(handlers::gallery::post_view),
warp::path!(String) );
.and(with_state(state.clone()))
.and(warp::get())
.and_then(handlers::talks::post_view),
);
index.or(post_view) let talk_base = warp::path!("talks" / ..);
}; let talk_index = talk_base
.and(warp::path::end())
let static_pages = { .and(with_state(state.clone()))
let contact = warp::path!("contact").and_then(handlers::contact); .and_then(handlers::talks::index);
let feeds = warp::path!("feeds").and_then(handlers::feeds); let talk_post_view = talk_base.and(
let resume = warp::path!("resume") warp::path!(String)
.and(with_state(state.clone())) .and(with_state(state.clone()))
.and_then(handlers::resume); .and(warp::get())
let signalboost = warp::path!("signalboost") .and_then(handlers::talks::post_view),
.and(with_state(state.clone())) );
.and_then(handlers::signalboost);
contact.or(feeds).or(resume).or(signalboost) let index = warp::get().and(path::end().and_then(handlers::index));
};
let files = { let contact = warp::path!("contact").and_then(handlers::contact);
let files = warp::path("static").and(warp::fs::dir("./static")); let feeds = warp::path!("feeds").and_then(handlers::feeds);
let css = warp::path("css").and(warp::fs::dir("./css")); let resume = warp::path!("resume")
let sw = warp::path("sw.js").and(warp::fs::file("./static/js/sw.js")); .and(with_state(state.clone()))
let robots = warp::path("robots.txt").and(warp::fs::file("./static/robots.txt")); .and_then(handlers::resume);
let signalboost = warp::path!("signalboost")
.and(with_state(state.clone()))
.and_then(handlers::signalboost);
files.or(css).or(sw).or(robots) let files = warp::path("static").and(warp::fs::dir("./static"));
}; 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 jsonfeed = warp::path("blog.json")
.and(with_state(state.clone()))
.map(handlers::feeds::jsonfeed);
let metrics_endpoint = warp::path("metrics").and(warp::path::end()).map(move || { let metrics_endpoint = warp::path("metrics").and(warp::path::end()).map(move || {
let encoder = TextEncoder::new(); let encoder = TextEncoder::new();
@ -124,14 +109,16 @@ async fn main() -> Result<()> {
.unwrap() .unwrap()
}); });
let site = files let site = index
.or(warp::get().and(path::end().and_then(handlers::index))) .or(contact.or(feeds))
.or(static_pages) .or(resume.or(signalboost))
.or(blog) .or(blog_index.or(series.or(series_view).or(post_view)))
.or(gallery) .or(gallery_index.or(gallery_post_view))
.or(talks) .or(talk_index.or(talk_post_view))
.or(healthcheck) .or(jsonfeed)
.or(metrics_endpoint) .or(files.or(css))
.or(sw.or(robots))
.or(healthcheck.or(metrics_endpoint))
.map(|reply| { .map(|reply| {
warp::reply::with_header( warp::reply::with_header(
reply, reply,

View File

@ -14,6 +14,37 @@ pub struct Post {
pub date: NaiveDate, pub date: NaiveDate,
} }
impl Into<jsonfeed::Item> for Post {
fn into(self) -> jsonfeed::Item {
let mut result = jsonfeed::Item::builder()
.title(self.front_matter.title)
.content_html(self.body_html)
.id(format!("https://christine.website/{}", self.link))
.url(format!("https://christine.website/{}", self.link))
.date_published(self.front_matter.date);
let mut tags: Vec<String> = vec![];
if let Some(series) = self.front_matter.series {
tags.push(series);
}
if let Some(mut meta_tags) = self.front_matter.tags {
tags.append(&mut meta_tags);
}
if tags.len() != 0 {
result = result.tags(tags);
}
if let Some(image_url) = self.front_matter.image {
result = result.image(image_url);
}
result.build().unwrap()
}
}
impl Ord for Post { impl Ord for Post {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(&other).unwrap() self.partial_cmp(&other).unwrap()