How this website works
Last Edited: Tue Dec 24 03:30:30 UTC 2024
For T.H.
Stack
Hosting: A $5/month nanode on linode running debian.
Very simple and nice to use. Plus cheap :)
Language: Clojure
duh
Server: http-kit
A clojure server.
Database: datalevin
This is a cool database that lets you use a subset of logical programming called "datalog." It's where I store my blog pages and my users.
Ex:
(def schema {:name {:db/unique :db.unique/identity
:db/valueType :db.type/string}
:dateofbirth {:db/valueType :db.type/instant}})
;data/users is a directory and file kinda like sqlite
(def users (datalevin/get-conn "data/users" schema))
;To add data
(datalevin/transact! users [:name "patrick" :dateofbirth (new java.util.Date)])
;To query it
(datalevin/q '[:find ?dob
:in $ ?name
:where [?e :name ?name] [?e :dateofbirth ?dob]
(datalevin/db users) "patrick")
;^ the query language is hard to understand but it's sooooo good once you get it
;This one would return a vector something like [:name "patrick" :dateofbirth [WHENEVER THE JAVA THING MAKES]
Authentication/Authorization: buddy
I use buddy which is a popular clojure library for doing steps in the authentication/authorization steps. I'm using session authorization and I just hash my passwords and store that hash with a uname in a "table."
Routing: reitit
An excellent library that lets you define your routes as data
["/:slug" {:get {:parameters {:path {:slug string?}}
:handler (fn [{{slug :slug} :path-params}]
{:status 200
:content-type "text/html"
:body (blogp/blogpost slug)})}}]
This block, which would be nested in a list with something like "/blog" ahead of it says:
With the path parameter :slug, define a :get request where the slug has to be a string. When I get this request, call a function that gets the relevant data from the request and returns a response with status 200, content-type "text/html", and a body with the blog page (blogp/blogpost) which that slug represents (which in turn calls the database).
Templating: hiccup
(defn blogpage
[{:keys [title date content]}]
(h/html5
[:head
[:title title]
[:link {:rel "stylesheet" :href "/css/base.css"}]
[:link {:rel "stylesheet" :href "/css/blogpost.css"}]
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
[:meta {:charset "utf-8"}]
;Code support
[:link {:rel "stylesheet" :href "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css"}]
[:script {:src "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"}]
[:script {:src "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/clojure.min.js"}]
[:script "hljs.highlightAll();"]]
[:body
[:header "Patrick's " [:span.gradient "Blog"]]
[:main
[:article
[:h1.title title]
[:p.date (str "Last Edited: " date)]
[:hr]
(md/md-to-html-string content)]]
[:footer "© " (currentyear) " Patrick Kingston"]
[:script {:src "/js/home.js"}]]))
This is the function that returns a blog page. It lets you define html tags as vectors where the first element is a keyword of the type of tag (p, a, h1, etc.).
Blogposts are stored as markdown strings in the database and then rendered to html as part of this template.
Interactivity: HTMX
The main place where I have interactivity is in filtering blog posts by tag. It's just an HTMX post request which swaps the whole list, filtered by the tags selected.
Styles: Plain, CSS, handmade with love
I just like it.
Animation: Javascript
I instantiate a bunch of chess pieces depending on how big the screen is and then they dance around on a set interval.
Site layout
The homepage is basically static (not programmatically generated). The blog pages are generated on demand because markdown rendering isn't that expensive (although I suppose I could cache them).
There's an admin panel which i'm writing this on. There's also the momblog which uses HTMX to give infinite scrolling.
Q&A
Text me questions and I'll answer them here or delete this section if you don't have any.