From f9af5e2be961e6bd45d70ae7d39c95e31cdc2d5f Mon Sep 17 00:00:00 2001 From: Bart Akeley Date: Sun, 29 Mar 2020 14:36:37 -0500 Subject: [PATCH] support for favorites --- scripts/ddl.sql | 19 ++++++--- src/aretherecookies/app.clj | 30 ++++++++------- src/aretherecookies/auth.clj | 21 +++++----- src/aretherecookies/db.clj | 33 +++++++++++----- src/aretherecookies/handler.clj | 68 ++++++++++++++++++++++++--------- src/aretherecookies/queries.sql | 20 +++++++++- 6 files changed, 133 insertions(+), 58 deletions(-) diff --git a/scripts/ddl.sql b/scripts/ddl.sql index c80cd5c..d180aa9 100644 --- a/scripts/ddl.sql +++ b/scripts/ddl.sql @@ -11,9 +11,9 @@ CREATE TYPE CATEGORY AS ENUM ('beverages', 'desserts', 'entrees', 'other'); DROP TABLE IF EXISTS food_items CASCADE; CREATE TABLE food_items ( id uuid PRIMARY KEY, - name VARCHAR(100), - place_id VARCHAR(50), - category CATEGORY, + name VARCHAR(100) NOT NULL, + place_id VARCHAR(50) NOT NULL, + category CATEGORY NOT NULL, images VARCHAR, loc geography(POINT,4326) ); @@ -22,7 +22,7 @@ DROP TABLE IF EXISTS images CASCADE; CREATE TABLE images ( filename VARCHAR(200) PRIMARY KEY, food_item_id uuid REFERENCES food_items (id) ON DELETE CASCADE, - username VARCHAR(100), + username VARCHAR(100) NOT NULL, date timestamp (1) with time zone ); @@ -30,10 +30,17 @@ DROP TABLE IF EXISTS quantities CASCADE; CREATE TABLE quantities ( food_item_id uuid REFERENCES food_items (id) ON DELETE CASCADE, date timestamp (1) with time zone, - quantity QUANTITY, - PRIMARY KEY(food_item_id, date) + quantity QUANTITY NOT NULL, + PRIMARY KEY (food_item_id, date) ); +DROP TABLE IF EXISTS faves CASCADE; +CREATE TABLE faves ( + food_item_id uuid REFERENCES food_items (id) ON DELETE CASCADE, + username VARCHAR(200) NOT NULL, + date timestamp (1) with time zone NOT NULL, + PRIMARY KEY (food_item_id, username) +); CREATE INDEX IF NOT EXISTS food_loc_index ON food_items USING GIST ( loc ); diff --git a/src/aretherecookies/app.clj b/src/aretherecookies/app.clj index 60b9096..356d241 100644 --- a/src/aretherecookies/app.clj +++ b/src/aretherecookies/app.clj @@ -4,18 +4,19 @@ quantity-handler add-food-item-handler add-image-handler - get-images-handler]] - [aretherecookies.auth :refer [facebook-auth-backend - google-auth-backend]] + get-images-handler + faves-get-handler + faves-put-handler + faves-delete-handler]] + [aretherecookies.auth :refer [auth0-auth-backend]] [environ.core :refer [env]] [compojure.handler :refer [api]] - [compojure.core :refer :all] + [compojure.core :refer [defroutes GET POST PUT DELETE]] [ring.adapter.jetty :as jetty] [ring.middleware.anti-forgery :refer :all] [ring.middleware.json :refer [wrap-json-body]] [ring.middleware.multipart-params :refer [wrap-multipart-params]] [ring.middleware.defaults :refer [wrap-defaults api-defaults]] - [buddy.auth :refer [throw-unauthorized]] [buddy.auth.middleware :refer [wrap-authentication wrap-authorization]])) (defroutes app-routes @@ -25,7 +26,10 @@ (POST "/quantity" [] quantity-handler) (POST "/addfooditem" [] add-food-item-handler) (POST "/images/:foodItemId" [] add-image-handler) - (GET "/images/:foodItemId" [] get-images-handler)) + (GET "/images/:foodItemId" [] get-images-handler) + (POST "/faves" [] faves-get-handler) + (PUT "/fave" [] faves-put-handler) + (DELETE "/fave" [] faves-delete-handler)) (def app-config (assoc-in api-defaults [:security :anti-forgery] false)) @@ -35,11 +39,9 @@ [& [port]] (let [port (Integer. (or port (env :port) 3000))] (-> - (api #'app) - (wrap-authorization facebook-auth-backend) - (wrap-authorization google-auth-backend) - (wrap-authentication facebook-auth-backend) - (wrap-authentication google-auth-backend) - wrap-multipart-params - (wrap-json-body {:keywords? true}) - (jetty/run-jetty {:port port :join? false})))) + (api #'app) + (wrap-authentication auth0-auth-backend) + (wrap-authorization auth0-auth-backend) + wrap-multipart-params + (wrap-json-body {:keywords? true}) + (jetty/run-jetty {:port port :join? false})))) diff --git a/src/aretherecookies/auth.clj b/src/aretherecookies/auth.clj index a20e658..183df64 100644 --- a/src/aretherecookies/auth.clj +++ b/src/aretherecookies/auth.clj @@ -2,16 +2,20 @@ (:require [buddy.auth.backends :as backends] [clj-http.client :as client])) +; TODO implement this as LRU cache to eliminate memory leaking (def tokens (atom {})) -(def facebook-me-url "https://graph.facebook.com/me?access_token=") - -(def google-userinfo-url "https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=") +(def auth0-userinfo-url "https://aretherecookies.auth0.com/userinfo") (defn verify-token "perform rest requesting using token and return boolean if successful" [token url] - (= 200 (:status (client/get (str url token) {:accept :json})))) + (try + (let [headers {:authorization (str "Bearer " token)} + res (if-not (empty? token) (client/get url {:accept :json :headers headers}) {}) + status (:status res)] + (= 200 status)) + (catch Exception e (print (str "Caught: " e))))) (defn cache-token "swap the new token into our cache map atom and return the token" @@ -21,7 +25,7 @@ (defn verify-and-cache-token "if a REST request is successful against url using token, cache the token and return it" [token url] - (if (verify-token token url) (cache-token token))) + (if (verify-token token url) (cache-token token) nil)) (defn url-backend "return a buddy auth backend that validates tokens agianst given url" @@ -29,8 +33,5 @@ (fn [_ token] (get @tokens (keyword token) (verify-and-cache-token token url)))) -(def facebook-auth-backend - (backends/token {:token-name "facebook-token" :authfn (url-backend facebook-me-url)})) - -(def google-auth-backend - (backends/token {:token-name "google-token" :authfn (url-backend google-userinfo-url)})) \ No newline at end of file +(def auth0-auth-backend + (backends/token {:token-name "auth0-token" :authfn (url-backend auth0-userinfo-url)})) diff --git a/src/aretherecookies/db.clj b/src/aretherecookies/db.clj index 410d7c8..126178c 100644 --- a/src/aretherecookies/db.clj +++ b/src/aretherecookies/db.clj @@ -77,16 +77,16 @@ (defn create-food-item [{:keys [:name :placeId :category :quantity :latitude :longitude]}] (let [food-item (first (insert-food-item - @pooled-db - {:name name - :placeId placeId - :category category - :longitude longitude - :latitude latitude})) + @pooled-db + {:name name + :placeId placeId + :category category + :longitude longitude + :latitude latitude})) quantity (first (insert-quantity {:foodItemId (:id food-item) :quantity quantity}))] (merge - food-item - (rename-keys (select-keys quantity [:date :quantity]) {:date :lastupdated})))) + food-item + (rename-keys (select-keys quantity [:date :quantity]) {:date :lastupdated})))) (defn get-images "query database for a list of images for a food item id" @@ -96,4 +96,19 @@ (defn add-image "update the list of images for given food item id" [image] - (insert-image @pooled-db image)) \ No newline at end of file + (insert-image @pooled-db image)) + +(defn get-faves + "select all faves associated with a username" + [username] + (select-from-faves @pooled-db {:username username})) + +(defn add-faves + "add a food item id to faves for this username" + [username foodItemIds] + (map (fn [foodItemId] (insert-into-faves @pooled-db {:username username :food_item_id foodItemId})) foodItemIds)) + +(defn delete-faves + "remove a list of food item ids from faves for this username" + [username foodItemIds] + (delete-from-faves @pooled-db {:username username :food_item_ids foodItemIds})) diff --git a/src/aretherecookies/handler.clj b/src/aretherecookies/handler.clj index 82d82fc..14484da 100644 --- a/src/aretherecookies/handler.clj +++ b/src/aretherecookies/handler.clj @@ -3,7 +3,10 @@ insert-quantity create-food-item get-images - add-image]] + add-image + get-faves + add-faves + delete-faves]] [aretherecookies.parsers :refer [parse-special-types parse-location]] [aretherecookies.aws :refer [put-s3 @@ -45,9 +48,9 @@ (hash-map :filter (:filter body) :fooditems (->> - (query-food-items body) - (map parse-location) - (map (update-key-to-s3 :thumbimage))))))) + (query-food-items body) + (map parse-location) + (map (update-key-to-s3 :thumbimage))))))) (defn quantity-handler @@ -60,7 +63,7 @@ (insert-quantity {:foodItemId foodItemId :quantity quantity}) :value-fn parse-special-types))) -(defn key-found? +(defn key-found? [obj key] (not (str/blank? (str (get obj key))))) @@ -68,8 +71,8 @@ "returns a list of required keys which were not found on the given food item" [foodItem] (remove - #(key-found? foodItem %) - [:name :placeId :latitude :longitude :category :quantity])) + #(key-found? foodItem %) + [:name :placeId :latitude :longitude :category :quantity])) (defn add-food-item-handler @@ -81,7 +84,7 @@ (if (> (count missing-keys) 0) (bad-request {"missingkeys" missing-keys}) (as-> - (create-food-item food-item) % + (create-food-item food-item) % (parse-location %) (safe-json %))))) @@ -91,15 +94,15 @@ [req] (let [foodItemId (get-in req [:params :foodItemId])] (->> - (get-images foodItemId) - (map #(update % :url build-s3-url)) - safe-json))) + (get-images foodItemId) + (map #(update % :url build-s3-url)) + safe-json))) (defn add-image-handler "given foodItemId from route and photo from multipart form post uploads image into s3 and adds URL into food item record" [req] - ;(if-not (authenticated? req) (throw-unauthorized)) + ; (if-not (authenticated? req) (throw-unauthorized)) (let [username (get-in req [:params :username] "Bart Akeley") foodItemId (get-in req [:params :foodItemId]) image (get-in req [:params :photo :tempfile]) @@ -107,9 +110,38 @@ (println "/addimage ---->" img-name) (put-s3 img-name image) (->> - (add-image {:food_item_id foodItemId - :filename img-name - :username username}) - first - (update-key-to-s3 :url) - safe-json))) \ No newline at end of file + (add-image {:food_item_id foodItemId + :filename img-name + :username username}) + first + (update-key-to-s3 :url) + safe-json))) + + +(defn faves-get-handler + "return all the faves for a given username" + [req] + (if-not (authenticated? req) (throw-unauthorized)) + (let [email (get-in req [:body :email])] + (println "/faves/ ----> POST " email) + (->> (get-faves email) safe-json))) + + +(defn faves-put-handler + "add a food item to faves for a user" + [req] + (if-not (authenticated? req) (throw-unauthorized)) + (let [email (get-in req [:body :email]) + foodItemIds (get-in req [:body :foodItemIds])] + (println "/fave/" email "----> PUT" foodItemIds) + (->> (add-faves email foodItemIds) safe-json))) + + +(defn faves-delete-handler + "add a food item to faves for a user" + [req] + (if-not (authenticated? req) (throw-unauthorized)) + (let [email (get-in req [:body :email]) + foodItemIds (get-in req [:body :foodItemIds])] + (println "/faves/" email "----> DELETE" foodItemIds) + (->> (delete-faves email foodItemIds) safe-json))) diff --git a/src/aretherecookies/queries.sql b/src/aretherecookies/queries.sql index b5d7acf..34c662a 100644 --- a/src/aretherecookies/queries.sql +++ b/src/aretherecookies/queries.sql @@ -88,4 +88,22 @@ RETURNING filename as url, date, username, - food_item_id \ No newline at end of file + food_item_id + + +-- :name select-from-faves +SELECT food_item_id, date +FROM faves +WHERE username=:v:username +ORDER BY date DESC + + +-- :name insert-into-faves +INSERT INTO faves (food_item_id, username, date) +VALUES (:v:food_item_id::uuid, :v:username, current_timestamp) +ON CONFLICT DO NOTHING +RETURNING food_item_id, date + + +-- :name delete-from-faves +DELETE FROM faves WHERE username=:v:username AND food_item_id IN (:v*:food_item_ids::uuid) RETURNING food_item_id, date