This is not a static blog. It dynamically loads all of my GitHub Gists that begin with blog_
. This presented a challenge as to how I would implement RSS, considering I don't have a backend from which I can build the RSS feed, and I also can't statically generate the feed with each deploy, as a Jekyll blog might do.
Wut do? My boring compromise (for now!) is to make a simple Heroku app that calls the Gist API to grab the blog posts, just like this blog does. Except instead of actually rendering the Gists into a blog, I give an RSS feed!
This should be a pretty fun, byte-sized task. Which makes it perfect for a blog post!
This project can be divided into the following tasks:
- Set up "Hello, world!" web app on Heroku
- Teach app to acquire blog post URL's via Gist API
- Render the results as an RSS feed
Let's work through these one by one, breaking them up into their own components!
Spoiler alert: Rust on Heroku is not supported natively (although of course it's still doable with a bit of effort - but no thanks), so instead I'm using Netlify where Rust is a first class citizen. To get started, I went with the default Rust template offered by Netlify.
Worked like a charm! Now we just need to edit main.rs
in the little template repo! In other words, we only have to do all of the actual work!
This part was easy since I just had to port the React code this blog uses to Rust. Here's how that came out:
use reqwest::header::USER_AGENT;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let res: Vec<serde_json::Value> = client
.get("https://api.github.com/users/seisvelas/gists?page=1&per_page=4")
.header(USER_AGENT, "Xandre's RSS")
.send()
.await?
.json::<Vec<serde_json::Value>>()
.await?;
for gist in &res {
let url = &gist["url"];
let desc = &gist["description"];
println!("{}: {}\n", url, desc);
}
Ok(())
}
Great! We can get the Gists this way. But this gives us every Gist, so to filter out those that aren't blog posts we can add a condition (and make the code way slower! Sigh):
for gist in &res {
let url = &gist["url"];
let desc = &gist["description"];
for (filename, _) in gist["files"].as_object().unwrap() {
if filename.contains("blog_") {
// TODO: format output as RSS feed
println!("{} {}", url, desc);
}
}
}
Great! Now we're on to the last step.
This part should be the easiest yet! Just wrap some print
statements around my code and BAM! I base my XML / RSS compliance on this random guide I found: RSS tutorial: building and using a feed, step by step.
So we'll see shortly whether that guy knows what he's talking about. Here's what the code looks like after being thoroughly fluffed up with println!
:
println!("<?xml version=\"1.0\" ?>");
println!("<rss version=\"2.0\">");
println!(" <channel>");
println!(" <title>Xandre's Blog</title>");
println!(" <link>https://seisvelas.github.io/</link>");
println!(" <description>Informal musings for curious spirits</description>");
for gist in &res {
let url = &gist["url"];
let desc = &gist["description"];
for (filename, _) in gist["files"].as_object().unwrap() {
if filename.contains("blog_") {
println!(" <item>");
println!(" <title>{}</title>", desc);
println!(" <link>{}</link>", url);
println!(" <description>{}</description>", desc);
println!(" </item>");
}
}
}
println!(" </channel>");
println!("</rss>");
And here's what it outputs (on my terminal, I haven't deployed this part to Netlify yet):
alex@m1 % cargo run
<?xml version="1.0" ?>
<rss version="2.0">
<channel>
<title>Xandre's Blog</title>
<link>https://seisvelas.github.io/</link>
<description>Informal musings for curious spirits</description>
<item>
<title>"Why do Salesforce SQL tables end with __c?"</title>
<link>"https://api.github.com/gists/765559d07008d1e04258086efebd2eb0"</link>
<description>"Why do Salesforce SQL tables end with __c?"</description>
</item>
</channel>
</rss>
Looks pretty legit to me :D Time to deploy!
My program is dependent on OpenSSL developer libraries, provided on Ubuntu by the package libssl-dev. However, Netlify does not give root or sudo access. So my deploy fails due to lack of a dependency that I cannot easily provide!
There are two simple solutions:
- Compile the static binary for my application locally and deploy it with dependencies included, or
- Download the dependency
But I'm almost done and I've been at this mezcalería for almost an hour working on this (and drinking mezcal!). If I stay much longer I'll be too inebriated to code anything at all. So I'm going to implement the solution that will be the quickest, if perhaps not the 'best'. I'm going to put a cronjob on my local machine to run the code we've written, compile it into an XML file, and push that to GitHub pages every hour.
So while the blog will be dynamic, the RSS feed will be static, but dynamic enough for now. Brb while I write the script…
Script written!
cd /Users/alexalvarado/.scripts
./feed_xml > feed.xml # feed_xml is our Rust program from earlier
rm -rf seisvelas.github.io
git clone [email protected]:seisvelas/seisvelas.github.io.git
cd seisvelas.github.io
git pull
git checkout gh-pages
mv ../feed.xml .
git add feed.xml
git commit -m 'update RSS feed'
git push
Aannd, it works! The cron job successfully updates the repo and the feed is present:
Unfortunate that my loyal devotees will have to get their RSS notifications up to an hour late when I drop one of my legendary blog posts (jk, I've spent much more work engineering bizarre hacks to make this blog work in cool ways than I ever have blogging)
To test things out (for realsies!) I went on feedly to subscribe to my RSS feed. Here's what it looks like:
Just one post at the moment, but that will change soon! Want to know when it will change, you say??
Well…you'll just have to…subscribe to my RSS feed and receive the next update!