diff --git a/project.clj b/project.clj index 330d5db..13d26ee 100644 --- a/project.clj +++ b/project.clj @@ -13,6 +13,7 @@ [ring-middleware-format "0.7.2"] [org.clojure/data.json "0.2.6"] [ring/ring-json "0.4.0"] + [ring/ring-mock "0.3.1"] [ring/ring-jetty-adapter "1.4.0"] [com.layerware/hugsql "0.4.8"]] :plugins [[lein-ring "0.9.7"] [lein-environ "1.1.0"]] diff --git a/scripts/ddl.sql b/scripts/ddl.sql index d1f21e9..2b9bb01 100644 --- a/scripts/ddl.sql +++ b/scripts/ddl.sql @@ -2,15 +2,15 @@ CREATE EXTENSION IF NOT EXISTS "postgis"; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -CREATE TYPE quantity AS ENUM ('none', 'few', 'many', 'lots'); +CREATE TYPE QUANTITY_ENUM AS ENUM ('lots', 'many', 'few', 'none'); -CREATE TYPE category AS ENUM ('beverages', 'desserts', 'entrees', 'other'); +CREATE TYPE CATEGORY_ENUM AS ENUM ('beverages', 'desserts', 'entrees', 'other'); create table food_items ( id uuid PRIMARY KEY, name VARCHAR(100), place_id VARCHAR(50), - category category, + category CATEGORY_ENUM, images VARCHAR, thumbImage VARCHAR, loc geography(POINT,4326) @@ -19,7 +19,7 @@ create table food_items ( create table quantities ( food_item_id uuid, date timestamp (1) with time zone, - quantity quantity, + quantity QUANTITY_ENUM, PRIMARY KEY(food_item_id, date) ); diff --git a/src/aretherecookies/db.clj b/src/aretherecookies/db.clj index 5e9405a..033382e 100644 --- a/src/aretherecookies/db.clj +++ b/src/aretherecookies/db.clj @@ -5,6 +5,13 @@ [hugsql.core :as hugsql]) (:import com.mchange.v2.c3p0.ComboPooledDataSource)) +; defines a number of functions in this namesapce as defined in queries.sql +; by-distance +; by-last-updated +; by-quantity +; select-food-items +; within-radius +; has-category (hugsql/def-db-fns "aretherecookies/queries.sql") (def db-spec {:uri (env :database-jdbc-uri) @@ -28,5 +35,26 @@ (defonce pooled-db (delay (pool db-spec))) -(defn query-food-items-by-distance [{:keys [lat lng radius]}] - (food-items-by-distance @pooled-db {:dist radius :lng lng :lat lat})) \ No newline at end of file +(defn get-orderby [orderby & args] + (apply + (cond + (= orderby "distance") by-distance + (= orderby "lastupdated") by-last-updated + (= orderby "quantity") by-quantity + :else by-distance) + args)) + +(defn get-where [{:keys [:lat :lng :filter]}] + (let [radius (or (get filter "radius") 10) + categories (get filter "categories")] + (cond + categories (has-category {:categories categories :lat lat :lng lng :dist radius}) + :else (within-radius {:lat lat :lng lng :dist radius})))) + +(defn query-food-items [{lat "lat" lng "lng" filter "filter" orderby "orderby"}] + (select-food-items + @pooled-db + {:lat lat + :lng lng + :where (get-where {:lat lat :lng lng :filter filter}) + :order (get-orderby orderby)})) \ No newline at end of file diff --git a/src/aretherecookies/handler.clj b/src/aretherecookies/handler.clj index d7d29d5..0fbeb1a 100644 --- a/src/aretherecookies/handler.clj +++ b/src/aretherecookies/handler.clj @@ -1,5 +1,7 @@ (ns aretherecookies.handler - (:require [aretherecookies.db :refer [query-food-items-by-distance]] + (:require [aretherecookies.db :refer [query-food-items]] + [aretherecookies.parsers :refer [food-items-to-json + parse-special-types]] [compojure.core :refer :all] [compojure.route :as route] [clojure.data.json :as json] @@ -7,45 +9,15 @@ [ring.middleware.anti-forgery :refer :all] [ring.middleware.json :refer [wrap-json-body]])) -(defn uuid-to-string [key value] - (if (instance? java.util.UUID value) (.toString value) value)) - -(defn get-coords [item] - (-> - item - (get :location) - json/read-str - (get "coordinates"))) - -(defn build-lat-lng [[lng lat]] - (hash-map :longitude lng :latitude lat)) - -(defn build-latlng [item] - (let [[lng lat] (get-coords item)] - (hash-map :longitude lng :latitude lat))) - -(defn parse-location [item] - (-> - item - build-latlng - (merge item) - (dissoc :location))) - -(defn food-items-to-json [query-result] - (map parse-location query-result)) - (defn food-items-handler [req] (println "req---->" (:body req)) - (let [{{lat "lat" lng "lng" filter "filter" orderby "orderby"} :body} req] + (let [{body :body} req] (json/write-str (hash-map - :orderby orderby - :filter filter - :fooditems (food-items-to-json - (query-food-items-by-distance {:lat lat - :lng lng - :radius (get filter "radius")}))) - :value-fn uuid-to-string))) + :orderby (get body "orderby") + :filter (get body "filter") + :fooditems (food-items-to-json (query-food-items body))) + :value-fn parse-special-types))) (defroutes app-routes (GET "/" [] (str {:csrf-token diff --git a/src/aretherecookies/parsers.clj b/src/aretherecookies/parsers.clj new file mode 100644 index 0000000..ce687a3 --- /dev/null +++ b/src/aretherecookies/parsers.clj @@ -0,0 +1,35 @@ +(ns aretherecookies.parsers + (:require [clojure.data.json :as json] + [clojure.string :as str] + [ring.middleware.anti-forgery :refer :all] + [ring.middleware.json :refer [wrap-json-body]])) + + (defn parse-special-types [key value] + (cond + (instance? java.util.UUID value) (.toString value) + (instance? java.sql.Timestamp value) (.getTime value) + :else value)) + + (defn get-coords [item] + (-> + item + (get :location) + json/read-str + (get "coordinates"))) + + (defn build-lat-lng [[lng lat]] + (hash-map :longitude lng :latitude lat)) + + (defn build-latlng [item] + (let [[lng lat] (get-coords item)] + (hash-map :longitude lng :latitude lat))) + + (defn parse-location [item] + (-> + item + build-latlng + (merge item) + (dissoc :location))) + + (defn food-items-to-json [query-result] + (map parse-location query-result)) \ No newline at end of file diff --git a/src/aretherecookies/queries.sql b/src/aretherecookies/queries.sql index a5d32b7..dac81ce 100644 --- a/src/aretherecookies/queries.sql +++ b/src/aretherecookies/queries.sql @@ -1,13 +1,37 @@ --- :name food-items-by-distance :raw +-- :name select-food-items :? :raw SELECT - id, - name, - place_id, - category, - images, - thumbImage, - ST_AsGeoJSON(loc) as location, - ST_Distance(loc, ST_SetSRID(ST_Point(:lng, :lat),4326)::geography) / 1609 as distance -FROM food_items -WHERE ST_DWithin(loc, ST_SetSRID(ST_Point(:lng, :lat),4326)::geography, :dist * 1609) -ORDER BY distance ASC \ No newline at end of file + f.id AS id, + f.name AS name, + f.place_id AS place_id, + f.category AS category, + f.images AS images, + f.thumbImage AS thumbImage, + ST_AsGeoJSON(f.loc) AS location, + ST_Distance(f.loc, ST_SetSRID(ST_Point(:lng, :lat), 4326)::geography) / 1609 AS distance, + q.quantity AS quantity, + q.updated AS lastUpdated +FROM food_items f +LEFT OUTER JOIN ( + SELECT food_item_id, quantity, MAX(date) AS updated FROM quantities GROUP BY food_item_id, quantity +) q +ON f.id = q.food_item_id +:snip:where +:snip:order + +-- :snip has-category +WHERE +ST_DWithin(loc, ST_SetSRID(ST_Point(:lng, :lat), 4326)::geography, :dist * 1609) +AND +category IN (:categories) + +-- :snip within-radius +WHERE ST_DWithin(loc, ST_SetSRID(ST_Point(:lng, :lat), 4326)::geography, :dist * 1609) + +-- :snip by-distance +ORDER BY distance ASC + +-- :snip by-last-updated +ORDER BY lastUpdated DESC + +-- :snip by-quantity +ORDER BY quantity DESC \ No newline at end of file diff --git a/test/aretherecookies/handler_test.clj b/test/aretherecookies/handler_test.clj index ffbc82d..370a057 100644 --- a/test/aretherecookies/handler_test.clj +++ b/test/aretherecookies/handler_test.clj @@ -1,7 +1,7 @@ (ns aretherecookies.handler-test (:require [clojure.test :refer :all] [ring.mock.request :as mock] - [aretherecookies.handler :refer :all])) + [aretherecookies.app :refer :all])) (deftest test-app (testing "main route"