zerosleeps

Since 2010

My website broke a different website

I’ve been using Feedbin for 8 years to follow blogs/sites/YouTube channels. A few weeks back Feedbin stopped showing me anything once I’d logged in though. I was getting nothing but an HTTP 500 error and no HTML content at all.

After about 24 hours of this I contacted Feedbin and less than 30 minutes later Ben Ubois (who created and runs the service) replied:

Feedbin had a bug that was actually triggered by: https://zerosleeps.com/feed.json.

The “author” key just has a string value, but Feedbin was expecting an object like author: {name: …}, as the spec describes.

I subscribe to my own feed in Feedbin to make sure it works as expected, and it turns out that I wasn’t following the JSON Feed version 1.1 specification. I’d been off-spec for years, but a change to Feedbin’s code brought the problem to the surface.

Ben had it fixed within half-an-hour, and I immediately fixed by side of the problem as well:

-    "author": "{{ site.author }}",
+    "authors": [
+        {
+            "name": "{{ site.author }}"
+        }
+    ],

I thought it was rather fun that my website could break someone else’s website!

Search!

Something that bugged me for the entire duration that this site was just a pile of static files was that I didn’t have a good solution for search. The previous incarnation of zerosleeps did have a search form on every blog page, but it just sent the user off to DuckDuckGo and performed a “site:zerosleeps.com” search there.

The results were never great, presumably because DuckDuckGo had no reason to make a decent index of a tiny site like this. Plus it was an external dependency which I’m never a fan of.

Well that changes today: blog posts are now searchable entirely in-house. I’m using PostgreSQL’s full-text search functionality, which is made a little easier by Django’s support for it in django.contrib.postgres. Just blog posts at the moment, but I want to get the reading log in there as well and have one global search.

Post titles carry more weight than the post body, and the results are sorted by whatever rank PostgreSQL comes up with. I’m also using the “headline” function to show the most relevant snippets. Behind the scenes it looks a little something like this:

vector = SearchVector("title", weight="A") + SearchVector("body")
return (
    Post.objects.annotate(
        search=vector,
        rank=SearchRank(vector, query),
        headline=SearchHeadline(
            Concat("title", Value(" "), "body"), query, max_fragments=2
        ),
    )
    .filter(search=query)
    .order_by("-rank")
)

It’s not perfect but it’s absolutely good enough. Better than good enough. Makes me glad I chose PostgreSQL over my other choice - SQLite - as well. SQLite does have full-text search built-in, but from what I can tell it involves creating virtual tables and keeping them up-to-date with the real content. Seems messy. This solution instead boils down to just one (slightly verbose and repetitive but who cares) SQL query which contains:

SELECT
    ts_rank(
        (
            setweight(to_tsvector(COALESCE("blog_post"."title", '')), 'A')
            ||
            to_tsvector(COALESCE("blog_post"."body", ''))
        ),
        plainto_tsquery('search term')
    ) AS "rank",
    ts_headline(
        CONCAT("blog_post"."title", ' ', "blog_post"."body"),
        plainto_tsquery('search term'),
        'MaxFragments=2'
    ) AS "headline"
FROM
    "blog_post"
WHERE
    (
        setweight(
            to_tsvector(COALESCE("blog_post"."title", '')), 'A')
            ||
            to_tsvector(COALESCE("blog_post"."body", ''))
    ) @@ (plainto_tsquery('search term'))
ORDER BY "rank" DESC;

Reading log for 2022

Reading log summary for 2022: just 22 books completed, continuing my downward yearly trend. I abandoned an additional two.

The first half of the year was a real mixed bag which probably put me off books a bit. Plenty of higher-than-average ratings in the second half of the year though.

Powered by Django

zerosleeps.com is now served by my very own Django application 🥳

Since first mentioning it 14 months ago, and posting about it several times since, it turns out I needed to actually sit down for a few hours and build the friggin’ thing. Who knew?!

Still a lot to do - I need to re-upload images for posts that have them. You’ll see there’s no favicon, and page titles are non-existent. I haven’t build my reading log yet, which I’m personally most excited about. And the database isn’t being automatically backed up yet, but that’s no big deal as so far it’s only this post that would be lost in a disaster.

Plus RSS/JSON feeds, maybe a site map, and site search. And the ability for me to create draft posts. And maybe different post “types”, like quick posts that don’t have a title, or image-only posts.

But at least the barrier of deploying this thing is now behind me.