diff --git a/scripts/ddl.sql b/scripts/ddl.sql index 4b994df..f4ea660 100644 --- a/scripts/ddl.sql +++ b/scripts/ddl.sql @@ -1,106 +1,100 @@ CREATE EXTENSION IF NOT EXISTS "postgis"; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +DROP TYPE IF EXISTS QUANTITY CASCADE; CREATE TYPE QUANTITY AS ENUM ('lots', 'many', 'few', 'none'); +DROP TYPE IF EXISTS CATEGORY CASCADE; 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, - images VARCHAR, - thumbImage VARCHAR, - loc geography(POINT,4326) + id uuid PRIMARY KEY, + name VARCHAR(100), + place_id VARCHAR(50), + category CATEGORY, + images VARCHAR, + thumbImage VARCHAR, + loc geography(POINT,4326) ); -DROP TABLE quantities CASCADE; +DROP TABLE IF EXISTS images CASCADE; +CREATE TABLE images ( + filename VARCHAR(200) PRIMARY KEY, + food_item_id uuid REFERENCES food_items (id), + username VARCHAR(100), + date timestamp (1) with time zone +); +DROP TABLE IF EXISTS quantities CASCADE; CREATE TABLE quantities ( - food_item_id uuid, + food_item_id uuid REFERENCES food_items (id), date timestamp (1) with time zone, quantity QUANTITY, PRIMARY KEY(food_item_id, date) ); CREATE INDEX IF NOT EXISTS food_loc_index ON food_items USING GIST ( loc ); -INSERT INTO food_items (id, name, place_id, category, images, thumbImage, loc) +DROP VIEW IF EXISTS latest_quantities; +CREATE VIEW latest_quantities AS SELECT food_item_id, quantity, date from quantities q1 where date = ( + SELECT max(date) FROM quantities q2 WHERE q1.food_item_id=q2.food_item_id +); +INSERT INTO food_items (id, name, place_id, category, loc) VALUES ( uuid_generate_v4(), 'Big John Cookies', 'ChIJf5QJFBK1RIYRjjfxZz9Z0O0', 'desserts', - 'https://s-media-cache-ak0.pinimg.com/564x/e7/5f/08/e75f08b00c0bc7f2b01b3d1a636389f6.jpg,http://images.media-allrecipes.com/userphotos/560x315/1107530.jpg', - 'http://images.media-allrecipes.com/userphotos/560x315/1107530.jpg', ST_GeogFromText('SRID=4326;POINT(-97.7532464 30.270667599999996)') ); -INSERT INTO food_items (id, name, place_id, category, images, thumbImage, loc) +INSERT INTO food_items (id, name, place_id, category, loc) VALUES ( uuid_generate_v4(), 'Jelly Filled Donuts', 'ChIJ72if-Qe1RIYRCzMucGEEdBA', 'desserts', - 'https://timenewsfeed.files.wordpress.com/2013/06/jellydonut.jpg', - 'https://timenewsfeed.files.wordpress.com/2013/06/jellydonut.jpg', ST_GeogFromText('SRID=4326;POINT(-97.742308 30.263963300000004)') ); -INSERT INTO food_items (id, name, place_id, category, images, thumbImage, loc) +INSERT INTO food_items (id, name, place_id, category, loc) VALUES ( uuid_generate_v4(), 'Spelt Brownies', 'ChIJG44vBQi1RIYRvWGHdYUolZY', 'desserts', - 'http://www.momshealthyeats.com/wp-content/uploads/2011/11/spelt-fudge-brownie.jpg', - 'http://www.momshealthyeats.com/wp-content/uploads/2011/11/spelt-fudge-brownie.jpg', ST_GeogFromText('SRID=4326;POINT(-97.7419085 30.265019100000004)') ); -INSERT INTO food_items (id, name, place_id, category, images, thumbImage, loc) +INSERT INTO food_items (id, name, place_id, category, loc) VALUES ( uuid_generate_v4(), 'Oatmeal Raisin Cookies', 'ChIJf5QJFBK1RIYRjjfxZz9Z0O0', 'desserts', - 'http://cookingontheside.com/wp-content/uploads/2009/04/oatmeal_raisin_cookies_stack-400.jpg', - 'http://cookingontheside.com/wp-content/uploads/2009/04/oatmeal_raisin_cookies_stack-400.jpg', ST_GeogFromText('SRID=4326;POINT(-97.7532464 30.270667599999996)') ); -INSERT INTO food_items (id, name, place_id, category, images, thumbImage, loc) +INSERT INTO food_items (id, name, place_id, category, loc) VALUES ( uuid_generate_v4(), 'Donuts with Sprinkles', 'ChIJ72if-Qe1RIYRCzMucGEEdBA', 'desserts', - 'https://dannivee.files.wordpress.com/2013/10/img_1950.jpg', - 'https://dannivee.files.wordpress.com/2013/10/img_1950.jpg', ST_GeogFromText('SRID=4326;POINT(-97.742308 30.263963300000004)') ); -INSERT INTO food_items (id, name, place_id, category, images, thumbImage, loc) +INSERT INTO food_items (id, name, place_id, category, loc) VALUES ( uuid_generate_v4(), 'Powdered Donuts', 'ChIJ72if-Qe1RIYRCzMucGEEdBA', 'desserts', - 'http://3.bp.blogspot.com/-NUKSXr1qLHs/UQmsaEFgbTI/AAAAAAAAA_Y/l4psfVl4a5A/s1600/white-powdered-sugar-doughnuts-tracie-kaska.jpg', - 'http://3.bp.blogspot.com/-NUKSXr1qLHs/UQmsaEFgbTI/AAAAAAAAA_Y/l4psfVl4a5A/s1600/white-powdered-sugar-doughnuts-tracie-kaska.jpg', ST_GeogFromText('SRID=4326;POINT(-97.742308 30.263963300000004)') ); -INSERT INTO food_items (id, name, place_id, category, images, thumbImage, loc) +INSERT INTO food_items (id, name, place_id, category, loc) VALUES ( uuid_generate_v4(), 'Snickerdoodles', 'ChIJr3szW6a1RIYRkM7LRpnBIO0', 'desserts', - 'http://josephcphillips.com/wp-content/uploads/2015/02/snickerdoodles2.jpg', - 'http://josephcphillips.com/wp-content/uploads/2015/02/snickerdoodles2.jpg', ST_GeogFromText('SRID=4326;POINT(-97.73798459999999 30.266898599999998)') ); -INSERT INTO food_items (id, name, place_id, category, images, thumbImage, loc) +INSERT INTO food_items (id, name, place_id, category, loc) VALUES ( uuid_generate_v4(), 'Pizza', 'ChIJr3szW6a1RIYRkM7LRpnBIO0', 'desserts', - 'http://www.foodandhealth.co.uk/wp-content/uploads/2016/05/Hot-stone-Vegan-Pizza.jpg', - 'http://www.foodandhealth.co.uk/wp-content/uploads/2016/05/Hot-stone-Vegan-Pizza.jpg', ST_GeogFromText('SRID=4326;POINT(-97.73798459999999 30.266898599999998)') ); -INSERT INTO quantities SELECT id, current_timestamp, 'many' FROM food_items; -CREATE VIEW latest_quantities AS SELECT food_item_id, quantity, date from quantities q1 where date = ( - SELECT max(date) FROM quantities q2 WHERE q1.food_item_id=q2.food_item_id -); \ No newline at end of file +INSERT INTO quantities SELECT id, current_timestamp, 'many' FROM food_items; \ No newline at end of file diff --git a/src/aretherecookies/aws.clj b/src/aretherecookies/aws.clj index f1364e0..bf837ff 100644 --- a/src/aretherecookies/aws.clj +++ b/src/aretherecookies/aws.clj @@ -5,7 +5,13 @@ (defcredential (env :aws-access-key-id) (env :aws-secret-access-key) (env :aws-region)) + (defn put-s3 "upload given filename and file to s3" [file-name file] - (put-object :bucket-name "aretherecookies" :key file-name :file file)) \ No newline at end of file + (put-object :bucket-name "aretherecookies" :key file-name :file file)) + +(defn build-s3-url + "given an image name return fully qualified URL" + [filename] + (str "https://s3-us-west-2.amazonaws.com/aretherecookies/" filename)) diff --git a/src/aretherecookies/db.clj b/src/aretherecookies/db.clj index 0d9d3e4..56a0254 100644 --- a/src/aretherecookies/db.clj +++ b/src/aretherecookies/db.clj @@ -84,10 +84,9 @@ (defn get-images "query database for a list of images for a food item id" [foodItemId] - (let [images (:images (select-images @pooled-db {:id foodItemId}))] - (filter #(not (str/blank? %)) (str/split (str images) #",")))) + (select-images @pooled-db {:id foodItemId})) -(defn set-images +(defn add-image "update the list of images for given food item id" - [foodItemId images] - (update-images @pooled-db {:id foodItemId :images (str/join "," images)})) \ No newline at end of file + [image] + (insert-image @pooled-db image)) \ No newline at end of file diff --git a/src/aretherecookies/handler.clj b/src/aretherecookies/handler.clj index 86a063a..4951746 100644 --- a/src/aretherecookies/handler.clj +++ b/src/aretherecookies/handler.clj @@ -3,32 +3,61 @@ insert-quantity create-food-item get-images - set-images]] - [aretherecookies.parsers :refer [parse-special-types parse-location]] - [aretherecookies.aws :refer [put-s3]] + add-image]] + [aretherecookies.parsers :refer [parse-special-types + parse-location]] + [aretherecookies.aws :refer [put-s3 + build-s3-url]] [clojure.data.json :as json] [clojure.string :as str] - [buddy.auth :refer [authenticated? throw-unauthorized]])) + [buddy.auth :refer [authenticated? + throw-unauthorized]]) + (:import (java.util UUID))) + (defn safe-json "converts hashmaps into a JSON string suitable for a network response" [data] (json/write-str data :value-fn parse-special-types)) + (defn bad-request "returns a HTTP 404 error with the supplied body" [body] {:status 400 :body (safe-json body)}) -(defn food-items-handler [req] + +(defn image-filename-to-url + "convert image filename key to url key" + [image] + (assoc image :url (build-s3-url (get image :filename)))) + + +(defn images-to-s3-urls + "convert all the images of a food item to fully qualified s3 URLs" + [food-item] + (if (get :images food-item) + (update food-item :images (map build-s3-url)) + food-item)) + + +(defn food-items-handler + "return a list of food items for a given filter and location" + [req] (println "/fooditems ---->" (:body req)) (let [{body :body} req] (safe-json (hash-map :filter (:filter body) - :fooditems (map parse-location (query-food-items body)))))) + :fooditems (->> + (query-food-items body) + (map parse-location) + (map images-to-s3-urls)))))) -(defn quantity-handler [req] + +(defn quantity-handler + "get the latest quantity for a food item" + [req] (let [{{foodItemId :foodItemId quantity :quantity} :body} req] (if-not (authenticated? req) (throw-unauthorized)) (println "/quantity ---->" foodItemId quantity) @@ -36,10 +65,14 @@ (insert-quantity {:foodItemId foodItemId :quantity quantity}) :value-fn parse-special-types))) -(def required-keys [:name :placeId :latitude :longitude :category :quantity]) + (defn get-missing-keys + "returns a list of required keys which were not found on the given food item" [foodItem] - (remove #(get foodItem %) required-keys)) + (remove + #(get foodItem %) + [:name :placeId :latitude :longitude :category :quantity])) + (defn add-food-item-handler "validate food item fields from request and insert into database returning newly created item" @@ -54,13 +87,18 @@ (parse-location %) (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" - [{{foodItemId :foodItemId {image :tempfile} :photo} :params}] - (let [existing-images (get-images foodItemId) - img-name (str foodItemId "/" (+ 1 (count existing-images)) ".png") - img-url (str "https://s3-us-west-2.amazonaws.com/aretherecookies/" img-name)] - (println "/addimage ---->" img-url) + [req] + (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]) + img-name (str foodItemId "/" (UUID/randomUUID) ".png")] + (println "/addimage ---->" img-name) (put-s3 img-name image) - (set-images foodItemId (conj existing-images img-url)) - img-url)) \ No newline at end of file + (add-image {:food_item_id foodItemId + :filename img-name + :username username}) + (build-s3-url img-name))) \ No newline at end of file diff --git a/src/aretherecookies/queries.sql b/src/aretherecookies/queries.sql index 3c1cda6..701fcc9 100644 --- a/src/aretherecookies/queries.sql +++ b/src/aretherecookies/queries.sql @@ -4,12 +4,12 @@ SELECT 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.date AS lastUpdated + q.date AS lastUpdated, + (select filename from images where food_item_id = f.id) as images FROM food_items f LEFT OUTER JOIN latest_quantities q ON f.id = q.food_item_id @@ -59,13 +59,19 @@ RETURNING name AS name, place_id AS place_id, category AS category, - images AS images, thumbImage AS thumbImage, ST_AsGeoJSON(loc) AS location, ST_Distance(loc, ST_SetSRID(ST_Point(:longitude, :latitude), 4326)::geography) / 1609 AS distance -- :name select-images -SELECT images FROM food_items WHERE id=:v:id::uuid +SELECT * FROM images WHERE food_item-id=:v:food_item_id:uuid --- :name update-images -UPDATE food_items SET images=:v:images WHERE id=:v:id::uuid RETURNING images \ No newline at end of file +-- :name insert-image +INSERT INTO images (filename, username, date, food_item_id) + VALUES ( + :v:filename, + :v:username, + current_timestamp, + :v:food_item_id::uuid + ) +RETURNING * \ No newline at end of file