Skip to content

Commit 20b7a91

Browse files
committed
Implement user resolvers, db config, and transit config.
1 parent 4bf3050 commit 20b7a91

File tree

17 files changed

+280
-33
lines changed

17 files changed

+280
-33
lines changed

Dockerfile.flyway

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM flyway/flyway:6.3.2-alpine
2+
3+
COPY migrations /flyway/sql
4+
5+
ENTRYPOINT ["/bin/sh"]
6+
7+
CMD ["-c", "/flyway/flyway -url=jdbc:postgresql://${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB} -user=${POSTGRES_USER} -password=${POSTGRES_PASSWORD} -connectRetries=60 migrate"]

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ In order to run this application, you need to have the following installed:
88
* [node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
99
* [java](https://adoptopenjdk.net/)
1010
* [clojure cli](https://clojure.org/guides/getting_started)
11+
* [docker](https://docs.docker.com/get-docker/)
12+
* [docker compose](https://docs.docker.com/compose/install/)
1113

1214
With these installed, run
1315
```bash
@@ -18,9 +20,23 @@ to install javascript dependencies.
1820

1921
## Running
2022

23+
### Database
24+
25+
We run a local postgres database inside docker-compose. To start the database, run
26+
```bash
27+
docker-compose up -d
28+
```
29+
After starting the database, you'll need to run migrations, which you can do with
30+
```bash
31+
./scripts/migrate-local.sh
32+
```
33+
There's also a convenience script available at `./scripts/psql` to open up a psql client connected to the database. There are resources to learn more about working with a database inside docker compose in the [documentation](https://docs.docker.com/compose/).
34+
35+
### Application
36+
2137
To run this application in development mode, start a shadow-cljs server with
2238
```bash
23-
npx shadow-cljs -d nrepl:0.7.0-beta1 -d cider/piggieback:0.4.2 -d refactor-nrepl:2.5.0 -d cider/cider-nrepl:0.25.0-SNAPSHOT server
39+
npx shadow-cljs -d nrepl:0.7.0 -d cider/piggieback:0.4.2 -d refactor-nrepl:2.5.0 -d cider/cider-nrepl:0.25.0-SNAPSHOT server
2440
```
2541

2642
With this running, you can control compilation by accessing the shadow-cljs server at http://localhost:9630. In addition, this command will start up an nrepl server, which you should connect to with your preferred REPL. Alternatively, CIDER users can run `cider-jack-in-clj&cljs`.

deps.edn

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
{:paths ["src" "resources"]
2-
:deps {com.fulcrologic/fulcro {:mvn/version "3.1.22"}
3-
com.taoensso/timbre {:mvn/version "4.10.0"}
4-
ring/ring-core {:mvn/version "1.8.0"}
5-
ring/ring-defaults {:mvn/version "0.3.2"}
6-
bk/ring-gzip {:mvn/version "0.3.0"}
7-
mount {:mvn/version "0.1.16"}
8-
aero {:mvn/version "1.1.5"}
9-
http-kit {:mvn/version "2.4.0-alpha6"}}
2+
:deps {com.fulcrologic/fulcro {:mvn/version "3.1.22"}
3+
com.wsscode/pathom {:mvn/version "2.2.31"}
4+
edn-query-language/eql {:mvn/version "0.0.9"}
5+
com.taoensso/timbre {:mvn/version "4.10.0"}
6+
com.cognitect/transit-clj {:mvn/version "1.0.324"}
7+
ring/ring-core {:mvn/version "1.8.0"}
8+
ring/ring-defaults {:mvn/version "0.3.2"}
9+
bk/ring-gzip {:mvn/version "0.3.0"}
10+
mount {:mvn/version "0.1.16"}
11+
seancorfield/next.jdbc {:mvn/version "1.0.409"}
12+
honeysql {:mvn/version "0.9.10"}
13+
nilenso/honeysql-postgres {:mvn/version "0.2.6"}
14+
org.postgresql/postgresql {:mvn/version "42.2.11"}
15+
hikari-cp {:mvn/version "2.11.0"}
16+
aero {:mvn/version "1.1.6"}
17+
http-kit {:mvn/version "2.4.0-alpha6"}}
1018
:aliases {:dev {:extra-paths ["dev"]
1119
:jvm-opts ["-Dtrace"]
1220
:extra-deps {org.clojure/tools.namespace {:mvn/version "0.3.1"}
1321
org.clojure/clojurescript {:mvn/version "1.10.597"}
1422
com.fulcrologic/semantic-ui-wrapper {:mvn/version "1.0.0"}
1523
org.clojure/core.async {:mvn/version "1.0.567"}
24+
com.cognitect/transit-cljs {:mvn/version "0.8.256"}
1625
com.wsscode/async {:mvn/version "1.0.2"}
1726
clj-commons/pushy {:mvn/version "0.3.10"}
18-
edn-query-language/eql {:mvn/version "0.0.9"}
1927
thheller/shadow-cljs {:mvn/version "2.8.83"}
2028
binaryage/devtools {:mvn/version "0.9.10"}}}
2129
:outdated {:extra-deps {olical/depot {:mvn/version "1.8.4"}}

docker-compose.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: "3.7"
2+
services:
3+
postgres:
4+
image: postgres:12.2-alpine
5+
restart: always
6+
environment:
7+
POSTGRES_PASSWORD: password
8+
ports:
9+
- "15432:5432"
10+
volumes:
11+
- db_data:/var/lib/postgresql/data
12+
volumes:
13+
db_data:

migrations/V1__CreateUserTable.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
2+
3+
CREATE TABLE "user" (
4+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
5+
email text NOT NULL UNIQUE,
6+
auth0_id text NOT NULL UNIQUE,
7+
created_at timestamp with time zone DEFAULT now() NOT NULL
8+
);

resources/config.edn

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
{:port #long #or [#env PORT 3000]}
1+
{:database-spec {:username #or [#env POSTGRES_USER "postgres"]
2+
:password #or [#env POSTGRES_PASSWORD "password"]
3+
:server-name #or [#env POSTGRES_HOSTNAME "localhost"]
4+
:port-number #long #or [#env POSTGRES_PORT 15432]
5+
:database-name #or [#env POSTGRES_DB "postgres"]
6+
:sslmode #or [#env POSTGRES_SSLMODE "disable"]}
7+
:port #long #or [#env PORT 3000]}

scripts/migrate-local.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
3+
pushd "$(git rev-parse --show-toplevel)"
4+
5+
docker build \
6+
-f Dockerfile.flyway \
7+
-t cpodonnell/mygiftlist-blog:migrate-local .
8+
9+
docker run --rm \
10+
--network mygiftlist-blog_default \
11+
-e POSTGRES_USER=postgres \
12+
-e POSTGRES_PASSWORD=password \
13+
-e POSTGRES_HOSTNAME=postgres \
14+
-e POSTGRES_PORT=5432 \
15+
-e POSTGRES_DB=postgres \
16+
cpodonnell/mygiftlist-blog:migrate-local
17+
18+
popd

scripts/psql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
docker-compose exec -u postgres postgres psql

src/rocks/mygiftlist/application.cljs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
(ns rocks.mygiftlist.application
22
(:require [com.fulcrologic.fulcro.application :as app]
33
[com.fulcrologic.fulcro.rendering.keyframe-render2 :as keyframe-render2]
4-
[com.fulcrologic.fulcro.networking.http-remote :as http-remote]))
4+
[com.fulcrologic.fulcro.networking.http-remote :as http-remote]
5+
[rocks.mygiftlist.transit :as transit]))
56

6-
(defonce SPA (app/fulcro-app {:optimized-render! keyframe-render2/render!
7-
:remotes {:remote (http-remote/fulcro-http-remote {})}}))
7+
(defonce SPA
8+
(app/fulcro-app
9+
{:optimized-render! keyframe-render2/render!
10+
:remotes {:remote (http-remote/fulcro-http-remote
11+
{:request-middleware
12+
(http-remote/wrap-fulcro-request
13+
identity transit/write-handlers)
14+
:response-middleware
15+
(http-remote/wrap-fulcro-response
16+
identity transit/read-handlers)})}}))

src/rocks/mygiftlist/client.cljs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
(:require
33
[rocks.mygiftlist.application :refer [SPA]]
44
[rocks.mygiftlist.authentication :as auth]
5+
[rocks.mygiftlist.model.user :as m.user]
6+
[rocks.mygiftlist.type.user :as user]
57
[com.fulcrologic.fulcro.algorithms.normalized-state :refer [swap!->]]
8+
[com.fulcrologic.fulcro.algorithms.tempid :as tempid]
69
[com.fulcrologic.fulcro.application :as app]
710
[com.fulcrologic.fulcro.components :as comp :refer [defsc]]
811
[com.fulcrologic.fulcro.dom :as dom]
@@ -194,7 +197,9 @@
194197
(do (comp/transact! SPA
195198
[(route-to {:path (url->path js/window.location.pathname)})])
196199
(let [{:keys [sub email]} (<! (auth/get-user-info))]
197-
(comp/transact! SPA [(set-current-user
198-
#:user{:id sub :email email})])))
200+
(comp/transact! SPA [(m.user/set-current-user
201+
#::user{:id (tempid/tempid)
202+
:auth0-id sub
203+
:email email})])))
199204
(comp/transact! SPA
200205
[(route-to {:path (dr/path-to LoginForm)})])))))

src/rocks/mygiftlist/config.clj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@
44

55
(def ^:private config (aero/read-config (io/resource "config.edn")))
66

7+
(def database-spec (:database-spec config))
8+
79
(def port (:port config))

src/rocks/mygiftlist/db.clj

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
(ns rocks.mygiftlist.db
2+
(:require [rocks.mygiftlist.config :as config]
3+
[mount.core :refer [defstate]]
4+
[hikari-cp.core :as pool]
5+
[next.jdbc :as jdbc]
6+
[next.jdbc.result-set :as result-set]
7+
[next.jdbc.prepare :as p]
8+
[clojure.string :as str]
9+
[honeysql.core :as sql]
10+
honeysql-postgres.format))
11+
12+
(def datasource-options
13+
(merge {:auto-commit true
14+
:read-only false
15+
:connection-timeout 30000
16+
:validation-timeout 5000
17+
:idle-timeout 600000
18+
:max-lifetime 1800000
19+
:minimum-idle 10
20+
:maximum-pool-size 10
21+
:pool-name "db-pool"
22+
:adapter "postgresql"
23+
:register-mbeans false}
24+
config/database-spec))
25+
26+
(defstate pool
27+
:start (pool/make-datasource datasource-options)
28+
:stop (pool/close-datasource pool))
29+
30+
(defn- qualify
31+
"Given a kebab-case database table name, returns the namespace that
32+
attributes coming from that table should have."
33+
[table]
34+
(when (seq table)
35+
(str "rocks.mygiftlist.type." table)))
36+
37+
(defn- snake->kebab [s]
38+
(str/replace s #"_" "-"))
39+
40+
(defn- as-qualified-kebab-maps [rs opts]
41+
(result-set/as-modified-maps rs
42+
(assoc opts
43+
:qualifier-fn (comp qualify snake->kebab)
44+
:label-fn snake->kebab)))
45+
46+
(def ^:private query-opts {:builder-fn as-qualified-kebab-maps})
47+
48+
(defn execute! [conn sql-map]
49+
(jdbc/execute! conn
50+
(sql/format sql-map :quoting :ansi)
51+
query-opts))
52+
53+
(defn execute-one! [conn sql-map]
54+
(jdbc/execute-one! conn
55+
(sql/format sql-map :quoting :ansi)
56+
query-opts))
57+
58+
(extend-protocol result-set/ReadableColumn
59+
60+
;; Automatically convert java.sql.Array into clojure vector in query
61+
;; results
62+
java.sql.Array
63+
(read-column-by-label ^clojure.lang.PersistentVector
64+
[^java.sql.Array v _]
65+
(vec (.getArray v)))
66+
(read-column-by-index ^clojure.lang.PersistentVector
67+
[^java.sql.Array v _2 _3]
68+
(vec (.getArray v)))
69+
70+
;; Output java.time.LocalDate instead of java.sql.Date in query
71+
;; results
72+
java.sql.Date
73+
(read-column-by-label ^java.time.LocalDate
74+
[^java.sql.Date v _]
75+
(.toLocalDate v))
76+
(read-column-by-index ^java.time.LocalDate
77+
[^java.sql.Date v _2 _3]
78+
(.toLocalDate v))
79+
80+
;; Output java.time.Instant instead of java.sql.Timestamp in query
81+
;; results
82+
java.sql.Timestamp
83+
(read-column-by-label ^java.time.Instant
84+
[^java.sql.Timestamp v _]
85+
(.toInstant v))
86+
(read-column-by-index ^java.time.Instant
87+
[^java.sql.Timestamp v _2 _3]
88+
(.toInstant v)))
89+
90+
91+
(extend-protocol p/SettableParameter
92+
93+
;; Accept java.time.Instant as a query param
94+
java.time.Instant
95+
(set-parameter
96+
[^java.time.Instant v ^java.sql.PreparedStatement ps ^long i]
97+
(.setTimestamp ps i (java.sql.Timestamp/from v)))
98+
99+
;; Accept java.time.LocalDate as a query param
100+
java.time.LocalDate
101+
(set-parameter
102+
[^java.time.LocalDate v ^java.sql.PreparedStatement ps ^long i]
103+
(.setTimestamp ps i (java.sql.Timestamp/valueOf (.atStartOfDay v)))))

src/rocks/mygiftlist/model/user.clj

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
11
(ns rocks.mygiftlist.model.user
22
(:require
33
[com.wsscode.pathom.connect :as pc :refer [defresolver defmutation]]
4+
[rocks.mygiftlist.db :as db]
45
[rocks.mygiftlist.type.user :as user]))
56

6-
(defonce users (atom {}))
7-
8-
(defresolver user-by-id [env {::user/keys [id]}]
7+
(defresolver user-by-id
8+
[{::db/keys [pool]} {::user/keys [id]}]
99
{::pc/input #{::user/id}
10-
::pc/output [::user/email]}
11-
(get @users id))
10+
::pc/output [::user/id ::user/email ::user/auth0-id ::user/created-at]}
11+
(db/execute-one! pool
12+
{:select [:id :email :auth0_id :created_at]
13+
:from [:user]
14+
:where [:= id :id]}))
15+
16+
(defn- assign-tempid [{::user/keys [id] :as user} tempid]
17+
(assoc user :tempids {tempid id}))
1218

13-
(defmutation insert-user [env {::user/keys [id] :as user}]
14-
{::pc/params #{::user/id ::user/email}
19+
(defmutation insert-user
20+
[{::db/keys [pool]} {::user/keys [id auth0-id email] :as user}]
21+
{::pc/params #{::user/email ::user/auth0-id}
1522
::pc/output [::user/id]}
16-
(swap! users assoc id user))
23+
(cond-> (db/execute-one! pool
24+
{:insert-into :user
25+
:values [{:auth0_id auth0-id
26+
:email email}]
27+
:upsert {:on-conflict [:auth0_id]
28+
:do-update-set [:email]}
29+
:returning [:id]})
30+
id (assign-tempid id)))
1731

1832
(def user-resolvers
1933
[user-by-id
2034
insert-user])
21-
22-
(comment
23-
@users
24-
)

src/rocks/mygiftlist/model/user.cljs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
(ns rocks.mygiftlist.model.user
2+
(:require
3+
[rocks.mygiftlist.type.user :as user]
4+
[edn-query-language.core :as eql]
5+
[com.fulcrologic.fulcro.algorithms.normalized-state :refer [swap!->]]
6+
[com.fulcrologic.fulcro.mutations :as m :refer [defmutation]]))
7+
8+
(defmutation set-current-user [{::user/keys [id auth0-id email]
9+
:as user}]
10+
(action [{:keys [state]}]
11+
(swap!-> state
12+
(assoc-in [:component/id :current-user] [::user/id id])
13+
(assoc-in [::user/id id] user)))
14+
(remote [_]
15+
(eql/query->ast1 `[(insert-user
16+
#::user{:id ~id
17+
:auth0-id ~auth0-id
18+
:email ~email})])))

src/rocks/mygiftlist/parser.clj

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[com.wsscode.pathom.connect :as pc :refer [defresolver]]
66
[com.wsscode.pathom.core :as p]
77
[clojure.core.async :refer [<!!]]
8+
[rocks.mygiftlist.db :as db]
89
[rocks.mygiftlist.type.user :as user]
910
[rocks.mygiftlist.model.user :as m.user]))
1011

@@ -57,7 +58,8 @@
5758
;; things to the resolver/mutation
5859
;; environment, like the server config,
5960
;; database connections, etc.
60-
env))
61+
(assoc env
62+
::db/pool db/pool)))
6163
(preprocess-parser-plugin log-requests)
6264
p/error-handler-plugin
6365
p/request-cache-plugin
@@ -74,6 +76,8 @@
7476
tx))))))
7577

7678
(comment
77-
(parser {} `[(m.user/insert-user #::user{:id 1 :email "me@example.com"})])
78-
(parser {} [{[::user/id 2] [::user/id ::user/email]}])
79+
(parser {} `[{(m.user/insert-user #::user{:auth0-id "auth0|abc123" :email "me@example.com"})
80+
[::user/id]}])
81+
(parser {} [{[::user/id #uuid "8d5c93d3-7d22-4925-bc66-118e5e3d7238"]
82+
[::user/id ::user/auth0-id ::user/email]}])
7983
)

0 commit comments

Comments
 (0)