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"]
[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"]]

View file

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

View file

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

View file

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

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

View file

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