From c190f20b14005775fa18e6b8a37aa0829b307f17 Mon Sep 17 00:00:00 2001 From: ajet Date: Thu, 16 Oct 2025 10:46:45 -0900 Subject: [PATCH] refactor --- src/ajet/todo/core.clj | 132 ++++---------------------------------- src/ajet/todo/db.clj | 12 +--- src/ajet/todo/handler.clj | 20 ++++++ src/ajet/todo/server.clj | 2 +- src/ajet/todo/util.clj | 27 ++++++++ src/ajet/todo/view.clj | 76 ++++++++++++++++++++++ src/dev/user.clj | 14 +++- 7 files changed, 149 insertions(+), 134 deletions(-) create mode 100644 src/ajet/todo/handler.clj create mode 100644 src/ajet/todo/util.clj create mode 100644 src/ajet/todo/view.clj diff --git a/src/ajet/todo/core.clj b/src/ajet/todo/core.clj index 7e61a81..406fad7 100644 --- a/src/ajet/todo/core.clj +++ b/src/ajet/todo/core.clj @@ -1,151 +1,41 @@ (ns ajet.todo.core (:require [ajet.todo.db :as db] - [clojure.data.json :as json] - [clojure.pprint :as pprint] + [ajet.todo.handler :as handler] + [ajet.todo.util :refer [fn-sse]] + [ajet.todo.view :as view] [compojure.core :refer [context defroutes DELETE GET let-routes PATCH POST]] [compojure.route :as route] [hiccup2.core :as h] - [ring.middleware.json :refer [wrap-json-body]] - [starfederation.datastar.clojure.adapter.common :refer [on-open]] - [starfederation.datastar.clojure.adapter.ring :refer [->sse-response]] - [starfederation.datastar.clojure.api :as d*] - [starfederation.datastar.clojure.expressions :refer [->expr]])) - -;; utils -(defmacro fn-sse [[req sse :as _bindings] - & body] - `(fn [~req respond# _#] - (respond# - (->sse-response - ~req - {on-open (fn [~sse] - (d*/with-open-sse ~sse - ~@body))})))) - -(defmacro defn-sse [var-name - [req sse :as _bindings] - & body] - `(defn ~var-name [~req respond# _#] - (respond# - (->sse-response - ~req - {on-open (fn [~sse] - (d*/with-open-sse ~sse - ~@body))})))) -(comment - (do - (defonce printer (bound-fn* pprint/pprint)) - (add-tap printer)) - - (remove-tap printer)) - -;; app -(defn render-page [content] - (str - (h/html - (h/raw "") - [:html - [:head - [:script {:src "https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.5/bundles/datastar.js" - :type "module" - :defer true}] - [:link {:rel "icon" - :type "imaage/x-icon" - :href "https://data-star.dev/cdn-cgi/image/format=auto,width=24/static/images/rocket-48x48-4c739bfaffe86a6ffcc3a6d77e3c5547730f03d74c11aa460209596d1811f7a3.png"}]] - [:body content]]))) - -(defn todos-fragment [] - (let [todos (db/get-all-todos)] ;; todo: make functional. sloppy sloppy - (h/html - [:div {:id "todos"} - [:div {:data-computed-show-toggle-display "($showComplete ? 'hide' : 'show') + ' done'"}] - [:ul - (for [{id :id - done :todo/done? - title :todo/title} todos] - [:li {:data-show (->expr (or (not ~done) - $showComplete))} - [:span {:style (str "text-decoration: " (if done "line-through" "none"))} - title] - [:input {:type "checkbox" - :checked done - :data-on-click (str "@patch('/sse/todo/" id "/" (when done "un") "set')") - :style "margin-right: 0.25rem;"}] - (when done - [:button {:data-on-click (->expr (@delete ~(str "/sse/todo/" id)))} - "X"])])] - [:div - [:p "settings"] - [:button {:data-on-click "$showComplete = !$showComplete" - :data-text "$showToggleDisplay" - :style "margin-right: 0.25rem;"}] - [:button {:data-on-click "$debugMode = !$debugMode"} - "toggle debug"]]]))) - -(defn create-todo-fragment [] - (h/html - [:div {:id "create-box" - :style "margin-top: 2rem;"} - [:label "Make a todo: "] - [:input {:name "title" - :data-bind "title" - :style "margin-right: 0.25rem;"}] - [:button {:data-on-click (->expr (@post "/sse/todo/create-todo"))} - "submit"]])) - -(defn app-fragment [] - (h/html - [:div {:data-signals #json{:showComplete true - :debugMode false - :title ""}}] - [:pre {:data-json-signals "true" - :data-show "$debugMode"}] - [:div {:id "app"} - (todos-fragment) - (create-todo-fragment)])) - -(defn refresh-todos! [sse] - (d*/patch-elements! sse - (str (todos-fragment)) - #:d*.elements{:selector "#todos" - :patch-mode "replace"})) - -(defn-sse create-todo-handler [req sse] - (let [title (-> req :body :title)] - (when title - (db/add-todo! title)) - (d*/with-open-sse sse - (refresh-todos! sse) - (d*/patch-signals! sse #json{:title ""})))) + [ring.middleware.json :refer [wrap-json-body]])) (defroutes approutes (GET "/" [] (fn [_ respond _] - (-> (app-fragment) - render-page + (-> (view/app-fragment) + view/render-page respond))) (-> (context "/sse/todo" [] (GET "/" [] (fn-sse [_req sse] - (refresh-todos! sse))) - (POST "/create-todo" [] create-todo-handler) + (handler/refresh-todos! sse))) + (POST "/create-todo" [] handler/create-todo-handler) (context ["/:id", :id #"[0-9]+"] [id] (let-routes [id (read-string id)] (PATCH "/set" [] (fn-sse [_req sse] (db/set-todo-done! id true) - (refresh-todos! sse))) + (handler/refresh-todos! sse))) (PATCH "/unset" [] (fn-sse [_req sse] (db/set-todo-done! id false) - (refresh-todos! sse))) + (handler/refresh-todos! sse))) (DELETE "/" [] (fn-sse [_req sse] (db/delete-todo! id) - (refresh-todos! sse)))))) + (handler/refresh-todos! sse)))))) (wrap-json-body {:keywords? true})) (route/not-found (str (h/html [:h2 "Not Found"])))) diff --git a/src/ajet/todo/db.clj b/src/ajet/todo/db.clj index d9a6e33..5c5643c 100644 --- a/src/ajet/todo/db.clj +++ b/src/ajet/todo/db.clj @@ -58,16 +58,8 @@ (sort-by first) (map normalize-todo)))) -(defn get-todo [id] - (let [db (d/db conn)] - (some->> (d/q '[:find ?e ?title ?done - :in $ ?e - :where [?e :todo/title ?title] - [?e :todo/done? ?done]] - db - id) - first - normalize-todo))) +(defn get-todo [db id] + (d/pull db '[*] id)) (defn get-todo-by-title [title] (let [db (d/db conn)] diff --git a/src/ajet/todo/handler.clj b/src/ajet/todo/handler.clj new file mode 100644 index 0000000..b9d9e48 --- /dev/null +++ b/src/ajet/todo/handler.clj @@ -0,0 +1,20 @@ +(ns ajet.todo.handler + (:require + [ajet.todo.db :as db] + [ajet.todo.util :refer [defn-sse]] + [ajet.todo.view :as view] + [starfederation.datastar.clojure.api :as d*])) + +(defn refresh-todos! [sse] + (d*/patch-elements! sse + (str (view/todos-fragment)) + #:d*.elements{:selector "#todos" + :patch-mode "replace"})) + +(defn-sse create-todo-handler [req sse] + (let [title (-> req :body :title)] + (when title + (db/add-todo! title)) + (d*/with-open-sse sse + (refresh-todos! sse) + (d*/patch-signals! sse #json{:title ""})))) diff --git a/src/ajet/todo/server.clj b/src/ajet/todo/server.clj index 17e3115..6ef0621 100644 --- a/src/ajet/todo/server.clj +++ b/src/ajet/todo/server.clj @@ -3,7 +3,7 @@ [ajet.todo.core :as core] [ring.adapter.jetty :refer [run-jetty]])) -(def port 1738) +(def port 80) (defn make-server [opts] (run-jetty #'core/app (merge {:join? false, diff --git a/src/ajet/todo/util.clj b/src/ajet/todo/util.clj new file mode 100644 index 0000000..5754f11 --- /dev/null +++ b/src/ajet/todo/util.clj @@ -0,0 +1,27 @@ +(ns ajet.todo.util + (:require + [starfederation.datastar.clojure.adapter.common :refer [on-open]] + [starfederation.datastar.clojure.adapter.ring :refer [->sse-response]] + [starfederation.datastar.clojure.api :as d*])) + +;; utils +(defmacro fn-sse [[req sse :as _bindings] + & body] + `(fn [~req respond# _#] + (respond# + (->sse-response + ~req + {on-open (fn [~sse] + (d*/with-open-sse ~sse + ~@body))})))) + +(defmacro defn-sse [var-name + [req sse :as _bindings] + & body] + `(defn ~var-name [~req respond# _#] + (respond# + (->sse-response + ~req + {on-open (fn [~sse] + (d*/with-open-sse ~sse + ~@body))})))) diff --git a/src/ajet/todo/view.clj b/src/ajet/todo/view.clj new file mode 100644 index 0000000..df55300 --- /dev/null +++ b/src/ajet/todo/view.clj @@ -0,0 +1,76 @@ +(ns ajet.todo.view + (:require + [ajet.todo.db :as db] + [clojure.data.json] + [hiccup2.core :as h] + [hiccup.util :as hu] + [starfederation.datastar.clojure.expressions :refer [->expr]])) + +(defn render-page [content] + (str + (h/html + (h/raw "") + [:html + [:head + [:script {:src "https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.5/bundles/datastar.js" + :type "module" + :defer true}] + [:script {:src "https://cdn.jsdelivr.net/npm/eruda" + :id "erudaLoaderScript" + :type "module" + :defer true}] + (hu/raw-string "") + [:link {:rel "icon" + :type "imaage/x-icon" + :href "https://data-star.dev/cdn-cgi/image/format=auto,width=24/static/images/rocket-48x48-4c739bfaffe86a6ffcc3a6d77e3c5547730f03d74c11aa460209596d1811f7a3.png"}]] + [:body content]]))) + +(defn todos-fragment [] + (let [todos (db/get-all-todos)] ;; todo: make functional. sloppy sloppy + (h/html + [:div {:id "todos"} + [:div {:data-computed-show-toggle-display "($showComplete ? 'hide' : 'show') + ' done'"}] + [:ul + (for [{id :id + done :todo/done? + title :todo/title} todos] + [:li {:data-show (->expr (or (not ~done) + $showComplete))} + [:span {:style (str "text-decoration: " (if done "line-through" "none"))} + title] + [:input {:type "checkbox" + :checked done + :data-on-click (str "@patch('/sse/todo/" id "/" (when done "un") "set')") + :style "margin-right: 0.25rem;"}] + (when done + [:button {:data-on-click (->expr (@delete ~(str "/sse/todo/" id)))} + "X"])])] + [:div + [:p "settings"] + [:button {:data-on-click "$showComplete = !$showComplete" + :data-text "$showToggleDisplay" + :style "margin-right: 0.25rem;"}] + [:button {:data-on-click "$debugMode = !$debugMode"} + "toggle debug"]]]))) + +(defn create-todo-fragment [] + (h/html + [:div {:id "create-box" + :style "margin-top: 2rem;"} + [:label "Make a todo: "] + [:input {:name "title" + :data-bind "title" + :style "margin-right: 0.25rem;"}] + [:button {:data-on-click (->expr (@post "/sse/todo/create-todo"))} + "submit"]])) + +(defn app-fragment [] + (h/html + [:div {:data-signals #json{:showComplete true + :debugMode false + :title ""}}] + [:pre {:data-json-signals "true" + :data-show "$debugMode"}] + [:div {:id "app"} + (todos-fragment) + (create-todo-fragment)])) diff --git a/src/dev/user.clj b/src/dev/user.clj index c87e0d2..da8b40c 100644 --- a/src/dev/user.clj +++ b/src/dev/user.clj @@ -1,11 +1,21 @@ (ns user - (:require [ajet.todo.server :as server])) + (:require + [ajet.todo.server :as server] + [clojure.pprint :as pprint])) (defonce server (server/make-server {})) (comment + ;; server controls server (. server stop) - (. server start)) + (. server start) + + ;; tap debugging + (do + (defonce printer (bound-fn* pprint/pprint)) + (add-tap printer)) + + (remove-tap printer))