Skip to content

Commit

Permalink
use postgresql instead of datomic for porteiro
Browse files Browse the repository at this point in the history
  • Loading branch information
macielti committed Nov 2, 2024
1 parent 03b99ee commit eb66ceb
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 28 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ of [keepachangelog.com](http://keepachangelog.com/).

## [Unreleased]

## [30.68.70] - 2024-11-02

### Changed

- Use PostgreSQL component (Integrant) for Porteiro source code instead of Datomic.

## [30.67.70] - 2024-11-01

### Added
Expand Down Expand Up @@ -990,7 +996,9 @@ of [keepachangelog.com](http://keepachangelog.com/).

- Add `loose-schema` function.

[Unreleased]: https://github.com/macielti/common-clj/compare/v30.67.70...HEAD
[Unreleased]: https://github.com/macielti/common-clj/compare/v30.68.70...HEAD

[30.68.70]: https://github.com/macielti/common-clj/compare/v30.67.70...v30.68.70

[30.67.70]: https://github.com/macielti/common-clj/compare/v30.66.70...v30.67.70

Expand Down
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject net.clojars.macielti/common-clj "30.67.70"
(defproject net.clojars.macielti/common-clj "30.68.70"
:description "Just common Clojure code that I use across projects"
:url "https://github.com/macielti/common-clj"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
Expand Down
7 changes: 7 additions & 0 deletions resources/migrations/002.create_customer_table.next.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE customers (
id UUID PRIMARY KEY,
username VARCHAR(255) NOT NULL,
name VARCHAR(255),
roles TEXT[],
hashed_password VARCHAR(255) NOT NULL
);
24 changes: 24 additions & 0 deletions src/common_clj/io/interceptors/postgresql.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(ns common-clj.io.interceptors.postgresql
(:require [common-clj.error.core :as common-error]
[io.pedestal.interceptor :as pedestal.interceptor]
[pg.core :as pg]
[pg.pool :as pool]
[schema.core :as s]))

(s/defn resource-existence-check-interceptor
"resource-identifier-fn -> function used to extract param used to query the resource, must receive a context as argument.
sql-query -> datomic query that will try to find the resource using the resource identifier"
[resource-identifier-fn
sql-query]
(pedestal.interceptor/interceptor {:name ::resource-existence-check-interceptor
:enter (fn [{{:keys [components]} :request :as context}]
(let [pool (:postgresql components)
resource-identifier (resource-identifier-fn context)
resource (-> (pool/with-connection [database-conn pool]
(pg/execute database-conn sql-query {:params [resource-identifier]})) first)]
(when-not resource
(common-error/http-friendly-exception 404
"resource-not-found"
"Resource could not be found"
"Not Found")))
context)}))
12 changes: 12 additions & 0 deletions src/common_clj/porteiro/adapters/customer.clj
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,15 @@
(s/defn wire->internal-role :- s/Keyword
[wire-role :- s/Str]
(camel-snake-kebab/->kebab-case-keyword wire-role))

(s/defn internal-role->wire-role :- s/Str
[wire-role :- s/Keyword]
(camel-snake-kebab/->snake_case_string wire-role))

(s/defn postgresql->internal :- models.customer/Customer
[{:keys [id username roles name hashed_password]}]
(medley/assoc-some {:customer/id id
:customer/username username
:customer/hashed-password hashed_password
:customer/roles (map wire->internal-role roles)}
:customer/name name))
9 changes: 7 additions & 2 deletions src/common_clj/porteiro/admin.clj
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
(ns common-clj.porteiro.admin
(:require [common-clj.porteiro.db.datomic.customer :as database.customer]
(:require [common-clj.porteiro.db.datomic.customer :as datomic.customer]
[common-clj.porteiro.db.postgresql.customer :as postgresql.customer]
[common-clj.porteiro.diplomat.http-server.customer :as diplomat.http-server.customer]
[datomic.api :as d]
[integrant.core :as ig]
[pg.pool :as pool]
[taoensso.timbre :as log]))

(defmethod ig/init-key ::admin
[_ {:keys [components]}]
(log/info :starting ::admin)
(let [{:keys [admin-customer-seed]} (:config components)]
(when-not (database.customer/by-username (get-in admin-customer-seed [:customer :username]) (-> components :datomic d/db))
(when-not (if (:datomic components)
(datomic.customer/by-username (get-in admin-customer-seed [:customer :username]) (-> components :datomic d/db))
(pool/with-connection [database-conn (:postgresql components)]
(postgresql.customer/by-username (get-in admin-customer-seed [:customer :username]) database-conn)))
(let [wire-customer-id (-> (diplomat.http-server.customer/create-customer! {:json-params admin-customer-seed
:components components})
(get-in [:body :customer :id]))]
Expand Down
48 changes: 38 additions & 10 deletions src/common_clj/porteiro/controllers/customer.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@
[buddy.sign.jwt :as jwt]
[common-clj.error.core :as common-error]
[common-clj.porteiro.adapters.customer :as adapters.customer]
[common-clj.porteiro.db.datomic.customer :as database.customer]
[common-clj.porteiro.db.datomic.customer :as datomic.customer]
[common-clj.porteiro.db.postgresql.customer :as postgresql.customer]
[common-clj.porteiro.models.customer :as models.customer]
[datomic.api :as d]
[java-time.api :as jt]
[pg.pool :as pool]
[schema.core :as s]))

(s/defn create-customer! :- models.customer/Customer
[customer :- models.customer/Customer
datomic]
(database.customer/insert! customer datomic))
datomic
postgresql]
(if datomic
(datomic.customer/insert! customer datomic)
(pool/with-connection [conn postgresql]
(postgresql.customer/insert! customer conn))))

(s/defn ->token :- s/Str
[map :- {s/Keyword s/Any}
Expand All @@ -24,8 +30,12 @@
(s/defn authenticate-customer! :- s/Str
[{:keys [username password]} :- models.customer/CustomerAuthentication
{:keys [jwt-secret]}
database]
(let [{:customer/keys [hashed-password] :as customer} (database.customer/by-username username database)]
datomic
postgresql]
(let [{:customer/keys [hashed-password] :as customer} (if datomic
(datomic.customer/by-username username (d/db datomic))
(pool/with-connection [conn postgresql]
(postgresql.customer/by-username username conn)))]
(if (and customer (:valid (hashers/verify password hashed-password)))
(-> {:customer (adapters.customer/internal->wire customer)}
(->token jwt-secret))
Expand All @@ -34,13 +44,31 @@
"Wrong username or/and password"
"Customer is trying to login using invalid credentials"))))

(s/defn add-role! :- models.customer/Customer
(defmulti add-role!
(fn [_customer-id _role datomic postgresql]
(cond datomic :datomic
postgresql :postgresql)))

(s/defmethod add-role! :datomic
[customer-id :- s/Uuid
role :- s/Keyword
datomic]
(if (database.customer/lookup customer-id (d/db datomic))
(do (database.customer/add-role! customer-id role datomic) ;;TODO: Check how to make this transaction already return the updated entity
(database.customer/lookup customer-id (d/db datomic)))
datomic
_]
(if (datomic.customer/lookup customer-id (d/db datomic))
(do (datomic.customer/add-role! customer-id role datomic) ;;TODO: Check how to make this transaction already return the updated entity
(datomic.customer/lookup customer-id (d/db datomic)))
(throw (ex-info "Customer not found"
{:status 404
:cause "Customer not found"}))))

(s/defmethod add-role! :postgresql
[customer-id :- s/Uuid
role :- s/Keyword
_
postgresql]
(pool/with-connection [conn postgresql]
(if (postgresql.customer/lookup customer-id conn)
(postgresql.customer/add-role! customer-id role conn)
(throw (ex-info "Customer not found"
{:status 404
:cause "Customer not found"})))))
43 changes: 43 additions & 0 deletions src/common_clj/porteiro/db/postgresql/customer.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
(ns common-clj.porteiro.db.postgresql.customer
(:require [common-clj.porteiro.adapters.customer :as adapters.customer]
[common-clj.porteiro.models.customer :as models.customer]
[pg.core :as pg]
[schema.core :as s]))

(s/defn insert! :- models.customer/Customer
[{:customer/keys [id username name roles hashed-password]} :- models.customer/Customer
database-conn]
(-> (pg/execute database-conn
"INSERT INTO customers (id, username, name, roles, hashed_password) VALUES ($1, $2, $3, $4, $5)
returning *"
{:params [id username name (or roles []) hashed-password]})
first
adapters.customer/postgresql->internal))

(s/defn by-username :- (s/maybe models.customer/Customer)
[username :- s/Str
database-conn]
(some-> (pg/execute database-conn
"SELECT * FROM customers WHERE username = $1"
{:params [username]})
first
adapters.customer/postgresql->internal))

(s/defn lookup :- (s/maybe models.customer/Customer)
[customer-id :- s/Uuid
database-conn]
(some-> (pg/execute database-conn
"SELECT * FROM customers WHERE id = $1"
{:params [customer-id]})
first
adapters.customer/postgresql->internal))

(s/defn add-role! :- (s/maybe models.customer/Customer)
[customer-id :- s/Uuid
role :- s/Keyword
database-conn]
(some-> (pg/execute database-conn
"UPDATE customers SET roles = array_append(roles, $1) WHERE id = $2 returning *"
{:params [(adapters.customer/internal-role->wire-role role) customer-id]})
first
adapters.customer/postgresql->internal))
17 changes: 8 additions & 9 deletions src/common_clj/porteiro/diplomat/http_server/customer.clj
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
(ns common-clj.porteiro.diplomat.http-server.customer
(:require [common-clj.porteiro.adapters.customer :as adapters.customer]
[common-clj.porteiro.controllers.customer :as controllers.customer]
[datomic.api :as d]
[schema.core :as s])
(:import (java.util UUID)))

(s/defn create-customer!
[{{:keys [customer]} :json-params
{:keys [datomic]} :components}]
[{{:keys [customer]} :json-params
{:keys [datomic postgresql]} :components}]
{:status 201
:body {:customer (-> (adapters.customer/wire->internal customer)
(controllers.customer/create-customer! datomic)
(controllers.customer/create-customer! datomic postgresql)
adapters.customer/internal->wire)}})

(s/defn authenticate-customer!
[{{:keys [customer]} :json-params
{:keys [datomic config]} :components}]
[{{:keys [customer]} :json-params
{:keys [datomic postgresql config]} :components}]
{:status 200
:body (-> (adapters.customer/wire->internal-customer-authentication customer)
(controllers.customer/authenticate-customer! config (d/db datomic))
(controllers.customer/authenticate-customer! config datomic postgresql)
adapters.customer/customer-token->wire)})

(s/defn add-role!
[{{wire-customer-id :customer-id
wire-role :role} :query-params
{:keys [datomic]} :components}]
{:keys [datomic postgresql]} :components}]
{:status 200
:body (-> (UUID/fromString wire-customer-id)
(controllers.customer/add-role! (adapters.customer/wire->internal-role wire-role) datomic)
(controllers.customer/add-role! (adapters.customer/wire->internal-role wire-role) datomic postgresql)
adapters.customer/internal->wire)})
15 changes: 10 additions & 5 deletions src/common_clj/porteiro/interceptors/customer.clj
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
(ns common-clj.porteiro.interceptors.customer
(:require [common-clj.error.core :as common-error]
[common-clj.porteiro.db.datomic.customer :as database.customer]
[datomic.api :as d]))
[common-clj.porteiro.db.datomic.customer :as datomic.customer]
[common-clj.porteiro.db.postgresql.customer :as postgresql.customer]
[datomic.api :as d]
[pg.pool :as pool]))

(def username-already-in-use-interceptor
{:name ::username-already-in-use-interceptor
:enter (fn [{{json-params :json-params
{:keys [datomic]} :components} :request :as context}]
:enter (fn [{{json-params :json-params
{:keys [datomic postgresql]} :components} :request :as context}]
(let [username (get-in json-params [:customer :username] "")
customer (database.customer/by-username username (d/db datomic))]
customer (if datomic
(datomic.customer/by-username username (d/db datomic))
(pool/with-connection [database-conn postgresql]
(postgresql.customer/by-username username database-conn)))]
(when-not (empty? customer)
(common-error/http-friendly-exception 409
"not-unique"
Expand Down
59 changes: 59 additions & 0 deletions test/unit/common_clj/porteiro/db/postgresql/customer_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
(ns common-clj.porteiro.db.postgresql.customer-test
(:require [clojure.test :refer :all]
[common-clj.integrant-components.postgresql :as postgresql]
[common-clj.porteiro.db.postgresql.customer :as database.customer]
[common-clj.porteiro.models.customer :as models.customer]
[matcher-combinators.test :refer [match?]]
[common-clj.test.helper.schema :as test.helper.schema]
[schema.test :as s]))

(def customer-id (random-uuid))
(def customer
(test.helper.schema/generate models.customer/Customer
{:customer/id customer-id
:customer/username "magal"}))

(s/deftest insert-test
(testing "Should insert a customer"
(let [conn (postgresql/mocked-postgresql-conn)]
(is (match? {:customer/hashed-password string?
:customer/id uuid?
:customer/roles ()
:customer/username string?}
(database.customer/insert! customer conn))))))

(s/deftest by-username-test
(testing "Should be able to query a customer by username"
(let [conn (postgresql/mocked-postgresql-conn)]
(database.customer/insert! customer conn)
(is (match? {:customer/id uuid?
:customer/username "magal"
:customer/hashed-password string?
:customer/roles []}
(database.customer/by-username "magal" conn)))

(is (nil? (database.customer/by-username "random-username" conn))))))

(s/deftest lookup-test
(testing "Should be able to query a customer by id"
(let [conn (postgresql/mocked-postgresql-conn)]
(database.customer/insert! customer conn)
(is (match? {:customer/id uuid?
:customer/username "magal"
:customer/hashed-password string?
:customer/roles []}
(database.customer/lookup customer-id conn)))

(is (nil? (database.customer/lookup (random-uuid) conn))))))

(s/deftest add-role-test
(testing "Should be able to query a customer by id"
(let [conn (postgresql/mocked-postgresql-conn)]
(database.customer/insert! customer conn)
(is (match? {:customer/id uuid?
:customer/username "magal"
:customer/hashed-password string?
:customer/roles [:test]}
(database.customer/add-role! customer-id :test conn)))

(is (nil? (database.customer/lookup (random-uuid) conn))))))

0 comments on commit eb66ceb

Please sign in to comment.