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/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"}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
(: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}]]})
|
||||
|
|
|
|||
|
|
@ -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 [[]]})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
))
|
||||
|
|
|
|||
|
|
@ -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"}]
|
||||
|
|
|
|||
|
|
@ -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}]})
|
||||
|
|
|
|||
Loading…
Reference in a new issue