refactored backend code
This commit is contained in:
parent
486562f12c
commit
94adb2a820
1 changed files with 144 additions and 170 deletions
222
flask/backend.py
222
flask/backend.py
|
|
@ -1,43 +1,39 @@
|
||||||
from os import environ
|
from os import environ
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import filetype
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from flask import Flask, request, abort, make_response, send_file
|
from flask import Flask, request, abort, make_response, send_file
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from deta import Deta
|
from deta import Deta
|
||||||
import filetype
|
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
deta = Deta()
|
deta = Deta()
|
||||||
|
|
||||||
### connect to dbs and drive
|
|
||||||
itemsDB = deta.Base('items')
|
itemsDB = deta.Base('items')
|
||||||
storesDB = deta.Base('stores')
|
storesDB = deta.Base('stores')
|
||||||
drive = deta.Drive('beerbuddy')
|
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):
|
def fetchStore(key = None):
|
||||||
if key is not None:
|
if key is not None: return storesDB.get(key)
|
||||||
data = storesDB.get(key)
|
|
||||||
return data
|
|
||||||
else:
|
else:
|
||||||
northing = float(request.args['northing'])
|
northing = float(request.args['northing'])
|
||||||
easting = float(request.args['easting'])
|
easting = float(request.args['easting'])
|
||||||
zone = int(request.args['zone'])
|
zone = int(request.args['zone'])
|
||||||
# fetch store from deta Base
|
|
||||||
fetch = storesDB.fetch({
|
fetch = storesDB.fetch({
|
||||||
'easting?r': [easting - 1, easting + 1],
|
'easting?r': [easting - 1, easting + 1],
|
||||||
'northing?r': [northing - 1, northing + 1],
|
'northing?r': [northing - 1, northing + 1],
|
||||||
'zone': zone,
|
'zone': zone,
|
||||||
})
|
})
|
||||||
data = fetch.items
|
store = fetch.items[0]
|
||||||
# if we didn't find the record on the first go around, try again
|
|
||||||
if not data:
|
if not store:
|
||||||
while (fetch.last is not None):
|
while (fetch.last is not None):
|
||||||
fetch = storesDB.fetch({
|
fetch = storesDB.fetch({
|
||||||
'easting?r': [easting - 1, easting + 1],
|
'easting?r': [easting - 1, easting + 1],
|
||||||
|
|
@ -45,120 +41,106 @@ def fetchStore(key = None):
|
||||||
'zone': zone,
|
'zone': zone,
|
||||||
last: fetch.last
|
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):
|
def fetchItem(store):
|
||||||
# fetch item from deta Base
|
|
||||||
fetch = itemsDB.fetch({
|
fetch = itemsDB.fetch({
|
||||||
'lowername': request.args['itemName'].lower(),
|
'lowername': request.args['itemName'].lower(),
|
||||||
'store': store['key'],
|
'store': store['key'],
|
||||||
})
|
})
|
||||||
data = fetch.items
|
item = fetch.items[0]
|
||||||
# if we didn't find the record on the first go around, try again
|
|
||||||
if not data:
|
if not item:
|
||||||
while (fetch.last is not None):
|
while (fetch.last is not None):
|
||||||
fetch = itemsDB.fetch({
|
fetch = itemsDB.fetch({
|
||||||
'lowername': request.args['itemName'].lower(),
|
'lowername': request.args['itemName'].lower(),
|
||||||
'store': store['key'],
|
'store': store['key'],
|
||||||
last: fetch.last
|
last: fetch.last
|
||||||
})
|
})
|
||||||
# check with each loop iteration if we've found it
|
|
||||||
if fetch.items:
|
if fetch.items:
|
||||||
data.append(fetch.items)
|
item = fetch.items[0]
|
||||||
# if so, break loop
|
|
||||||
break
|
break
|
||||||
return data
|
|
||||||
|
|
||||||
# fetches all items from item database
|
return item
|
||||||
def fetchAllItems(store):
|
|
||||||
# fetch item from deta Base
|
def fetchItems(store):
|
||||||
fetch = itemsDB.fetch({
|
fetch = itemsDB.fetch({
|
||||||
'store': store['key'],
|
'store': store['key'],
|
||||||
})
|
})
|
||||||
data = fetch.items
|
items = fetch.items
|
||||||
|
|
||||||
while (fetch.last is not None):
|
while (fetch.last is not None):
|
||||||
fetch = itemsDB.fetch({
|
fetch = itemsDB.fetch({
|
||||||
'store': store['key'],
|
'store': store['key'],
|
||||||
last: fetch.last
|
last: fetch.last
|
||||||
})
|
})
|
||||||
data = data + fetch.items
|
items = items + fetch.items
|
||||||
return data
|
|
||||||
|
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):
|
def updateCheapest(item, store):
|
||||||
cheapest = True
|
cheapest = True
|
||||||
storeItems = fetchAllItems(store)
|
storeItems = fetchItems(store)
|
||||||
itemPerFloz = item['perFloz']
|
itemPerFloz = item['perFloz']
|
||||||
|
|
||||||
for storeItem in storeItems:
|
for storeItem in storeItems:
|
||||||
storeItemPerFloz = storeItem['perFloz']
|
if storeItem['perFloz'] < itemPerFloz: cheapest = False
|
||||||
if storeItemPerFloz < itemPerFloz: cheapest = False
|
|
||||||
if cheapest:
|
if cheapest:
|
||||||
store['cheapestItem'] = item['name']
|
store['cheapestItem'] = item['name']
|
||||||
store['cheapestFloz'] = itemPerFloz
|
store['cheapestFloz'] = itemPerFloz
|
||||||
storesDB.put(store)
|
storesDB.put(store)
|
||||||
|
|
||||||
### routes
|
|
||||||
|
|
||||||
@app.route('/', methods=['GET'])
|
@app.route('/', methods=['GET'])
|
||||||
def get():
|
def get():
|
||||||
# if there are no arguments on the request
|
if request.args:
|
||||||
if not request.args:
|
if 'storeKey' in request.args: return fetchStore(request.args['storeKey'])
|
||||||
# get all stores
|
|
||||||
|
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({})
|
fetch = storesDB.fetch({})
|
||||||
data = fetch.items
|
stores = fetch.items
|
||||||
while (fetch.last is not None):
|
while (fetch.last is not None):
|
||||||
fetch = storesDB.fetch({
|
fetch = storesDB.fetch({
|
||||||
last: fetch.last
|
last: fetch.last
|
||||||
})
|
})
|
||||||
data = data + fetch.items
|
stores = stores + fetch.items
|
||||||
else:
|
|
||||||
# get store location details from URL arguments
|
for store in stores:
|
||||||
if 'easting' in request.args and 'northing' in request.args and 'zone' in request.args:
|
store['items'] = fetchItems(store)
|
||||||
stores = fetchStore()
|
|
||||||
if not stores: abort(404)
|
return stores
|
||||||
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
|
|
||||||
|
|
||||||
@app.route('/', methods=['POST'])
|
@app.route('/', methods=['POST'])
|
||||||
def post():
|
def post():
|
||||||
# checks for args
|
|
||||||
# easier to work with args here because it allows for reuse of functions above
|
|
||||||
if request.args:
|
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:
|
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'])
|
northing = float(request.args['northing'])
|
||||||
easting = float(request.args['easting'])
|
easting = float(request.args['easting'])
|
||||||
zone = int(request.args['zone'])
|
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({
|
fetch = storesDB.fetch({
|
||||||
'easting?r': [easting - 10, easting + 10],
|
'easting?r': [easting - 10, easting + 10],
|
||||||
'northing?r': [northing - 10, northing + 10],
|
'northing?r': [northing - 10, northing + 10],
|
||||||
'zone': zone,
|
'zone': zone,
|
||||||
'lowername?contains': request.args['name'].lower(),
|
'lowername?contains': request.args['name'].lower(),
|
||||||
})
|
})
|
||||||
stores = fetch.items
|
if fetch.items: abort(409)
|
||||||
if not stores:
|
|
||||||
while (fetch.last is not None):
|
while (fetch.last is not None):
|
||||||
fetch = storesDB.fetch({
|
fetch = storesDB.fetch({
|
||||||
'easting?r': [easting - 10, easting + 10],
|
'easting?r': [easting - 10, easting + 10],
|
||||||
|
|
@ -167,16 +149,8 @@ def post():
|
||||||
'lowername?contains': request.args['name'].lower(),
|
'lowername?contains': request.args['name'].lower(),
|
||||||
last: fetch.last
|
last: fetch.last
|
||||||
})
|
})
|
||||||
# check with each loop iteration if we've found it
|
if fetch.items: abort(409)
|
||||||
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'])
|
price = float(request.args['itemPrice'])
|
||||||
volume = int(request.args['itemVolume'])
|
volume = int(request.args['itemVolume'])
|
||||||
item = {
|
item = {
|
||||||
|
|
@ -187,7 +161,7 @@ def post():
|
||||||
'perFloz': round(price / float(volume), 2),
|
'perFloz': round(price / float(volume), 2),
|
||||||
'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
|
'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
}
|
}
|
||||||
# upload store to DB
|
|
||||||
store = storesDB.put({
|
store = storesDB.put({
|
||||||
'easting': easting,
|
'easting': easting,
|
||||||
'northing': northing,
|
'northing': northing,
|
||||||
|
|
@ -199,24 +173,22 @@ def post():
|
||||||
'cheapestFloz': item['perFloz'],
|
'cheapestFloz': item['perFloz'],
|
||||||
'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
|
'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
})
|
})
|
||||||
# upload item to DB
|
|
||||||
item['store'] = store['key']
|
item['store'] = store['key']
|
||||||
itemsDB.put(item)
|
itemsDB.put(item)
|
||||||
# if there is an image upload
|
|
||||||
if request.files:
|
if request.files:
|
||||||
if 'file' in request.files:
|
if 'file' in request.files: drive.put(store['key'], data=request.files['file'].read())
|
||||||
file = request.files['file']
|
|
||||||
drive.put(store['key'], data=request.files['file'].read())
|
|
||||||
# finish successfully
|
|
||||||
return make_response("Success", 200)
|
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 'storeKey' in request.args and 'itemName' in request.args and 'itemPrice' in request.args and 'itemVolume' in request.args:
|
||||||
store = fetchStore(request.args['storeKey'])
|
store = fetchStore(request.args['storeKey'])
|
||||||
if not store: abort(404)
|
if not store: abort(404)
|
||||||
# check for existence of duplicate item
|
|
||||||
found = fetchItem(store)
|
found = fetchItem(store)
|
||||||
if (found): abort(409)
|
if (found): abort(409)
|
||||||
# let's create the record
|
|
||||||
price = float(request.args['itemPrice'])
|
price = float(request.args['itemPrice'])
|
||||||
volume = int(request.args['itemVolume'])
|
volume = int(request.args['itemVolume'])
|
||||||
item = {
|
item = {
|
||||||
|
|
@ -228,74 +200,72 @@ def post():
|
||||||
'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
|
'lastUpdated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
'store': store['key']
|
'store': store['key']
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCheapest(item, store)
|
updateCheapest(item, store)
|
||||||
# beam it up
|
|
||||||
itemsDB.put(item)
|
itemsDB.put(item)
|
||||||
# finish successfully
|
|
||||||
return make_response("Success", 200)
|
return make_response("Success", 200)
|
||||||
|
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
@app.route('/', methods=['PUT'])
|
@app.route('/', methods=['PUT'])
|
||||||
def put():
|
def put():
|
||||||
# checks for arguments
|
|
||||||
if request.args:
|
if request.args:
|
||||||
if 'storeKey' in request.args:
|
if 'storeKey' in request.args:
|
||||||
# find store
|
|
||||||
store = fetchStore(request.args['storeKey'])
|
store = fetchStore(request.args['storeKey'])
|
||||||
if not store: abort(404)
|
if not store: abort(404)
|
||||||
# updating item price
|
|
||||||
if 'itemName' in request.args and 'itemPrice' in request.args:
|
if 'itemName' in request.args and 'itemPrice' in request.args:
|
||||||
# find item
|
item = fetchItem(store)
|
||||||
items = fetchItem(store)
|
if not item: abort (404)
|
||||||
if not items: abort (404)
|
|
||||||
item = items[0]
|
|
||||||
# change price
|
|
||||||
price = float(request.args['itemPrice'])
|
price = float(request.args['itemPrice'])
|
||||||
volume = int(item['volumeFloz'])
|
volume = int(item['volumeFloz'])
|
||||||
|
|
||||||
item['price'] = price
|
item['price'] = price
|
||||||
item['perFloz'] = round(price / float(volume), 2)
|
item['perFloz'] = round(price / float(volume), 2)
|
||||||
item['lastUpdated'] = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
|
item['lastUpdated'] = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
updateCheapest(item, store)
|
updateCheapest(item, store)
|
||||||
# beam it up
|
|
||||||
itemsDB.put(item)
|
itemsDB.put(item)
|
||||||
# finish successfully
|
|
||||||
return make_response("Success", 200)
|
return make_response("Success", 200)
|
||||||
# updating store image
|
|
||||||
# if there is an image upload
|
|
||||||
if request.files:
|
if request.files:
|
||||||
if 'file' in request.files:
|
if 'file' in request.files:
|
||||||
store['lastUpdated'] = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
|
store['lastUpdated'] = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
storesDB.put(store)
|
storesDB.put(store)
|
||||||
|
|
||||||
file = request.files['file']
|
file = request.files['file']
|
||||||
drive.put(store['key'], data=request.files['file'].read())
|
drive.put(store['key'], data=request.files['file'].read())
|
||||||
# finish successfully
|
|
||||||
return make_response("Success", 200)
|
return make_response("Success", 200)
|
||||||
|
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
@app.route('/img', methods=['GET'])
|
@app.route('/img', methods=['GET'])
|
||||||
def getImage():
|
def getImage():
|
||||||
if not request.args: abort(400)
|
if request.args:
|
||||||
else:
|
|
||||||
if 'imageKey' in request.args:
|
if 'imageKey' in request.args:
|
||||||
mimetype = filetype.guess(drive.get(request.args['imageKey']).read()).mime
|
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)
|
return send_file(drive.get(request.args['imageKey']), download_name = request.args['imageKey'] + ".png", mimetype = mimetype)
|
||||||
|
abort(400)
|
||||||
|
|
||||||
@app.route('/search', methods=['GET'])
|
@app.route('/search', methods=['GET'])
|
||||||
def search():
|
def search():
|
||||||
if not request.args: abort(400)
|
if request.args:
|
||||||
else:
|
|
||||||
if 'easting' in request.args and 'northing' and 'query' in 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'])
|
northing = float(request.args['northing'])
|
||||||
easting = float(request.args['easting'])
|
easting = float(request.args['easting'])
|
||||||
# fetch store from deta Base
|
|
||||||
fetch = storesDB.fetch({
|
fetch = storesDB.fetch({
|
||||||
'easting?r': [easting - 80000, easting + 80000],
|
'easting?r': [easting - 80000, easting + 80000],
|
||||||
'northing?r': [northing - 80000, northing + 80000]
|
'northing?r': [northing - 80000, northing + 80000]
|
||||||
})
|
})
|
||||||
stores = fetch.items
|
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):
|
while (fetch.last is not None):
|
||||||
fetch = storesDB.fetch({
|
fetch = storesDB.fetch({
|
||||||
'easting?r': [easting + 80000, easting + 80000],
|
'easting?r': [easting + 80000, easting + 80000],
|
||||||
|
|
@ -303,18 +273,21 @@ def search():
|
||||||
last: fetch.last
|
last: fetch.last
|
||||||
})
|
})
|
||||||
stores = stores + fetch.items
|
stores = stores + fetch.items
|
||||||
|
|
||||||
if not stores: abort(404)
|
if not stores: abort(404)
|
||||||
# for item matching query
|
|
||||||
items = []
|
items = []
|
||||||
|
|
||||||
for store in stores:
|
for store in stores:
|
||||||
# fetch item from deta Base
|
|
||||||
querySubstrings = request.args['query'].split()
|
querySubstrings = request.args['query'].split()
|
||||||
|
|
||||||
for querySubstring in querySubstrings:
|
for querySubstring in querySubstrings:
|
||||||
fetch = itemsDB.fetch({
|
fetch = itemsDB.fetch({
|
||||||
'store': store['key'],
|
'store': store['key'],
|
||||||
'lowername?contains': querySubstring.lower(),
|
'lowername?contains': querySubstring.lower(),
|
||||||
})
|
})
|
||||||
items = items + fetch.items
|
items = items + fetch.items
|
||||||
|
|
||||||
while (fetch.last is not None):
|
while (fetch.last is not None):
|
||||||
fetch = itemsDB.fetch({
|
fetch = itemsDB.fetch({
|
||||||
'store': store['key'],
|
'store': store['key'],
|
||||||
|
|
@ -322,11 +295,12 @@ def search():
|
||||||
last: fetch.last
|
last: fetch.last
|
||||||
})
|
})
|
||||||
items = items + fetch.items
|
items = items + fetch.items
|
||||||
# append distance information
|
|
||||||
for item in items:
|
for item in items:
|
||||||
if item['store'] == store['key']: item['distance'] = max(abs(store['easting'] - easting), abs(store['northing'] - northing))
|
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)
|
if not items: abort(404)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
abort(400)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue