diff --git a/.gitignore b/.gitignore index de3bf7f..cf3d6a4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ pom.xml.asc /.lsp /.nrepl-port /.prepl-port +*.db diff --git a/README.md b/README.md index 93dc2a5..840fb41 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ default, the server listens on port 3000. * `reitit`: for handling routes * `jetty`: web server * `muuntaja`: JSON handling +* `next.jdbc`: database interface (SQLite) ## Documentation links @@ -29,3 +30,5 @@ The following links proved more than useful when working on this assignment: * * * +* +* diff --git a/project.clj b/project.clj index 0f4e0c6..a2d60c4 100644 --- a/project.clj +++ b/project.clj @@ -8,7 +8,9 @@ [ring/ring-jetty-adapter "1.15.3"] [metosin/reitit "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 :target-path "target/%s" :profiles {:uberjar {:aot :all diff --git a/src/yohoho/core.clj b/src/yohoho/core.clj index c225d98..8e573bd 100644 --- a/src/yohoho/core.clj +++ b/src/yohoho/core.clj @@ -4,7 +4,10 @@ [reitit.ring.middleware.muuntaja :as muuntaja] [muuntaja.core :as m] [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)) @@ -23,6 +26,38 @@ [: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 ------------------------------------------------------- (defn ahoy-handler [_] @@ -32,19 +67,18 @@ (defn get-items-handler [_] {:status 200 - :body {:id 1 - :name "Jack Sparrow" - :email "jack.sparrow@triangle.bm"}}) + :body {:items (db-get-all-items)}}) -(defn post-items-handler +(defn create-item-handler [request] (let [item (:body-params request) - id 1 name (:name item) - email (:email item)] + email (:email item) + id (db-store-item! name email)] + {:status 201 :headers {"Location" (str "/item/" id)} - :body {:id id, :name name, :email email}})) + :body (db-get-item id)})) ;; Custom middleware ---------------------------------------------- @@ -68,7 +102,7 @@ (ring/router [["/ahoy" {:get ahoy-handler}] ["/items" {:get get-items-handler - :post {:handler post-items-handler + :post {:handler create-item-handler :parameters {:body Item}}}]] ;; Middlewares: ;; - wrap-content-type-json: ensure POST routes are sent JSON payload @@ -92,5 +126,8 @@ "HolidayPirates take-home assignement. Goal: build a (very) small REST API that exposes to endpoints." [& args] + (println "Initializing SQLite database...") + (init-db!) + (println "Starting server...") (http-server/run-jetty app {:port 3000 :join? false}) (println "Ahoy! Yo-ho-ho API running on port 3000"))