{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreickqlhfryopdczdepolq6ldkxvs3ak2cv5lmin327p3wrek5xwjya",
"uri": "at://did:plc:cwdkf4xxjpznceembuuspt3d/app.bsky.feed.post/3lgmugqxjg22b"
},
"canonicalUrl": "https://jack.is/posts/comments-api/",
"description": "On how I learned something new, and made my comments better in the process",
"path": "/posts/comments-api/",
"publishedAt": "2025-01-26T07:02:45.000Z",
"site": "at://did:plc:cwdkf4xxjpznceembuuspt3d/site.standard.publication/3mdrpafzz7c2m",
"tags": [
"atproto",
"blog"
],
"textContent": "Have we yak shaved enough yet on comments? I don't think we have, what's one\nmore post among friends.\n\nThe problem\n\nAs mentioned in Bluesky Comments, my posting workflow\ndoesn't allow me to know the Bluesky post ahead of time. So I thought, well I'll\njust use the search API to find the post URL based on my echofeed post. That\nworked pretty well, but then I saw this skeet:\n\nhttps://bsky.app/profile/bnewbold.net/post/3lg4jigj4dc2v\n\nMy first reaction was \"Oh it'll be fine this is just limited use and there was\nanother post that says it'll be fine for limited use\". It was fine, but after a\nday or so after Bryan's post, I started getting CORS errors from my search\nfunction, and I figured that was that, and I needed to do this a better way,\nand maybe learn something along the way.\n\nThe research\n\nI had seen Jetstream in my research prior to now, and figured some sort of\ndatabase would probably be the best bet, where it's just a single lookup, rather\nthan every browser of the site having to do the search (even if it wasn't blocked).\n\nMy initial thought was using Typescript for a server API, and there's definitely\nstill benefits to there, being a robust ecosystem of ATProto packages. I however\nwanted to Think Different™ so I decided to write it in Rust, both because I\ncould, and also because I wanted to learn a bit of Rust \"systems\" programming,\nsince I've been spending a lot of my day in Typescript.\n\nAfter doing some very detailed Kagi searches [^1], I discovered Rocket\nand determined it was actually perfect for my needs. Simple declarations of\nhandler functions, and it's nice and type-safe. What's not to like.\n\nIt's Rust time\n\nWith just a bit of boilerplate code, I can spin up the API handler needed for\nthe metadata info for my front-end.\n\nThere's two important parts here in the main server, and I've labeled them A and\nB.\n\nPart A: Metadata endpoint\n\nWe'll say for the sake of argument that somehow the web server has the info\nfor a given post already (since we'll discuss the Jetstream and DB in part B),\nso part A is the thing that looks up info for a given post. One of the nice\nthings about Rocket is that it has lots of type guards, so all I need to do\nis specify that it should be expecting a string input in the path, and it's\ngoing to return a Result<Json<models::Post>, NotFound<String>>. We call out to\ndb::post_meta(slug) which checks the database using the post slug, and returns\nthe Post object. Since Post is tagged as a Serialize struct in db.rs, Rocket\ncan just convert it to JSON using library code and safely return it. If it's not\nfound, then it just returns a 404 and we move on with our day.\n\nThe comments code now just needs to make a request to the endpoint to get the\nBluesky post rkey:\n\nAfter getting the metadata, it just acts like normal.\n\nPart B: Websockets and databases\n\nSo how do we _get_ the post info to put in a database for the API route to have\nwhat it needs? Bluesky Jetstream of course. Using jetstream-oxide,\nas well as the patches from cyypherus, we can now listen to the Jetstream and\nfilter for just my posts. This actually ends up being more robust than the old\nsearch API, as here I can restrict my filter to \"just posts with 📝 as the first\ncharacter\", which couldn't easily be done using the search API.\n\nUsing Diesel the websocket loop then inserts into a postgres\ndatabase with all the info needed for the front-end to find the post.\n\nNext steps\n\nThere are one or two improvements I'd like to make here, primarily around the\ndatabase handling. It's not using connection pooling at the moment, but figuring\nout how Rocket handles all of that (and passing a connection to an arbitrary tokio\nfunction) was out of scope for this MVP.\n\nIn case you're curious, you can find the code here: at-comments-listener.\nIt's primarily designed for me, so there's a lot of stuff in there that's hard\ncoded, but maybe someone will find it interesting.\n\n[^1]: \"rust web server framework\"",
"title": "Comments API"
}