from extensions import db, login_manager from flask_login import UserMixin from datetime import datetime ROLES = ['president', 'manager', 'coach', 'player', 'scout'] # Popular E-Sports games list ESPORT_GAMES = [ 'Valorant', 'League of Legends', 'Counter-Strike 2', 'Apex Legends', 'Overwatch 2', 'Rainbow Six Siege', 'Fortnite', 'Rocket League', 'Call of Duty', 'Dota 2', 'Super Smash Bros.', 'Street Fighter 6', ] class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) password_hash = db.Column(db.String(128), nullable=False) role = db.Column(db.String(20), nullable=False, default='player') full_name = db.Column(db.String(100), nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) phone = db.Column(db.String(20), nullable=True) team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True) is_active_account = db.Column(db.Boolean, default=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) # 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 evaluations_given = db.relationship('Evaluation', foreign_keys='Evaluation.evaluator_id', backref='evaluator', lazy='dynamic') evaluations_received = db.relationship('Evaluation', foreign_keys='Evaluation.player_id', backref='player', lazy='dynamic') tryout_registrations = db.relationship('TryoutRegistration', backref='player', lazy='dynamic') team_assignments = db.relationship('TeamMember', foreign_keys='TeamMember.player_id', backref='player_ref', lazy='dynamic') def get_games_list(self): """Return games as a list.""" if self.games: return [g.strip() for g in self.games.split(',') if g.strip()] return [] def has_role(self, *roles): return self.role in roles def can_evaluate(self): return self.role in ['president', 'manager', 'coach'] def can_manage_users(self): return self.role == 'president' def can_manage_tryouts(self): return self.role in ['president', 'manager', 'coach', 'scout'] def can_manage_teams(self): return self.role in ['president', 'manager'] def can_schedule_matches(self): return self.role in ['president', 'manager', 'coach'] def can_manage_this_tryout(self, tryout): """Check if user can manage a specific tryout (president, or manager/coach in charge of it).""" if self.role == 'president': return True if self.role == 'manager' and tryout.created_by == self.id: return True if self.role == 'coach': org_team = OrgTeam.query.filter_by(coach_id=self.id).first() if org_team and tryout.target_org_team_id == org_team.id: return True return False def can_manage_this_org_team(self, org_team): """Check if user can manage a specific org team (president, or manager/coach in charge of it).""" if self.role == 'president': return True if self.role == 'manager': return True # Managers can manage all org teams (create/edit/delete) if self.role == 'coach' and org_team.coach_id == self.id: return True return False @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) class OrgTeam(db.Model): """Persistent organization teams (e.g., Varsity, JV) that exist across tryouts.""" __tablename__ = 'org_teams' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False, unique=True) coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) coach = db.relationship('User', foreign_keys=[coach_id], backref='coached_org_team', uselist=False) creator = db.relationship('User', foreign_keys=[created_by]) players = db.relationship('User', foreign_keys='User.team_id', backref='org_team', lazy='dynamic') class Tryout(db.Model): __tablename__ = 'tryouts' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) description = db.Column(db.Text, nullable=True) date = db.Column(db.Date, nullable=False) location = db.Column(db.String(200), nullable=True) status = db.Column(db.String(20), default='upcoming') # upcoming, in_progress, completed max_players = db.Column(db.Integer, nullable=True) created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) target_org_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) creator = db.relationship('User', backref='created_tryouts') registrations = db.relationship('TryoutRegistration', backref='tryout', lazy='dynamic') evaluations = db.relationship('Evaluation', backref='tryout', lazy='dynamic') teams = db.relationship('Team', backref='tryout', lazy='dynamic') target_org_team = db.relationship('OrgTeam', backref='tryouts', foreign_keys=[target_org_team_id]) class TryoutRegistration(db.Model): __tablename__ = 'tryout_registrations' id = db.Column(db.Integer, primary_key=True) tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False) player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) registered_at = db.Column(db.DateTime, default=datetime.utcnow) status = db.Column(db.String(20), default='registered') # registered, attended, no_show notes = db.Column(db.Text, nullable=True) class Evaluation(db.Model): __tablename__ = 'evaluations' id = db.Column(db.Integer, primary_key=True) tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False) player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) evaluator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) speed_score = db.Column(db.Integer, nullable=True) agility_score = db.Column(db.Integer, nullable=True) technique_score = db.Column(db.Integer, nullable=True) teamwork_score = db.Column(db.Integer, nullable=True) attitude_score = db.Column(db.Integer, nullable=True) overall_score = db.Column(db.Float, nullable=True) comments = db.Column(db.Text, nullable=True) position_recommendation = db.Column(db.String(50), nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) __table_args__ = ( db.UniqueConstraint('tryout_id', 'player_id', 'evaluator_id', name='unique_evaluation'), ) class Team(db.Model): """Tryout-specific teams (e.g., Alpha, Bravo within a single tryout).""" __tablename__ = 'teams' id = db.Column(db.Integer, primary_key=True) tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False) name = db.Column(db.String(100), nullable=False) created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) creator = db.relationship('User', backref='created_teams') members = db.relationship('TeamMember', backref='team', lazy='dynamic') class TeamMember(db.Model): __tablename__ = 'team_members' id = db.Column(db.Integer, primary_key=True) team_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=False) player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) position = db.Column(db.String(50), nullable=True) added_at = db.Column(db.DateTime, default=datetime.utcnow) player = db.relationship('User', overlaps="player_ref,team_assignments") # Many-to-one, no dynamic loader class Match(db.Model): """Matches/scrimmages scheduled within tryouts.""" __tablename__ = 'matches' id = db.Column(db.Integer, primary_key=True) tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False) title = db.Column(db.String(200), nullable=False) description = db.Column(db.Text, nullable=True) date = db.Column(db.Date, nullable=False) start_time = db.Column(db.Time, nullable=True) end_time = db.Column(db.Time, nullable=True) location = db.Column(db.String(200), nullable=True) status = db.Column(db.String(20), default='scheduled') # scheduled, completed, cancelled match_type = db.Column(db.String(20), nullable=False) # team_vs_team, player_scrim created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) # For team vs team matches team1_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=True) team2_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=True) creator = db.relationship('User', backref='created_matches') tryout = db.relationship('Tryout', backref='matches') team1 = db.relationship('Team', foreign_keys=[team1_id], backref='matches_as_team1') team2 = db.relationship('Team', foreign_keys=[team2_id], backref='matches_as_team2') participants = db.relationship('MatchParticipant', backref='match', lazy='dynamic') def get_participating_players(self): """Return list of players participating in this match.""" return [p.player_id for p in self.participants.all()] class PlayerDisponibility(db.Model): """Player availability in 30-minute time blocks for scheduling matches.""" __tablename__ = 'player_disponibilities' id = db.Column(db.Integer, primary_key=True) player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) day_of_week = db.Column(db.Integer, nullable=False) # 0=Monday, 6=Sunday start_time = db.Column(db.Time, nullable=False) end_time = db.Column(db.Time, nullable=False) # Always 30 minutes after start_time created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) player = db.relationship('User', backref='disponibilities') class MatchParticipant(db.Model): """Players participating in player scrimmage matches.""" __tablename__ = 'match_participants' id = db.Column(db.Integer, primary_key=True) match_id = db.Column(db.Integer, db.ForeignKey('matches.id'), nullable=False) player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) team_side = db.Column(db.Integer, nullable=True) # 1 for team 1, 2 for team 2 (for player_vs_player matches) position = db.Column(db.String(50), nullable=True) # Position for this match added_at = db.Column(db.DateTime, default=datetime.utcnow) player = db.relationship('User')