# # hosts dashboard webpage on flask # dashboard.py contains web interface to clue DB + router # from flask import Flask, request, render_template, send_from_directory, jsonify import db import router app = Flask(__name__) # # BIKE APP API # # called by making a post request to hh.acetyl.net/ with expected JSON attached # enable eligibility to receive routes. can be disabled either in app or on dashboard, so before a trip must be re-enabled. # responds with your next route @app.route("/enableTeam", methods=['POST']) # Expected JSON # {"team_name" : "XXXX", str # "longitude" : xx.xxxxxx, float //current team location # "latitude" : xx.xxxxxx, float } # Returns JSON # {"status" : "OK"/"ERROR XX", str } expect something like: http://acetyl.net:5000/route/v1/bike/-71.0553792,42.3688272;-71.0688746,42.3576234 without the waypoints section # ERROR CODES: 1 = missing fields, 2 = invalid coordinates, 4 = team ALREADY exists, def enableTeam(): content = request.get_json() if not ('team_name' in content and 'longitude' in content and 'latitude' in content): status = "ERROR 1" return jsonify({'status' : status}) if not ( (type(content['longitude']) is float ) and (type(content['latitude']) is float )): status = "ERROR 2" return jsonify({'status' : status}) if db.addBike(content['team_name'], content['latitude'], content['longitude']) == 4: status = "ERROR 4" return jsonify({'status' : status}) return jsonify({'status' : "OK"}) # disable eligibility to receive routes, e.g. if team is done while another is not. can be re-enabled either in app or on dashboard # responds with your next route @app.route("/disableTeam", methods=['POST']) # Expected JSON # {"team_name" : "XXXX", str } # Returns JSON # {"status" : "OK"/"ERROR XX", str } # ERROR CODES: 1 = missing fields, 4 = team does not exist, def disableTeam(): content = request.get_json() if not ('team_name' in content): status = "ERROR 1" return jsonify({'status' : status}) if db.deleteBike(content['team_name']) == 4: status = "ERROR 4" return jsonify({'status' : status}) return jsonify({'status' : "OK"}) # requests a route to the best clue given the team's current coordinates @app.route("/requestRoute", methods=['POST']) # Expected JSON # {"team_name" : "XXXX", str # "longitude" : xx.xxxxxx, float //current team location # "latitude" : xx.xxxxxx, float } # Returns JSON # {"status" : "OK"/"ERROR XX", str # "clue_name" : "XXXX", str # "clue_long" : xx.xxxxxx, float # "clue_lat" : xx.xxxxxx, float # "clue_info" : "Xxxx xxx xxx", str # "route" : {...}, JSON } expect something like: http://acetyl.net:5000/route/v1/bike/-71.0553792,42.3688272;-71.0688746,42.3576234 without the waypoints section # ERROR CODES: 1 = missing fields, 2 = invalid coordinates, 3 = cluster calculation in progress, 4 = team not found, 5 = no more clues, 6 = routing error def requestRoute(): content = request.get_json() # verify request if not ('team_name' in content and 'longitude' in content and 'latitude' in content): return jsonify({'status' : "ERROR 1"}) if not ( (type(content['longitude']) is float ) and (type(content['latitude']) is float)): return jsonify({'status' : "ERROR 2"}) if db.pingBike(content['team_name'], content['latitude'], content['longitude']) == 4: return jsonify({'status' : "ERROR 4"}) if db.currently_updating: return jsonify({'status' : "ERROR 3"}) bike, clue = db.getBikeCluePair(content['team_name']) if clue == None: return jsonify({'status' : "ERROR 5"}) route = router.getRouteFullJSON(bike, clue) if route['code'] != 'Ok': # or some other code indicating routing problem? return jsonify({'status' : "ERROR 6"}) db.reassignIndividualRoute(bike, route) # update displayed route on dashboard reply = {"status" : "OK", "clue_name" : clue.name, "clue_long" : clue.longitude, "clue_lat" : clue.latitude, "clue_info" : clue.info, "route" : route} return jsonify(reply) # periodically called to update team location in the management dashboard @app.route("/updateTeamLocation", methods=['POST']) # Expected JSON # {"team_name" : "XXXX", str # "longitude" : xx.xxxxxx, float # "latitude" : xx.xxxxxx, float } # Returns JSON # {"status" : "OK"/"ERROR XX" } # ERROR CODES: 1 = missing fields, 2 = invalid coordinates, 4 = no active team found under given name, def updateTeamLocation(): status = "OK" content = request.get_json() if not ('team_name' in content and 'longitude' in content and 'latitude' in content): status = "ERROR 1" return jsonify({'status' : status}) if not ( (type(content['longitude']) is float ) and (type(content['latitude']) is float)): status = "ERROR 2" return jsonify({'status' : status}) if db.pingBike(content['team_name'], content['latitude'], content['longitude']) == 4: status = "ERROR 4" return jsonify({'status' : status}) return jsonify({'status' : "OK"}) # mark clue as visited from app @app.route("/visitClueTeam", methods=['POST']) # Expected JSON # {"team_name" : xxxx, str # "clue_name" : xxxx, str # "longitude" : xx.xxxxxx, float # "latitude" : xx.xxxxxx, float } # Returns JSON # {"status" : "OK"/"ERROR XX" } # ERROR CODES: 1 = missing fields, 2 = invalid coordinates, 3 = too far from clue location, 4 = no such team, 5 = no such clue, 6 = already visited def visitTeam(): content = request.get_json() if not ('team_name' in content and 'longitude' in content and 'latitude' in content and 'clue_name' in content): return jsonify({'status' : "ERROR 1"}) if not ( (type(content['longitude']) is float ) and (type(content['latitude']) is float)): status = "ERROR 2" return jsonify({'status' : status}) if db.pingBike(content['team_name'], content['latitude'], content['longitude']) == 4: return jsonify({'status' : "ERROR 4"}) result = db.visitClueTeam(content['team_name'], content['clue_name']) if result != 0: return jsonify({'status' : f"ERROR {result}"}) return jsonify({'status' : "OK"}) # # DISCORD BOT API # @app.route("/visitClueGeneric", methods=['POST']) # Expected JSON # {"clue_name" : xxxx, str} # Returns JSON # {"status" : "OK"/"ERROR XX" } # ERROR CODES: 1 = missing fields, 2 = clue doesn't exist, 3 = already marked as visited def visitGeneric(): content = request.get_json() if not ('clue_name' in content): return jsonify({'status' : "ERROR 1"}) result = db.visitClue(content['clue_name']) if result != 0: return jsonify({'status' : f"ERROR {result}"}) return jsonify({'status' : "OK"}) @app.route("/enableClue", methods=['POST']) # Expected JSON # {"clue_name" : xxxx, str} # Returns JSON # {"status" : "OK"/"ERROR XX" } # ERROR CODES: 1 = missing fields, 2 = clue doesn't exist, 3 = already marked as visited def enableClueToggle(): content = request.get_json() if not ('clue_name' in content): return jsonify({'status' : "ERROR 1"}) result = db.toggleEnableClue(content['clue_name']) if result != 0: return jsonify({'status' : f"ERROR {result}"}) return jsonify({'status' : "OK"}) # # WEB / DASHBOARD API # # send updated bike/clue/home info # POST = request above @app.route("/getLatestInfo", methods=['POST']) def getLatestInfo(): # run first update if db.startup == False: db.startup = True db.updateRoutes() content = request.get_json() last_timestamp = content['info_age'] data = {'timestamp' : db.getTime(), 'clues_changed' : False, 'home_changed' : False, 'routes_changed' : False} cl = db.getCluesJSON(last_timestamp) if cl != False: data['clues_changed'] = True data['clues'] = cl h = db.getHomeBaseJSON(last_timestamp) if h != False: data['home_changed'] = True data['homebase'] = h r = db.getRoutesJSON(last_timestamp) if r != False: data['routes_changed'] = True data['routes'] = r data['calculating_routes'] = db.currently_updating data['bikes'] = db.getBikesJSON() data['status'] = "OK" return jsonify(data) @app.route("/addClue", methods=['POST']) def addClueWeb(): content = request.get_json() if not ('clue_name' in content and 'longitude' in content and 'latitude' in content and 'clue_info' in content): return jsonify({'status' : "ERROR 1"}) #db.addClue() return jsonify({'status' : "OK",}) # main page # GET = get main page @app.route("/", methods=['GET']) def siteIndex(): #clues = db.getClues(); bikes = db.getBikes() return render_template("index.html")#, clues=clues, bikes=bikes) # add csv, download as csv @app.route("/controls", methods=['GET', 'POST']) def siteControls(): if request.method == "GET": return render_template("controls.html") if request.method == "POST": cmd = request.form.get('command') print(cmd) if cmd == "downloadSave": print("send") db.save() return send_from_directory(".", "savefile.csv", as_attachment=True) elif cmd == "loadDirty": dirty_csv = request.files['dirtyfile'] dirty_csv.save("dirt.csv") if db.loadDirty("dirt.csv") != 0: return jsonify({"status" : "ERROR"}) return jsonify({"status" : "OK"}) elif cmd == "loadSave": dirty_csv = request.files['cleanfile'] dirty_csv.save("clean.csv") if db.load("clean.csv") != 0: return jsonify({"status" : "ERROR"}) return jsonify({"status" : "OK"}) return render_template("controls.html") if __name__ == "__main__": app.config['SESSION_TYPE'] = 'filesystem' app.secret_key = 'hf8f3sd0zmqpmhss7dr3' app.run(host="127.0.0.1", port=5001, debug=True)