diff --git a/deps.edn b/deps.edn index ab971a1..86ecc00 100644 --- a/deps.edn +++ b/deps.edn @@ -27,6 +27,7 @@ com.taoensso/slf4j-telemere {:mvn/version "1.0.0-beta14"} com.taoensso/telemere {:mvn/version "1.0.0-beta14"} + org.sqids/sqids-clojure {:mvn/version "1.0.15"} org.xerial/sqlite-jdbc {:mvn/version "3.45.2.0"} com.github.seancorfield/honeysql {:mvn/version "2.6.1126"} diff --git a/resources/migrations/20240331175207-create-user.down.sql b/resources/migrations/20240331175207-create-user.down.sql deleted file mode 100644 index 411d374..0000000 --- a/resources/migrations/20240331175207-create-user.down.sql +++ /dev/null @@ -1 +0,0 @@ -DELETE TABLE IF EXISTS users; diff --git a/resources/migrations/20240331175207-create-user.up.sql b/resources/migrations/20240331175207-create-user.up.sql deleted file mode 100644 index d999469..0000000 --- a/resources/migrations/20240331175207-create-user.up.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS users ( - id uuid PRIMARY KEY, - email text NOT NULL UNIQUE, - joined_at timestamp DEFAULT CURRENT_TIMESTAMP, - foo text, - bar text -); diff --git a/resources/migrations/20240331175244-create-auth-code.down.sql b/resources/migrations/20240331175244-create-auth-code.down.sql deleted file mode 100644 index 33a5fa2..0000000 --- a/resources/migrations/20240331175244-create-auth-code.down.sql +++ /dev/null @@ -1 +0,0 @@ -DELETE TABLE IF EXISTS auth_code; diff --git a/resources/migrations/20240331175244-create-auth-code.up.sql b/resources/migrations/20240331175244-create-auth-code.up.sql deleted file mode 100644 index e0ac334..0000000 --- a/resources/migrations/20240331175244-create-auth-code.up.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS auth_code ( - id uuid PRIMARY KEY, - email text NOT NULL UNIQUE, - code text NOT NULL, - created_at timestamp NOT NULL, - failed_attempts integer DEFAULT 0 -); diff --git a/resources/migrations/20250312151923-create-game.down.sql b/resources/migrations/20250312151923-create-game.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/resources/migrations/20250312151923-create-game.up.sql b/resources/migrations/20250312151923-create-game.up.sql new file mode 100644 index 0000000..c81bd75 --- /dev/null +++ b/resources/migrations/20250312151923-create-game.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS game ( + id text PRIMARY KEY, + code text UNIQUE, + display_session text NOT NULL, + control_session text, + active boolean not null +); diff --git a/resources/migrations/20250312151928-create-players.down.sql b/resources/migrations/20250312151928-create-players.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/resources/migrations/20250312151928-create-players.up.sql b/resources/migrations/20250312151928-create-players.up.sql new file mode 100644 index 0000000..d81233e --- /dev/null +++ b/resources/migrations/20250312151928-create-players.up.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS player ( + id INTEGER PRIMARY KEY, + score INTEGER NOT NULL, + name text NOT NULL, + game_code text NOT NULL, + FOREIGN KEY (game_code) + REFERENCES game(code) +); diff --git a/resources/public/css/my.css b/resources/public/css/my.css new file mode 100644 index 0000000..dd0e421 --- /dev/null +++ b/resources/public/css/my.css @@ -0,0 +1,3 @@ +.hidden { + display: none; +} diff --git a/src/com/biffweb/my_project/app.clj b/src/com/biffweb/my_project/app.clj index 3a3e5b8..c4677f9 100644 --- a/src/com/biffweb/my_project/app.clj +++ b/src/com/biffweb/my_project/app.clj @@ -1,48 +1,170 @@ (ns com.biffweb.my-project.app - (:require [com.biffweb :as biff] - [com.biffweb.my-project.middleware :as mid] - [com.biffweb.my-project.ui :as ui] - [honey.sql :as sql] - [com.biffweb.my-project.settings :as settings] - [next.jdbc :as jdbc])) + (:require + [clojure.pprint :as pprint] + [clojure.pprint :as pp] + [clojure.string :as str] + [com.biffweb :as biff] + [com.biffweb.my-project.middleware :refer [wrap-session]] + [com.biffweb.my-project.settings :as settings] + [com.biffweb.my-project.ui :as ui] + [honey.sql :as sql] + [next.jdbc :as jdbc] + [org.sqids.clojure :as sqids])) -(defn bar-form [{:keys [value]}] - (biff/form - {:hx-post "/app/set-bar" - :hx-swap "outerHTML"} - [:label {:for "bar"} "Bar: " - [:span (pr-str value)]] - [:div - [:input#bar {:type "text" :name "bar" :value value}] - [:button {:type "submit"} "Update"]] - "This demonstrates updating a value with HTMX.")) +(defn reset [_] + {:status 200 + :headers {"HX-Redirect" "/"} + :session {}}) + +;; thanks https://stackoverflow.com/a/58098360 +(def sqids (sqids/sqids {:alphabet "cdefhjknprtvwxy2345689"})) + +(defn game-code [] + (apply str (take 6 (sqids/encode sqids [(rand-int Integer/MAX_VALUE)])))) + +(defn error-style [s] + [:h4.pico-color-red-500 s]) (defn set-bar [{:keys [example/ds session params] :as _ctx}] - (jdbc/execute! ds (sql/format {:update :users - :set {:bar (:bar params)} - :where [:= :id (:uid session)]})) - (biff/render (bar-form {:value (:bar params)}))) + (let [players (map str/trim (-> params :players str/split-lines)) + id (-> session :id) + code (game-code)] + (if (> 2 (count players)) + (error-style "Need at least two players") + (do + (jdbc/execute! ds (sql/format {:insert-into :game + :values [{:id id + :code code + :display_session id + :active false}] + :on-conflict :id + :do-update-set [:code :display_session :active]})) -(defn app [{:keys [session example/ds] :as _ctx}] - (let [query (sql/format {:select [:*] - :from [:users] :where [:= :id (:uid session)]}) - {:users/keys [email bar]} (jdbc/execute-one! ds query)] + (jdbc/execute! ds (sql/format {:insert-into :player + :values (for [p players] + {:score 0 + :name p + :game_code code})})) + + {:status 200 + :headers {"HX-redirect" "/game"} + :session {:id id}})))) + +(defn game [{:keys [session params] + :example/keys [ds] + :as _ctx}] + (let [id (:id session) + _ (println id) + _ (println (str id)) + game (jdbc/execute-one! ds (sql/format {:select :* + :from :game + :where [:= :id id]})) + players (jdbc/execute! ds (sql/format {:select :* + :from :player + :where [:= :game_code (:game/code game)]}))] + + (println id) + (pprint/pprint game) (ui/page {} - [:header.container - [:hgroup - {:style - {:display "flex" - :align-items "center" - :justify-content "space-between"}} - "some text for " email - (biff/form - {:action "/auth/signout" - :class "inline"} - [:button {:type "submit"} - "Sign out"])]] + [:div + [:nav + [:ul [:li [:strong "Score the pigs"]]] + [:ul [:li (biff/form {:id "reset" + :hx-get "/reset"} + [:button.secondary "Reset"])]]] + [:h4 id] - (bar-form {:value bar})))) + [:div + [:h4 "Game code is " (:game/code game)] + [:table + [:thead + [:tr + [:th {:scope "col"} "Player"] + [:th {:scope "col"} "Score"]]] + + (into [:tbody] + (for [p players] + [:tr + [:th {:scope "row"} (:player/name p)] + [:td (:player/score p)]]))]]]))) + +(defn control-view [{:keys [session params path-params] + :example/keys [ds] + :as _ctx}] + + (let [code (:code path-params) + players (jdbc/execute! ds (sql/format {:select :* :from :player :where [:= :game_code code]}))] + (ui/page {} + [:div [:nav + [:ul [:li [:strong "Score the pigs"]]] + [:ul [:li (biff/form {:id "reset" + :hx-get "/reset"} + [:button.secondary "Reset"])]]] + [:h5 (str "Game " code)] + [:table + [:thead + [:tr + [:th {:scope "col"} "Player"] + [:th {:scope "col"} "Score"]]] + + (into [:tbody] + (for [p players] + [:tr + [:th {:scope "row"} (:player/name p)] + [:td (:player/score p)]]))]]))) + +(defn connect + [{:keys [session params] + :example/keys [ds] + :as _ctx}] + (let [code (:game-code params) + game (delay (jdbc/execute-one! ds (sql/format {:select :* :from :game + :where [:= :code code]})))] + (println params) + (if (and code @game) + {:status 200 + :session session + :headers {"HX-Redirect" (str "/game/" code)}} + (error-style "Code does not exist.")))) + +(defn app [{:keys [session params] + :example/keys [ds] + :as _ctx}] + (let [id (:id session)] + + (ui/page + {} + [:div + [:nav + [:ul [:li [:strong "Score the pigs"]]] + [:ul [:li (biff/form {:id "reset" + :hx-get "/reset"} + [:button.secondary "Reset"])]]] + [:h4 id] + + [:section + [:button {:_ "on click toggle the *display of #new-game-form"} "New game"]] + + (biff/form + {:hx-post "/set-bar" + :style {:display :none} + :hx-swap "afterend" + :id ::new-game-form} + [:div + [:textarea#players {:type "textarea" :rows "8" :name "players"}] + [:button {:type "submit"} "Start"]]) + (biff/form + {:hx-post "/connect" + :hx-swap "afterend" + :id ::connect-to-game} + [:fieldset + {:role "group"} + [:input + {:name "game-code", + :type "text", + :placeholder "Connect to an existing game"}] + [:input {:type "submit", :value "Connect"}]])]))) (def about-page (ui/page @@ -57,8 +179,13 @@ (def module {:static {"/about/" about-page} - :routes ["/app" {:middleware [mid/wrap-signed-in]} + :routes ["/" {:middleware [wrap-session]} ["" {:get app}] + ["game/" + ["" {:get game}] + [":code" {:get control-view}]] - ["/set-bar" {:post set-bar}]] + ["connect" {:post connect}] + ["reset" {:get reset}] + ["set-bar" {:post set-bar}]] :api-routes [["/api/echo" {:post echo}]]}) diff --git a/src/com/biffweb/my_project/home.clj b/src/com/biffweb/my_project/home.clj index a041235..7754202 100644 --- a/src/com/biffweb/my_project/home.clj +++ b/src/com/biffweb/my_project/home.clj @@ -118,9 +118,4 @@ :on-error "/signin"}})])) (def module - {:routes [["" {:middleware [mid/wrap-redirect-signed-in]} - ["/" {:get home-page}]] - ["/link-sent" {:get link-sent}] - ["/verify-link" {:get verify-email-page}] - ["/signin" {:get signin-page}] - ["/verify-code" {:get enter-code-page}]]}) + {:routes [[]]}) diff --git a/src/com/biffweb/my_project/middleware.clj b/src/com/biffweb/my_project/middleware.clj index 60d3c85..304f0c2 100644 --- a/src/com/biffweb/my_project/middleware.clj +++ b/src/com/biffweb/my_project/middleware.clj @@ -1,8 +1,10 @@ (ns com.biffweb.my-project.middleware (:require [com.biffweb :as biff] [muuntaja.middleware :as muuntaja] + [org.sqids.clojure :as sqids] [ring.middleware.anti-forgery :as csrf] - [ring.middleware.defaults :as rd])) + [ring.middleware.defaults :as rd] + [clojure.string :as str])) (defn wrap-redirect-signed-in [handler] (fn [{:keys [session] :as ctx}] @@ -18,6 +20,18 @@ {:status 303 :headers {"location" "/signin?error=not-signed-in"}}))) +(defn wrap-session [handler] + (fn [{:keys [session] :as req}] + (let [req (update-in req [:path-params :code] str/lower-case)] + (if (some? (:id session)) + (do + (println "found session id" (:id session)) + (handler req)) + + (let [new-id (random-uuid)] + (println "no session id, adding new one:" new-id) + (handler (assoc-in req [:session :id] new-id))))))) + ;; Stick this function somewhere in your middleware stack below if you want to ;; inspect what things look like before/after certain middleware fns run. #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} @@ -60,4 +74,6 @@ biff/wrap-resource biff/wrap-internal-error biff/wrap-ssl - biff/wrap-log-requests)) + + ;; biff/wrap-log-requests + )) diff --git a/src/com/biffweb/my_project/ui.clj b/src/com/biffweb/my_project/ui.clj index 0870df9..cc3c9da 100644 --- a/src/com/biffweb/my_project/ui.clj +++ b/src/com/biffweb/my_project/ui.clj @@ -56,6 +56,7 @@ (update :base/head (fn [head] (concat [[:link {:rel "stylesheet" :href "/css/pico.min.css"}] [:link {:rel "stylesheet" :href "/css/pico.colors.min.css"}] + [:link {:rel "stylesheet" :href "/css/my.css"}] [:script {:src "/js/main.js"}] [:script {:src "/js/htmx-1.9.11.min.js"}] [:script {:src "/js/htmx-1.9.11-ext-ws.min.js"}] diff --git a/src/com/biffweb/my_project/worker.clj b/src/com/biffweb/my_project/worker.clj index 11eee23..4206b99 100644 --- a/src/com/biffweb/my_project/worker.clj +++ b/src/com/biffweb/my_project/worker.clj @@ -21,7 +21,7 @@ (callback job))) (def module - {:tasks [{:task #'print-usage - :schedule #(every-n-minutes 5)}] + {;; :tasks [{:task #'print-usage + ;; :schedule #(every-n-minutes 5)}] :queues [{:id :echo :consumer #'echo-consumer}]})