mirror of
https://gitlab.com/wheres-the-tp/server.git
synced 2026-01-25 04:34:55 -06:00
support ordering and filtering from request body
This commit is contained in:
parent
923014d563
commit
b3a94903c8
7 changed files with 115 additions and 55 deletions
|
|
@ -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"]]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)}))
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
35
src/aretherecookies/parsers.clj
Normal file
35
src/aretherecookies/parsers.clj
Normal 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))
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue