making progress
This commit is contained in:
parent
162ff2f407
commit
da1e391830
15 changed files with 207 additions and 65 deletions
1
deps.edn
1
deps.edn
|
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
com.taoensso/slf4j-telemere {:mvn/version "1.0.0-beta14"}
|
com.taoensso/slf4j-telemere {:mvn/version "1.0.0-beta14"}
|
||||||
com.taoensso/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"}
|
org.xerial/sqlite-jdbc {:mvn/version "3.45.2.0"}
|
||||||
com.github.seancorfield/honeysql {:mvn/version "2.6.1126"}
|
com.github.seancorfield/honeysql {:mvn/version "2.6.1126"}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
DELETE TABLE IF EXISTS users;
|
|
||||||
|
|
@ -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
|
|
||||||
);
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
DELETE TABLE IF EXISTS auth_code;
|
|
||||||
|
|
@ -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
|
|
||||||
);
|
|
||||||
0
resources/migrations/20250312151923-create-game.down.sql
Normal file
0
resources/migrations/20250312151923-create-game.down.sql
Normal file
7
resources/migrations/20250312151923-create-game.up.sql
Normal file
7
resources/migrations/20250312151923-create-game.up.sql
Normal file
|
|
@ -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
|
||||||
|
);
|
||||||
|
|
@ -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)
|
||||||
|
);
|
||||||
3
resources/public/css/my.css
Normal file
3
resources/public/css/my.css
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
@ -1,48 +1,170 @@
|
||||||
(ns com.biffweb.my-project.app
|
(ns com.biffweb.my-project.app
|
||||||
(:require [com.biffweb :as biff]
|
(:require
|
||||||
[com.biffweb.my-project.middleware :as mid]
|
[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]
|
[com.biffweb.my-project.ui :as ui]
|
||||||
[honey.sql :as sql]
|
[honey.sql :as sql]
|
||||||
[com.biffweb.my-project.settings :as settings]
|
[next.jdbc :as jdbc]
|
||||||
[next.jdbc :as jdbc]))
|
[org.sqids.clojure :as sqids]))
|
||||||
|
|
||||||
(defn bar-form [{:keys [value]}]
|
(defn reset [_]
|
||||||
(biff/form
|
{:status 200
|
||||||
{:hx-post "/app/set-bar"
|
:headers {"HX-Redirect" "/"}
|
||||||
:hx-swap "outerHTML"}
|
:session {}})
|
||||||
[:label {:for "bar"} "Bar: "
|
|
||||||
[:span (pr-str value)]]
|
;; thanks https://stackoverflow.com/a/58098360
|
||||||
[:div
|
(def sqids (sqids/sqids {:alphabet "cdefhjknprtvwxy2345689"}))
|
||||||
[:input#bar {:type "text" :name "bar" :value value}]
|
|
||||||
[:button {:type "submit"} "Update"]]
|
(defn game-code []
|
||||||
"This demonstrates updating a value with HTMX."))
|
(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}]
|
(defn set-bar [{:keys [example/ds session params] :as _ctx}]
|
||||||
(jdbc/execute! ds (sql/format {:update :users
|
(let [players (map str/trim (-> params :players str/split-lines))
|
||||||
:set {:bar (:bar params)}
|
id (-> session :id)
|
||||||
:where [:= :id (:uid session)]}))
|
code (game-code)]
|
||||||
(biff/render (bar-form {:value (:bar params)})))
|
(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}]
|
(jdbc/execute! ds (sql/format {:insert-into :player
|
||||||
(let [query (sql/format {:select [:*]
|
:values (for [p players]
|
||||||
:from [:users] :where [:= :id (:uid session)]})
|
{:score 0
|
||||||
{:users/keys [email bar]} (jdbc/execute-one! ds query)]
|
: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
|
(ui/page
|
||||||
{}
|
{}
|
||||||
[:header.container
|
[:div
|
||||||
[:hgroup
|
[:nav
|
||||||
{:style
|
[:ul [:li [:strong "Score the pigs"]]]
|
||||||
{:display "flex"
|
[:ul [:li (biff/form {:id "reset"
|
||||||
:align-items "center"
|
:hx-get "/reset"}
|
||||||
:justify-content "space-between"}}
|
[:button.secondary "Reset"])]]]
|
||||||
"some text for " email
|
[:h4 id]
|
||||||
(biff/form
|
|
||||||
{:action "/auth/signout"
|
|
||||||
:class "inline"}
|
|
||||||
[:button {:type "submit"}
|
|
||||||
"Sign out"])]]
|
|
||||||
|
|
||||||
(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
|
(def about-page
|
||||||
(ui/page
|
(ui/page
|
||||||
|
|
@ -57,8 +179,13 @@
|
||||||
|
|
||||||
(def module
|
(def module
|
||||||
{:static {"/about/" about-page}
|
{:static {"/about/" about-page}
|
||||||
:routes ["/app" {:middleware [mid/wrap-signed-in]}
|
:routes ["/" {:middleware [wrap-session]}
|
||||||
["" {:get app}]
|
["" {: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}]]})
|
:api-routes [["/api/echo" {:post echo}]]})
|
||||||
|
|
|
||||||
|
|
@ -118,9 +118,4 @@
|
||||||
:on-error "/signin"}})]))
|
:on-error "/signin"}})]))
|
||||||
|
|
||||||
(def module
|
(def module
|
||||||
{:routes [["" {:middleware [mid/wrap-redirect-signed-in]}
|
{:routes [[]]})
|
||||||
["/" {:get home-page}]]
|
|
||||||
["/link-sent" {:get link-sent}]
|
|
||||||
["/verify-link" {:get verify-email-page}]
|
|
||||||
["/signin" {:get signin-page}]
|
|
||||||
["/verify-code" {:get enter-code-page}]]})
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
(ns com.biffweb.my-project.middleware
|
(ns com.biffweb.my-project.middleware
|
||||||
(:require [com.biffweb :as biff]
|
(:require [com.biffweb :as biff]
|
||||||
[muuntaja.middleware :as muuntaja]
|
[muuntaja.middleware :as muuntaja]
|
||||||
|
[org.sqids.clojure :as sqids]
|
||||||
[ring.middleware.anti-forgery :as csrf]
|
[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]
|
(defn wrap-redirect-signed-in [handler]
|
||||||
(fn [{:keys [session] :as ctx}]
|
(fn [{:keys [session] :as ctx}]
|
||||||
|
|
@ -18,6 +20,18 @@
|
||||||
{:status 303
|
{:status 303
|
||||||
:headers {"location" "/signin?error=not-signed-in"}})))
|
: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
|
;; Stick this function somewhere in your middleware stack below if you want to
|
||||||
;; inspect what things look like before/after certain middleware fns run.
|
;; inspect what things look like before/after certain middleware fns run.
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
|
|
@ -60,4 +74,6 @@
|
||||||
biff/wrap-resource
|
biff/wrap-resource
|
||||||
biff/wrap-internal-error
|
biff/wrap-internal-error
|
||||||
biff/wrap-ssl
|
biff/wrap-ssl
|
||||||
biff/wrap-log-requests))
|
|
||||||
|
;; biff/wrap-log-requests
|
||||||
|
))
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@
|
||||||
(update :base/head (fn [head]
|
(update :base/head (fn [head]
|
||||||
(concat [[:link {:rel "stylesheet" :href "/css/pico.min.css"}]
|
(concat [[:link {:rel "stylesheet" :href "/css/pico.min.css"}]
|
||||||
[:link {:rel "stylesheet" :href "/css/pico.colors.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/main.js"}]
|
||||||
[:script {:src "/js/htmx-1.9.11.min.js"}]
|
[:script {:src "/js/htmx-1.9.11.min.js"}]
|
||||||
[:script {:src "/js/htmx-1.9.11-ext-ws.min.js"}]
|
[:script {:src "/js/htmx-1.9.11-ext-ws.min.js"}]
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
(callback job)))
|
(callback job)))
|
||||||
|
|
||||||
(def module
|
(def module
|
||||||
{:tasks [{:task #'print-usage
|
{;; :tasks [{:task #'print-usage
|
||||||
:schedule #(every-n-minutes 5)}]
|
;; :schedule #(every-n-minutes 5)}]
|
||||||
:queues [{:id :echo
|
:queues [{:id :echo
|
||||||
:consumer #'echo-consumer}]})
|
:consumer #'echo-consumer}]})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue