From 96f735c2a63c381be55a0e4b7d5e8e3a9f6bc134 Mon Sep 17 00:00:00 2001 From: DelaMarc Date: Sat, 25 Apr 2020 18:15:53 +0200 Subject: [PATCH] Implemented database class that encapsulates all database operations end try/except exceptions related to the database with this logic, the database is accessed through a singleton that is initialized before running the server. If an exception is caught during a database operation, the value -1 is returned. Thus, whenever a database operation returns -1 in a server function, an error message is immediately returned with the error code 500. The error code and messages are the same everywhere by now but can be personnalized later on. server.py and tools.py have been modified following this logic and the return of every database call is checked. Also in the SAL and Connect4 functions in server.py, conditions have been added to redirect the user and log them out if their email happens to not be in the associated dictionnaries. --- database.py | 79 +++++++++++++++ server.py | 283 ++++++++++++++++++++++++++-------------------------- tools.py | 17 ++-- 3 files changed, 233 insertions(+), 146 deletions(-) create mode 100644 database.py diff --git a/database.py b/database.py new file mode 100644 index 0000000..919bff0 --- /dev/null +++ b/database.py @@ -0,0 +1,79 @@ +from flask_mysqldb import MySQL + +class DB(object): + """docstring for DB""" + def __init__(self, app): + super (DB, self).__init__() + try: + self.mysql = MySQL(app) + self.cursor = None + self.connected = True + except: + self.connected = False + + def select(self, sql_string, args = None, quantity = "one"): + try: + self.cursor = self.mysql.connection.cursor() + self.cursor.execute(sql_string, args) + retult = 0 + if (quantity == "one"): + result = self.cursor.fetchone() + else: + result = self.cursor.fetchall() + self.cursor.close() + return (result) + except Exception as e: + print(e) + return -1 + + #used to call a database stored procedure + def callproc(self, procName, args, quantity = "one"): + try: + self.cursor = self.mysql.connection.cursor() + self.cursor.callproc(procName, args) + if (quantity == "one"): + result = self.cursor.fetchone() + else: + result = self.cursor.fetchall() + self.cursor.close() + return (result) + except Exception as e: + print(e) + return -1 + + #insert and update are duplicated methods for the sake of readability and in case we want to add specific behaviour to one of the operations + #they return the number of affected rows + def insert(self, sql_string, args = None): + try: + self.cursor = self.mysql.connection.cursor() + self.cursor.execute(sql_string, args) + result = self.cursor.rowcount + self.mysql.connection.commit() + self.cursor.close() + return (result) + except Exception as e: + print(e) + return -1 + + def update(self, sql_string, args = None): + try: + self.cursor = self.mysql.connection.cursor() + self.cursor.execute(sql_string, args) + result = self.cursor.rowcount + self.mysql.connection.commit() + self.cursor.close() + return (result) + except Exception as e: + print(e) + return -1 + + def close(self): + self.mysql.connection.close() + return (None) + +#implement the singleton logic for the database class +_DB = None + +def NewConexion(app): + global _DB + _DB = DB(app) \ No newline at end of file diff --git a/server.py b/server.py index b062f29..1e53931 100644 --- a/server.py +++ b/server.py @@ -1,9 +1,10 @@ from flask import Flask,render_template,request,redirect, url_for, make_response -from flask_mysqldb import MySQL +#from flask_mysqldb import MySQL from flask_socketio import SocketIO, send, emit from tools import * from functools import wraps import random +import database app = Flask(__name__) app.config.from_object('config.Config') @@ -14,8 +15,6 @@ app.config['MYSQL_PASSWORD'] = 'Game_server' #change this password to your MySQL password for root@localhost app.config['MYSQL_DB'] = 'Game_server' -mysql = MySQL(app) - GAME_ID_SNAKE = 1 GAME_ID_CONNECT4 = 2 @@ -69,16 +68,17 @@ def Register(): return render_template('register.html', error = error) #Valid input handling - cur=mysql.connection.cursor() - _sql = "select * from Login_Credentials where PlayerID = '{0}'" - cur.execute(_sql.format(email)) - data=cur.fetchall() + data = database._DB.select("SELECT PlayerID FROM Login_Credentials WHERE PlayerID = %s;", (email,), "all") + if (data == -1): + return errorDB, 500 if(len(data) is 0): error = None - cur.execute("INSERT INTO Player_Profile(PlayerID,firstName,lastName) VALUES(%s,%s,%s)",(email,firstName,lastName)) - cur.execute("INSERT INTO Login_Credentials VALUES(%s,MD5(%s))",(email,password)) - mysql.connection.commit() - cur.close() + res = database._DB.insert("INSERT INTO Player_Profile(PlayerID,firstName,lastName) VALUES (%s,%s,%s);", (email, firstName, lastName)) + if (res == -1): + return errorDB, 500 + res = database._DB.insert("INSERT INTO Login_Credentials VALUES(%s,MD5(%s));", (email, password)) + if (res == -1): + return errorDB, 500 return redirect(url_for('Login')) else: error = 'Email already registered!' @@ -90,25 +90,24 @@ def Login(): if request.method=='POST': email=request.form['loginEmail'] password=request.form['loginPassword'] - cur=mysql.connection.cursor() - _sql = "select md5('{0}')" - cur.execute(_sql.format(password)) - enc_string=cur.fetchall() - _sql = "select password from Login_Credentials where PlayerID = '{0}'" - cur.execute(_sql.format(email)) - stored=cur.fetchall() + enc_string = database._DB.select("SELECT MD5(%s);", (password,), "all") + if (enc_string == -1): + return errorDB, 500 + stored = database._DB.select("SELECT password FROM Login_Credentials WHERE PlayerID = %s;", (email,), "all") + if (stored == -1): + return errorDB, 500 if(len(stored) is 0): error = 'Email not found!' else: if(enc_string==stored): logged_in_users.append(email) #get the playerID - cur.callproc("fullName", (email,)) - res = cur.fetchone() + res = database._DB.callproc("fullName", (email,)) + if res == -1: + return errorDB, 500 resp = make_response(redirect(url_for('Index'))) resp.set_cookie('email',email) resp.set_cookie('fullName', res[0]) - cur.close() return resp else: error = 'Invalid password' @@ -124,24 +123,21 @@ def Logout(): logged_in_users.remove(email) return resp -@app.route('/profile') +@app.route('/profile', methods=['GET', 'POST']) @login_required def Profile(): error = None email = request.cookies.get('email') - cur=mysql.connection.cursor() if request.method=='POST': firstName = request.form['firstName'] lastName = request.form['lastName'] - _sql = "update Player_Profile set firstName='{0}', lastName='{1}' where PlayerID = '{2}'" - cur.execute(_sql.format(firstName,lastName,email)) - mysql.connection.commit() - - _sql = "select * from Player_Profile where PlayerID = '{0}'" - cur.execute(_sql.format(email)) - values = cur.fetchall() - cur.close() - (user_email,firstName,lastName,cash,gold) = values[0] + res = database._DB.update("UPDATE Player_Profile SET firstName = %s, lastName = %s WHERE PlayerID = %s;", (firstName,lastName,email)) + if (res == -1): + return errorDB, 500 + res = database._DB.select("SELECT PlayerID, firstName, lastName, Cash, Gold FROM Player_Profile WHERE PlayerID = %s;", (email,)) + if (res == -1): + return errorDB, 500 + (user_email,firstName,lastName,cash,gold) = res name = firstName+" "+lastName resp = make_response(render_template('profile.html', email=user_email, name=name, cash=cash, gold=gold)) #reset the fullName cookie if it has been updated @@ -152,43 +148,40 @@ def Profile(): @app.route('/leaderboard') @login_required def Leaderboard(): - cur = mysql.connect.cursor() - _sql = "select @rank:=@rank+1 as _rank, firstName, lastName, Cash from Player_Profile p, (select @rank := 0) r order by Cash desc" - cur.execute(_sql) - values = cur.fetchall() + _sql = "SELECT @rank:=@rank+1 as _rank, firstName, lastName, Cash FROM Player_Profile p, (select @rank := 0) r ORDER BY Cash DESC;" + values = database._DB.select(_sql, None, "all") + if (values == -1): + return errorDB, 500 cash_leaderboard = [list(x) for x in values] - _sql = "select @rank:=@rank+1 as _rank, firstName, lastName, Gold from Player_Profile p, (select @rank := 0) r order by Gold desc" - cur.execute(_sql) - values = cur.fetchall() + _sql = "SELECT @rank:=@rank+1 as _rank, firstName, lastName, Gold FROM Player_Profile p, (select @rank := 0) r ORDER BY Gold DESC;" + gold_leaderboard = database._DB.select(_sql, None, "all") + if (gold_leaderboard == -1): + return errorDB, 500 gold_leaderboard = [list(x) for x in values] - cur.close() return render_template('leaderboard.html',cash_leaderboard=cash_leaderboard, gold_leaderboard=gold_leaderboard) @app.route('/history') @login_required def PlayerHistory(): email = request.cookies.get('email') - cur = mysql.connect.cursor() - _sql = "select @rank:=@rank+1 as _rank, Cash, Gold from Player_History p, (select @rank := 0) r where PlayerID='{0}' and GameID={1} order by Cash desc" - cur.execute(_sql.format(email,GAME_ID_SNAKE)) - values = cur.fetchall() + _sql = "SELECT @rank:=@rank+1 AS _rank, Cash, Gold FROM Player_History p, (SELECT @rank := 0) r WHERE PlayerID=%s AND GameID=%s ORDER BY Cash DESC;" + values = database._DB.select(_sql, (email, GAME_ID_SNAKE), "all") + if (values == -1): + return errorDB, 500 snake_history = [list(x) for x in values] - _sql = "select @rank:=@rank+1 as _rank, Cash, Gold from Player_History p, (select @rank := 0) r where PlayerID='{0}' and GameID={1} order by Cash desc" - cur.execute(_sql.format(email,GAME_ID_CONNECT4)) - values = cur.fetchall() + _sql = "SELECT @rank:=@rank+1 AS _rank, Cash, Gold FROM Player_History p, (SELECT @rank := 0) r WHERE PlayerID=%s AND GameID=%s ORDER BY Cash DESC;" + values = database._DB.select(_sql, (email, GAME_ID_CONNECT4), "all") connect4_history = [list(x) for x in values] - cur.close() return render_template('playerHistory.html',snake_history=snake_history, connect4_history=connect4_history) @app.route('/shop') @login_required def Shop(): - cur = mysql.connect.cursor() - _sql = "select * from Perks_Available" - cur.execute(_sql) - values = cur.fetchall() + _sql = "SELECT * from Perks_Available;" + values = database._DB.select(_sql, None, "all") + if (values == -1): + return errorDB, 500 perks = [list(x) for x in values] - cur.close() return render_template('store.html', perks = perks) @app.route('/index.html') @@ -207,44 +200,40 @@ def MiniGames(): @app.route('/waitingPage.html') @login_required def Waiting(): - email = request.cookies.get('email') game_id = int(request.args.get('game_id')) - - cur=mysql.connection.cursor() - _sql = "select GameID from Players_in_Game where GameID = '{0}'" - cur.execute(_sql.format(game_id)) - stored=cur.fetchall() - + _sql = "SELECT GameID FROM Players_in_Game WHERE GameID = %s;" + stored = database._DB.select(_sql, (game_id,), "all") + if (stored == -1): + return errorDB, 500 if(len(stored)%2==0): - - _sql = "select No_of_rooms from Mini_Game where GameID = '{0}'" - cur.execute(_sql.format(game_id)) - stored=cur.fetchall() - curr_rooms = stored[0][0] + _sql = "SELECT No_of_rooms FROM Mini_Game WHERE GameID = %s;" + stored = database._DB.select(_sql, (game_id,)) + if (stored == -1): + return errorDB, 500 + curr_rooms = stored[0] curr_rooms = curr_rooms + 1 - _sql = "update Mini_Game set No_of_rooms = '{0}' where GameID = '{1}'" - cur.execute(_sql.format(curr_rooms,game_id)) - _sql = "insert into Players_in_Game values('{0}','{1}','{2}')" - cur.execute(_sql.format(email, game_id, curr_rooms)) - mysql.connection.commit() + _sql = "UPDATE Mini_Game SET No_of_rooms = %s WHERE GameID = %s;" + res = database._DB.update(_sql, (curr_rooms, game_id)) + if (res == -1): + return errorDB, 500 + _sql = "INSERT INTO Players_in_Game VALUES(%s,%s,%s);" + res = database._DB.insert(_sql, (email, game_id, curr_rooms)) return render_template('waitingPage.html', email=email, game_id=game_id) - else: - - _sql = "select No_of_rooms from Mini_Game where GameID = '{0}'" - cur.execute(_sql.format(game_id)) - stored=cur.fetchall() - curr_rooms = stored[0][0] - _sql = "insert into Players_in_Game values('{0}','{1}','{2}')" - cur.execute(_sql.format(email, game_id, curr_rooms)) - mysql.connection.commit() - + _sql = "SELECT No_of_rooms FROM Mini_Game WHERE GameID = %s;" + stored = database._DB.select(_sql, (game_id,)) + if (stored == -1): + return errorDB, 500 + curr_rooms = stored[0] + _sql = "INSERT INTO Players_in_Game VALUES(%s,%s,%s);" + res = database._DB.insert(_sql, (email, game_id, curr_rooms)) + if (res == -1): + return errorDB, 500 if game_id == GAME_ID_SNAKE: snakePartners[email]=snakeWaitingSid[0] snakePartners[snakeWaitingSid[0]]=email return redirect(url_for('SAL')) - elif game_id == GAME_ID_CONNECT4: c4pairs[email] = c4WaitingSid[0] c4pairs[c4WaitingSid[0]] = email @@ -254,6 +243,9 @@ def Waiting(): @login_required def SAL(): email = request.cookies.get('email') + #redirect to index if the email is not in snakePartners + if email not in snakePartners: + return redirect(url_for("Logout")) player2 = GetFullName(snakePartners[email]) player1 = request.cookies.get("fullName") return render_template('snakegame.html', player1=player1, player2=player2) @@ -262,6 +254,9 @@ def SAL(): @login_required def Connect4(): email = request.cookies.get('email') + #redirect to index if the email is not in the dictionnary + if email not in c4pairs: + return redirect(url_for("Logout")) paired_email = GetFullName(c4pairs[email]) fullName = request.cookies.get("fullName") if email in c4WaitingSid: @@ -272,7 +267,6 @@ def Connect4(): # Socket IO functionality @socketio.on('waiting_id', namespace='/private') def receive_waiting_user(data): - if int(data['game_id']) == GAME_ID_SNAKE: snakeWaitingSid.clear() snakeWaitingSid.append(data['player']) @@ -300,39 +294,40 @@ def leave_waiting(arr): def send_move(arr): email = request.cookies.get('email') if(arr[0] == 'check2x'): - cur=mysql.connection.cursor() arr.clear() - _sql = "select Quantity from Owned_Perk where PlayerID = '{0}' and PerkID = '{1}'" - cur.execute(_sql.format(email,1)) - stored=cur.fetchall() + _sql = "SELECT Quantity FROM Owned_Perk WHERE PlayerID = %s AND PerkID = %s;" + stored = database._DB.select(_sql, (email, 1), "all") + if (stored == -1): + return errorDB, 500 if(len(stored)==0 or stored[0][0]==0): arr.append('twoxFailed') else: newVal = stored[0][0] - 1 - # print(newVal) - _sql = "update Owned_Perk set Quantity='{0}' where PlayerID = '{1}' and PerkID = {2}" - cur.execute(_sql.format(newVal,email,1)) - mysql.connection.commit() + _sql = "UPDATE Owned_Perk SET Quantity=%s WHERE PlayerID = %s AND PerkID = %s;" + res = database._DB(_sql, (newVal, email, 1)) + if (res == -1): + return errorDB, 500 arr.append('twoxPassed') emit('getMove',arr,room=snakeUsers[email]) elif(arr[0] == 'checkHeadStart'): player1 = True if(arr[1]=='p2'): player1 = False - cur=mysql.connection.cursor() arr.clear() arr.append(player1) - _sql = "select Quantity from Owned_Perk where PlayerID = '{0}' and PerkID = '{1}'" - cur.execute(_sql.format(email,2)) - stored=cur.fetchall() + _sql = "SELECT Quantity FROM Owned_Perk WHERE PlayerID = %s AND PerkID = %s;" + stored = database._DB.select(_sql, (email, 2), "all") + if (stored == -1): + return errorDB, 500 if(len(stored)==0 or stored[0][0]==0): arr.append('headStartFailed') else: newVal = stored[0][0] - 1 print(newVal) - _sql = "update Owned_Perk set Quantity='{0}' where PlayerID = '{1}' and PerkID = {2}" - cur.execute(_sql.format(newVal,email,2)) - mysql.connection.commit() + _sql = "UPDATE Owned_Perk SET Quantity= %s WHERE PlayerID = %s AND PerkID = %s;" + res = database._DB.update(_sql, (newVal, email, 2)) + if (res == -1): + return errorDB, 500 arr.append('headStartPassed') arr.append(random.randint(1,11)) emit('getMove',arr,room=snakeUsers[email]) @@ -345,18 +340,19 @@ def send_move(arr): def running_game(data): if data == "twoXMultiplier": email = request.cookies.get('email') - cur=mysql.connection.cursor() - _sql = "select Quantity from Owned_Perk where PlayerID = '{0}' and PerkID = '{1}'" - cur.execute(_sql.format(email,1)) - stored=cur.fetchall() + _sql = "SELECT Quantity FROM Owned_Perk WHERE PlayerID = %s AND PerkID = %s;" + stored = database._DB.select(_sql, (email, 1), "all") + if (stored == -1): + return errorDB, 500 if(len(stored)==0 or stored[0][0]==0): emit('game_state',"fail",room=c4users[email]) else: newVal = stored[0][0] - 1 print(newVal) - _sql = "update Owned_Perk set Quantity='{0}' where PlayerID = '{1}' and PerkID = {2}" - cur.execute(_sql.format(newVal,email,1)) - mysql.connection.commit() + _sql = "UPDATE Owned_Perk SET Quantity='{0}' WHERE PlayerID = %s AND PerkID = %s;" + res = database._DB.update(_sql, (newVal, email, 1)) + if (res == -1): + return errorDB, 500 emit('game_state',"passed",room=c4users[email]) else: email = data['user'] @@ -366,59 +362,68 @@ def running_game(data): @socketio.on('update_database', namespace='/private') def update_db(arr): email = request.cookies.get('email') - cur=mysql.connection.cursor() - _sql = "select GameID,RoomID from Players_in_Game where PlayerID = '{0}'" + _sql = "SELECT GameID,RoomID FROM Players_in_Game WHERE PlayerID = %s;" + stored = database._DB.select(_sql, (email,)) cur.execute(_sql.format(email)) stored=cur.fetchall() - gameID = stored[0][0] - roomID = stored[0][1] - _sql = "insert into Player_History values ('{0}',{1},{2},{3},{4});" - cur.execute(_sql.format(email,gameID,roomID,arr[0],arr[1])) - _sql = "delete from Players_in_Game where PlayerID = '{0}'" - cur.execute(_sql.format(email)) - _sql = "select Cash,Gold from Player_Profile where PlayerID = '{0}'" - cur.execute(_sql.format(email)) - stored = cur.fetchall() - cash = stored[0][0] - gold = stored[0][1] + gameID = stored[0] + roomID = stored[1] + _sql = "INSERT INTO Player_History VALUES (%s,%s,%s,%s,%s);" + args = (email, gameID, roomID, arr[0], arr[1]) + res = database._DB.insert(_sql, args) + if (res == -1): + return errorDB, 500 + _sql = "DELETE FROM Players_in_Game WHERE PlayerID = %s;" + res = database._DB.delete(_sql, (email,)) + if (res == -1): + return errorDB, 500 + _sql = "SELECT Cash,Gold FROM Player_Profile WHERE PlayerID = %s;" + stored = database._DB.select(_sql, (email,)) + cash = stored[0] + gold = stored[1] cash += arr[0] gold += arr[1] - _sql = "update Player_Profile set Cash={0}, Gold={1} where PlayerID='{2}'" - cur.execute(_sql.format(cash,gold,email)) - mysql.connection.commit() - cur.close() + _sql = "UPDATE Player_Profile SET Cash=%s, Gold=%s WHERE PlayerID=%s;" + res = database._DB.update(_sql, (cash, gold, email)) + if (res == -1): + return errorDB, 500 @socketio.on('buyPerk', namespace='/private') def buyPerk(arr): email = request.cookies.get('email') - cur=mysql.connection.cursor() if(arr[0]=='getAvailableGold'): - _sql = "select Gold from Player_Profile where PlayerID = '{0}'" - cur.execute(_sql.format(email)) - stored = cur.fetchall() - gold = stored[0][0] + _sql = "SELECT Gold FROM Player_Profile WHERE PlayerID = %s;" + stored = database._DB.select(_sql, (email,)) + if (stored == -1): + return errorDB, 500 + gold = stored[0] arr.clear() arr.append('goldAvailable') arr.append(gold) emit('perkResult',arr,room=request.sid) else: - _sql = "select Quantity from Owned_Perk where PlayerID = '{0}' and PerkID = {1}" - # print('HERE',_sql.format(email,arr[1])) - cur.execute(_sql.format(email,arr[1])) - stored=cur.fetchall() - # print(stored) + _sql = "SELECT Quantity FROM Owned_Perk WHERE PlayerID = %s AND PerkID = %s;" + stored = database._DB.select(_sql, (email, arr[1])) if(len(stored) is 0): quantity = 1 - _sql = "insert into Owned_Perk values('{0}',{1},{2})" - cur.execute(_sql.format(email,arr[1],quantity)) + _sql = "INSERT INTO Owned_Perk VALUES(%s,%s,%s);" + args = (email, arr[1], quantity) + res = database.db.insert(_sql, args) + if (res == -1): + return errorDB, 500 else: - quantity = stored[0][0] + quantity = stored[0] quantity = quantity + 1 - _sql = "update Owned_Perk set Quantity ={0} where PlayerID = '{1}' and PerkID = {2}" - cur.execute(_sql.format(quantity,email,arr[1])) - _sql = "update Player_Profile set Gold ={0} where PlayerID = '{1}'" - cur.execute(_sql.format(arr[2],email)) - mysql.connection.commit() + _sql = "UPDATE Owned_Perk SET Quantity = %s WHERE PlayerID = %s AND PerkID = %s;" + res = database._DB.update(_sql, (quantity, email, arr[1])) + if (res == -1): + return errorDB, 500 + _sql = "UPDATE Player_Profile SET Gold = %s WHERE PlayerID = %s;" + res = database._DB.update(_sql, (arr[2], email)) + if (res == -1): + return errorDB, 500 if __name__ == "__main__": + #initiate database connexion + database.NewConexion(app) socketio.run(app) \ No newline at end of file diff --git a/tools.py b/tools.py index 7ccef4a..f685228 100644 --- a/tools.py +++ b/tools.py @@ -1,12 +1,15 @@ from flask_mysqldb import MySQL -from server import mysql +#from server import mysql +import database + +#default error message in case an error occurs with the database +errorDB = "Database unreachable" #get a user full name by their id def GetFullName(id): #get opponent full name - cur=mysql.connection.cursor() - cur.callproc("fullName", (id,)) - res=cur.fetchone() - fullName = res[0] - cur.close - return fullName \ No newline at end of file + res = database._DB.callproc("fullName", (id,)) + if (res == -1): + return "ERROR DATABASE" + fullName = res[0] + return fullName \ No newline at end of file