diff options
author | lonkaars <l.leblansch@gmail.com> | 2021-04-16 16:57:26 +0200 |
---|---|---|
committer | lonkaars <l.leblansch@gmail.com> | 2021-04-16 16:57:26 +0200 |
commit | 07c2b124e4348b15f1e5ec18c6cdfd77248c6bc8 (patch) | |
tree | e4a29123d3ebedc1d25500390c904c66b3b02489 /api | |
parent | aa2c999702dadba2afbcf2be9f597f890aafcc87 (diff) |
spaces > tabs in python :(
Diffstat (limited to 'api')
34 files changed, 844 insertions, 829 deletions
diff --git a/api/auth/login.py b/api/auth/login.py index 1d5a4b2..e3d5fde 100644 --- a/api/auth/login.py +++ b/api/auth/login.py @@ -8,46 +8,46 @@ login = Blueprint('login', __name__) @login.route('/login', methods=['POST']) def index(): - data = request.get_json() - - # get form data - email = data.get("email") or "" - password = data.get("password") or "" - - # return malformed request if email or password is missing - if not email or not password: - return "", 400 - - # resolve user_id from username or email - user_id = None - user_id = user_id or cursor.execute( - "select user_id from users where email = ?", [email] - ).fetchone() - user_id = user_id or cursor.execute( - "select user_id from users where lower(username) = lower(?)", [email] - ).fetchone() - if user_id == None: return "", 401 - - # check the password - passwd = cursor.execute( - "select password_hash from users where user_id = ?", [user_id[0]] - ).fetchone() - check = passwords.check_password(password, passwd[0]) - if not check: return "", 401 - - # generate a new authentication token and add it to the users valid token list - new_token = token.generate_token() - token.add_token(user_id[0], token.hash_token(new_token)) - - # make response with the set_cookie header - res = make_response("", 200) - res.set_cookie( - "token", - new_token["token"], - expires=int(new_token["expirationDate"] / 1000) - ) - - return res + data = request.get_json() + + # get form data + email = data.get("email") or "" + password = data.get("password") or "" + + # return malformed request if email or password is missing + if not email or not password: + return "", 400 + + # resolve user_id from username or email + user_id = None + user_id = user_id or cursor.execute( + "select user_id from users where email = ?", [email] + ).fetchone() + user_id = user_id or cursor.execute( + "select user_id from users where lower(username) = lower(?)", [email] + ).fetchone() + if user_id == None: return "", 401 + + # check the password + passwd = cursor.execute( + "select password_hash from users where user_id = ?", [user_id[0]] + ).fetchone() + check = passwords.check_password(password, passwd[0]) + if not check: return "", 401 + + # generate a new authentication token and add it to the users valid token list + new_token = token.generate_token() + token.add_token(user_id[0], token.hash_token(new_token)) + + # make response with the set_cookie header + res = make_response("", 200) + res.set_cookie( + "token", + new_token["token"], + expires=int(new_token["expirationDate"] / 1000) + ) + + return res dynamic_route = ["/auth", login] diff --git a/api/auth/login_token.py b/api/auth/login_token.py index b5b1579..de770b3 100644 --- a/api/auth/login_token.py +++ b/api/auth/login_token.py @@ -5,12 +5,12 @@ from auth.token import validate_token, hash_token # get user_id from authentication token def token_login(token): - hashed = hash_token({"token": token, "expirationDate": 0}) - user_id = cursor.execute( - "select user_id from users where valid_tokens like ?", - [f"%{hashed['token']}%"] - ).fetchone() - return None if not user_id else user_id[0] + hashed = hash_token({"token": token, "expirationDate": 0}) + user_id = cursor.execute( + "select user_id from users where valid_tokens like ?", + [f"%{hashed['token']}%"] + ).fetchone() + return None if not user_id else user_id[0] token = Blueprint('token', __name__) @@ -19,12 +19,12 @@ token = Blueprint('token', __name__) # this endpoint is currently unused, but verifies that a token is valid @token.route('/token', methods=['POST']) def index(): - data = request.get_json() + data = request.get_json() - auth_token = data.get("token") or "" - if not auth_token: return "", 400 + auth_token = data.get("token") or "" + if not auth_token: return "", 400 - return "", 200 if token_login(auth_token) else 401 + return "", 200 if token_login(auth_token) else 401 dynamic_route = ["/auth", token] diff --git a/api/auth/signup.py b/api/auth/signup.py index 98c07a7..a18796b 100644 --- a/api/auth/signup.py +++ b/api/auth/signup.py @@ -9,20 +9,20 @@ import re # checks if the usename is between 3 and 35 charachters def validate_username(username): - return len(username) in range(3, 35 + 1) + return len(username) in range(3, 35 + 1) # garbage email validation (see todo) def validate_email(email): - #TODO: use node_modules/email-validator/index.js - return len(email) > 1 and \ - "@" in email + #TODO: use node_modules/email-validator/index.js + return len(email) > 1 and \ + "@" in email # checks if the password is safe (regex explanation in pages/register.tsx) def validate_password(password): - passwordRegex = r"^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$" # r"" = raw string - return re.match(passwordRegex, password) + passwordRegex = r"^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$" # r"" = raw string + return re.match(passwordRegex, password) signup = Blueprint('signup', __name__) @@ -30,63 +30,63 @@ signup = Blueprint('signup', __name__) @signup.route('/signup', methods=['POST']) def index(): - # parse request data if the content-type header is set to application/json - data = request.get_json() - - # force string if {}.get(...) returns None - username = data.get("username") or "" - email = data.get("email") or "" - password = data.get("password") or "" - - # return 400 (malformed request) if any of the required data is missing - if not username or \ - not email or \ - not password: - return "", 400 - - # return 403 (forbidden) if any of the required data is invalid - if not validate_username(username) or \ - not validate_email(email) or \ - not validate_password(password): - return {"error": "form_data_invalid"}, 403 - - # check if username is taken - if cursor.execute( - "select username from users where lower(username) = lower(?)", - [username] - ).fetchone(): - return {"error": "username_taken"}, 403 - - # check if email is taken - if cursor.execute("select email from users where email = ?", - [email]).fetchone(): - return {"error": "email_taken"}, 403 - - # create new user_id, hash password and note timestamp - user_id = new_uuid("users") - password_hash = passwords.password_hash(password) - registered = int(time.time() * 1000) - - # write new user to database and commit - cursor.execute( - "insert into users values (?, ?, ?, NULL, NULL, ?, ?, \"[]\", FALSE, \"user\", \"{}\", \"online\") ", - (user_id, username, email, password_hash, registered) - ) - connection.commit() - - # create a new token for the user to use for authentication - new_token = token.generate_token() - token.add_token(user_id, token.hash_token(new_token)) - - # create a flask response object to add the set-cookie header to - res = make_response("", 200) - res.set_cookie( - "token", - new_token["token"], - expires=int(new_token["expirationDate"] / 1000) - ) - - return res + # parse request data if the content-type header is set to application/json + data = request.get_json() + + # force string if {}.get(...) returns None + username = data.get("username") or "" + email = data.get("email") or "" + password = data.get("password") or "" + + # return 400 (malformed request) if any of the required data is missing + if not username or \ + not email or \ + not password: + return "", 400 + + # return 403 (forbidden) if any of the required data is invalid + if not validate_username(username) or \ + not validate_email(email) or \ + not validate_password(password): + return {"error": "form_data_invalid"}, 403 + + # check if username is taken + if cursor.execute( + "select username from users where lower(username) = lower(?)", + [username] + ).fetchone(): + return {"error": "username_taken"}, 403 + + # check if email is taken + if cursor.execute("select email from users where email = ?", + [email]).fetchone(): + return {"error": "email_taken"}, 403 + + # create new user_id, hash password and note timestamp + user_id = new_uuid("users") + password_hash = passwords.password_hash(password) + registered = int(time.time() * 1000) + + # write new user to database and commit + cursor.execute( + "insert into users values (?, ?, ?, NULL, NULL, ?, ?, \"[]\", FALSE, \"user\", \"{}\", \"online\") ", + (user_id, username, email, password_hash, registered) + ) + connection.commit() + + # create a new token for the user to use for authentication + new_token = token.generate_token() + token.add_token(user_id, token.hash_token(new_token)) + + # create a flask response object to add the set-cookie header to + res = make_response("", 200) + res.set_cookie( + "token", + new_token["token"], + expires=int(new_token["expirationDate"] / 1000) + ) + + return res dynamic_route = ["/auth", signup] diff --git a/api/auth/token.py b/api/auth/token.py index e94b014..d439924 100644 --- a/api/auth/token.py +++ b/api/auth/token.py @@ -7,54 +7,54 @@ import time # get valid token hashes for a given user_id def valid_tokens(user_id): - tokens = json.loads( - cursor.execute( - "select valid_tokens from users where user_id = ?", [user_id] - ).fetchone()[0] - ) - # return only tokens that aren't expired - return [ - token for token in tokens - if token["expirationDate"] > int(time.time() * 1000) - ] + tokens = json.loads( + cursor.execute( + "select valid_tokens from users where user_id = ?", [user_id] + ).fetchone()[0] + ) + # return only tokens that aren't expired + return [ + token for token in tokens + if token["expirationDate"] > int(time.time() * 1000) + ] def validate_token(user_id, token): - tokens = valid_tokens(user_id) - return hashlib.sha256(str(token).encode()).hexdigest() in [ - t["token"] for t in tokens - if t["expirationDate"] > int(time.time() * 1000) - ] + tokens = valid_tokens(user_id) + return hashlib.sha256(str(token).encode()).hexdigest() in [ + t["token"] for t in tokens + if t["expirationDate"] > int(time.time() * 1000) + ] def modify_tokens(user_id, formatted_token, remove): - temp_tokens = valid_tokens(user_id) - temp_tokens.remove(formatted_token - ) if remove else temp_tokens.append(formatted_token) - cursor.execute( - "update users set valid_tokens = ? where user_id = ?", - [json.dumps(temp_tokens), user_id] - ) - connection.commit() + temp_tokens = valid_tokens(user_id) + temp_tokens.remove(formatted_token + ) if remove else temp_tokens.append(formatted_token) + cursor.execute( + "update users set valid_tokens = ? where user_id = ?", + [json.dumps(temp_tokens), user_id] + ) + connection.commit() def add_token(user_id, formatted_token): - modify_tokens(user_id, formatted_token, False) + modify_tokens(user_id, formatted_token, False) def revoke_token(user_id, formatted_token): - modify_tokens(user_id, formatted_token, True) + modify_tokens(user_id, formatted_token, True) def hash_token(token): - return { - "token": hashlib.sha256(str(token["token"]).encode()).hexdigest(), - "expirationDate": token["expirationDate"] - } + return { + "token": hashlib.sha256(str(token["token"]).encode()).hexdigest(), + "expirationDate": token["expirationDate"] + } def generate_token(): - return { - "token": secrets.token_hex(128), - "expirationDate": int(time.time() * 1000) + (24 * 60 * 60 * 1000) - } + return { + "token": secrets.token_hex(128), + "expirationDate": int(time.time() * 1000) + (24 * 60 * 60 * 1000) + } diff --git a/api/dynamic_import.py b/api/dynamic_import.py index ecf9ea1..542af92 100644 --- a/api/dynamic_import.py +++ b/api/dynamic_import.py @@ -9,24 +9,24 @@ import glob files = glob.glob(os.path.dirname(__file__) + "/**/*.py", recursive=True) files.remove(__file__) files = [ - str(filename).replace(os.path.dirname(__file__) + "/", - '').replace("/", ".").replace(".py", '') - for filename in files + str(filename).replace(os.path.dirname(__file__) + "/", + '').replace("/", ".").replace(".py", '') + for filename in files ] def route(dynamic_route): - app.register_blueprint(dynamic_route[1], url_prefix=dynamic_route[0]) - path = (dynamic_route[0] + "/" + dynamic_route[1].name).replace('//', '/') - log.info(f"dynamically routing {path}") + app.register_blueprint(dynamic_route[1], url_prefix=dynamic_route[0]) + path = (dynamic_route[0] + "/" + dynamic_route[1].name).replace('//', '/') + log.info(f"dynamically routing {path}") for file in files: - mod = importlib.import_module(file) - # check if module has `dynamic_route` defined (single route) - if hasattr(mod, "dynamic_route"): - route(mod.dynamic_route) - # check if module has `dynamic_routes` defined (multiple routes as list) - elif hasattr(mod, "dynamic_routes"): - for dynamic_route in mod.dynamic_routes: - route(dynamic_route) + mod = importlib.import_module(file) + # check if module has `dynamic_route` defined (single route) + if hasattr(mod, "dynamic_route"): + route(mod.dynamic_route) + # check if module has `dynamic_routes` defined (multiple routes as list) + elif hasattr(mod, "dynamic_routes"): + for dynamic_route in mod.dynamic_routes: + route(dynamic_route) diff --git a/api/events.py b/api/events.py index 695edb1..3097471 100644 --- a/api/events.py +++ b/api/events.py @@ -7,4 +7,4 @@ from hierarchy import io_auth_required @io.on("connect") @io_auth_required("none") def connect(data, user_id): - join_room("user-" + user_id) + join_room("user-" + user_id) diff --git a/api/game/accept.py b/api/game/accept.py index 3510ffd..e0681c8 100644 --- a/api/game/accept.py +++ b/api/game/accept.py @@ -16,14 +16,14 @@ join_game = Blueprint('game_accept', __name__) @join_game.route('/accept', methods=['POST']) @game_id_with_viewer def index(game_id, user_id): - if not user_id: return "", 400 - if cursor.execute("select status from games where game_id = ?", - [game_id]).fetchone()[0] != "wait_for_opponent": - return "", 403 + if not user_id: return "", 400 + if cursor.execute("select status from games where game_id = ?", + [game_id]).fetchone()[0] != "wait_for_opponent": + return "", 403 - start_game(game_id, user_id) + start_game(game_id, user_id) - return {"id": game_id, "player_1": False, "game_started": True}, 200 + return {"id": game_id, "player_1": False, "game_started": True}, 200 dynamic_route = ["/game", join_game] diff --git a/api/game/cleanup.py b/api/game/cleanup.py index cc0aab8..8f7266d 100644 --- a/api/game/cleanup.py +++ b/api/game/cleanup.py @@ -5,26 +5,26 @@ import time # cleanup function that's ran every five minutes def cleanup(): - now = int(time.time() * 1000) - old_games = cursor.execute( - "select game_id from games where (status = \"wait_for_opponent\" or status = \"in_progress\") and last_activity < ?", - [now - 5 * 60 * 1e3] - ).fetchall() - for game_id in old_games: - cursor.execute("delete from games where game_id = ?", [game_id[0]]) - connection.commit() + now = int(time.time() * 1000) + old_games = cursor.execute( + "select game_id from games where (status = \"wait_for_opponent\" or status = \"in_progress\") and last_activity < ?", + [now - 5 * 60 * 1e3] + ).fetchall() + for game_id in old_games: + cursor.execute("delete from games where game_id = ?", [game_id[0]]) + connection.commit() def set_interval( - func, sec + func, sec ): # https://stackoverflow.com/questions/2697039/python-equivalent-of-setinterval - def func_wrapper(): - set_interval(func, sec) - func() + def func_wrapper(): + set_interval(func, sec) + func() - t = threading.Timer(sec, func_wrapper) - t.start() - return t + t = threading.Timer(sec, func_wrapper) + t.start() + return t # run every five minutes diff --git a/api/game/info.py b/api/game/info.py index b150dbc..fa23616 100644 --- a/api/game/info.py +++ b/api/game/info.py @@ -9,54 +9,54 @@ import valid def format_game(game_id, user_id=None): - game = cursor.execute( - "select " + ", ".join( - [ - "game_id", # 0 - "parent_game", # 1 - "moves", # 2 - "player_1_id", # 3 - "player_2_id", # 4 - "outcome", # 5 - "created", # 6 - "started", # 7 - "duration", # 8 - "rating_delta_player_1", # 9 - "rating_delta_player_2", # 10 - "ruleset", # 11 - "status", # 12 - "private", # 13 - ] - ) + " from games where game_id = ?", - [game_id] - ).fetchone() + game = cursor.execute( + "select " + ", ".join( + [ + "game_id", # 0 + "parent_game", # 1 + "moves", # 2 + "player_1_id", # 3 + "player_2_id", # 4 + "outcome", # 5 + "created", # 6 + "started", # 7 + "duration", # 8 + "rating_delta_player_1", # 9 + "rating_delta_player_2", # 10 + "ruleset", # 11 + "status", # 12 + "private", # 13 + ] + ) + " from games where game_id = ?", + [game_id] + ).fetchone() - is_player_1 = game[4] != user_id + is_player_1 = game[4] != user_id - # get opponent from perspective of `user_id` - #TODO: return .players as array of player_1 and player_2 but format_user()'d - opponent = game[4] if is_player_1 else game[3] + # get opponent from perspective of `user_id` + #TODO: return .players as array of player_1 and player_2 but format_user()'d + opponent = game[4] if is_player_1 else game[3] - # parse moves into list and return empty list if moves string is empty - moves = [] if len(game[2]) == 0 else [ - int(move) for move in str(game[2] + "0").split(",") - ] + # parse moves into list and return empty list if moves string is empty + moves = [] if len(game[2]) == 0 else [ + int(move) for move in str(game[2] + "0").split(",") + ] - return { - "id": game[0], - "parent": game[1], - "moves": moves, - "opponent": None if not opponent else format_user(opponent), - "outcome": None if not game[5] else outcome(game[5], is_player_1), - "created": game[6], - "started": game[7], - "duration": game[8], - "rating": game[9] if is_player_1 else game[10], - "rating_opponent": game[10] if is_player_1 else game[9], - "ruleset": resolve_ruleset(game[11]), - "status": game[12], - "private": bool(game[13]), - } + return { + "id": game[0], + "parent": game[1], + "moves": moves, + "opponent": None if not opponent else format_user(opponent), + "outcome": None if not game[5] else outcome(game[5], is_player_1), + "created": game[6], + "started": game[7], + "duration": game[8], + "rating": game[9] if is_player_1 else game[10], + "rating_opponent": game[10] if is_player_1 else game[9], + "ruleset": resolve_ruleset(game[11]), + "status": game[12], + "private": bool(game[13]), + } game_info = Blueprint('game_info', __name__) @@ -65,7 +65,7 @@ game_info = Blueprint('game_info', __name__) @game_info.route('/info', methods=['POST']) @game_id_with_viewer def index(game_id, viewer): - return format_game(game_id, viewer), 200 + return format_game(game_id, viewer), 200 dynamic_route = ["/game", game_info] diff --git a/api/game/new.py b/api/game/new.py index 8c936de..edc7f52 100644 --- a/api/game/new.py +++ b/api/game/new.py @@ -8,46 +8,46 @@ from hierarchy import auth_required def create_game(user_1_id, private=False, user_2_id=None): - timestamp = int(time.time() * 1000) + timestamp = int(time.time() * 1000) - game_id = new_uuid("games") + game_id = new_uuid("games") - cursor.execute( - "insert into games values (?, NULL, \"\", ?, ?, NULL, ?, NULL, ?, NULL, NULL, NULL, \"wait_for_opponent\", \"default\", ?, FALSE) ", - (game_id, user_1_id, user_2_id, timestamp, timestamp, private) - ) - connection.commit() + cursor.execute( + "insert into games values (?, NULL, \"\", ?, ?, NULL, ?, NULL, ?, NULL, NULL, NULL, \"wait_for_opponent\", \"default\", ?, FALSE) ", + (game_id, user_1_id, user_2_id, timestamp, timestamp, private) + ) + connection.commit() - return game_id + return game_id def start_game(game_id, user_2_id): - timestamp = int(time.time() * 1000) - - db_game = cursor.execute( - "select player_2_id, status, private from games where game_id = ?", - [game_id] - ).fetchone() - if db_game[1] != "wait_for_opponent": return False - - if db_game[0] == None: - cursor.execute( - "update games set player_2_id = ? where game_id = ?", - (user_2_id, game_id) - ) - cursor.execute( - "update games set status = \"in_progress\", started = ?, last_activity = ? where game_id = ?", - (timestamp, timestamp, game_id) - ) - connection.commit() - - players = cursor.execute( - "select player_1_id, player_2_id from games where game_id = ?", - [game_id] - ).fetchone() - games[game_id] = game(game_id, io, players[0], players[1]) - - io.emit("gameStart", room=games[game_id].room) + timestamp = int(time.time() * 1000) + + db_game = cursor.execute( + "select player_2_id, status, private from games where game_id = ?", + [game_id] + ).fetchone() + if db_game[1] != "wait_for_opponent": return False + + if db_game[0] == None: + cursor.execute( + "update games set player_2_id = ? where game_id = ?", + (user_2_id, game_id) + ) + cursor.execute( + "update games set status = \"in_progress\", started = ?, last_activity = ? where game_id = ?", + (timestamp, timestamp, game_id) + ) + connection.commit() + + players = cursor.execute( + "select player_1_id, player_2_id from games where game_id = ?", + [game_id] + ).fetchone() + games[game_id] = game(game_id, io, players[0], players[1]) + + io.emit("gameStart", room=games[game_id].room) new_game = Blueprint('new_game', __name__) @@ -56,10 +56,10 @@ new_game = Blueprint('new_game', __name__) @new_game.route('/new', methods=["GET", "POST"]) @auth_required("user") def index(user_id): - # create a new private game (join by link) - #TODO: friend invites + notifications - game_id = create_game(user_id, True) - return {"id": game_id}, 200 + # create a new private game (join by link) + #TODO: friend invites + notifications + game_id = create_game(user_id, True) + return {"id": game_id}, 200 dynamic_route = ["/game", new_game] diff --git a/api/game/random.py b/api/game/random.py index 559c9e5..aa5383c 100644 --- a/api/game/random.py +++ b/api/game/random.py @@ -15,31 +15,31 @@ random_game = Blueprint('random', __name__) @random_game.route('/random') @auth_required("user") def index(user_id): - # get public_games (random opponent queue) - public_games = cursor.execute( - "select game_id from games where private = FALSE and status = \"wait_for_opponent\"" - ).fetchall() - - game_started = False - - # create a new public game if the queue is empty - if len(public_games) == 0: - game_id = create_game(user_id) - player_1 = True - # otherwise join a random public game - else: - game_id = random.choice(public_games)[0] - - start_game(game_id, user_id) - - player_1 = False - game_started = True - - return { - "id": game_id, - "player_1": player_1, - "game_started": game_started - }, 200 + # get public_games (random opponent queue) + public_games = cursor.execute( + "select game_id from games where private = FALSE and status = \"wait_for_opponent\"" + ).fetchall() + + game_started = False + + # create a new public game if the queue is empty + if len(public_games) == 0: + game_id = create_game(user_id) + player_1 = True + # otherwise join a random public game + else: + game_id = random.choice(public_games)[0] + + start_game(game_id, user_id) + + player_1 = False + game_started = True + + return { + "id": game_id, + "player_1": player_1, + "game_started": game_started + }, 200 dynamic_route = ["/game", random_game] diff --git a/api/game/socket.py b/api/game/socket.py index ca28346..587ad7d 100644 --- a/api/game/socket.py +++ b/api/game/socket.py @@ -11,121 +11,121 @@ games = {} def participants_only(func): - ''' + ''' listener should have two parameters: listener(data: socket.io.data, user_id: str, game: game) listener should only be executed if the request comes from one of the game participants (player_1_id | player_2_id) ''' - def wrapper(data, user_id): - game_id = data["game_id"] + def wrapper(data, user_id): + game_id = data["game_id"] - if not game_id or \ - not game_id in games: - return + if not game_id or \ + not game_id in games: + return - game = games[game_id] - if game.player_1_id != user_id and \ - game.player_2_id != user_id: - return + game = games[game_id] + if game.player_1_id != user_id and \ + game.player_2_id != user_id: + return - return func(data, user_id, game) + return func(data, user_id, game) - wrapper.__name__ = func.__name__ - return wrapper + wrapper.__name__ = func.__name__ + return wrapper class game: - def __init__(self, game_id, io, player_1_id, player_2_id): - self.game_id = game_id - self.room = "game-" + game_id - self.board = bord(7, 6) - self.io = io - self.player_1_id = player_1_id - self.player_2_id = player_2_id - - # drop a disc in `column` - def move(self, user_id, column): - if len(self.board.win_positions) > 0: return - if self.board.board_full: return - - move = self.player_1_id if self.board.player_1 else self.player_2_id - if user_id != move: return - - self.board.drop_fisje(column) - - io.emit("fieldUpdate", {"field": self.board.board}, room=self.room) - - now = int(time.time() * 1000) - cursor.execute( - "update games set last_activity = ?, moves = moves || ? || ',' where game_id = ?", - [now, column, self.game_id] - ) - connection.commit() - - if len(self.board.win_positions) > 0 or self.board.board_full: - outcome = "d" - if not self.board.board_full: - winner = self.board.board[int(self.board.win_positions[0][0])] - outcome = "w" if winner == "2" else "l" - io.emit( - "finish", { - "winPositions": self.board.win_positions, - "boardFull": self.board.board_full - }, - room=self.room - ) - self.close("finished", outcome) - return - - io.emit("turnUpdate", {"player1": self.board.player_1}, room=self.room) - - def resign(self): - self.board.kill_voerbak() - io.emit("resign", room=self.room) - self.close("resign", "d") - - def close(self, new_status, outcome): - cursor.execute( - " ".join( - [ - "update games set", "moves = moves || '0',", - "duration = ?,", "status = ?,", "outcome = ?", - "where game_id = ?" - ] - ), [ - int(time.time() * 1000) - cursor.execute( - "select started from games where game_id = ?", - [self.game_id] - ).fetchone()[0], new_status, outcome, self.game_id - ] - ) - connection.commit() - - games.pop(self.game_id) + def __init__(self, game_id, io, player_1_id, player_2_id): + self.game_id = game_id + self.room = "game-" + game_id + self.board = bord(7, 6) + self.io = io + self.player_1_id = player_1_id + self.player_2_id = player_2_id + + # drop a disc in `column` + def move(self, user_id, column): + if len(self.board.win_positions) > 0: return + if self.board.board_full: return + + move = self.player_1_id if self.board.player_1 else self.player_2_id + if user_id != move: return + + self.board.drop_fisje(column) + + io.emit("fieldUpdate", {"field": self.board.board}, room=self.room) + + now = int(time.time() * 1000) + cursor.execute( + "update games set last_activity = ?, moves = moves || ? || ',' where game_id = ?", + [now, column, self.game_id] + ) + connection.commit() + + if len(self.board.win_positions) > 0 or self.board.board_full: + outcome = "d" + if not self.board.board_full: + winner = self.board.board[int(self.board.win_positions[0][0])] + outcome = "w" if winner == "2" else "l" + io.emit( + "finish", { + "winPositions": self.board.win_positions, + "boardFull": self.board.board_full + }, + room=self.room + ) + self.close("finished", outcome) + return + + io.emit("turnUpdate", {"player1": self.board.player_1}, room=self.room) + + def resign(self): + self.board.kill_voerbak() + io.emit("resign", room=self.room) + self.close("resign", "d") + + def close(self, new_status, outcome): + cursor.execute( + " ".join( + [ + "update games set", "moves = moves || '0',", + "duration = ?,", "status = ?,", "outcome = ?", + "where game_id = ?" + ] + ), [ + int(time.time() * 1000) - cursor.execute( + "select started from games where game_id = ?", + [self.game_id] + ).fetchone()[0], new_status, outcome, self.game_id + ] + ) + connection.commit() + + games.pop(self.game_id) @io.on("newMove") @io_auth_required("none") @participants_only def new_move(data, user_id, game): - move = data.get("move") - if not move: return + move = data.get("move") + if not move: return - game.move(user_id, move) + game.move(user_id, move) @io.on("resign") @io_auth_required("none") @participants_only def resign(data, user_id, game): - game.resign() + game.resign() @io.on("registerGameListener") def register_game_listener(data): - game_id = data.get("game_id") - if not game_id: return + game_id = data.get("game_id") + if not game_id: return - join_room("game-" + game_id) + join_room("game-" + game_id) diff --git a/api/game/voerbak_connector.py b/api/game/voerbak_connector.py index 0b51bd5..54f5fa6 100644 --- a/api/game/voerbak_connector.py +++ b/api/game/voerbak_connector.py @@ -13,81 +13,81 @@ if os.name == "nt": VOERBAK_LOCATION += ".exe" class bord: - def __init__(self, w, h): - self.width = w - self.height = h - self.player_1 = True - self.board = "0" * (w * h) - self.board_full = False - self.win_positions = [] - self.process = subprocess.Popen( - [VOERBAK_LOCATION, f"-w {w}", f"-h {h}"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=None - ) - self.process.stdin.flush() + def __init__(self, w, h): + self.width = w + self.height = h + self.player_1 = True + self.board = "0" * (w * h) + self.board_full = False + self.win_positions = [] + self.process = subprocess.Popen( + [VOERBAK_LOCATION, f"-w {w}", f"-h {h}"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=None + ) + self.process.stdin.flush() - # get output from voerbak without trailing newline character (this might break on windows because crlf) - def get_output(self): - return self.process.stdout.readline().decode()[:-1] + # get output from voerbak without trailing newline character (this might break on windows because crlf) + def get_output(self): + return self.process.stdout.readline().decode()[:-1] - def kill_voerbak(self): - self.process.stdin.write(bytearray("0", "utf-8")) - self.process.stdin.flush() + def kill_voerbak(self): + self.process.stdin.write(bytearray("0", "utf-8")) + self.process.stdin.flush() - # read messages from voerbak - def update_board(self): - buffer = self.get_output() - while not buffer.isdigit(): - # win message - if buffer.startswith("w:"): - self.win_positions.append(buffer[2:].split("-")) - log.info(f"won: {buffer[2:].split('-')}") - self.kill_voerbak() - # error message - elif buffer.startswith("e:"): - log.warning(buffer[2:]) - # turn update message - elif buffer.startswith("m:"): - substr = buffer[2:] - self.player_1 = True if substr == "true" else False - # draw game message - elif buffer.startswith("d:"): - self.board_full = True - self.kill_voerbak() - buffer = self.get_output() - self.board = buffer + # read messages from voerbak + def update_board(self): + buffer = self.get_output() + while not buffer.isdigit(): + # win message + if buffer.startswith("w:"): + self.win_positions.append(buffer[2:].split("-")) + log.info(f"won: {buffer[2:].split('-')}") + self.kill_voerbak() + # error message + elif buffer.startswith("e:"): + log.warning(buffer[2:]) + # turn update message + elif buffer.startswith("m:"): + substr = buffer[2:] + self.player_1 = True if substr == "true" else False + # draw game message + elif buffer.startswith("d:"): + self.board_full = True + self.kill_voerbak() + buffer = self.get_output() + self.board = buffer - # debug board print function - def print(self): - for y in range(self.height - 1, -1, -1): - for x in range(self.width): - state = self.board[x + y * self.width] - char = [EMPTY, DISC_A, DISC_B] - print(char[int(state)], end=" ") - print("\n", end="") + # debug board print function + def print(self): + for y in range(self.height - 1, -1, -1): + for x in range(self.width): + state = self.board[x + y * self.width] + char = [EMPTY, DISC_A, DISC_B] + print(char[int(state)], end=" ") + print("\n", end="") - def drop_fisje(self, column): - self.process.stdin.write(bytearray(f"{column}\n", "utf-8")) - self.process.stdin.flush() - self.update_board() + def drop_fisje(self, column): + self.process.stdin.write(bytearray(f"{column}\n", "utf-8")) + self.process.stdin.flush() + self.update_board() # debug game def main(): - gert = bord(7, 6) - while True: - print(gert.player_1) - if len(gert.win_positions) > 0: - print(f"won: {gert.win_positions}") - exit(0) - gert.print() - column = int(input("column?: ")) - 1 - if column not in range(gert.width): - continue - gert.drop_fisje(column + 1) + gert = bord(7, 6) + while True: + print(gert.player_1) + if len(gert.win_positions) > 0: + print(f"won: {gert.win_positions}") + exit(0) + gert.print() + column = int(input("column?: ")) - 1 + if column not in range(gert.width): + continue + gert.drop_fisje(column + 1) if __name__ == "__main__": - main() + main() diff --git a/api/hierarchy.py b/api/hierarchy.py index c50aa81..87b9db9 100644 --- a/api/hierarchy.py +++ b/api/hierarchy.py @@ -8,7 +8,7 @@ ranks = ["none", "user", "moderator", "admin", "bot"] def util_two_id(type="user"): - ''' + ''' type?: "user" | "game" ! only used internally ! func(token_id?: str, explicit_id?: str) @@ -16,51 +16,51 @@ def util_two_id(type="user"): This decorator doesn't check for hierarchy constraints, but does make sure that token_id or explicit_id are valid user_id's ''' - def decorator(func): - def wrapper(): - token_id = None - explicit_id = None + def decorator(func): + def wrapper(): + token_id = None + explicit_id = None - token = request.cookies.get("token") or "" - if token: token_id = token_login(token) + token = request.cookies.get("token") or "" + if token: token_id = token_login(token) - data = request.get_json() - if data: explicit_id = data.get("id") + data = request.get_json() + if data: explicit_id = data.get("id") - # if there's an explicit_id, validate it using `type` - if explicit_id and \ - not valid.validate(explicit_id, type): - explicit_id = None + # if there's an explicit_id, validate it using `type` + if explicit_id and \ + not valid.validate(explicit_id, type): + explicit_id = None - return func(token_id, explicit_id) + return func(token_id, explicit_id) - wrapper.__name__ = func.__name__ - return wrapper + wrapper.__name__ = func.__name__ + return wrapper - return decorator + return decorator def two_person(func): - ''' + ''' endpoint should have two parameters: endpoint(user_1_id: str, user_2_id: str) no authentication, just runs endpoint() if both token_id and explicit_id are present from @util_two_id. ''' - @util_two_id("user") - def wrapper(token_id, explicit_id): - if not all_def([token_id, explicit_id]): - return "", 400 + @util_two_id("user") + def wrapper(token_id, explicit_id): + if not all_def([token_id, explicit_id]): + return "", 400 - return func(token_id, explicit_id) + return func(token_id, explicit_id) - wrapper.__name__ = func.__name__ - return wrapper + wrapper.__name__ = func.__name__ + return wrapper def one_person(func): - ''' + ''' endpoint should have two parameters: endpoint(user_id: str, viewer?: str) @@ -68,35 +68,35 @@ def one_person(func): doesn't check for authentication expects that func takes these arguments: (user_id, viewer?) ''' - @util_two_id("user") - def wrapper(token_id, explicit_id): - if all_notdef([token_id, explicit_id]): - return "", 400 + @util_two_id("user") + def wrapper(token_id, explicit_id): + if all_notdef([token_id, explicit_id]): + return "", 400 - return func(explicit_id or token_id, token_id) + return func(explicit_id or token_id, token_id) - wrapper.__name__ = func.__name__ - return wrapper + wrapper.__name__ = func.__name__ + return wrapper def game_id_with_viewer(func): - ''' + ''' endpoint should have two parameters: endpoint(game_id: str, viewer?: str) ''' - @util_two_id("game") - def wrapper(token_id, game_id): - if all_notdef([token_id, game_id]): - return "", 400 + @util_two_id("game") + def wrapper(token_id, game_id): + if all_notdef([token_id, game_id]): + return "", 400 - return func(game_id, token_id) + return func(game_id, token_id) - wrapper.__name__ = func.__name__ - return wrapper + wrapper.__name__ = func.__name__ + return wrapper def auth_required(level): - ''' + ''' level = "none" | "user" | "moderator" | "admin" | "bot" endpoint should have one parameter for the user_id of the request author: endpoint(user_id: str) # `user_id` can only be `None` when `level == "none"` @@ -105,33 +105,33 @@ def auth_required(level): This decorator only runs endpoint() if token_id from @util_two_id is not None and passes hierarchy constraints ''' - def decorator(func): - @util_two_id("user") - def wrapper(token_id, explicit_id): - if not token_id: - if level == ranks[0]: - return func(None) - else: - return "", 400 + def decorator(func): + @util_two_id("user") + def wrapper(token_id, explicit_id): + if not token_id: + if level == ranks[0]: + return func(None) + else: + return "", 400 - user_rank_text = cursor.execute( - "select type from users where user_id = ?", [token_id] - ).fetchone()[0] + user_rank_text = cursor.execute( + "select type from users where user_id = ?", [token_id] + ).fetchone()[0] - required_rank = ranks.index(level) - user_rank = ranks.index(user_rank_text) - if required_rank > user_rank: return "", 403 + required_rank = ranks.index(level) + user_rank = ranks.index(user_rank_text) + if required_rank > user_rank: return "", 403 - return func(token_id) + return func(token_id) - wrapper.__name__ = func.__name__ - return wrapper + wrapper.__name__ = func.__name__ + return wrapper - return decorator + return decorator def io_auth_required(level): - ''' + ''' level = "none" | "user" | "moderator" | "admin" | "bot" endpoint should have two parameters: endpoint(data: socket.io.data, user_id: str) # `user_id` can only be `None` when `level == "none"` @@ -139,22 +139,22 @@ def io_auth_required(level): uses the @auth_required decorator, but is only for use with @io.on decorators ''' - def decorator(func): - # data is the original @io.on data - def wrapper(data={}): + def decorator(func): + # data is the original @io.on data + def wrapper(data={}): - token = request.cookies.get("token") or "" - user_id = token_login(token) + token = request.cookies.get("token") or "" + user_id = token_login(token) - if not user_id: - if level == ranks[0]: - return func(data, None) - else: - return + if not user_id: + if level == ranks[0]: + return func(data, None) + else: + return - return func(data, user_id) + return func(data, user_id) - wrapper.__name__ = func.__name__ - return wrapper + wrapper.__name__ = func.__name__ + return wrapper - return decorator + return decorator diff --git a/api/main.py b/api/main.py index 8f4e087..717bb5e 100644 --- a/api/main.py +++ b/api/main.py @@ -6,4 +6,4 @@ import game.socket # start the flask/socket.io server if __name__ == "__main__": - io.run(app, host="127.0.0.1", port=5000, debug=True) + io.run(app, host="127.0.0.1", port=5000, debug=True) diff --git a/api/passwords.py b/api/passwords.py index 9cd56da..761e6a3 100644 --- a/api/passwords.py +++ b/api/passwords.py @@ -3,14 +3,14 @@ import bcrypt # encode string as utf-8 def enc(string): - return string.encode('utf-8') + return string.encode('utf-8') # check if password matches against hash in database def check_password(password, password_hash): - return bcrypt.checkpw(enc(password), password_hash) + return bcrypt.checkpw(enc(password), password_hash) # hash a password for storing in the database def password_hash(password): - return bcrypt.hashpw(enc(password), bcrypt.gensalt()) + return bcrypt.hashpw(enc(password), bcrypt.gensalt()) diff --git a/api/randid.py b/api/randid.py index 6c1ca9d..e0baf43 100644 --- a/api/randid.py +++ b/api/randid.py @@ -6,13 +6,13 @@ tables = {"users": "user_id", "games": "game_id"} # generate a new uuid and check for collisions (unlikely but still) def new_uuid(table_name): - temp_uuid = str(uuid.uuid4()) - column_name = tables[table_name] - # check if id is already taken - if cursor.execute( - f"select {column_name} from {table_name} where {column_name} = ?", - [temp_uuid] - ).fetchone(): - return new_uuid(table_name) - else: - return temp_uuid + temp_uuid = str(uuid.uuid4()) + column_name = tables[table_name] + # check if id is already taken + if cursor.execute( + f"select {column_name} from {table_name} where {column_name} = ?", + [temp_uuid] + ).fetchone(): + return new_uuid(table_name) + else: + return temp_uuid diff --git a/api/rating.py b/api/rating.py index 406969b..cfdf5e4 100644 --- a/api/rating.py +++ b/api/rating.py @@ -2,40 +2,40 @@ from db import cursor def outcome(outcome_str, player_1): - outcome_int = {"w": 1, "l": -1, "d": 0}[outcome_str] - if not player_1: outcome_int *= -1 - return {1: "w", -1: "l", 0: "d"}[outcome_int] + outcome_int = {"w": 1, "l": -1, "d": 0}[outcome_str] + if not player_1: outcome_int *= -1 + return {1: "w", -1: "l", 0: "d"}[outcome_int] def rating_v1(won_games): # python is a garbage language - return abs(won_games)**(1 / 2.2) * 23 * (1, -1)[won_games < 0] + return abs(won_games)**(1 / 2.2) * 23 * (1, -1)[won_games < 0] def get_all_games(user_id): - return cursor.execute("select player_1_id, player_2_id, outcome " + \ - "from games " + \ - "where (player_1_id = ? or player_2_id = ?) " + \ - "and status = \"finished\" or status = \"resign\"", [user_id, user_id]).fetchall() + return cursor.execute("select player_1_id, player_2_id, outcome " + \ + "from games " + \ + "where (player_1_id = ? or player_2_id = ?) " + \ + "and status = \"finished\" or status = \"resign\"", [user_id, user_id]).fetchall() # simple rating function that doesn't use game analysis def get_rating(user_id): - score = 400 - games = get_all_games(user_id) - # get all games for user_id and switch perspective in which user_id is player_2_id - mapped_games = [ - game if game[0] == user_id else - (game[1], game[0], outcome(game[2], False)) for game in games - ] - counted_opponents = {} - for game in mapped_games: - # calculate sum score against user (+1 for win, -1 for lose, 0 for draw game) - counted_opponents[game[1]] = (counted_opponents.get(game[1]) or 0) + { - "w": 1, - "l": -1, - "d": 0 - }[game[2]] - for opponent in counted_opponents: - # apply the cool curve to the sum score and add to the base score of 400 - score += rating_v1(counted_opponents.get(opponent)) - return int(score) + score = 400 + games = get_all_games(user_id) + # get all games for user_id and switch perspective in which user_id is player_2_id + mapped_games = [ + game if game[0] == user_id else + (game[1], game[0], outcome(game[2], False)) for game in games + ] + counted_opponents = {} + for game in mapped_games: + # calculate sum score against user (+1 for win, -1 for lose, 0 for draw game) + counted_opponents[game[1]] = (counted_opponents.get(game[1]) or 0) + { + "w": 1, + "l": -1, + "d": 0 + }[game[2]] + for opponent in counted_opponents: + # apply the cool curve to the sum score and add to the base score of 400 + score += rating_v1(counted_opponents.get(opponent)) + return int(score) diff --git a/api/readme.md b/api/readme.md index 648171a..f5cd981 100644 --- a/api/readme.md +++ b/api/readme.md @@ -80,7 +80,8 @@ API return type classes are mostly defined in api/api.ts ```ts { - id: userID; + id: + userID; } ``` @@ -90,7 +91,7 @@ API return type classes are mostly defined in api/api.ts ```ts { - userInfo; + userInfo; } ``` @@ -107,7 +108,7 @@ API return type classes are mostly defined in api/api.ts ```ts { - userInfo; + userInfo; } ``` @@ -122,7 +123,8 @@ API return type classes are mostly defined in api/api.ts ```ts { - id: userID; + id: + userID; } ``` @@ -190,7 +192,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - image: base64PNG; + image: + base64PNG; } ``` @@ -209,7 +212,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - preferences: userPreferences; + preferences: + userPreferences; } ``` @@ -224,7 +228,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - newPreferences: userPreferences; + newPreferences: + userPreferences; } ``` @@ -295,7 +300,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - status: string; + status: + string; } ``` @@ -312,7 +318,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - id: userID; + id: + userID; } ``` @@ -329,7 +336,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - id: userID; + id: + userID; } ``` @@ -346,7 +354,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - id: userID; + id: + userID; } ``` @@ -363,7 +372,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - query: string; + query: + string; } ``` @@ -386,7 +396,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - id: userID; + id: + userID; } ``` @@ -403,7 +414,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - id: userID; + id: + userID; } ``` @@ -503,7 +515,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - id: gameID; + id: + gameID; } ``` @@ -513,7 +526,7 @@ returns error when image is not .png or larger than 256x256 ```ts { - gameInfo; + gameInfo; } ``` @@ -528,7 +541,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - id: gameID; + id: + gameID; } ``` @@ -555,7 +569,8 @@ returns error when image is not .png or larger than 256x256 ```ts { - id: gameID; + id: + gameID; } ``` diff --git a/api/ruleset.py b/api/ruleset.py index 5f33e09..40458de 100644 --- a/api/ruleset.py +++ b/api/ruleset.py @@ -3,35 +3,35 @@ import json # predefined rulesets rulesets = { - "default": { - "timelimit": { - "enabled": False, - "minutes": 0, - "seconds": 0, - "addmove": 0, - "shared": False, - }, - "ranked": True, - } + "default": { + "timelimit": { + "enabled": False, + "minutes": 0, + "seconds": 0, + "addmove": 0, + "shared": False, + }, + "ranked": True, + } } # resolve ruleset from ruleset name or dict def resolve_ruleset(ruleset): - # create return variable - export = {} - try: - # try to parse the ruleset as json - export = json.loads(ruleset) - merged = dict(rulesets["default"]) + # create return variable + export = {} + try: + # try to parse the ruleset as json + export = json.loads(ruleset) + merged = dict(rulesets["default"]) - # fill missing keys in dict - merge(merged, export) - export = merged - except ValueError as e: - # if the ruleset is a name like 'default' or 'columns+2', read it from the predefined rulesets - if ruleset in rulesets: - export = rulesets[ruleset] - if not export: - export = rulesets["default"] - return export + # fill missing keys in dict + merge(merged, export) + export = merged + except ValueError as e: + # if the ruleset is a name like 'default' or 'columns+2', read it from the predefined rulesets + if ruleset in rulesets: + export = rulesets[ruleset] + if not export: + export = rulesets["default"] + return export diff --git a/api/social/create_relation.py b/api/social/create_relation.py index 5367ac5..01ad0eb 100644 --- a/api/social/create_relation.py +++ b/api/social/create_relation.py @@ -6,46 +6,46 @@ import time def create_relation(user_1_id, user_2_id, relation_type): - remove_relation(user_1_id, user_2_id) - remove_relation(user_2_id, user_1_id) - timestamp = int(time.time() * 1000) - cursor.execute( - "insert into social values (?, ?, ?, ?)", - [user_1_id, user_2_id, relation_type, timestamp] - ) - connection.commit() + remove_relation(user_1_id, user_2_id) + remove_relation(user_2_id, user_1_id) + timestamp = int(time.time() * 1000) + cursor.execute( + "insert into social values (?, ?, ?, ?)", + [user_1_id, user_2_id, relation_type, timestamp] + ) + connection.commit() # remove relation between user_1_id and user_2_id (one-way) def remove_relation(user_1_id, user_2_id): - cursor.execute( - "delete from social where user_1_id = ? and user_2_id = ?", - [user_1_id, user_2_id] - ) - connection.commit() + cursor.execute( + "delete from social where user_1_id = ? and user_2_id = ?", + [user_1_id, user_2_id] + ) + connection.commit() def create_relation_route(relation_type): - @two_person - def route(user_1_id, user_2_id): - create_relation(user_1_id, user_2_id, relation_type) + @two_person + def route(user_1_id, user_2_id): + create_relation(user_1_id, user_2_id, relation_type) - if relation_type == "outgoing": - io.emit("incomingFriendRequest", room="user-" + user_2_id) + if relation_type == "outgoing": + io.emit("incomingFriendRequest", room="user-" + user_2_id) - return "", 200 + return "", 200 - return route + return route friend_request = Blueprint('friend_request', __name__) friend_request.add_url_rule( - '/request', 'route', create_relation_route("outgoing"), methods=["POST"] + '/request', 'route', create_relation_route("outgoing"), methods=["POST"] ) block = Blueprint('block', __name__) block.add_url_rule( - '/block', 'route', create_relation_route("block"), methods=["POST"] + '/block', 'route', create_relation_route("block"), methods=["POST"] ) dynamic_routes = [["/social", friend_request], ["/social", block]] diff --git a/api/social/destroy_relation.py b/api/social/destroy_relation.py index 2aa793b..d2e4388 100644 --- a/api/social/destroy_relation.py +++ b/api/social/destroy_relation.py @@ -12,16 +12,16 @@ remove = Blueprint('remove', __name__) @remove.route('/remove', methods=['POST']) @two_person def index(user_1_id, user_2_id): - relation = get_relation_to(user_1_id, user_2_id) - if relation == "none": return "", 403 + relation = get_relation_to(user_1_id, user_2_id) + if relation == "none": return "", 403 - remove_relation(user_1_id, user_2_id) - remove_relation(user_2_id, user_1_id) + remove_relation(user_1_id, user_2_id) + remove_relation(user_2_id, user_1_id) - io.emit("changedRelation", {"id": user_2_id}, room="user-" + user_1_id) - io.emit("changedRelation", {"id": user_1_id}, room="user-" + user_2_id) + io.emit("changedRelation", {"id": user_2_id}, room="user-" + user_1_id) + io.emit("changedRelation", {"id": user_1_id}, room="user-" + user_2_id) - return "", 200 + return "", 200 unblock = Blueprint('unblock', __name__) @@ -30,10 +30,10 @@ unblock = Blueprint('unblock', __name__) @unblock.route('/unblock', methods=['POST']) @two_person def index(user_1_id, user_2_id): - if get_relation_to(user_1_id, user_2_id) != "blocked": return "", 403 + if get_relation_to(user_1_id, user_2_id) != "blocked": return "", 403 - remove_relation(user_1_id, user_2_id) - return "", 200 + remove_relation(user_1_id, user_2_id) + return "", 200 dynamic_routes = [["/social", remove], ["/social", unblock]] diff --git a/api/social/friend_accept.py b/api/social/friend_accept.py index b434272..45edc72 100644 --- a/api/social/friend_accept.py +++ b/api/social/friend_accept.py @@ -10,16 +10,16 @@ accept = Blueprint('accept', __name__) @accept.route("/accept", methods=['POST']) @two_person def route(user_1_id, user_2_id): - cursor.execute( - "update social set type = \"friendship\" where user_1_id = ? and user_2_id = ?", - [user_2_id, user_1_id] - ) - connection.commit() + cursor.execute( + "update social set type = \"friendship\" where user_1_id = ? and user_2_id = ?", + [user_2_id, user_1_id] + ) + connection.commit() - io.emit("changedRelation", {"id": user_2_id}, room="user-" + user_1_id) - io.emit("changedRelation", {"id": user_1_id}, room="user-" + user_2_id) + io.emit("changedRelation", {"id": user_2_id}, room="user-" + user_1_id) + io.emit("changedRelation", {"id": user_1_id}, room="user-" + user_2_id) - return "", 200 + return "", 200 dynamic_route = ["/social", accept] diff --git a/api/social/request_list.py b/api/social/request_list.py index 9b79203..f991ce2 100644 --- a/api/social/request_list.py +++ b/api/social/request_list.py @@ -10,18 +10,18 @@ requests = Blueprint('requests', __name__) @requests.route("/requests") @auth_required("user") def route(user_2_id): - # get a list of friend requests - request_list = cursor.execute( - "select user_1_id from social where user_2_id = ? and type = \"outgoing\"", - [user_2_id] - ).fetchall() + # get a list of friend requests + request_list = cursor.execute( + "select user_1_id from social where user_2_id = ? and type = \"outgoing\"", + [user_2_id] + ).fetchall() - # get user_id for each result to prevent repeat user/info requests - formatted_request_list = [] - for user_1_id in [q[0] for q in request_list]: - formatted_request_list.append(format_user(user_1_id)) + # get user_id for each result to prevent repeat user/info requests + formatted_request_list = [] + for user_1_id in [q[0] for q in request_list]: + formatted_request_list.append(format_user(user_1_id)) - return {"requests": formatted_request_list}, 200 + return {"requests": formatted_request_list}, 200 dynamic_route = ["/social/list", requests] diff --git a/api/social/search.py b/api/social/search.py index f0ce8a2..c0ef312 100644 --- a/api/social/search.py +++ b/api/social/search.py @@ -8,26 +8,26 @@ search = Blueprint('search', __name__) @search.route('/search', methods=['POST']) def index(): - data_string = request.data or "{}" - data = json.loads(data_string) - query = data.get("query") or "" - if not query: return "", 400 - if len(query) < 3: return "", 403 - - # use levenshtein with max distance 3 to search for users - #TODO: use mysql and sort by best match - results = cursor.execute( - "select user_id from users where levenshtein(lower(username), lower(?), 3)", - [query] - ).fetchmany(20) - - formatted = {"results": []} - - # get user_id for each result to prevent repeat user/info requests - for user in results: - formatted["results"].append(format_user(user[0])) - - return formatted, 200 + data_string = request.data or "{}" + data = json.loads(data_string) + query = data.get("query") or "" + if not query: return "", 400 + if len(query) < 3: return "", 403 + + # use levenshtein with max distance 3 to search for users + #TODO: use mysql and sort by best match + results = cursor.execute( + "select user_id from users where levenshtein(lower(username), lower(?), 3)", + [query] + ).fetchmany(20) + + formatted = {"results": []} + + # get user_id for each result to prevent repeat user/info requests + for user in results: + formatted["results"].append(format_user(user[0])) + + return formatted, 200 dynamic_route = ["/social", search] diff --git a/api/status.py b/api/status.py index a47c6c4..32f96ee 100644 --- a/api/status.py +++ b/api/status.py @@ -6,15 +6,15 @@ status = Blueprint('server_status', __name__) @status.route('/status') def index(): - return { - # "users": int, - "games": - len( - cursor.execute( - "select game_id from games where status = \"in_progress\"" - ).fetchall() - ) - } + return { + # "users": int, + "games": + len( + cursor.execute( + "select game_id from games where status = \"in_progress\"" + ).fetchall() + ) + } dynamic_route = ["/", status] diff --git a/api/user/avatar.py b/api/user/avatar.py index bfbc097..3589653 100644 --- a/api/user/avatar.py +++ b/api/user/avatar.py @@ -13,27 +13,27 @@ avatar = Blueprint('avatar', __name__) @avatar.route('/avatar', methods=["GET"]) @auth_required("none") def get_avatar(token_id): - user_id = request.args.get("id") or token_id - if not user_id: return "", 400 - if not valid.user_id(user_id): return "", 403 + user_id = request.args.get("id") or token_id + if not user_id: return "", 400 + if not valid.user_id(user_id): return "", 403 - avatar_path = f"database/avatars/{user_id}.png" - avatar = "" - if exists(avatar_path): - avatar = open(avatar_path, "rb").read() - return Response(avatar or default_avatar, 200, mimetype="image/png") + avatar_path = f"database/avatars/{user_id}.png" + avatar = "" + if exists(avatar_path): + avatar = open(avatar_path, "rb").read() + return Response(avatar or default_avatar, 200, mimetype="image/png") #TODO: pillow image size validation (client side resize) @avatar.route('/avatar', methods=["POST"]) @auth_required("user") def update_avatar(user_id): - if not request.data: return "", 400 + if not request.data: return "", 400 - open(f"database/avatars/{user_id}.png", "wb") \ - .write(decode(request.data, "base64")) + open(f"database/avatars/{user_id}.png", "wb") \ + .write(decode(request.data, "base64")) - return "", 200 + return "", 200 dynamic_route = ["/user", avatar] diff --git a/api/user/games.py b/api/user/games.py index da5f422..d3b650c 100644 --- a/api/user/games.py +++ b/api/user/games.py @@ -11,54 +11,54 @@ import json # get total game outcome amount for user def sum_games(user_id): #! SANITIZE USER_ID FIRST - wld_querys = [ - ' '.join( - [ - "select count(game_id)", - "from games", - "where", - f"player_{x[0]}_id = \"{user_id}\" and", - f"outcome = \"{x[1]}\"", - ] - ) for x in [(1, "w"), (1, "l"), (2, "w"), (2, "l")] - ] - wld_querys.insert( - 0, ' '.join( - [ - "select count(game_id)", - "from games", - "where", - f"(player_1_id = \"{user_id}\" or player_2_id = \"{user_id}\") and", - "outcome = \"d\"", - ] - ) - ) + wld_querys = [ + ' '.join( + [ + "select count(game_id)", + "from games", + "where", + f"player_{x[0]}_id = \"{user_id}\" and", + f"outcome = \"{x[1]}\"", + ] + ) for x in [(1, "w"), (1, "l"), (2, "w"), (2, "l")] + ] + wld_querys.insert( + 0, ' '.join( + [ + "select count(game_id)", + "from games", + "where", + f"(player_1_id = \"{user_id}\" or player_2_id = \"{user_id}\") and", + "outcome = \"d\"", + ] + ) + ) - big_query = "select " + ", ".join([f"({query})" for query in wld_querys]) + big_query = "select " + ", ".join([f"({query})" for query in wld_querys]) - results = cursor.execute(big_query).fetchone() + results = cursor.execute(big_query).fetchone() - # win and lose are calculated from user_id's perspective (player_1_id, player_2_id in db) - return { - "draw": results[0], - "win": results[1] + results[4], - "lose": results[2] + results[3], - "games": reduce(lambda a, b: a + b, results) - } + # win and lose are calculated from user_id's perspective (player_1_id, player_2_id in db) + return { + "draw": results[0], + "win": results[1] + results[4], + "lose": results[2] + results[3], + "games": reduce(lambda a, b: a + b, results) + } # get `count` games that `user_id` participated in, sorted by newest game def fetch_games(user_id, count): - game_ids = cursor.execute( - "select game_id from games where player_1_id = ? or player_2_id = ? order by created desc", - [user_id, user_id] - ).fetchmany(count) - export = [] + game_ids = cursor.execute( + "select game_id from games where player_1_id = ? or player_2_id = ? order by created desc", + [user_id, user_id] + ).fetchmany(count) + export = [] - for game_id in game_ids: - export.append(format_game(game_id[0], user_id)) + for game_id in game_ids: + export.append(format_game(game_id[0], user_id)) - return export + return export games = Blueprint('games', __name__) @@ -67,10 +67,10 @@ games = Blueprint('games', __name__) @games.route('/games', methods=['GET', 'POST']) @one_person def index(user_id, viewer): - return { - "totals": sum_games(user_id), - "games": fetch_games(user_id, 20) - }, 200 + return { + "totals": sum_games(user_id), + "games": fetch_games(user_id, 20) + }, 200 dynamic_route = ["/user", games] diff --git a/api/user/info.py b/api/user/info.py index a5e2aac..fc303a6 100644 --- a/api/user/info.py +++ b/api/user/info.py @@ -7,53 +7,53 @@ import json # get relation to user_2_id from user_1_id's perspective def get_relation_to(user_1_id, user_2_id): - relation = cursor.execute("select * from social where " + \ - "(user_1_id = ? and user_2_id = ?) or " + \ - "(user_1_id = ? and user_2_id = ?)", [user_1_id, user_2_id, user_2_id, user_1_id]).fetchone() - if not relation: return "none" - if relation[2] == "friendship": return "friends" - if relation[2] == "outgoing" and relation[0] == user_1_id: - return "outgoing" - if relation[2] == "outgoing" and relation[1] == user_1_id: - return "incoming" - if relation[2] == "block" and relation[0] == user_1_id: return "blocked" - return "none" + relation = cursor.execute("select * from social where " + \ + "(user_1_id = ? and user_2_id = ?) or " + \ + "(user_1_id = ? and user_2_id = ?)", [user_1_id, user_2_id, user_2_id, user_1_id]).fetchone() + if not relation: return "none" + if relation[2] == "friendship": return "friends" + if relation[2] == "outgoing" and relation[0] == user_1_id: + return "outgoing" + if relation[2] == "outgoing" and relation[1] == user_1_id: + return "incoming" + if relation[2] == "block" and relation[0] == user_1_id: return "blocked" + return "none" # get users friend count def count_friends(user_id): - query = cursor.execute( - "select type from social where (user_1_id = ? or user_2_id = ?) and type = \"friendship\"", - [user_id, user_id] - ).fetchall() - return len(query) #FIXME: use SQL count() instead of python's len() + query = cursor.execute( + "select type from social where (user_1_id = ? or user_2_id = ?) and type = \"friendship\"", + [user_id, user_id] + ).fetchall() + return len(query) #FIXME: use SQL count() instead of python's len() # get user/info of `user_id` as `viewer` (id) def format_user(user_id, viewer=''): - user = cursor.execute( - "select " + ", ".join( - [ - "username", - "user_id", - "country", - "registered", - "status", - ] - ) + " from users where user_id = ?", [user_id] - ).fetchone() - formatted_user = { - "username": user[0], - "id": user[1], - "country": user[2], - "registered": user[3], - "status": user[4], - "friends": count_friends(user_id), - "rating": - get_rating(user_id), #TODO: calculate rating based on game analysis - } - if viewer: formatted_user["relation"] = get_relation_to(viewer, user_id) - return formatted_user + user = cursor.execute( + "select " + ", ".join( + [ + "username", + "user_id", + "country", + "registered", + "status", + ] + ) + " from users where user_id = ?", [user_id] + ).fetchone() + formatted_user = { + "username": user[0], + "id": user[1], + "country": user[2], + "registered": user[3], + "status": user[4], + "friends": count_friends(user_id), + "rating": + get_rating(user_id), #TODO: calculate rating based on game analysis + } + if viewer: formatted_user["relation"] = get_relation_to(viewer, user_id) + return formatted_user info = Blueprint('info', __name__) @@ -64,8 +64,8 @@ info = Blueprint('info', __name__) @info.route('/info', methods=['GET', 'POST']) @one_person def index(user_id, viewer): - user = format_user(user_id, viewer) - return user, 200 + user = format_user(user_id, viewer) + return user, 200 dynamic_route = ["/user", info] diff --git a/api/user/password.py b/api/user/password.py index 51ab6db..731f7e4 100644 --- a/api/user/password.py +++ b/api/user/password.py @@ -7,13 +7,13 @@ password = Blueprint('password', __name__) # this endpoint is unfinished @password.route('/password') def index(): - data = request.get_json() + data = request.get_json() - if not data["password"] or \ - not data["newPassword"]: - return "", 400 + if not data["password"] or \ + not data["newPassword"]: + return "", 400 - return {}, 200 + return {}, 200 dynamic_route = ["/user", password] diff --git a/api/user/preferences.py b/api/user/preferences.py index 2ca4a05..8779eaf 100644 --- a/api/user/preferences.py +++ b/api/user/preferences.py @@ -7,17 +7,17 @@ import json # fill missing dict keys in preferences object def format_preferences(prefs): - return { - "darkMode": - prefs.get("darkMode") or False, - "ruleset": - resolve_ruleset(json.dumps(prefs.get("ruleset") or {}) or "default"), - "userColors": { - "diskA": prefs.get("userColors", {}).get("diskA") or "", - "diskB": prefs.get("userColors", {}).get("diskB") or "", - "background": prefs.get("userColors", {}).get("background") or "" - } - } + return { + "darkMode": + prefs.get("darkMode") or False, + "ruleset": + resolve_ruleset(json.dumps(prefs.get("ruleset") or {}) or "default"), + "userColors": { + "diskA": prefs.get("userColors", {}).get("diskA") or "", + "diskB": prefs.get("userColors", {}).get("diskB") or "", + "background": prefs.get("userColors", {}).get("background") or "" + } + } preferences = Blueprint('preferences', __name__) @@ -26,27 +26,27 @@ preferences = Blueprint('preferences', __name__) @preferences.route('/preferences', methods=["GET"]) @auth_required("user") def get_preferences(login): - user_prefs = cursor.execute( - "select preferences from users where user_id = ?", [login] - ).fetchone() - return {"preferences": format_preferences(json.loads(user_prefs[0]))}, 200 + user_prefs = cursor.execute( + "select preferences from users where user_id = ?", [login] + ).fetchone() + return {"preferences": format_preferences(json.loads(user_prefs[0]))}, 200 @preferences.route('/preferences', methods=["POST"]) @auth_required("user") def index(login): - data = request.get_json() - new_preferences = data.get("newPreferences") or "" + data = request.get_json() + new_preferences = data.get("newPreferences") or "" - formatted_json = format_preferences(new_preferences) + formatted_json = format_preferences(new_preferences) - cursor.execute( - "update users set preferences = ? where user_id = ?", - [json.dumps(formatted_json), login] - ) - connection.commit() + cursor.execute( + "update users set preferences = ? where user_id = ?", + [json.dumps(formatted_json), login] + ) + connection.commit() - return "", 200 + return "", 200 dynamic_route = ["/user", preferences] diff --git a/api/user/status.py b/api/user/status.py index 219481a..050ed33 100644 --- a/api/user/status.py +++ b/api/user/status.py @@ -9,17 +9,17 @@ status = Blueprint('user_status', __name__) @status.route('/status', methods=['POST']) @auth_required("user") def index(user_id): - data = request.get_json() - status = data.get("status") or "" - if not status: return "", 400 + data = request.get_json() + status = data.get("status") or "" + if not status: return "", 400 - cursor.execute( - "update users set status = ? where user_id = ?", - [status[0:200], user_id] - ) - connection.commit() + cursor.execute( + "update users set status = ? where user_id = ?", + [status[0:200], user_id] + ) + connection.commit() - return "", 200 + return "", 200 dynamic_route = ["/user", status] diff --git a/api/util.py b/api/util.py index 777820a..836c524 100644 --- a/api/util.py +++ b/api/util.py @@ -1,6 +1,6 @@ def all_def(props): - return all(bool(v) for v in props) + return all(bool(v) for v in props) def all_notdef(props): - return all(not v for v in props) + return all(not v for v in props) diff --git a/api/valid.py b/api/valid.py index f407460..1c63703 100644 --- a/api/valid.py +++ b/api/valid.py @@ -2,20 +2,20 @@ from db import cursor def validate(id, type): - types = { - "user": ["user_id", "users"], - "game": ["game_id", "games"], - } - fields = types[type] - query = cursor.execute( - f"select {fields[0]} from {fields[1]} where {fields[0]} = ?", [id] - ).fetchone() - return bool(query) + types = { + "user": ["user_id", "users"], + "game": ["game_id", "games"], + } + fields = types[type] + query = cursor.execute( + f"select {fields[0]} from {fields[1]} where {fields[0]} = ?", [id] + ).fetchone() + return bool(query) def user_id(user_id): - return validate(user_id, "user") + return validate(user_id, "user") def game_id(game_id): - return validate(game_id, "game") + return validate(game_id, "game") |