189 lines
5.9 KiB
Clojure
189 lines
5.9 KiB
Clojure
(ns net.deertopia.doerg.server
|
||
(:require [clojure.pprint :refer [pprint]]
|
||
[clojure.tools.logging :as l]
|
||
[hiccup2.core :as hiccup]
|
||
[net.deertopia.doerg.html :as doerg-html]
|
||
[net.deertopia.doerg.config :as-alias cfg]
|
||
[net.deertopia.doerg.slug :as slug]
|
||
[net.deertopia.doerg.config :as cfg]
|
||
[net.deertopia.doerg.roam :as roam]
|
||
[org.httpkit.server :as http]
|
||
[reitit.coercion]
|
||
[reitit.coercion.spec]
|
||
[reitit.ring.coercion]
|
||
[reitit.core :as r]
|
||
[reitit.ring]
|
||
[reitit.ring.middleware.exception :as reitit-exception]
|
||
[ring.util.response :as response]
|
||
[spec-tools.spell]
|
||
[reitit.spec]
|
||
[reitit.dev.pretty]
|
||
[clojure.spec.alpha :as s]
|
||
[net.deertopia.doerg.render :as doerg-render]
|
||
[net.deertopia.doerg.cached-file :as cached-file]
|
||
[babashka.fs :as fs]
|
||
[aero.core :as aero]
|
||
[clojure.string :as str]
|
||
[net.deertopia.doerg :as-alias doerg]
|
||
[net.deertopia.doerg.config :as doerg-config]))
|
||
|
||
|
||
;;; Routes
|
||
|
||
(def homepage-slug "68XqhHerTWCbE--RYLEdHw")
|
||
(def not-found-slug "PGDHTvUzQ62Js1Y5db-A8g")
|
||
|
||
(defn hello [req]
|
||
(-> (hiccup/html {}
|
||
[:html
|
||
[:head
|
||
[:title "hello"]
|
||
doerg-html/charset
|
||
doerg-html/viewport]
|
||
[:body
|
||
[:pre
|
||
(with-out-str
|
||
(pprint req))]]])
|
||
str
|
||
response/response
|
||
(response/content-type "text/html")))
|
||
|
||
(defn html-dir []
|
||
(-> cfg/*cfg* ::cfg/state-directory (fs/file "html")))
|
||
|
||
(defn not-found [req]
|
||
(response/not-found "not found"))
|
||
|
||
(defn org-file->html-file [org-file]
|
||
(fs/file (html-dir)
|
||
(-> org-file
|
||
fs/file-name
|
||
(fs/strip-ext {:ext "org"})
|
||
(str ".html"))))
|
||
|
||
(defn slug-link [slug & contents]
|
||
[:a {:href (str "/n/" slug)}
|
||
contents])
|
||
|
||
(defmethod doerg-render/org-link "id"
|
||
[{:keys [path raw-link children]}]
|
||
[:span.org-link
|
||
(slug-link (slug/from-uuid path)
|
||
(or (seq children) raw-link))
|
||
#_[:a {:href (str "/n/" (slug/from-uuid path))}
|
||
(or (seq children) raw-link)]])
|
||
|
||
(defn backlinks-postamble [node]
|
||
[:section#backlinks
|
||
[:h2 "Backlinks"]
|
||
[:ul
|
||
(for [n (->> (roam/backlinks node)
|
||
(sort-by (comp str/lower-case roam/title)))]
|
||
[:li (slug-link (roam/slug n)
|
||
(roam/title n))])]])
|
||
|
||
(defn node-by-slug [{{:keys [slug]} :path-params :as req}]
|
||
(if-some [node (some-> slug slug/from-string roam/get-node)]
|
||
(let [org-file (roam/org-file node)
|
||
html-file (org-file->html-file org-file)]
|
||
(cached-file/cached-file
|
||
:file html-file
|
||
:stale? (cached-file/newer-than? org-file html-file)
|
||
:compute #(doerg-render/to-html
|
||
org-file
|
||
:postamble (backlinks-postamble node)))
|
||
(-> (str html-file)
|
||
response/file-response
|
||
(response/content-type "text/html")))
|
||
(not-found req)))
|
||
|
||
(defn node-by-id [req]
|
||
(hello req))
|
||
|
||
(def exception-middleware
|
||
(reitit-exception/create-exception-middleware
|
||
(merge
|
||
reitit-exception/default-handlers
|
||
{::reitit-exception/wrap
|
||
(fn [handler e request]
|
||
(l/error e "error in fucking somwhere dude")
|
||
(handler e request))})))
|
||
|
||
(defn handle-homepage [req]
|
||
(-> req
|
||
(assoc-in [:path-params :slug] homepage-slug)
|
||
node-by-slug))
|
||
|
||
(defn handle-resource [{:keys [uri]}]
|
||
(if-some [[_ resource] (re-matches #"^/resource/ibm-plex-web/(.*)" uri)]
|
||
(-> resource
|
||
(response/file-response
|
||
{:root (-> doerg-config/*cfg* ::doerg/ibm-plex-web str)}))
|
||
(-> uri
|
||
(str/replace-first #"^/resource/" "")
|
||
(response/resource-response
|
||
{:root "net/deertopia/doerg/public"
|
||
:allow-symlinks? true}))))
|
||
|
||
(defn handle-favicon [_]
|
||
(response/resource-response "net/deertopia/doerg/favicon.ico"))
|
||
|
||
(def router
|
||
(reitit.ring/router
|
||
#{["/" #'handle-homepage]
|
||
["/n/:slug" #'node-by-slug]
|
||
["/id/:id" #'node-by-id]
|
||
["/resource/*" #'handle-resource]
|
||
["/myreq" #'hello]
|
||
["/favicon.ico" #'handle-favicon]}
|
||
{:validate reitit.spec/validate
|
||
:exception reitit.dev.pretty/exception
|
||
:spec :reitit.spec/default-data
|
||
:data
|
||
{:coercion reitit.coercion.spec/coercion
|
||
:middleware [exception-middleware
|
||
reitit.ring.coercion/coerce-request-middleware
|
||
reitit.ring.coercion/coerce-response-middleware
|
||
#_reitit.ring.coercion/coerce-exceptions-middleware]}}))
|
||
|
||
|
||
;;; Server API
|
||
|
||
(def app (reitit.ring/ring-handler router))
|
||
|
||
(defonce server (atom nil))
|
||
|
||
(defn stop! []
|
||
(when @server
|
||
(http/server-stop! @server {:timeout 100})
|
||
(reset! server nil)
|
||
(l/info "Stopped server")))
|
||
|
||
;; For some reason, the log messages from `stop!` are not flushed
|
||
;; before the JVM shuts dowm. Nevertheless, the server /does/ come to
|
||
;; a graceful halt.
|
||
(def ^:private shutdown-hook (Thread. stop!))
|
||
|
||
(defn start! []
|
||
(if @server
|
||
(throw (IllegalStateException. "Server already started"))
|
||
(do (reset! server
|
||
(http/run-server (bound-fn* #'app)
|
||
{:port (-> cfg/*cfg* ::cfg/port)
|
||
:legacy-return-value? false}))
|
||
;; For some reason, the log messages are not flushed before
|
||
;; the JVM shuts dowm. Nevertheless, the server /does/ come
|
||
;; to a graceful halt.
|
||
(try (.addShutdownHook (Runtime/getRuntime) shutdown-hook)
|
||
(catch IllegalArgumentException e
|
||
(when (not= "Hook previously registered"
|
||
(ex-message e))
|
||
(throw e))))
|
||
(l/infof "Server started on port %d"
|
||
(-> cfg/*cfg* ::cfg/port)))))
|
||
|
||
(defn status []
|
||
(if @server
|
||
(http/server-status @server)
|
||
:stopped))
|