From 96d77a8e60f7abac771d191331f639959f0e6e8b Mon Sep 17 00:00:00 2001 From: Mercury Date: Thu, 23 Jan 2025 12:16:35 +0100 Subject: [PATCH] Add password reset endpoint, experimental 2v2 TrueScore calculation --- calls.py | 29 ++++++++++++- main.py | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/calls.py b/calls.py index f751207..dd84141 100644 --- a/calls.py +++ b/calls.py @@ -45,7 +45,34 @@ def authenticate_user(email, password): finally: cursor.close() conn.close() - + +def reset_password(uid, email, new_password): + hashed_password = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()).decode() + + conn = get_db_connection() + cursor = conn.cursor() + + try: + cursor.execute( + """ + UPDATE users + SET password_hash = %s + WHERE uid = %s AND email = %s; + """, + (hashed_password, uid, email) + ) + + if cursor.rowcount == 0: + raise ValueError("No user found with the provided UID and email.") + + conn.commit() + except Exception as e: + conn.rollback() + raise e + finally: + cursor.close() + conn.close() + def add_friend(token, friend_uid): conn = get_db_connection() diff --git a/main.py b/main.py index f2dcc20..33bc8fd 100644 --- a/main.py +++ b/main.py @@ -23,7 +23,11 @@ class RegisterRequest(BaseModel): class LoginRequest(BaseModel): email: str password: str - + +class ResetPasswordRequest(BaseModel): + uid: int + email: EmailStr + new_password: str class FriendRequest(BaseModel): token: str @@ -64,6 +68,15 @@ def login(request: LoginRequest): else: raise HTTPException(status_code=401, detail="Invalid credentials") +@app.post("/reset-password") +def reset_user_password(request: ResetPasswordRequest): + try: + reset_password(request.uid, request.email, request.new_password) + return {"message": "Password reset successfully"} + except ValueError as ve: + raise HTTPException(status_code=404, detail=str(ve)) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) @app.post("/add_friend") def add_friend_endpoint(request: FriendRequest): @@ -412,4 +425,117 @@ def get_latest_commit_hashes(): return {"error": "Failed to fetch commit hashes from Forgejo"} except Exception as e: - return {"error": str(e)} \ No newline at end of file + return {"error": str(e)} + +## EXPERIMENTAL: 4 player match TS calculation +import trueskill +ts = trueskill.TrueSkill(mu=25.0, sigma=8.333, beta=4.166, tau=0.083, draw_probability=0.1) + +class EndFourMatch(BaseModel): + match_id: int + player1_team1_score: int + player2_team1_score: int + player1_team2_score: int + player2_team2_score: int + +def get_rolling_base(): + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute( + """ + SELECT AVG(current_elo) AS rolling_base + FROM users + WHERE current_elo != 0; + """ + ) + result = cursor.fetchone() + return result["rolling_base"] if result else 10 + +def elo_to_trueskill(elo, base, k=40): + mu = (elo - base) / k + sigma = 8.333 + return ts.create_rating(mu=mu, sigma=sigma) + +@app.post("/endfour") +async def end_four_match(request: EndFourMatch): + conn = get_db_connection() + cursor = conn.cursor() + try: + cursor.execute( + """ + SELECT player1_team1_uid, player2_team1_uid, player1_team2_uid, player2_team2_uid + FROM fourmatches + WHERE match_id = %s; + """, + (request.match_id,) + ) + match = cursor.fetchone() + if not match: + raise HTTPException(status_code=404, detail="Match not found") + + player1_team1_uid, player2_team1_uid, player1_team2_uid, player2_team2_uid = match + + cursor.execute( + """ + SELECT uid, trueskill_mu, trueskill_sigma + FROM users + WHERE uid IN (%s, %s, %s, %s); + """, + (player1_team1_uid, player2_team1_uid, player1_team2_uid, player2_team2_uid) + ) + players = { + row["uid"]: ts.create_rating(mu=row["trueskill_mu"], sigma=row["trueskill_sigma"]) + for row in cursor.fetchall() + } + + team1_score = request.player1_team1_score + request.player2_team1_score + team2_score = request.player1_team2_score + request.player2_team2_score + + ranks = [0, 1] if team1_score > team2_score else [1, 0] + + new_ratings = ts.rate( + [[players[player1_team1_uid], players[player2_team1_uid]], + [players[player1_team2_uid], players[player2_team2_uid]]], + ranks=ranks + ) + + updates = [ + (uid, rating.mu, rating.sigma) + for uid, rating in zip( + [player1_team1_uid, player2_team1_uid, player1_team2_uid, player2_team2_uid], + [new_ratings[0][0], new_ratings[0][1], new_ratings[1][0], new_ratings[1][1]] + ) + ] + for uid, new_mu, new_sigma in updates: + cursor.execute( + """ + UPDATE users + SET trueskill_mu = %s, trueskill_sigma = %s + WHERE uid = %s; + """, + (new_mu, new_sigma, uid) + ) + + # Update match results + cursor.execute( + """ + UPDATE fourmatches + SET player1_team1_score = %s, player2_team1_score = %s, + player1_team2_score = %s, player2_team2_score = %s + WHERE match_id = %s; + """, + (request.player1_team1_score, request.player2_team1_score, + request.player1_team2_score, request.player2_team2_score, request.match_id) + ) + + conn.commit() + + return {"message": "Match ended and TrueSkill ratings updated successfully"} + + except Exception as e: + conn.rollback() + raise HTTPException(status_code=400, detail=str(e)) + finally: + cursor.close() + conn.close() +