Persist items to the database
The database that was chosen here is SQLite, because it's dead simple to setup and more than enough for this project. Please note that I took some liberty with the assignment. I chose to use a numeric field for the `id` column of an item. This leverages automatic creation and incrementation of the id by SQLite itself.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ pom.xml.asc
|
|||||||
/.lsp
|
/.lsp
|
||||||
/.nrepl-port
|
/.nrepl-port
|
||||||
/.prepl-port
|
/.prepl-port
|
||||||
|
*.db
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ default, the server listens on port 3000.
|
|||||||
* `reitit`: for handling routes
|
* `reitit`: for handling routes
|
||||||
* `jetty`: web server
|
* `jetty`: web server
|
||||||
* `muuntaja`: JSON handling
|
* `muuntaja`: JSON handling
|
||||||
|
* `next.jdbc`: database interface (SQLite)
|
||||||
|
|
||||||
## Documentation links
|
## Documentation links
|
||||||
|
|
||||||
@@ -29,3 +30,5 @@ The following links proved more than useful when working on this assignment:
|
|||||||
* <https://github.com/metosin/reitit/blob/master/doc/ring/content_negotiation.md>
|
* <https://github.com/metosin/reitit/blob/master/doc/ring/content_negotiation.md>
|
||||||
* <https://ostash.dev/posts/2021-08-22-data-validation-in-clojure/>
|
* <https://ostash.dev/posts/2021-08-22-data-validation-in-clojure/>
|
||||||
* <https://clojurecivitas.github.io/malli/elements_of_malli.html>
|
* <https://clojurecivitas.github.io/malli/elements_of_malli.html>
|
||||||
|
* <https://github.com/metosin/reitit/blob/master/doc/coercion/malli_coercion.md>
|
||||||
|
* <https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.3.1086/doc/getting-started>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
[ring/ring-jetty-adapter "1.15.3"]
|
[ring/ring-jetty-adapter "1.15.3"]
|
||||||
[metosin/reitit "0.10.0"]
|
[metosin/reitit "0.10.0"]
|
||||||
[metosin/reitit-malli "0.10.0"]
|
[metosin/reitit-malli "0.10.0"]
|
||||||
[metosin/muuntaja "0.6.11"]]
|
[metosin/muuntaja "0.6.11"]
|
||||||
|
[com.github.seancorfield/next.jdbc "1.3.1086"]
|
||||||
|
[org.xerial/sqlite-jdbc "3.51.1.0"]]
|
||||||
:main ^:skip-aot yohoho.core
|
:main ^:skip-aot yohoho.core
|
||||||
:target-path "target/%s"
|
:target-path "target/%s"
|
||||||
:profiles {:uberjar {:aot :all
|
:profiles {:uberjar {:aot :all
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||||
[muuntaja.core :as m]
|
[muuntaja.core :as m]
|
||||||
[reitit.coercion.malli :as malli]
|
[reitit.coercion.malli :as malli]
|
||||||
[reitit.ring.coercion :as coercion])
|
[reitit.ring.coercion :as coercion]
|
||||||
|
[next.jdbc :as jdbc]
|
||||||
|
[next.jdbc.sql :as sql]
|
||||||
|
[next.jdbc.result-set :as rs])
|
||||||
(:gen-class))
|
(:gen-class))
|
||||||
|
|
||||||
|
|
||||||
@@ -23,6 +26,38 @@
|
|||||||
[:email Email]])
|
[:email Email]])
|
||||||
|
|
||||||
|
|
||||||
|
;; Database stuff -------------------------------------------------
|
||||||
|
(def db {:dbtype "sqlite" :dbname "yohoho.db"})
|
||||||
|
|
||||||
|
(defn init-db!
|
||||||
|
"Create database structure if needed"
|
||||||
|
[]
|
||||||
|
(jdbc/execute!
|
||||||
|
db
|
||||||
|
["CREATE TABLE IF NOT EXISTS items (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
email TEXT NOT NULL)"]))
|
||||||
|
|
||||||
|
(defn db-store-item!
|
||||||
|
"Store a new item in database and returns it ID"
|
||||||
|
[name email]
|
||||||
|
(let [db-result (sql/insert! db :items {:name name, :email email})]
|
||||||
|
;; SQLite returns the generated ID of the new item like this:
|
||||||
|
;; {:last_insert_rowid() 42}
|
||||||
|
(get db-result (keyword "last_insert_rowid()"))))
|
||||||
|
|
||||||
|
(defn db-get-item
|
||||||
|
"Retrieve an item given its ID"
|
||||||
|
[id]
|
||||||
|
(sql/get-by-id db :items id
|
||||||
|
{:builder-fn rs/as-unqualified-lower-maps}))
|
||||||
|
|
||||||
|
(defn db-get-all-items
|
||||||
|
"Retrieve every item from the database"
|
||||||
|
[]
|
||||||
|
(sql/query db ["select * from items"] {:builder-fn rs/as-unqualified-lower-maps}))
|
||||||
|
|
||||||
;; Handlers -------------------------------------------------------
|
;; Handlers -------------------------------------------------------
|
||||||
(defn ahoy-handler
|
(defn ahoy-handler
|
||||||
[_]
|
[_]
|
||||||
@@ -32,19 +67,18 @@
|
|||||||
(defn get-items-handler
|
(defn get-items-handler
|
||||||
[_]
|
[_]
|
||||||
{:status 200
|
{:status 200
|
||||||
:body {:id 1
|
:body {:items (db-get-all-items)}})
|
||||||
:name "Jack Sparrow"
|
|
||||||
:email "jack.sparrow@triangle.bm"}})
|
|
||||||
|
|
||||||
(defn post-items-handler
|
(defn create-item-handler
|
||||||
[request]
|
[request]
|
||||||
(let [item (:body-params request)
|
(let [item (:body-params request)
|
||||||
id 1
|
|
||||||
name (:name item)
|
name (:name item)
|
||||||
email (:email item)]
|
email (:email item)
|
||||||
|
id (db-store-item! name email)]
|
||||||
|
|
||||||
{:status 201
|
{:status 201
|
||||||
:headers {"Location" (str "/item/" id)}
|
:headers {"Location" (str "/item/" id)}
|
||||||
:body {:id id, :name name, :email email}}))
|
:body (db-get-item id)}))
|
||||||
|
|
||||||
|
|
||||||
;; Custom middleware ----------------------------------------------
|
;; Custom middleware ----------------------------------------------
|
||||||
@@ -68,7 +102,7 @@
|
|||||||
(ring/router
|
(ring/router
|
||||||
[["/ahoy" {:get ahoy-handler}]
|
[["/ahoy" {:get ahoy-handler}]
|
||||||
["/items" {:get get-items-handler
|
["/items" {:get get-items-handler
|
||||||
:post {:handler post-items-handler
|
:post {:handler create-item-handler
|
||||||
:parameters {:body Item}}}]]
|
:parameters {:body Item}}}]]
|
||||||
;; Middlewares:
|
;; Middlewares:
|
||||||
;; - wrap-content-type-json: ensure POST routes are sent JSON payload
|
;; - wrap-content-type-json: ensure POST routes are sent JSON payload
|
||||||
@@ -92,5 +126,8 @@
|
|||||||
"HolidayPirates take-home assignement.
|
"HolidayPirates take-home assignement.
|
||||||
Goal: build a (very) small REST API that exposes to endpoints."
|
Goal: build a (very) small REST API that exposes to endpoints."
|
||||||
[& args]
|
[& args]
|
||||||
|
(println "Initializing SQLite database...")
|
||||||
|
(init-db!)
|
||||||
|
(println "Starting server...")
|
||||||
(http-server/run-jetty app {:port 3000 :join? false})
|
(http-server/run-jetty app {:port 3000 :join? false})
|
||||||
(println "Ahoy! Yo-ho-ho API running on port 3000"))
|
(println "Ahoy! Yo-ho-ho API running on port 3000"))
|
||||||
|
|||||||
Reference in New Issue
Block a user