from math import floor from app import app, db from pandas import read_csv from dataclasses import dataclass from sqlalchemy.orm import Mapped from datetime import date, datetime from random import randint, getrandbits from typing import Tuple, Optional, Sequence from sqlalchemy.orm import mapped_column as mc from names import get_first_name, get_last_name from sqlalchemy import String, Boolean, Integer, ForeignKey, select class User(db.Model): id: Mapped[int] = mc(primary_key=True) username: Mapped[str] = mc(String(64), index=True, unique=True) password_hash: Mapped[Optional[str]] = mc(String(256)) def __repr__(self): return f"User {self.username}" class Court(db.Model): id: Mapped[int] = mc(primary_key=True) status: Mapped[bool] = mc(Boolean, default=True) def __repr__(self): return f"Court {self.id} is {"active" if self.status else "inactive"}" class Courts: def __init__(self): with app.app_context(): if app.config["COURT_COUNT"] is not None: db.session.query(Court).delete() for i in range(int(app.config["COURT_COUNT"])): db.session.add(Court()) db.session.commit() def get(self, id: int) -> bool: court = db.session.get(Court, id) if court is None: raise ValueError(f"Court {id} doesn't exist.") return court.status def toggle(self, id: int) -> bool: court = db.session.get(Court, id) if court is None: raise ValueError(f"Court {id} doesn't exist.") new_status = not court.status court.status = new_status db.session.commit() return new_status def active(self): return db.session.query(Court).where(Court.status).all() def all(self) -> list[Court]: courts = [court for court in db.session.query(Court).order_by(Court.id)] return courts def reset(self): for court in db.session.scalars(select(Court)): court.status = True db.session.commit() class Player(db.Model): id: Mapped[int] = mc(primary_key=True) first_name: Mapped[str] = mc(String(64), index=True, default="---") last_name: Mapped[str] = mc(String(64), index=True, default="---") team: Mapped[int] = mc(Integer, index=True) status: Mapped[bool] = mc(Boolean) level: Mapped[int] = mc(Integer) def __init__( self, id, first_name, last_name, team=0, status=False, level=int(app.config["MIN_PLAYER_LEVEL"]), ): self.id = id self.first_name = first_name self.last_name = last_name self.team = team self.status = status self.level = level def __repr__(self): return f"{self.first_name} {self.last_name} is {"active" if self.status else "inactive"}" def initialize_random_database( player_count: int = 80, guest_per_level: int = 4 ) -> None: db.session.add(Player(**{"id": 0, "first_name": "---", "last_name": ""})) db.session.add(Player(**{"id": 1, "first_name": "RESERVED", "last_name": ""})) total_teams = 5 players_per_team = 5 for i in range(player_count): if i < total_teams * players_per_team: team = floor(i / players_per_team) + 1 else: team = 0 args = { "first_name": get_first_name(), "last_name": get_last_name(), "team": team, "status": bool(getrandbits(1)), "level": randint( app.config["MIN_PLAYER_LEVEL"], app.config["MAX_PLAYER_LEVEL"] ), } db.session.add(Player(**args)) meta_data = {"first_name": "Guest", "team": 0, "status": False} for level_id, level in enumerate(app.config["GUEST_LEVELS"]): for player_id in range(guest_per_level): unique_id = (level_id * guest_per_level) + player_id + 1 args = meta_data | {"last_name": str(unique_id), "level": level} db.session.add(Player(**args)) db.session.commit() class Players: def __init__(self) -> None: with app.app_context(): if app.config["DATABASE_CSV"] is not None: data = read_csv(app.config["DATABASE_CSV"]) db.session.query(Player).delete() db.session.add( Player(**{"id": 0, "first_name": "---", "last_name": ""}) ) db.session.add( Player(**{"id": 1, "first_name": "RESERVED", "last_name": ""}) ) for entry in data.to_dict("records"): args = entry | {"status": False} db.session.add(Player(**args)) # type: ignore db.session.commit() else: if len(self.all()) == 0: initialize_random_database() def default_id(self): query = select(Player).where(Player.first_name == "---") player = db.session.scalars(query).first() if player is None: raise ValueError(f"Database isn't initialized.") return player.id def reserved_id(self): query = select(Player).where(Player.first_name == "RESERVED") player = db.session.scalars(query).first() if player is None: raise ValueError(f"Database isn't initialized.") return player.id def get(self, id: int) -> bool: player = db.session.get(Player, id) if player is None: raise ValueError(f"User {id} doesn't exist.") return player.status def toggle(self, id: int) -> Player: player = db.session.get(Player, id) if player is None: raise ValueError(f"User {id} doesn't exist.") player.status = not player.status db.session.commit() return player def all(self) -> Tuple[list[Player], list[Player]]: players = ( db.session.query(Player) .where(Player.id > 1) .order_by(Player.first_name, Player.last_name) ) regulars = [p for p in players if p.first_name != "Guest"] guests = [p for p in players if p.first_name == "Guest"] guests.sort(key=lambda x: int(x.last_name)) return regulars, guests def active(self) -> Sequence[Player]: query = ( select(Player) .where(Player.status) .order_by(Player.first_name, Player.last_name) ) return db.session.scalars(query).all() def reset(self): for player in db.session.scalars(select(Player)): player.status = False db.session.commit() class Game(db.Model): id: Mapped[int] = mc(primary_key=True) round_id: Mapped[int] = mc(Integer, index=True) court_id: Mapped[int] = mc(ForeignKey(Court.id), index=True) player_1: Mapped[int] = mc(ForeignKey(Player.id), index=True) player_2: Mapped[int] = mc(ForeignKey(Player.id), index=True) player_3: Mapped[int] = mc(ForeignKey(Player.id), index=True) player_4: Mapped[int] = mc(ForeignKey(Player.id), index=True) date: Mapped[datetime] = mc(index=True, default=lambda: date.today()) def __init__( self, round_id: int, court_id: int, player_1: int, player_2: int, player_3: int, player_4: int, ): self.round_id = round_id self.court_id = court_id self.player_1 = player_1 self.player_2 = player_2 self.player_3 = player_3 self.player_4 = player_4 class DisplayPlayer: def __init__(self, player: Player): self.id = player.id self.first_name = player.first_name self.last_name = player.last_name @dataclass class DisplayTeam: player_1: DisplayPlayer player_2: DisplayPlayer @dataclass class DisplayGame: court_id: int team_1: DisplayTeam team_2: DisplayTeam