317 lines
11 KiB
Python
317 lines
11 KiB
Python
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
|
|
|
|
load_dotenv()
|
|
app = Flask(__name__)
|
|
CORS(app)
|
|
deta = Deta()
|
|
|
|
itemsDB = deta.Base('items')
|
|
storesDB = deta.Base('stores')
|
|
drive = deta.Drive('beerbuddy')
|
|
|
|
RADIUS = 80000
|
|
|
|
def fetchStore(key = None):
|
|
""" Fetch a store with the given key. If no key is provided, fetch store based on location data in request arguments """
|
|
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 = storesDB.fetch({
|
|
'easting?r': [easting - 1, easting + 1],
|
|
'northing?r': [northing - 1, northing + 1],
|
|
'zone': zone,
|
|
})
|
|
store = fetch.items[0]
|
|
|
|
if not store:
|
|
while (fetch.last is not None):
|
|
fetch = storesDB.fetch({
|
|
'easting?r': [easting - 1, easting + 1],
|
|
'northing?r': [northing - 1, northing + 1],
|
|
'zone': zone,
|
|
last: fetch.last
|
|
})
|
|
|
|
if fetch.items:
|
|
store = fetch.items[0]
|
|
break
|
|
|
|
return store
|
|
|
|
def fetchItem(store):
|
|
""" Fetch an item from given store that matches request arguments """
|
|
fetch = itemsDB.fetch({
|
|
'lowername': request.args['itemName'].lower(),
|
|
'store': store['key'],
|
|
})
|
|
|
|
if fetch.items:
|
|
item = fetch.items[0]
|
|
return item
|
|
else:
|
|
while (fetch.last is not None):
|
|
fetch = itemsDB.fetch({
|
|
'lowername': request.args['itemName'].lower(),
|
|
'store': store['key'],
|
|
last: fetch.last
|
|
})
|
|
|
|
if fetch.items:
|
|
item = fetch.items[0]
|
|
return item
|
|
|
|
def fetchItems(store):
|
|
""" Fetch all items from a given store """
|
|
fetch = itemsDB.fetch({
|
|
'store': store['key'],
|
|
})
|
|
items = fetch.items
|
|
|
|
while (fetch.last is not None):
|
|
fetch = itemsDB.fetch({
|
|
'store': store['key'],
|
|
last: fetch.last
|
|
})
|
|
items = items + fetch.items
|
|
|
|
return items
|
|
|
|
def updateCheapest(item, store):
|
|
""" Compare price of current item to other items in the given store and update store's cheapest item if this item is cheaper than the others """
|
|
cheapest = True
|
|
storeItems = fetchItems(store)
|
|
itemPerFloz = item['perFloz']
|
|
|
|
for storeItem in storeItems:
|
|
if storeItem['perFloz'] < itemPerFloz: cheapest = False
|
|
|
|
if cheapest:
|
|
store['cheapestItem'] = item['name']
|
|
store['cheapestFloz'] = itemPerFloz
|
|
storesDB.put(store)
|
|
|
|
@app.route('/', methods=['GET'])
|
|
def get():
|
|
""" Returns store based on request arguments. If no arguments are provided, returns all stores in database """
|
|
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():
|
|
""" Adds a store or item to the database depending on request arguments """
|
|
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'])
|
|
|
|
# +/- 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(),
|
|
})
|
|
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,
|
|
'lowername?contains': request.args['name'].lower(),
|
|
last: fetch.last
|
|
})
|
|
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)
|
|
|
|
found = fetchItem(store)
|
|
if (found): 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': store['key']
|
|
}
|
|
|
|
updateCheapest(item, store)
|
|
|
|
itemsDB.put(item)
|
|
|
|
return make_response("Success", 200)
|
|
|
|
abort(400)
|
|
|
|
@app.route('/', methods=['PUT'])
|
|
def put():
|
|
""" Updates item or store picture depending on request arguments """
|
|
if request.args:
|
|
if 'storeKey' in request.args:
|
|
store = fetchStore(request.args['storeKey'])
|
|
if not store: abort(404)
|
|
|
|
if 'itemName' in request.args and 'itemPrice' in request.args:
|
|
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)
|
|
|
|
itemsDB.put(item)
|
|
|
|
return make_response("Success", 200)
|
|
|
|
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())
|
|
|
|
return make_response("Success", 200)
|
|
|
|
abort(400)
|
|
|
|
@app.route('/img', methods=['GET'])
|
|
def getImage():
|
|
""" Returns store image based on image key provided in request arguments """
|
|
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():
|
|
""" Returns items matching query string in searchable radius (currently 50mi/80km) """
|
|
if request.args:
|
|
if 'easting' in request.args and 'northing' and 'query' in request.args:
|
|
northing = float(request.args['northing'])
|
|
easting = float(request.args['easting'])
|
|
|
|
fetch = storesDB.fetch({
|
|
'easting?r': [easting - RADIUS, easting + RADIUS],
|
|
'northing?r': [northing - RADIUS, northing + RADIUS]
|
|
})
|
|
stores = fetch.items
|
|
|
|
while (fetch.last is not None):
|
|
fetch = storesDB.fetch({
|
|
'easting?r': [easting + RADIUS, easting + RADIUS],
|
|
'northing?r': [northing - RADIUS, northing + RADIUS],
|
|
last: fetch.last
|
|
})
|
|
stores = stores + fetch.items
|
|
|
|
if not stores: abort(404)
|
|
|
|
items = []
|
|
|
|
for store in stores:
|
|
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'],
|
|
'lowername?contains': querySubstring.lower(),
|
|
last: fetch.last
|
|
})
|
|
items = items + fetch.items
|
|
|
|
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)
|