temporary session are now available

This commit is contained in:
Jonas Weinz 2019-04-04 13:44:48 +02:00
parent ef5696f61d
commit 0c342eff7b
8 changed files with 125 additions and 81 deletions

View File

@ -39,6 +39,7 @@ communication with the web client is done by a (far from any standard and almost
"data": { "data": {
"id": "...", "id": "...",
"revoke_time": <revoke_time>, "revoke_time": <revoke_time>,
"ranked": "<true | false">,
"match_state": <null| <match_state>> "match_state": <null| <match_state>>
} }
} }
@ -101,12 +102,18 @@ server response:
**login or register**: **login or register**:
keep pw null for requesting a temporary session (will be deleted after one hour of inactivity and matches are not ranked)
```json ```json
{ {
"type": "login", "type": "login",
"data": { "data": {
"name": "<player_name>", "name": "<player_name>",
"pw": "<password>" "pw": "<password> | null"
} }
} }
``` ```
@ -119,6 +126,7 @@ response:
"data": { "data": {
"success": <true|false>, "success": <true|false>,
"id": "<session-id>", "id": "<session-id>",
"registered": <true | false>,
"msg": "..." "msg": "..."
} }
} }

View File

@ -141,11 +141,14 @@ class ConnectionHandler(object):
"success": True, "success": True,
"id": conn.id, "id": conn.id,
"user": conn.user_name, "user": conn.user_name,
"registered": conn.is_registered_user,
"msg": "" "msg": ""
} }
})) }))
await self.send_elo(conn) await self.send_elo(conn)
await self.send_friends(conn) if conn.is_registered_user:
await self.send_friends(conn)
await self._on_match_state_req(conn, None) await self._on_match_state_req(conn, None)
return conn return conn
await socket.send(json.dumps({ await socket.send(json.dumps({
@ -154,45 +157,12 @@ class ConnectionHandler(object):
"success": False, "success": False,
"id": None, "id": None,
"user": None, "user": None,
"registered": False,
"msg": "session not available" "msg": "session not available"
} }
})) }))
return None return None
elif msg['type'] == 'temp_session':
name = msg['data']['name'].lower()
if valid_name(name) and len(self.session_manager.get_session_by_temp_user(name)) == 0:
if len(self.user_manager.get_user(name)) == 0:
if len(msg['data']['name']) < 16 and ';' not in name and '\'' not in name and '\"' not in name:
id = self.session_manager.create_session_for_temp_user(
name)
conn = Connection(
id=id, user_name=name, registered=False, websocket=socket)
self._add_connection(conn)
await socket.send(json.dumps({
"type": "login_response",
"data": {
"success": True,
"id": id,
"msg": "logged in as temporary user " + name
}
}))
return conn
await socket.send(json.dumps({
"type": "login_response",
"data": {
"success": False,
"id": None,
"msg": "user name not available"
}
}))
return None
elif msg['type'] == 'login': elif msg['type'] == 'login':
response_msg = "" response_msg = ""
success = False success = False
@ -200,6 +170,7 @@ class ConnectionHandler(object):
name = None name = None
pw = None pw = None
conn = None conn = None
registered_user = True
try: try:
@ -225,15 +196,32 @@ class ConnectionHandler(object):
else: else:
response_msg = "invalid password for user " + name response_msg = "invalid password for user " + name
elif valid_name(name) and len(name) <= 16 and len(pw) == 0:
# no password -> temporary account
registered_user = False
# check whether already a temporary session exists for that user:
if (len(self.session_manager.get_session_by_temp_user(name)) > 0) or (len(self.user_manager.get_user(name)) > 0):
response_msg = f"user '{name}' already exists"
success = False
else:
session_id = self.session_manager.create_session_for_temp_user(
name)
response_msg = f"created a temporary session for user {name}"
success = True
else: else:
response_msg = "invalid username or pw. Only usernames containing alphanumerical symbols (including '_','-') between 3 and 16 characters are allowed!" response_msg = "invalid username or pw. Only usernames containing alphanumerical symbols (including '_','-') between 3 and 16 characters are allowed!"
except Exception as e: except Exception as e:
print("error: " + str(e))
response_msg = "invalid username or pw. Only usernames containing alphanumerical symbols (including '_','-') between 3 and 16 characters are allowed!" response_msg = "invalid username or pw. Only usernames containing alphanumerical symbols (including '_','-') between 3 and 16 characters are allowed!"
if success: if success:
conn = Connection(id=session_id, user_name=name, conn = Connection(id=session_id, user_name=name,
registered=True, websocket=socket) registered=registered_user, websocket=socket)
self._add_connection(conn) self._add_connection(conn)
await socket.send(json.dumps({ await socket.send(json.dumps({
@ -241,6 +229,7 @@ class ConnectionHandler(object):
"data": { "data": {
"success": success, "success": success,
"id": session_id, "id": session_id,
"registered": registered_user,
"msg": response_msg "msg": response_msg
} }
})) }))
@ -265,6 +254,7 @@ class ConnectionHandler(object):
"data": { "data": {
"id": m.id, "id": m.id,
"revoke_time": m.get_sql_revoke_time(), "revoke_time": m.get_sql_revoke_time(),
"ranked": m.ranked,
"match_state": state "match_state": state
} }
} }
@ -278,6 +268,7 @@ class ConnectionHandler(object):
"data": { "data": {
"id": m.id, "id": m.id,
"revoke_time": m.get_sql_revoke_time(), "revoke_time": m.get_sql_revoke_time(),
"ranked": m.ranked,
"match_state": state "match_state": state
} }
} }
@ -355,7 +346,7 @@ class ConnectionHandler(object):
return return
try: try:
if valid_name(opponent): if valid_name(opponent):
if len(self.user_manager.get_user(opponent)) > 0: if len(self.user_manager.get_user(opponent)) > 0 or len(self.session_manager.get_session_by_temp_user(opponent)) > 0:
if len(self.match_manager.get_matches_for_user(opponent)) >= 5: if len(self.match_manager.get_matches_for_user(opponent)) >= 5:
await conn.websocket.send( await conn.websocket.send(
@ -435,6 +426,7 @@ class ConnectionHandler(object):
"data": { "data": {
"id": db_match['id'], "id": db_match['id'],
"revoke_time": match.get_sql_revoke_time(), "revoke_time": match.get_sql_revoke_time(),
"ranked": match.ranked,
"match_state": json.loads(match.to_json_state()) "match_state": json.loads(match.to_json_state())
} }
})) }))
@ -469,6 +461,7 @@ class ConnectionHandler(object):
'data': { 'data': {
'id': match.id, 'id': match.id,
'revoke_time': match.get_sql_revoke_time(), 'revoke_time': match.get_sql_revoke_time(),
'ranked': match.ranked,
'match_state': json.loads(match_state) 'match_state': json.loads(match_state)
} }
})) }))
@ -487,6 +480,7 @@ class ConnectionHandler(object):
'data': { 'data': {
'id': match.id, 'id': match.id,
'revoke_time': match.get_sql_revoke_time(), 'revoke_time': match.get_sql_revoke_time(),
'ranked': match.ranked,
'match_state': json.loads(match_state) 'match_state': json.loads(match_state)
} }
})) }))
@ -507,34 +501,37 @@ class ConnectionHandler(object):
return return
if not match.game_over: if not match.game_over:
# check whether both player made a move. If so, the match is ranked as lost for the player who aborted the match if match.ranked:
if match.complete_field.__contains__(Match.FIELD_USER_A) and match.complete_field.__contains__(Match.FIELD_USER_B): # check whether both player made a move. If so, the match is ranked as lost for the player who aborted the match
if match.complete_field.__contains__(Match.FIELD_USER_A) and match.complete_field.__contains__(Match.FIELD_USER_B):
# update rankings: # update rankings:
player_lost = conn.user_name player_lost = conn.user_name
player_won = match.player_a_name if player_lost == match.player_b_name else match.player_b_name player_won = match.player_a_name if player_lost == match.player_b_name else match.player_b_name
elo_won = self.user_manager.get_elo(player_won) elo_won = self.user_manager.get_elo(player_won)
elo_lost = self.user_manager.get_elo(player_lost) elo_lost = self.user_manager.get_elo(player_lost)
# calculate elo values: # calculate elo values:
p_won = elo_p_win(elo_won, elo_lost) p_won = elo_p_win(elo_won, elo_lost)
p_lost = 1 - p_won p_lost = 1 - p_won
new_elo_won = elo_update(elo_won, 1, p_won) new_elo_won = elo_update(elo_won, 1, p_won)
new_elo_lost = elo_update(elo_lost, 0, p_lost) new_elo_lost = elo_update(elo_lost, 0, p_lost)
self.user_manager.update_elo(player_won, new_elo_won) self.user_manager.update_elo(
self.user_manager.update_elo(player_lost, new_elo_lost) player_won, new_elo_won)
self.user_manager.update_elo(
player_lost, new_elo_lost)
await self.send_elo(conn) await self.send_elo(conn)
if player_won in self.open_connections_by_user: if player_won in self.open_connections_by_user:
other_conn = self.open_connections_by_user[player_won] other_conn = self.open_connections_by_user[player_won]
await self.send_elo(other_conn) await self.send_elo(other_conn)
debug( debug(
f"Match {match.id} is aborted by {player_lost} (against {player_won}). Update elo-rankings: {elo_won}->{new_elo_won} and {elo_lost}->{new_elo_lost}") f"Match {match.id} is aborted by {player_lost} (against {player_won}). Update elo-rankings: {elo_won}->{new_elo_won} and {elo_lost}->{new_elo_lost}")
match.game_over = True match.game_over = True
@ -547,6 +544,7 @@ class ConnectionHandler(object):
'data': { 'data': {
'id': match_id, 'id': match_id,
'revoke_time': match.get_sql_revoke_time(), 'revoke_time': match.get_sql_revoke_time(),
'ranked': match.ranked,
'match_state': json.loads(match_state) 'match_state': json.loads(match_state)
} }
}) })
@ -570,6 +568,7 @@ class ConnectionHandler(object):
'data': { 'data': {
'id': match.id, 'id': match.id,
'revoke_time': match.get_sql_revoke_time(), 'revoke_time': match.get_sql_revoke_time(),
'ranked': match.ranked,
'match_state': json.loads(match_state) 'match_state': json.loads(match_state)
} }
})) }))
@ -656,6 +655,18 @@ class ConnectionHandler(object):
top_names, top_elos = self.user_manager.get_highscores(100, 0) top_names, top_elos = self.user_manager.get_highscores(100, 0)
if not conn.is_registered_user:
await conn.send(json.dumps({
'type': 'elo_update',
'data': {
'elo': None,
'rank': None,
'top_names': top_names,
'top_elos': top_elos
}
}))
return
await conn.send(json.dumps({ await conn.send(json.dumps({
'type': 'elo_update', 'type': 'elo_update',
'data': { 'data': {
@ -687,7 +698,8 @@ class ConnectionHandler(object):
t = msg['type'] t = msg['type']
self.user_manager.touch_user(conn.user_name) if conn.is_registered_user:
self.user_manager.touch_user(conn.user_name)
self.session_manager.touch_session(conn.id) self.session_manager.touch_session(conn.id)
if t == "match_request": if t == "match_request":
await self._on_match_req(conn, msg['data']) await self._on_match_req(conn, msg['data'])

View File

@ -17,10 +17,10 @@ def create_tables():
queries = [ queries = [
"DROP TABLE IF EXISTS matches", "DROP TABLE IF EXISTS matches",
"DROP TABLE IF EXISTS sessions", "DROP TABLE IF EXISTS sessions",
"DROP TABLE IF EXISTS users",
"DROP TABLE IF EXISTS friends", "DROP TABLE IF EXISTS friends",
"DROP TABLE IF EXISTS users",
"CREATE TABLE users (name varchar(16) NOT NULL, pw_hash varchar(128) NOT NULL, pw_salt varchar(32) NOT NULL, last_seen datetime NOT NULL, elo int NOT NULL, PRIMARY KEY (name)) CHARACTER SET " + settings.db_charset, "CREATE TABLE users (name varchar(16) NOT NULL, pw_hash varchar(128) NOT NULL, pw_salt varchar(32) NOT NULL, last_seen datetime NOT NULL, elo int NOT NULL, PRIMARY KEY (name)) CHARACTER SET " + settings.db_charset,
"CREATE TABLE matches (id varchar(32) NOT NULL, user_a varchar(16) NOT NULL, user_b varchar(16) NOT NULL, match_state varchar(4096) NOT NULL, active_user varchar(16), last_active datetime NOT NULL, FOREIGN KEY (user_a) REFERENCES users(name), FOREIGN KEY (user_b) REFERENCES users(name), FOREIGN KEY (active_user) REFERENCES users(name)) CHARACTER SET " + settings.db_charset, "CREATE TABLE matches (id varchar(32) NOT NULL, user_a varchar(16) NOT NULL, user_b varchar(16) NOT NULL, match_state varchar(4096) NOT NULL, active_user varchar(16), last_active datetime NOT NULL, ranked BOOLEAN NOT NULL) CHARACTER SET " + settings.db_charset,
"CREATE TABLE sessions (id varchar(32) NOT NULL, registered_user varchar(16), temp_user varchar(16), last_seen datetime NOT NULL, PRIMARY KEY (id), FOREIGN KEY(registered_user) REFERENCES users(name)) CHARACTER SET " + settings.db_charset, "CREATE TABLE sessions (id varchar(32) NOT NULL, registered_user varchar(16), temp_user varchar(16), last_seen datetime NOT NULL, PRIMARY KEY (id), FOREIGN KEY(registered_user) REFERENCES users(name)) CHARACTER SET " + settings.db_charset,
"CREATE TABLE friends (user varchar(16) NOT NULL, friend varchar(16) NOT NULL, FOREIGN KEY(user) REFERENCES users(name), FOREIGN KEY(friend) REFERENCES users(name)) CHARACTER SET " + settings.db_charset "CREATE TABLE friends (user varchar(16) NOT NULL, friend varchar(16) NOT NULL, FOREIGN KEY(user) REFERENCES users(name), FOREIGN KEY(friend) REFERENCES users(name)) CHARACTER SET " + settings.db_charset
] ]

View File

@ -15,7 +15,7 @@ import datetime
from tools import debug from tools import debug
um = UserManager(account_revoke_time) um = UserManager(account_revoke_time)
sm = SessionManager(session_revove_time) sm = SessionManager(session_revove_time, temporary_session_revoke_time)
mm = MatchManager(um, match_revoke_time) mm = MatchManager(um, match_revoke_time)
ch = ConnectionHandler(sm, um, mm, revoke_check_interval) ch = ConnectionHandler(sm, um, mm, revoke_check_interval)

View File

@ -38,7 +38,7 @@ class Match(object):
FIELD_USER_B = 2 FIELD_USER_B = 2
FIELD_DRAW = 3 FIELD_DRAW = 3
def __init__(self, n, match_id, revoke_time, player_a_name, player_b_name, json_state=None): def __init__(self, n, match_id, revoke_time, player_a_name, player_b_name, json_state=None, ranked=False):
self.n = n self.n = n
self.id = match_id self.id = match_id
self.complete_field = np.zeros(shape=(n*n, n*n), dtype=int) self.complete_field = np.zeros(shape=(n*n, n*n), dtype=int)
@ -51,6 +51,7 @@ class Match(object):
self.player_a_name = player_a_name self.player_a_name = player_a_name
self.player_b_name = player_b_name self.player_b_name = player_b_name
self.revoke_time = revoke_time self.revoke_time = revoke_time
self.ranked = ranked
if json_state is not None: if json_state is not None:
self.from_json_state(json_state) self.from_json_state(json_state)

View File

@ -22,7 +22,7 @@ class MatchManager(object):
revoke_time = result[0]['last_active'] + self.match_lifespan_timedelta revoke_time = result[0]['last_active'] + self.match_lifespan_timedelta
match = Match(n=settings.n, match_id=id, revoke_time=revoke_time, player_a_name=result[0]['user_a'], match = Match(n=settings.n, match_id=id, revoke_time=revoke_time, player_a_name=result[0]['user_a'],
player_b_name=result[0]['user_b'], json_state=result[0]['match_state']) player_b_name=result[0]['user_b'], json_state=result[0]['match_state'], ranked=result[0]['ranked'])
return match return match
def get_matches_for_user(self, user_name: str) -> list: def get_matches_for_user(self, user_name: str) -> list:
@ -35,13 +35,16 @@ class MatchManager(object):
if len(DatabaseConnection.global_single_query("SELECT id FROM matches WHERE id=%s", (match_id))) > 0: if len(DatabaseConnection.global_single_query("SELECT id FROM matches WHERE id=%s", (match_id))) > 0:
return self.create_new_match(user_a, user_b) return self.create_new_match(user_a, user_b)
ranked = len(self.user_manager.get_user(user_a)) > 0 and len(
self.user_manager.get_user(user_b)) > 0
now = datetime.datetime.now() now = datetime.datetime.now()
match = Match(n=settings.n, match_id=match_id, revoke_time=now + self.match_lifespan_timedelta, match = Match(n=settings.n, match_id=match_id, revoke_time=now + self.match_lifespan_timedelta,
player_a_name=user_a, player_b_name=user_b) player_a_name=user_a, player_b_name=user_b, ranked=ranked)
query = "INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active) VALUES (%s, %s, %s, %s, %s,%s)" query = "INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active, ranked) VALUES (%s, %s, %s, %s, %s,%s, %s)"
DatabaseConnection.global_single_execution( DatabaseConnection.global_single_execution(
query, (match_id, user_a, user_b, match.to_json_state(), match.get_current_player(), get_sql_time(now))) query, (match_id, user_a, user_b, match.to_json_state(), match.get_current_player(), get_sql_time(now), ranked))
return match return match
def update_match(self, match_id: str, match: Match, update_in_db=True) -> None: def update_match(self, match_id: str, match: Match, update_in_db=True) -> None:
@ -53,7 +56,7 @@ class MatchManager(object):
query, (match.to_json_state(), match.get_current_player(), now, match_id)) query, (match.to_json_state(), match.get_current_player(), now, match_id))
# check whether we have to update the elo values (game over triggered by the last move) # check whether we have to update the elo values (game over triggered by the last move)
if match.game_over: if match.game_over and match.ranked:
if match.player_won is not None: if match.player_won is not None:
player_won = match.player_won player_won = match.player_won

View File

@ -7,8 +7,9 @@ from tools import debug
class SessionManager(object): class SessionManager(object):
def __init__(self, session_lifespan_timedelta): def __init__(self, session_lifespan_timedelta, temp_session_lifespan_timedelta):
self.session_lifespan_timedelta = session_lifespan_timedelta self.session_lifespan_timedelta = session_lifespan_timedelta
self.temp_session_lifespan_timedelta = temp_session_lifespan_timedelta
def get_session_by_id(self, session_id): def get_session_by_id(self, session_id):
query = "SELECT * FROM sessions WHERE id=%s" query = "SELECT * FROM sessions WHERE id=%s"
@ -16,7 +17,8 @@ class SessionManager(object):
def touch_session(self, session_id): def touch_session(self, session_id):
query = "UPDATE sessions SET last_seen=%s WHERE id=%s" query = "UPDATE sessions SET last_seen=%s WHERE id=%s"
DatabaseConnection.global_single_execution(query, (datetime.datetime.now(), session_id)) DatabaseConnection.global_single_execution(
query, (datetime.datetime.now(), session_id))
def get_session_by_registered_user(self, user_name): def get_session_by_registered_user(self, user_name):
query = "SELECT * FROM sessions WHERE registered_user=%s" query = "SELECT * FROM sessions WHERE registered_user=%s"
@ -38,7 +40,8 @@ class SessionManager(object):
DatabaseConnection.global_single_execution(query, (user_name)) DatabaseConnection.global_single_execution(query, (user_name))
query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, %s, NULL, %s)" query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, %s, NULL, %s)"
DatabaseConnection.global_single_execution(query, (new_id, user_name, datetime.datetime.now())) DatabaseConnection.global_single_execution(
query, (new_id, user_name, datetime.datetime.now()))
return new_id return new_id
@ -54,19 +57,38 @@ class SessionManager(object):
DatabaseConnection.global_single_execution(query, (user_name)) DatabaseConnection.global_single_execution(query, (user_name))
query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, NULL, %s, %s)" query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, NULL, %s, %s)"
DatabaseConnection.global_single_execution(query, (new_id, user_name, datetime.datetime.now())) DatabaseConnection.global_single_execution(
query, (new_id, user_name, datetime.datetime.now()))
return new_id return new_id
def delete_session(self, session_id): def delete_session(self, session_id):
query = "DELETE FROM sessions WHERE id=%s" query = "DELETE FROM sessions WHERE id=%s"
DatabaseConnection.global_single_execution(query, (session_id)) DatabaseConnection.global_single_execution(query, (session_id))
def revoke_inactive_temporary_sessions(self):
revoke_time = datetime.datetime.now() - self.temp_session_lifespan_timedelta
query = "SELECT * from sessions WHERE last_seen < %s AND temp_user IS NOT NULL"
revoked_temp_sessions = DatabaseConnection.global_single_query(
query, (get_sql_time(revoke_time)))
query = "DELETE FROM sessions WHERE last_seen < %s AND temp_user IS NOT NULL"
DatabaseConnection.global_single_execution(
query, (get_sql_time(revoke_time)))
for session in revoked_temp_sessions:
# remove from matches:
query = "DELETE FROM matches WHERE user_a=%s OR user_b=%s"
DatabaseConnection.global_single_execution(
query, (session['temp_user'], session['temp_user']))
debug("delete revoked sessions: " + str(revoked_temp_sessions))
def revoke_inactive_sessions(self): def revoke_inactive_sessions(self):
revoke_time = datetime.datetime.now() - self.session_lifespan_timedelta revoke_time = datetime.datetime.now() - self.session_lifespan_timedelta
query = "SELECT * from sessions WHERE last_seen < %s" query = "SELECT * from sessions WHERE last_seen < %s"
revoked_sessions = DatabaseConnection.global_single_query(query, (get_sql_time(revoke_time))) revoked_sessions = DatabaseConnection.global_single_query(
query, (get_sql_time(revoke_time)))
query = "DELETE FROM sessions WHERE last_seen < %s" query = "DELETE FROM sessions WHERE last_seen < %s"
DatabaseConnection.global_single_execution(query, (get_sql_time(revoke_time))) DatabaseConnection.global_single_execution(
query, (get_sql_time(revoke_time)))
debug("delete revoked sessions: " + str(revoked_sessions)) debug("delete revoked sessions: " + str(revoked_sessions))

View File

@ -61,8 +61,6 @@ class UserManager(object):
query, (user_name, pw_hash, pw_salt, datetime.datetime.now(), elo_start_value)) query, (user_name, pw_hash, pw_salt, datetime.datetime.now(), elo_start_value))
def touch_user(self, user_name): def touch_user(self, user_name):
matches = self.get_user(user_name)
assert len(matches) == 1
query = "UPDATE users SET last_seen=%s WHERE name=%s" query = "UPDATE users SET last_seen=%s WHERE name=%s"
DatabaseConnection.global_single_execution( DatabaseConnection.global_single_execution(
query, (datetime.datetime.now(), user_name)) query, (datetime.datetime.now(), user_name))