diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index 4e4b009..f4abcd0 100644 Binary files a/__pycache__/models.cpython-313.pyc and b/__pycache__/models.cpython-313.pyc differ diff --git a/__pycache__/seed.cpython-313.pyc b/__pycache__/seed.cpython-313.pyc index 4c9bade..2a8e047 100644 Binary files a/__pycache__/seed.cpython-313.pyc and b/__pycache__/seed.cpython-313.pyc differ diff --git a/instance/team_tryouts.db b/instance/team_tryouts.db index 3aee324..f3d14f2 100644 Binary files a/instance/team_tryouts.db and b/instance/team_tryouts.db differ diff --git a/models.py b/models.py index 4de4e87..5ef6c63 100644 --- a/models.py +++ b/models.py @@ -1,6 +1,7 @@ from extensions import db, login_manager from flask_login import UserMixin from datetime import datetime +from urllib.parse import quote ROLES = ['president', 'manager', 'coach', 'player', 'scout'] @@ -35,7 +36,6 @@ class User(UserMixin, db.Model): # E-Sports specific fields games = db.Column(db.Text, nullable=True) # Comma-separated list of games - trn_username = db.Column(db.String(120), nullable=True) # Tracker Network username discord_username = db.Column(db.String(100), nullable=True) # Discord handle league_os_profile = db.Column(db.String(255), nullable=True) # League OS profile URL or ID @@ -90,12 +90,97 @@ class User(UserMixin, db.Model): return True return False + def get_gamertags(self): + """Return gamertags as a dict keyed by game.""" + return {gt.game: {'gamertag': gt.gamertag, 'platform': gt.platform} for gt in self.gamertags} + @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) +# Platform options for games that require platform specification +GAME_PLATFORMS = { + 'Valorant': [], # No platform needed + 'League of Legends': [], + 'Counter-Strike 2': [], + 'Apex Legends': ['PC', 'PlayStation', 'Xbox', 'Nintendo Switch'], + 'Overwatch 2': ['PC', 'PlayStation', 'Xbox', 'Nintendo Switch'], + 'Rainbow Six Siege': ['Ubisoft', 'PlayStation', 'Xbox'], # Ubisoft = Uplay/Steam + 'Fortnite': ['PC', 'PlayStation', 'Xbox', 'Nintendo Switch', 'Mobile'], +'Rocket League': ['Epic', 'PlayStation', 'Xbox', 'Nintendo Switch'], # Epic uses EpicID, others use username + 'Call of Duty': ['PC', 'PlayStation', 'Xbox'], + 'Dota 2': [], + 'Super Smash Bros.': ['Nintendo Switch'], + 'Street Fighter 6': ['PC', 'PlayStation', 'Xbox'], +} + + +# Platform code mapping for TRN URLs (platform -> TRN code) +PLATFORM_CODES = { + 'Ubisoft': 'ubi', + 'PlayStation': 'psn', + 'Xbox': 'xbl', + 'Nintendo Switch': 'switch', + 'PC': 'pc', + 'Mobile': 'mobile', + 'Steam': 'steam', + 'Epic': 'epic', +} + +# TRN URL mapping for each game +TRN_URLS = { + 'Valorant': 'https://tracker.gg/valorant/profile/riot/{username}', + 'League of Legends': 'https://tracker.gg/lol/profile/{username}', + 'Counter-Strike 2': 'https://tracker.gg/cs2/profile/steam/{username}', + 'Apex Legends': 'https://tracker.gg/apex/profile/{platform}/{username}', + 'Overwatch 2': 'https://tracker.gg/overwatch/profile/{platform}/{username}', + 'Rainbow Six Siege': 'https://r6.tracker.network/r6siege/profile/{platform_code}/{username}', + 'Fortnite': 'https://tracker.gg/fortnite/profile/{platform}/{username}', + 'Rocket League': 'https://rocketleague.tracker.network/rocket-league/profile/{platform_code}/{username}', + 'Call of Duty': 'https://tracker.gg/call-of-duty/profile/{platform}/{username}', + 'Dota 2': 'https://tracker.gg/dota2/profile/steam/{username}', + 'Super Smash Bros.': 'https://tracker.gg/smash/profile/{username}', + 'Street Fighter 6': 'https://tracker.gg/streetfighter/profile/{username}', +} + + +class UserGamertag(db.Model): + """Store gamertag per game for each user.""" + __tablename__ = 'user_gamertags' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + game = db.Column(db.String(50), nullable=False) + gamertag = db.Column(db.String(120), nullable=False) + platform = db.Column(db.String(30), nullable=True) # For games that need platform (e.g., PSN, Xbox Live) + + user = db.relationship('User', backref='gamertags') + + __table_args__ = ( + db.UniqueConstraint('user_id', 'game', name='unique_user_game'), + ) + + def get_trn_url(self): + """Generate the TRN URL for this gamertag.""" + if self.game not in TRN_URLS: + return None + + url = TRN_URLS[self.game] + # URL-encode the gamertag to handle special characters like #, spaces, etc. + encoded_gamertag = quote(self.gamertag, safe='') + + # Check for platform_code placeholder (used for R6 and Rocket League) + if '{platform_code}' in url and '{username}' in url: + platform_code = PLATFORM_CODES.get(self.platform, self.platform.lower().replace(' ', '-')) if self.platform else '' + return url.format(platform_code=platform_code, username=encoded_gamertag) + elif '{platform}' in url and '{username}' in url: + return url.format(platform=self.platform.lower().replace(' ', '-'), username=encoded_gamertag) + elif '{username}' in url: + return url.format(username=encoded_gamertag) + return url + + class OrgTeam(db.Model): """Persistent organization teams (e.g., Varsity, JV) that exist across tryouts.""" __tablename__ = 'org_teams' diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index dfbe9b2..00814b3 100644 Binary files a/routes/__pycache__/main.cpython-313.pyc and b/routes/__pycache__/main.cpython-313.pyc differ diff --git a/routes/__pycache__/matches.cpython-313.pyc b/routes/__pycache__/matches.cpython-313.pyc index 36f6716..f836925 100644 Binary files a/routes/__pycache__/matches.cpython-313.pyc and b/routes/__pycache__/matches.cpython-313.pyc differ diff --git a/routes/__pycache__/tryouts.cpython-313.pyc b/routes/__pycache__/tryouts.cpython-313.pyc index b059970..c8536fd 100644 Binary files a/routes/__pycache__/tryouts.cpython-313.pyc and b/routes/__pycache__/tryouts.cpython-313.pyc differ diff --git a/routes/__pycache__/users.cpython-313.pyc b/routes/__pycache__/users.cpython-313.pyc index a0aac6d..34d7a03 100644 Binary files a/routes/__pycache__/users.cpython-313.pyc and b/routes/__pycache__/users.cpython-313.pyc differ diff --git a/routes/users.py b/routes/users.py index 0afe1cc..c21e8ad 100644 --- a/routes/users.py +++ b/routes/users.py @@ -1,7 +1,7 @@ from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify from flask_login import login_required, current_user from extensions import db, hash_password -from models import User, ROLES, ESPORT_GAMES, PlayerDisponibility +from models import User, ROLES, ESPORT_GAMES, PlayerDisponibility, UserGamertag, GAME_PLATFORMS from datetime import datetime, timedelta users_bp = Blueprint('users', __name__, url_prefix='/users') @@ -34,10 +34,9 @@ def edit_user(user_id): if role not in ROLES: flash('Invalid role selected.', 'danger') - return render_template('pages/edit_user.html', user=user, roles=ROLES, esport_games=ESPORT_GAMES) + return render_template('pages/edit_user.html', user=user, roles=ROLES, esport_games=ESPORT_GAMES, game_platforms=GAME_PLATFORMS) selected_games = request.form.getlist('games') - trn_username = request.form.get('trn_username', '').strip() discord_username = request.form.get('discord_username', '').strip() league_os_profile = request.form.get('league_os_profile', '').strip() @@ -47,10 +46,33 @@ def edit_user(user_id): user.role = role user.is_active_account = is_active user.games = ','.join(selected_games) if selected_games else None - user.trn_username = trn_username or None user.discord_username = discord_username or None user.league_os_profile = league_os_profile or None + # Handle gamertags - save or delete based on form input + # First, get all existing gamertags for this user + existing_gamertags = {gt.game: gt for gt in user.gamertags} + + for game in selected_games: + gamertag = request.form.get(f'gamertag_{game}', '').strip() + platform = request.form.get(f'platform_{game}', '').strip() if GAME_PLATFORMS.get(game) else None + + existing = existing_gamertags.get(game) + if gamertag: + if existing: + existing.gamertag = gamertag + existing.platform = platform + else: + gt = UserGamertag(user_id=user.id, game=game, gamertag=gamertag, platform=platform) + db.session.add(gt) + elif existing: + db.session.delete(existing) + + # Remove gamertags for games that are no longer selected + for game in existing_gamertags: + if game not in selected_games: + db.session.delete(existing_gamertags[game]) + password = request.form.get('password') if password: user.password_hash = hash_password(password) @@ -59,7 +81,8 @@ def edit_user(user_id): flash(f'User {user.full_name} updated successfully!', 'success') return redirect(url_for('users.list_users')) - return render_template('pages/edit_user.html', user=user, roles=ROLES, esport_games=ESPORT_GAMES) + user_gamertags = {gt.game: {'gamertag': gt.gamertag, 'platform': gt.platform} for gt in user.gamertags} + return render_template('pages/edit_user.html', user=user, roles=ROLES, esport_games=ESPORT_GAMES, game_platforms=GAME_PLATFORMS, user_gamertags=user_gamertags) @users_bp.route('//delete', methods=['POST']) @login_required @@ -135,22 +158,44 @@ def edit_profile(): phone = request.form.get('phone') selected_games = request.form.getlist('games') - trn_username = request.form.get('trn_username', '').strip() discord_username = request.form.get('discord_username', '').strip() league_os_profile = request.form.get('league_os_profile', '').strip() if email != current_user.email and User.query.filter_by(email=email).first(): flash('Email already in use.', 'danger') - return render_template('pages/edit_profile.html', user=current_user, esport_games=ESPORT_GAMES) + return render_template('pages/edit_profile.html', user=current_user, esport_games=ESPORT_GAMES, game_platforms=GAME_PLATFORMS) current_user.full_name = full_name current_user.email = email current_user.phone = phone current_user.games = ','.join(selected_games) if selected_games else None - current_user.trn_username = trn_username or None current_user.discord_username = discord_username or None current_user.league_os_profile = league_os_profile or None + # Handle gamertags - save or delete based on form input + # First, get all existing gamertags for this user + existing_gamertags = {gt.game: gt for gt in current_user.gamertags} + + for game in selected_games: + gamertag = request.form.get(f'gamertag_{game}', '').strip() + platform = request.form.get(f'platform_{game}', '').strip() if GAME_PLATFORMS.get(game) else None + + existing = existing_gamertags.get(game) + if gamertag: + if existing: + existing.gamertag = gamertag + existing.platform = platform + else: + gt = UserGamertag(user_id=current_user.id, game=game, gamertag=gamertag, platform=platform) + db.session.add(gt) + elif existing: + db.session.delete(existing) + + # Remove gamertags for games that are no longer selected + for game in existing_gamertags: + if game not in selected_games: + db.session.delete(existing_gamertags[game]) + password = request.form.get('password') if password: current_user.password_hash = hash_password(password) @@ -159,7 +204,8 @@ def edit_profile(): flash('Profile updated successfully!', 'success') return redirect(url_for('users.profile')) - return render_template('pages/edit_profile.html', user=current_user, esport_games=ESPORT_GAMES) + user_gamertags = {gt.game: {'gamertag': gt.gamertag, 'platform': gt.platform} for gt in current_user.gamertags} + return render_template('pages/edit_profile.html', user=current_user, esport_games=ESPORT_GAMES, game_platforms=GAME_PLATFORMS, user_gamertags=user_gamertags) # Day names for disponibility @@ -332,4 +378,4 @@ def delete_disponibility(disponibility_id): db.session.delete(disponibility) db.session.commit() - return jsonify({'success': True}) + return jsonify({'success': True}) \ No newline at end of file diff --git a/seed.py b/seed.py index 3b05c83..8ce6229 100644 --- a/seed.py +++ b/seed.py @@ -1,6 +1,6 @@ from sqlalchemy import text from extensions import db, hash_password -from models import User, Tryout, TryoutRegistration, Evaluation, Team, TeamMember, OrgTeam, PlayerDisponibility +from models import User, Tryout, TryoutRegistration, Evaluation, Team, TeamMember, OrgTeam, PlayerDisponibility, UserGamertag from datetime import datetime, timedelta, time import random @@ -16,6 +16,7 @@ def seed_database(): db.session.execute(text('DELETE FROM tryout_registrations')) db.session.execute(text('DELETE FROM tryouts')) db.session.execute(text('DELETE FROM org_teams')) + db.session.execute(text('DELETE FROM user_gamertags')) db.session.execute(text('DELETE FROM users')) db.session.commit() @@ -30,25 +31,45 @@ def seed_database(): {'username': 'scout1', 'password': 'password', 'role': 'scout', 'full_name': 'Alex Rivera', 'email': 'alex@teampro.com', 'phone': '555-0107'}, ] - # Create players with E-Sports profile data + # Create players with E-Sports profile data (gamertags per game) player_data = [ - {'username': 'jplayer1', 'full_name': 'James Wilson', 'email': 'james@email.com', 'games': 'Valorant,Counter-Strike 2', 'trn': 'jameswilson_tr', 'discord': 'JamesW#7291', 'league_os': 'https://leagueos.gg/player/jameswilson'}, - {'username': 'jplayer2', 'full_name': 'Emma Garcia', 'email': 'emma@email.com', 'games': 'League of Legends,Valorant', 'trn': 'emmagarcia_gg', 'discord': 'EmmaG#4452', 'league_os': 'https://leagueos.gg/player/emmagarcia'}, - {'username': 'jplayer3', 'full_name': 'Liam Brown', 'email': 'liam@email.com', 'games': 'Apex Legends,Fortnite', 'trn': 'liambrown_fn', 'discord': 'LiamB#8103', 'league_os': 'https://leagueos.gg/player/liambrown'}, - {'username': 'jplayer4', 'full_name': 'Sophia Lee', 'email': 'sophia@email.com', 'games': 'Overwatch 2,Valorant', 'trn': 'sophialee_ow', 'discord': 'SophiaL#3327', 'league_os': 'https://leagueos.gg/player/sophialee'}, - {'username': 'jplayer5', 'full_name': 'Noah Taylor', 'email': 'noah@email.com', 'games': 'Counter-Strike 2,Rainbow Six Siege', 'trn': 'noahtaylor_r6', 'discord': 'NoahT#6614', 'league_os': 'https://leagueos.gg/player/noahtaylor'}, - {'username': 'jplayer6', 'full_name': 'Olivia Martin', 'email': 'olivia@email.com', 'games': 'Rocket League,Fortnite', 'trn': 'oliviamartin_rl', 'discord': 'OliviaM#2298', 'league_os': 'https://leagueos.gg/player/oliviamartin'}, - {'username': 'jplayer7', 'full_name': 'Ethan Clark', 'email': 'ethan@email.com', 'games': 'Valorant,Apex Legends', 'trn': 'ethanclark_val', 'discord': 'EthanC#7743', 'league_os': 'https://leagueos.gg/player/ethanclark'}, - {'username': 'jplayer8', 'full_name': 'Ava White', 'email': 'ava@email.com', 'games': 'League of Legends,Counter-Strike 2', 'trn': 'avawhite_lol', 'discord': 'AvaW#5561', 'league_os': 'https://leagueos.gg/player/avawhite'}, - {'username': 'jplayer9', 'full_name': 'Mason Hall', 'email': 'mason@email.com', 'games': 'Call of Duty,Rocket League', 'trn': 'masonhall_cod', 'discord': 'MasonH#1189', 'league_os': 'https://leagueos.gg/player/masonhall'}, - {'username': 'jplayer10', 'full_name': 'Isabella Adams', 'email': 'isabella@email.com', 'games': 'Overwatch 2,Dota 2', 'trn': 'isabellaadams_ow', 'discord': 'IsabellaA#4437', 'league_os': 'https://leagueos.gg/player/isabellaadams'}, + {'username': 'jplayer1', 'full_name': 'James Wilson', 'email': 'james@email.com', 'games': 'Valorant,Counter-Strike 2', + 'gamertags': {'Valorant': 'jameswilson_val', 'Counter-Strike 2': 'jameswilson_cs'}, + 'discord': 'JamesW#7291', 'league_os': 'https://leagueos.gg/player/jameswilson'}, + {'username': 'jplayer2', 'full_name': 'Emma Garcia', 'email': 'emma@email.com', 'games': 'League of Legends,Valorant', + 'gamertags': {'League of Legends': 'emmagarcia_lol', 'Valorant': 'emmagarcia_val'}, + 'discord': 'EmmaG#4452', 'league_os': 'https://leagueos.gg/player/emmagarcia'}, + {'username': 'jplayer3', 'full_name': 'Liam Brown', 'email': 'liam@email.com', 'games': 'Apex Legends,Fortnite', + 'gamertags': {'Apex Legends': 'liambrown_apex', 'platform': 'PC', 'Fortnite': 'liambrown_fn', 'platform_fn': 'PC'}, + 'discord': 'LiamB#8103', 'league_os': 'https://leagueos.gg/player/liambrown'}, + {'username': 'jplayer4', 'full_name': 'Sophia Lee', 'email': 'sophia@email.com', 'games': 'Overwatch 2,Valorant', + 'gamertags': {'Overwatch 2': 'sophialee_ow', 'platform_ow': 'PC', 'Valorant': 'sophialee_val'}, + 'discord': 'SophiaL#3327', 'league_os': 'https://leagueos.gg/player/sophialee'}, + {'username': 'jplayer5', 'full_name': 'Noah Taylor', 'email': 'noah@email.com', 'games': 'Counter-Strike 2,Rainbow Six Siege', + 'gamertags': {'Counter-Strike 2': 'noahtaylor_cs', 'Rainbow Six Siege': 'noahtaylor_r6', 'platform_r6': 'PC'}, + 'discord': 'NoahT#6614', 'league_os': 'https://leagueos.gg/player/noahtaylor'}, + {'username': 'jplayer6', 'full_name': 'Olivia Martin', 'email': 'olivia@email.com', 'games': 'Rocket League,Fortnite', + 'gamertags': {'Rocket League': 'oliviamartin_rl', 'platform_rl': 'PC', 'Fortnite': 'oliviamartin_fn', 'platform_fn': 'PC'}, + 'discord': 'OliviaM#2298', 'league_os': 'https://leagueos.gg/player/oliviamartin'}, + {'username': 'jplayer7', 'full_name': 'Ethan Clark', 'email': 'ethan@email.com', 'games': 'Valorant,Apex Legends', + 'gamertags': {'Valorant': 'ethanclark_val', 'Apex Legends': 'ethanclark_apex', 'platform_apex': 'PC'}, + 'discord': 'EthanC#7743', 'league_os': 'https://leagueos.gg/player/ethanclark'}, + {'username': 'jplayer8', 'full_name': 'Ava White', 'email': 'ava@email.com', 'games': 'League of Legends,Counter-Strike 2', + 'gamertags': {'League of Legends': 'avawhite_lol', 'Counter-Strike 2': 'avawhite_cs'}, + 'discord': 'AvaW#5561', 'league_os': 'https://leagueos.gg/player/avawhite'}, + {'username': 'jplayer9', 'full_name': 'Mason Hall', 'email': 'mason@email.com', 'games': 'Call of Duty,Rocket League', + 'gamertags': {'Call of Duty': 'masonhall_cod', 'platform_cod': 'PC', 'Rocket League': 'masonhall_rl', 'platform_rl': 'PC'}, + 'discord': 'MasonH#1189', 'league_os': 'https://leagueos.gg/player/masonhall'}, + {'username': 'jplayer10', 'full_name': 'Isabella Adams', 'email': 'isabella@email.com', 'games': 'Overwatch 2,Dota 2', + 'gamertags': {'Overwatch 2': 'isabellaadams_ow', 'platform_ow': 'PC', 'Dota 2': 'isabellaadams_dota'}, + 'discord': 'IsabellaA#4437', 'league_os': 'https://leagueos.gg/player/isabellaadams'}, ] for i, data in enumerate(player_data, start=10): users_data.append({ 'username': data['username'], 'password': 'password', 'role': 'player', 'full_name': data['full_name'], 'email': data['email'], 'phone': f'555-01{i:02d}', - 'games': data['games'], 'trn_username': data['trn'], + 'games': data['games'], 'gamertags': data.get('gamertags', {}), 'discord_username': data['discord'], 'league_os_profile': data['league_os'] }) @@ -63,7 +84,6 @@ def seed_database(): email=data['email'], phone=data.get('phone', ''), games=data.get('games'), - trn_username=data.get('trn_username'), discord_username=data.get('discord_username'), league_os_profile=data.get('league_os_profile') ) @@ -82,6 +102,41 @@ def seed_database(): scout = user_map['scout1'] players = [user_map[f'jplayer{i}'] for i in range(1, 11)] + # Create gamertags for players + gamertag_data = [ + {'user': users[7], 'game': 'Valorant', 'gamertag': 'jameswilson_val'}, + {'user': users[7], 'game': 'Counter-Strike 2', 'gamertag': 'jameswilson_cs'}, + {'user': users[8], 'game': 'League of Legends', 'gamertag': 'emmagarcia_lol'}, + {'user': users[8], 'game': 'Valorant', 'gamertag': 'emmagarcia_val'}, + {'user': users[9], 'game': 'Apex Legends', 'gamertag': 'liambrown_apex', 'platform': 'PC'}, + {'user': users[9], 'game': 'Fortnite', 'gamertag': 'liambrown_fn', 'platform': 'PC'}, + {'user': users[10], 'game': 'Overwatch 2', 'gamertag': 'sophialee_ow', 'platform': 'PC'}, + {'user': users[10], 'game': 'Valorant', 'gamertag': 'sophialee_val'}, + {'user': users[11], 'game': 'Counter-Strike 2', 'gamertag': 'noahtaylor_cs'}, + {'user': users[11], 'game': 'Rainbow Six Siege', 'gamertag': 'noahtaylor_r6', 'platform': 'Ubisoft'}, + {'user': users[12], 'game': 'Rocket League', 'gamertag': 'oliviamartin_rl', 'platform': 'Epic'}, + {'user': users[12], 'game': 'Fortnite', 'gamertag': 'oliviamartin_fn', 'platform': 'PC'}, + {'user': users[13], 'game': 'Valorant', 'gamertag': 'ethanclark_val'}, + {'user': users[13], 'game': 'Apex Legends', 'gamertag': 'ethanclark_apex', 'platform': 'PC'}, + {'user': users[14], 'game': 'League of Legends', 'gamertag': 'avawhite_lol'}, + {'user': users[14], 'game': 'Counter-Strike 2', 'gamertag': 'avawhite_cs'}, + {'user': users[15], 'game': 'Call of Duty', 'gamertag': 'masonhall_cod', 'platform': 'PC'}, + {'user': users[15], 'game': 'Rocket League', 'gamertag': 'masonhall_rl', 'platform': 'Epic'}, + {'user': users[16], 'game': 'Overwatch 2', 'gamertag': 'isabellaadams_ow', 'platform': 'PC'}, + {'user': users[16], 'game': 'Dota 2', 'gamertag': 'isabellaadams_dota'}, + ] + + for gt in gamertag_data: + gamertag = UserGamertag( + user_id=gt['user'].id, + game=gt['game'], + gamertag=gt['gamertag'], + platform=gt.get('platform') + ) + db.session.add(gamertag) + db.session.commit() + print(f"[OK] Created {len(gamertag_data)} user gamertags") + # Create Organization Teams (OrgTeams) org_teams_data = [ {'name': 'Varsity', 'coach': coaches[0], 'creator': president}, diff --git a/templates/pages/edit_profile.html b/templates/pages/edit_profile.html index ca97748..4d43df3 100644 --- a/templates/pages/edit_profile.html +++ b/templates/pages/edit_profile.html @@ -42,17 +42,44 @@ Select all games you're signing in for. -
-
- - - Others can click your TRN to view your stats on tracker.gg. + + +