diff --git a/flask/backend.py b/flask/backend.py index 2ae380f..1960ac1 100644 --- a/flask/backend.py +++ b/flask/backend.py @@ -1,43 +1,39 @@ from os import environ from io import BytesIO +import filetype from dotenv import load_dotenv + from flask import Flask, request, abort, make_response, send_file from flask_cors import CORS + from datetime import datetime + from deta import Deta -import filetype load_dotenv() app = Flask(__name__) CORS(app) deta = Deta() -### connect to dbs and drive itemsDB = deta.Base('items') storesDB = deta.Base('stores') drive = deta.Drive('beerbuddy') -### reusable functions - -# fetch requested store from store database based on request arguments -# arguments are assumed checked beforehand def fetchStore(key = None): - if key is not None: - data = storesDB.get(key) - return data + if key is not None: return storesDB.get(key) else: northing = float(request.args['northing']) easting = float(request.args['easting']) zone = int(request.args['zone']) - # fetch store from deta Base + fetch = storesDB.fetch({ 'easting?r': [easting - 1, easting + 1], 'northing?r': [northing - 1, northing + 1], 'zone': zone, }) - data = fetch.items - # if we didn't find the record on the first go around, try again - if not data: + store = fetch.items[0] + + if not store: while (fetch.last is not None): fetch = storesDB.fetch({ 'easting?r': [easting - 1, easting + 1], @@ -45,178 +41,154 @@ def fetchStore(key = None): 'zone': zone, last: fetch.last }) - # check with each loop iteration if we've found it - if fetch.items: - data = fetch.items - # if so, break loop - break - return data -# fetch requested item from item database based on request arguments and passed store + if fetch.items: + store = fetch.items[0] + break + + return store + def fetchItem(store): - # fetch item from deta Base fetch = itemsDB.fetch({ 'lowername': request.args['itemName'].lower(), 'store': store['key'], }) - data = fetch.items - # if we didn't find the record on the first go around, try again - if not data: + item = fetch.items[0] + + if not item: while (fetch.last is not None): fetch = itemsDB.fetch({ 'lowername': request.args['itemName'].lower(), 'store': store['key'], last: fetch.last }) - # check with each loop iteration if we've found it - if fetch.items: - data.append(fetch.items) - # if so, break loop - break - return data -# fetches all items from item database -def fetchAllItems(store): - # fetch item from deta Base + if fetch.items: + item = fetch.items[0] + break + + return item + +def fetchItems(store): fetch = itemsDB.fetch({ 'store': store['key'], }) - data = fetch.items + items = fetch.items + while (fetch.last is not None): fetch = itemsDB.fetch({ 'store': store['key'], last: fetch.last }) - data = data + fetch.items - return data + items = items + fetch.items + + return items -# compares this item to other items from its store to determine if it is the cheapest then updates store accordingly def updateCheapest(item, store): cheapest = True - storeItems = fetchAllItems(store) + storeItems = fetchItems(store) itemPerFloz = item['perFloz'] + for storeItem in storeItems: - storeItemPerFloz = storeItem['perFloz'] - if storeItemPerFloz < itemPerFloz: cheapest = False + if storeItem['perFloz'] < itemPerFloz: cheapest = False + if cheapest: store['cheapestItem'] = item['name'] store['cheapestFloz'] = itemPerFloz storesDB.put(store) -### routes - @app.route('/', methods=['GET']) def get(): - # if there are no arguments on the request - if not request.args: - # get all stores - fetch = storesDB.fetch({}) - data = fetch.items - while (fetch.last is not None): - fetch = storesDB.fetch({ - last: fetch.last - }) - data = data + fetch.items - else: - # get store location details from URL arguments - if 'easting' in request.args and 'northing' in request.args and 'zone' in request.args: - stores = fetchStore() - if not stores: abort(404) - store = stores[0] - items = fetchAllItems(store) - store['items'] = items - # if an item GET request - if 'itemName' in request.args: - # passing through the store at position [0] in the fetched stores array - items = fetchItem(store) - if not items: abort(404) - data = items[0] - # otherwise is a store GET request - else: data = store - # else store key passed, get by key - elif 'storeKey' in request.args: data = fetchStore(request.args['storeKey']) - # otherwise is a malformed request - else: abort(400) - # otherwise return data - return data + if request.args: + if 'storeKey' in request.args: return fetchStore(request.args['storeKey']) + + if 'easting' not in request.args and 'northing' not in request.args and 'zone' not in request.args: abort(400) + + store = fetchStore() + + if not store: abort(404) + + store['items'] = fetchItems(store) + return store + + fetch = storesDB.fetch({}) + stores = fetch.items + while (fetch.last is not None): + fetch = storesDB.fetch({ + last: fetch.last + }) + stores = stores + fetch.items + + for store in stores: + store['items'] = fetchItems(store) + + return stores @app.route('/', methods=['POST']) def post(): - # checks for args - # easier to work with args here because it allows for reuse of functions above if request.args: if 'easting' in request.args and 'northing' in request.args and 'zone' in request.args and 'zoneLetter' and 'name' in request.args: northing = float(request.args['northing']) easting = float(request.args['easting']) zone = int(request.args['zone']) - # checks if this is posting a new store - # test for presence of stores in close vicinity with similar name + + # +/- 10 meters fetch = storesDB.fetch({ 'easting?r': [easting - 10, easting + 10], 'northing?r': [northing - 10, northing + 10], 'zone': zone, 'lowername?contains': request.args['name'].lower(), }) - stores = fetch.items - if not stores: - while (fetch.last is not None): - fetch = storesDB.fetch({ - 'easting?r': [easting - 10, easting + 10], - 'northing?r': [northing - 10, northing + 10], - 'zone': zone, - 'lowername?contains': request.args['name'].lower(), - last: fetch.last - }) - # check with each loop iteration if we've found it - if fetch.items: - stores = stores + fetch.items - # if so, break loop - break - # if found a store within +/-10 meters named similarly, abort - if stores: abort(409) - # by this point, app would have aborted if there were a problem - # let's create the records - # first the item dict - price = float(request.args['itemPrice']) - volume = int(request.args['itemVolume']) - item = { - 'name': request.args['itemName'], - 'lowername': request.args['itemName'].lower(), - 'volumeFloz': volume, - 'price': price, - 'perFloz': round(price / float(volume), 2), - 'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'), - } - # upload store to DB - store = storesDB.put({ - 'easting': easting, - 'northing': northing, + if fetch.items: abort(409) + + while (fetch.last is not None): + fetch = storesDB.fetch({ + 'easting?r': [easting - 10, easting + 10], + 'northing?r': [northing - 10, northing + 10], 'zone': zone, - 'zoneLetter': request.args['zoneLetter'], - 'name': request.args['name'], - 'lowername': request.args['name'].lower(), - 'cheapestItem': item['name'], - 'cheapestFloz': item['perFloz'], - 'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'), + 'lowername?contains': request.args['name'].lower(), + last: fetch.last }) - # upload item to DB - item['store'] = store['key'] - itemsDB.put(item) - # if there is an image upload - if request.files: - if 'file' in request.files: - file = request.files['file'] - drive.put(store['key'], data=request.files['file'].read()) - # finish successfully - return make_response("Success", 200) - # or if it's posting a new item in the store - elif 'storeKey' in request.args and 'itemName' in request.args and 'itemPrice' in request.args and 'itemVolume' in request.args: + if fetch.items: abort(409) + + price = float(request.args['itemPrice']) + volume = int(request.args['itemVolume']) + item = { + 'name': request.args['itemName'], + 'lowername': request.args['itemName'].lower(), + 'volumeFloz': volume, + 'price': price, + 'perFloz': round(price / float(volume), 2), + 'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'), + } + + store = storesDB.put({ + 'easting': easting, + 'northing': northing, + 'zone': zone, + 'zoneLetter': request.args['zoneLetter'], + 'name': request.args['name'], + 'lowername': request.args['name'].lower(), + 'cheapestItem': item['name'], + 'cheapestFloz': item['perFloz'], + 'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'), + }) + + item['store'] = store['key'] + itemsDB.put(item) + + if request.files: + if 'file' in request.files: drive.put(store['key'], data=request.files['file'].read()) + + return make_response("Success", 200) + + if 'storeKey' in request.args and 'itemName' in request.args and 'itemPrice' in request.args and 'itemVolume' in request.args: store = fetchStore(request.args['storeKey']) if not store: abort(404) - # check for existence of duplicate item + found = fetchItem(store) if (found): abort(409) - # let's create the record + price = float(request.args['itemPrice']) volume = int(request.args['itemVolume']) item = { @@ -228,93 +200,94 @@ def post(): 'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'), 'store': store['key'] } + updateCheapest(item, store) - # beam it up + itemsDB.put(item) - # finish successfully + return make_response("Success", 200) + abort(400) @app.route('/', methods=['PUT']) def put(): - # checks for arguments if request.args: if 'storeKey' in request.args: - # find store store = fetchStore(request.args['storeKey']) if not store: abort(404) - # updating item price + if 'itemName' in request.args and 'itemPrice' in request.args: - # find item - items = fetchItem(store) - if not items: abort (404) - item = items[0] - # change price + item = fetchItem(store) + if not item: abort (404) + price = float(request.args['itemPrice']) volume = int(item['volumeFloz']) + item['price'] = price item['perFloz'] = round(price / float(volume), 2) item['lastUpdated'] = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') + updateCheapest(item, store) - # beam it up + itemsDB.put(item) - # finish successfully + return make_response("Success", 200) - # updating store image - # if there is an image upload + if request.files: if 'file' in request.files: store['lastUpdated'] = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') storesDB.put(store) + file = request.files['file'] drive.put(store['key'], data=request.files['file'].read()) - # finish successfully + return make_response("Success", 200) + abort(400) @app.route('/img', methods=['GET']) def getImage(): - if not request.args: abort(400) - else: + if request.args: if 'imageKey' in request.args: mimetype = filetype.guess(drive.get(request.args['imageKey']).read()).mime return send_file(drive.get(request.args['imageKey']), download_name = request.args['imageKey'] + ".png", mimetype = mimetype) + abort(400) @app.route('/search', methods=['GET']) def search(): - if not request.args: abort(400) - else: + if request.args: if 'easting' in request.args and 'northing' and 'query' in request.args: - # search all stores within 50 mile radius (80 km) northing = float(request.args['northing']) easting = float(request.args['easting']) - # fetch store from deta Base + fetch = storesDB.fetch({ 'easting?r': [easting - 80000, easting + 80000], 'northing?r': [northing - 80000, northing + 80000] }) stores = fetch.items - # if we didn't find records on the first go around, try again - if not stores: - while (fetch.last is not None): - fetch = storesDB.fetch({ - 'easting?r': [easting + 80000, easting + 80000], - 'northing?r': [northing - 80000, northing + 80000], - last: fetch.last - }) - stores = stores + fetch.items - if not stores: abort(404) - # for item matching query + + while (fetch.last is not None): + fetch = storesDB.fetch({ + 'easting?r': [easting + 80000, easting + 80000], + 'northing?r': [northing - 80000, northing + 80000], + last: fetch.last + }) + stores = stores + fetch.items + + if not stores: abort(404) + items = [] + for store in stores: - # fetch item from deta Base querySubstrings = request.args['query'].split() + for querySubstring in querySubstrings: fetch = itemsDB.fetch({ 'store': store['key'], 'lowername?contains': querySubstring.lower(), }) items = items + fetch.items + while (fetch.last is not None): fetch = itemsDB.fetch({ 'store': store['key'], @@ -322,11 +295,12 @@ def search(): last: fetch.last }) items = items + fetch.items - # append distance information + for item in items: if item['store'] == store['key']: item['distance'] = max(abs(store['easting'] - easting), abs(store['northing'] - northing)) - if not items: abort(404) - return items - abort(400) -# no deletion planned (for now) \ No newline at end of file + if not items: abort(404) + + return items + + abort(400)