support ordering and filtering from request body

This commit is contained in:
Bart Akeley 2017-11-05 13:35:30 -06:00
parent 923014d563
commit b3a94903c8
7 changed files with 115 additions and 55 deletions

View file

@ -13,6 +13,7 @@
[ring-middleware-format "0.7.2"] [ring-middleware-format "0.7.2"]
[org.clojure/data.json "0.2.6"] [org.clojure/data.json "0.2.6"]
[ring/ring-json "0.4.0"] [ring/ring-json "0.4.0"]
[ring/ring-mock "0.3.1"]
[ring/ring-jetty-adapter "1.4.0"] [ring/ring-jetty-adapter "1.4.0"]
[com.layerware/hugsql "0.4.8"]] [com.layerware/hugsql "0.4.8"]]
:plugins [[lein-ring "0.9.7"] [lein-environ "1.1.0"]] :plugins [[lein-ring "0.9.7"] [lein-environ "1.1.0"]]

View file

@ -2,15 +2,15 @@
CREATE EXTENSION IF NOT EXISTS "postgis"; CREATE EXTENSION IF NOT EXISTS "postgis";
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; 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 ( create table food_items (
id uuid PRIMARY KEY, id uuid PRIMARY KEY,
name VARCHAR(100), name VARCHAR(100),
place_id VARCHAR(50), place_id VARCHAR(50),
category category, category CATEGORY_ENUM,
images VARCHAR, images VARCHAR,
thumbImage VARCHAR, thumbImage VARCHAR,
loc geography(POINT,4326) loc geography(POINT,4326)
@ -19,7 +19,7 @@ create table food_items (
create table quantities ( create table quantities (
food_item_id uuid, food_item_id uuid,
date timestamp (1) with time zone, date timestamp (1) with time zone,
quantity quantity, quantity QUANTITY_ENUM,
PRIMARY KEY(food_item_id, date) PRIMARY KEY(food_item_id, date)
); );

View file

@ -5,6 +5,13 @@
[hugsql.core :as hugsql]) [hugsql.core :as hugsql])
(:import com.mchange.v2.c3p0.ComboPooledDataSource)) (: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") (hugsql/def-db-fns "aretherecookies/queries.sql")
(def db-spec {:uri (env :database-jdbc-uri) (def db-spec {:uri (env :database-jdbc-uri)
@ -28,5 +35,26 @@
(defonce pooled-db (delay (pool db-spec))) (defonce pooled-db (delay (pool db-spec)))
(defn query-food-items-by-distance [{:keys [lat lng radius]}] (defn get-orderby [orderby & args]
(food-items-by-distance @pooled-db {:dist radius :lng lng :lat lat})) (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)}))

View file

@ -1,5 +1,7 @@
(ns aretherecookies.handler (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.core :refer :all]
[compojure.route :as route] [compojure.route :as route]
[clojure.data.json :as json] [clojure.data.json :as json]
@ -7,45 +9,15 @@
[ring.middleware.anti-forgery :refer :all] [ring.middleware.anti-forgery :refer :all]
[ring.middleware.json :refer [wrap-json-body]])) [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] (defn food-items-handler [req]
(println "req---->" (:body req)) (println "req---->" (:body req))
(let [{{lat "lat" lng "lng" filter "filter" orderby "orderby"} :body} req] (let [{body :body} req]
(json/write-str (json/write-str
(hash-map (hash-map
:orderby orderby :orderby (get body "orderby")
:filter filter :filter (get body "filter")
:fooditems (food-items-to-json :fooditems (food-items-to-json (query-food-items body)))
(query-food-items-by-distance {:lat lat :value-fn parse-special-types)))
:lng lng
:radius (get filter "radius")})))
:value-fn uuid-to-string)))
(defroutes app-routes (defroutes app-routes
(GET "/" [] (str {:csrf-token (GET "/" [] (str {:csrf-token

View file

@ -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))

View file

@ -1,13 +1,37 @@
-- :name food-items-by-distance :raw -- :name select-food-items :? :raw
SELECT SELECT
id, f.id AS id,
name, f.name AS name,
place_id, f.place_id AS place_id,
category, f.category AS category,
images, f.images AS images,
thumbImage, f.thumbImage AS thumbImage,
ST_AsGeoJSON(loc) as location, ST_AsGeoJSON(f.loc) AS location,
ST_Distance(loc, ST_SetSRID(ST_Point(:lng, :lat),4326)::geography) / 1609 as distance ST_Distance(f.loc, ST_SetSRID(ST_Point(:lng, :lat), 4326)::geography) / 1609 AS distance,
FROM food_items q.quantity AS quantity,
WHERE ST_DWithin(loc, ST_SetSRID(ST_Point(:lng, :lat),4326)::geography, :dist * 1609) q.updated AS lastUpdated
ORDER BY distance ASC 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

View file

@ -1,7 +1,7 @@
(ns aretherecookies.handler-test (ns aretherecookies.handler-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[ring.mock.request :as mock] [ring.mock.request :as mock]
[aretherecookies.handler :refer :all])) [aretherecookies.app :refer :all]))
(deftest test-app (deftest test-app
(testing "main route" (testing "main route"