diff --git a/publisher/src/net/deertopia/publisher/server.clj b/publisher/src/net/deertopia/publisher/server.clj index fd5fc98..5a19777 100644 --- a/publisher/src/net/deertopia/publisher/server.clj +++ b/publisher/src/net/deertopia/publisher/server.clj @@ -3,10 +3,16 @@ [clojure.tools.logging :as l] [hiccup2.core :as hiccup] [net.deertopia.doerg.html :as doerg-html] + [net.deertopia.publisher.slug :as slug] [org.httpkit.server :as http] + [reitit.coercion] + [reitit.coercion.spec] [reitit.ring] [ring.util.response :as response])) + +;;; Routes + (defn hello [req] (-> (hiccup/html {} [:html @@ -22,9 +28,27 @@ response/response (response/content-type "text/html"))) +(defn node-by-slug [req] + (hello req)) + +(defn node-by-id [req] + (hello req)) + (def router (reitit.ring/router - #{["/" {:get hello}]})) + #{["/" {:get hello}] + ["/n/:slug" {:get node-by-slug + #_#_#_#_:coercion reitit.coercion.spec/coercion + :parameters {:path {:slug ::slug/slug}}}] + ["/id/:id" {:get node-by-id}]} + #_{:compile reitit.coercion/compile-request-coercers})) + +(defn match-by-path-and-coerce! [path] + (if-let [match (r/match-by-path router path)] + (assoc match :parameters (reitit.coercion/coerce! match)))) + + +;;; Server API (def app (reitit.ring/ring-handler router)) diff --git a/publisher/src/net/deertopia/publisher/slug.clj b/publisher/src/net/deertopia/publisher/slug.clj new file mode 100644 index 0000000..d5d2a11 --- /dev/null +++ b/publisher/src/net/deertopia/publisher/slug.clj @@ -0,0 +1,66 @@ +(ns net.deertopia.publisher.slug + (:require [clojure.spec.alpha :as s]) + (:import (java.nio ByteBuffer) + (java.util Base64 UUID))) + +(defrecord Slug [slug-string] + Object + (toString [this] + (:slug-string this))) + +(defn from-string [s] + (try (let [decoder (Base64/getUrlDecoder)] + (when (= 16 (count (.decode decoder s))) + (Slug. s))) + (catch IllegalArgumentException e + (when (not= (ex-message e) + (str "Input byte[] should at least " + "have 2 bytes for base64 bytes")) + (throw e))))) + +(defn to-string [s] + (str s)) + +(defn- coerce-to-uuid [string-or-uuid] + (cond (string? string-or-uuid) (UUID/fromString string-or-uuid) + (uuid? string-or-uuid) string-or-uuid)) + +(defn- uuid->bytes [string-or-uuid] + (let [uuid (coerce-to-uuid string-or-uuid)] + (.array (doto (ByteBuffer/wrap (byte-array 16)) + (.putLong (.getMostSignificantBits uuid)) + (.putLong (.getLeastSignificantBits uuid)))))) + +(defn- bytes->uuid [bytes] + (when (= (count bytes) 16) + (let [bb (ByteBuffer/wrap bytes) + high (.getLong bb) + low (.getLong bb)] + (UUID. high low)))) + +(defn from-uuid [string-or-uuid] + (let [uuid (coerce-to-uuid string-or-uuid) + encoder (.withoutPadding (Base64/getUrlEncoder))] + (Slug. (.encodeToString encoder (uuid->bytes uuid))))) + +(defn to-uuid [slug] + (let [decoder (Base64/getUrlDecoder)] + (bytes->uuid (.decode decoder (str slug))))) + +#_ +(let [uuid #uuid "f9eab66e-7773-4b87-b854-0bfc8f563809" + slug (from-uuid uuid) + round-tripped (to-uuid slug)] + {:uuid uuid, :slug slug, :round-tripped round-tripped}) + +(defn make-slug [string] + (assert (try (to-uuid string) + (catch Throwable _ + nil)) + "invalid slug") + (->Slug string)) + +(defn slug? [s] + (some? (from-string s))) + +(s/def ::slug slug?)