diff --git a/Test/__pycache__/test_one.cpython-39.pyc b/Test/__pycache__/test_one.cpython-39.pyc deleted file mode 100644 index 71c41fd540580ef134734179b3304732ca9a4e7e..0000000000000000000000000000000000000000 Binary files a/Test/__pycache__/test_one.cpython-39.pyc and /dev/null differ diff --git a/Test/manualtestplan.pdf b/Test/manualtestplan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6456f68175b8d1bde5430333d007ecf7763eca20 Binary files /dev/null and b/Test/manualtestplan.pdf differ diff --git a/Test/key.env b/src/.env similarity index 100% rename from Test/key.env rename to src/.env diff --git a/src/api.py b/src/api.py index 609aa7928205cd777a6bc78f8268b9ed5d84b9a6..dacac10c9dffe092e11ca2181334dfd6bbec8faf 100644 --- a/src/api.py +++ b/src/api.py @@ -9,12 +9,67 @@ import requests import json import pymongo import argparse -from flask import Flask, request, jsonify, render_template +from query_parser import Query +from flask import Flask, request, jsonify, render_template, send_from_directory from database import get_key, get_collection, valid_champ -app = Flask(__name__) +app = Flask(__name__, static_folder="static") +@app.route('/') +def home(): + #file_template = send_from_directory('client', "index.html") + #return render_template("index.html") + return send_from_directory('templates', "index.html") + +@app.route('/PRVisual') +def pr_champ_vis(): + #file_template = send_from_directory('client', "index.html") + #return render_template("index.html") + return send_from_directory('templates', "pr_champion_visual.html") + +@app.route('/WRVisual') +def wr_champ_vis(): + #file_template = send_from_directory('client', "index.html") + #return render_template("index.html") + return send_from_directory('templates', "wr_champion_visual.html") + + +"""GET request for search and a query.""" +@app.route('/search') +def get_search(): + query = request.args.get("q") # @UndefinedVariable + query_parse = Query() + + if query is None or len(request.args) > 1: + bad_input_error = { + "status": 400, + "error":"Bad Request" + } + + return bad_input_error + + query_info = query_parse.parse_user_input(query) + query_result = query_parse.query_handler(query_info[0], query_info[1], query_info[2]) + if not query_result: + error = { + "status": 500, + "error":"Internal Server Error", + "message":"No entries found with given query" + } + + return error + + query_result_cleaned = [] + + for i in query_result: + tmp = {} + for j in i.keys(): + if j != "_id": + tmp[j] = i[j] + query_result_cleaned.append(tmp) + + return jsonify({'result' : query_result_cleaned}) @app.route('/champions', methods=['GET']) def getAllChampions(): """ @@ -130,3 +185,6 @@ def delete_champion(): return "deleted champion with name " + _id +if __name__ == '__main__': + app.run(debug=False) + diff --git a/src/database.py b/src/database.py index f21622d03304bcaff2c27dad19382fd70121f90c..6f91a9646faa402876876455ad40102e87880716 100644 --- a/src/database.py +++ b/src/database.py @@ -13,7 +13,8 @@ def get_key(): be in src in terminal """ load_dotenv('./key.env') - return os.getenv("SECRET_KEY") + #return os.getenv("SECRET_KEY") + return "mongodb+srv://JohnIm:4MY7jaApcsPmj4Kl@cluster0.0fsik.mongodb.net/Cluster0?ssl=true&ssl_cert_reqs=CERT_NONE" def database_handler(ret_arr1): """ diff --git a/src/query_parser.py b/src/query_parser.py new file mode 100644 index 0000000000000000000000000000000000000000..f9aaa90e55eca60bda010d0c63a6478c81c97125 --- /dev/null +++ b/src/query_parser.py @@ -0,0 +1,152 @@ +import re +from database import get_collection +class Query(object): + ''' + classdocs + ''' + + + def __init__(self): + print("query start") + + def parse_user_input(self, input_string): + form = re.compile(r"^[a-zA-Z]+\.[a-zA-Z\_]+\:.*$") + f = re.search(form, input_string) + if f is None: + raise ValueError('bad format, q not in form of obj.field:content') + + + obj_pattern = re.compile(r"^[a-zA-Z]+\.") + obj = re.search(obj_pattern, input_string) + + if obj is None: + raise ValueError('Object:bad format') + else: + obj = obj.group(0) + + field_pattern = re.compile(r"\.[a-zA-Z\_]+\:") + field = re.findall(field_pattern, input_string) + + if field is None: + raise ValueError('field, invalid format') + elif len(field) > 1: + raise ValueError('too many operators') + else: + field = field[0] + field_value_pattern = re.compile(r'\:((\s[>,<]{1}\s[0-9]+)|(\"[a-zA-Z0-9]+\")|([a-zA-Z0-9]+\s[a-zA-Z0-9]*)|(\sNOT\s[a-zA-Z0-9]+)|([a-zA-Z0-9]+\sAND\s[a-zA-Z0-9]+)|([a-zA-Z0-9]+\sOR\s[a-zA-Z0-9]+))$') + content = re.search(field_value_pattern, input_string) + + if content is None: + raise ValueError('content invalid format, [<,>] operators should be used with numbers, AND, NOT, OR needs keywords') + else: + content = content.group(0) + + return obj, field, content + + def valid_parse(self, obj, field): + obj_tmp = obj.replace('.',"") + obj_tmp = obj_tmp.strip() + + if obj_tmp != "champion": + raise ValueError('Object, bad value') + + + + field_tmp = field.replace('.',"") + field_tmp = field_tmp.replace(':',"") + field_tmp = field_tmp.strip() + + model_dict = {"name": "Lex", + "win_rate" : "79%", + "pick_rate" : "48%", + "champ_tier" : "Tier 4", + "counter_champs" : ["Azir","Yasuo", "Yone"], + "strong_against" : ["Zed", "Akali", "Alistar"], + } + + if obj_tmp == "champion" and field_tmp not in model_dict.keys(): + raise ValueError('Field, bad value for author') + + return None + + def query_handler(self, obj, field, content): + self.valid_parse(obj, field) + obj = obj.replace('.',"") + obj = obj.strip() + + field = field.replace('.',"") + field = field.replace(':',"") + field = field.strip() + content = content.replace(":","") + print(obj, field, content) + print(content) + if "<" in content: + return self.handle_less(obj, field, content) + elif ">" in content: + return self.handle_greater(obj, field, content) + elif '"' in content: + return self.handle_quotes(obj, field, content) + elif "AND" in content: + return self.handle_and(obj, field, content) + elif "OR" in content: + return self.handle_or(obj, field, content) + elif "NOT" in content: + return self.handle_not(obj, field, content) + else: + return self.handle_standard(obj, field, content) + + def is_num(self, input_string): + for c in input_string: + if c.isdigit(): + input_string = int(input_string) + return input_string + return input_string + + def clean_content(self, content, tmp): + cont = content.replace(tmp,"") + cont = cont.strip() + return cont + + def handle_greater(self, obj, field, content): + tmp = ">" + cont = self.clean_content(content,tmp) + collection_list = [] + collection = self.get_collection(obj) + + for i in collection.find(): + if int(i[field]) > int(cont): + collection_list.append(i) + + return collection_list + + def handle_less(self, obj, field, content): + tmp = "<" + cont = self.clean_content(content,tmp) + collection_list = [] + collection = self.get_collection(obj) + + for i in collection.find(): + if float(i[field].strip('%')) < float(cont): + collection_list.append(i) + return collection_list + + + def handle_standard(self,obj, field, content): + cont = content + collection_list = [] + collection = self.get_collection(obj) + + for i in collection.find(): + if cont in i[field]: + collection_list.append(i) + + return collection_list + + def get_collection(self, obj): + return get_collection() + +if __name__ == '__main__': + D = Query() + obj, f, c = D.parse_user_input('champion.win_rate: < 79') + print(obj, f, c) + print(D.query_handler(obj, f, c)) \ No newline at end of file diff --git a/src/scraper.py b/src/scraper.py index e6ab765ab6f728025cd1f046961fc1536764511b..3314a094296bf0a00a87b137def00228683f9bf1 100644 --- a/src/scraper.py +++ b/src/scraper.py @@ -9,7 +9,8 @@ class Scraper: #This Function is the Constructor of the Scrape Class def __init__(self): print("scraper start") - + #got help from https://stackoverflow.com/questions/53652731/getting-http-404-error-when-web-scraping-in-python-3-7 for the headers for user-agent + #https://stackoverflow.com/questions/61991072/getting-error-404-while-scraping-using-beautiful-soup-even-though-the-site-exis #this function scrapes the links of all champion pages of all the champions on op.gg def scrape_champion_links(self): url = 'https://na.op.gg/champion/statistics' diff --git a/src/static/script.js b/src/static/script.js new file mode 100644 index 0000000000000000000000000000000000000000..71528b4f098eaaae086833c7f9c9cc80cd815400 --- /dev/null +++ b/src/static/script.js @@ -0,0 +1,141 @@ +function handleGetChamp(name) { + console.log(name) + var u = "http://127.0.0.1:5000/champion?name="+name + console.log(u) + fetch(u) + .then(response => { + console.log(response) + if (!response.ok) { + throw Error("ERROR"); + } + return response.json(); + }).then(data=> { + console.log(data) + var counters = data.map(counterChamps => { + temp = [] + for (x = 0; x < counterChamps.counter_champs.length; x++) { + temp.push(`<p>counter_champs : ` + counterChamps.counter_champs[x] + `</p>`) + } + return temp + }); + var strong = data.map(strongChamps => { + temp2 = [] + for (x = 0; x < strongChamps.counter_champs.length; x++) { + temp.push(`<p>strong_against : ` + strongChamps.strong_against[x] + `</p>`) + } + return temp2 + }); + const html = data.map(champ => { + var s = '<p>name : ' + champ.name + '</p>' + + '<p>win_rate : ' + champ.win_rate + '</p>' + + '<p>pick_rate : ' + champ.pick_rate + '</p>' + + '<p>champ_tier : ' + champ.champ_tier + '</p>' + return s + temp + temp2; + }); + document.querySelector('#championGet').innerHTML = ""; + document.querySelector('#championGet').insertAdjacentHTML('afterbegin', html); + }).catch(error=> { + const returnObject = '<p>Error: ' + 'Not a valid name' + '</p>' + document.querySelector('#championGet').insertAdjacentHTML('afterbegin', returnObject); + }); +} + +function handleGetQuery(querystring) { + var u = "http://127.0.0.1:5000/search?q=" + querystring + console.log(u) + var dict = []; + fetch(u) + .then(response => { + if (!response.ok) { + throw Error("ERROR"); + } + return response.json(); + }).then(data=> { + console.log(data) + for (x = 0; x < data.result.length; x++) { + var counters = data.result.map(counterChamps => { + temp = [] + for (x = 0; x < counterChamps.counter_champs.length; x++) { + temp.push(`<p>counter_champs : ` + counterChamps.counter_champs[x] + `</p>`) + } + return temp + }); + var strong = data.result.map(strongChamps => { + temp2 = [] + for (x = 0; x < strongChamps.counter_champs.length; x++) { + temp.push(`<p>strong_against : ` + strongChamps.strong_against[x] + `</p>`) + } + return temp2 + }); + const html = data.result.map(champ => { + var s = '<p>name : ' + champ.name + '</p>' + + '<p>win_rate : ' + champ.win_rate + '</p>' + + '<p>pick_rate : ' + champ.pick_rate + '</p>' + + '<p>champ_tier : ' + champ.champ_tier + '</p>' + return s + temp + temp2; + }); + document.querySelector('#championGet').innerHTML = ""; + document.querySelector('#championGet').insertAdjacentHTML('afterbegin', html); + } + }).catch(error=> { + console.log(error) + const returnObject = '<p>Error: ' + 'Not a valid name' + '</p>' + document.querySelector('#championGet').insertAdjacentHTML('afterbegin', returnObject); + }); +} + +function test(querystring) { + var u = "http://127.0.0.1:5000/search?q=" + querystring + console.log(u) + var dict = []; + fetch(u) + .then(response => { + if (!response.ok) { + throw Error("ERROR"); + } + return response.json(); + }).then(data=> { + console.log(data) + for (x = 0; x < data.result.length; x++) { + const html = data.result.map(champ => { + dict.push({ + name : champ.name, + win_rate : champ.win_rate, + pick_rate : champ.pick_rate, + champ_tier : champ.champ_tier + }) + }); + addTable(dict) + } + }) +} + +function addTable(arr2) { + arr = arr2 + setTimeout(function(){ + var myTableDiv = document.getElementById("myDynamicTable"); + + var table = document.createElement('TABLE'); + table.border = '1'; + + var tableBody = document.createElement('TBODY'); + table.appendChild(tableBody); + + for (var i = 0; i < 3; i++) { + var tr = document.createElement('TR'); + tableBody.appendChild(tr); + for (var j = 0; j < 4; j++) { + var td = document.createElement('TD'); + td.width = '75'; + td.appendChild(document.createTextNode(arr[j]['name'])); + tr.appendChild(td); + } + } + myTableDiv.appendChild(table); + }, 5000); +} + + +function clearValues() { + document.querySelector('#championGet').innerHTML = ""; +} diff --git a/src/templates/index.html b/src/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..beb30977865faffffaa3c811d18b1964473c9832 --- /dev/null +++ b/src/templates/index.html @@ -0,0 +1,31 @@ +<!doctype html> +<html> + <head> + <title>Start Page</title> + </head> + <body> + <h1> A League of Legends Statistics Website</h1><br> + <nav> + <a href="/PRVisual">Top Champions by Pick Rate</a> | + <a href="/WRVisual">Top Champions by Win Rate</a> + </nav><br><br> + <form> + <label>Champion Name:</label><br><br> + + <input type="text" id="field" name="name" style="height:50px; width:712px"><br><br> + <button type = "button" onclick="handleGetChamp(field.value)" style="height:50px; width:720px">Get Champ</button><br><br><br><br> + <label>Query (Ex : champion.win_rate: > 55):</label><br><br> + <input type="text" id="field2" name="wr" style="height:50px; width:712px"><br><br> + <button type = "button" onclick="test(field2.value)" style="height:50px; width:720px">Get Query</button><br><br><br><br> + <button type = "button" onclick="clearValues()" style="height:50px; width:720px">Clear Results</button><br><br> + </form> + <table> + <thead></thead> + <tbody></tbody> + </table> + <h1> Results Below </h1> + <div id="championGet"></div> + <div id="myDynamicTable"></div> + </body> + <script src="../static/script.js"></script> +</html> \ No newline at end of file diff --git a/src/templates/pr_champion_visual.html b/src/templates/pr_champion_visual.html new file mode 100644 index 0000000000000000000000000000000000000000..dfe217fa5cc8ab04efadc3ced80edda7e8918478 --- /dev/null +++ b/src/templates/pr_champion_visual.html @@ -0,0 +1,18 @@ +<!doctype html> +<html> + <head> + <title>Pick Rate Page</title> + </head> + <body> + <h1> Top k Champions by Pick Rate </h1> + <nav> + <a href="/">homepage</a> + + </nav> + <h1>Pick k below</h1> + <label>k:</label> + <input type="text" id="k" name="k"><br><br> + <button>Generate Graph</button> + + </body> +</html> \ No newline at end of file diff --git a/src/templates/wr_champion_visual.html b/src/templates/wr_champion_visual.html new file mode 100644 index 0000000000000000000000000000000000000000..8edde44fccc2a6a58586d7bba32e29ae6df9d83a --- /dev/null +++ b/src/templates/wr_champion_visual.html @@ -0,0 +1,18 @@ +<!doctype html> +<html> + <head> + <title>Win Rate Page</title> + </head> + <body> + <h1> Top k Champions by Win Rate </h1> + <nav> + <a href="/">homepage</a> + + </nav> + <h1>Pick k below</h1> + <label>k:</label> + <input type="text" id="k" name="k"><br><br> + <button>Generate Graph</button> + + </body> +</html> \ No newline at end of file