diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7783289 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# 1. Datenbanken +*.db +*.sqlite3 + +# 2. Python Caches +__pycache__/ +*.py[cod] + +# 3. Virtuelle Umgebungen +venv/ +.venv/ +env/ + +# 4. Geheime Konfigurationen (Umgebungsvariablen) +.env + +# 5. IDE / Editor Einstellungen +.vscode/ +.idea/ diff --git a/.nicegui/storage-user-83ffc178-0f94-4ada-8ca6-1c51b99b4b9c.json b/.nicegui/storage-user-83ffc178-0f94-4ada-8ca6-1c51b99b4b9c.json new file mode 100644 index 0000000..69d47aa --- /dev/null +++ b/.nicegui/storage-user-83ffc178-0f94-4ada-8ca6-1c51b99b4b9c.json @@ -0,0 +1 @@ +{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":1} \ No newline at end of file diff --git a/database.py b/database.py new file mode 100644 index 0000000..a7727ff --- /dev/null +++ b/database.py @@ -0,0 +1,91 @@ +import sqlite3 + +def init_db(): + # Neue englische Datenbank-Datei + connection = sqlite3.connect("warhammer_league.db") + cursor = connection.cursor() + + # Fremdschlüssel (Foreign Keys) in SQLite aktivieren + cursor.execute('PRAGMA foreign_keys = ON;') + + # Tabelle: players (ehemals spieler) + cursor.execute(''' + CREATE TABLE IF NOT EXISTS players ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + discord_id TEXT UNIQUE, + name TEXT NOT NULL, + mmr INTEGER DEFAULT 1000, + points INTEGER DEFAULT 0, + games INTEGER DEFAULT 0 + ) + ''') + + # Tabelle: matches (ehemals spiele) + cursor.execute(''' + CREATE TABLE IF NOT EXISTS matches ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + player1_id INTEGER, + score_player1 INTEGER, + player2_id INTEGER, + score_player2 INTEGER, + FOREIGN KEY (player1_id) REFERENCES players (id), + FOREIGN KEY (player2_id) REFERENCES players (id) + ) + ''') + + connection.commit() + connection.close() + + print("Warhammer database successfully initialized!") + +def get_or_create_player(discord_id, discord_name): + # 1. Verbindung öffnen + connection = sqlite3.connect("warhammer_league.db") + cursor = connection.cursor() + + # 2. Suchen, ob der Spieler schon existiert + cursor.execute("SELECT id, name, mmr, points, games FROM players WHERE discord_id = ?", (discord_id,)) + player = cursor.fetchone() + + # 3. Wenn der Spieler nicht gefunden wurde (ist "None") + if player is None: + # Neu anlegen + cursor.execute("INSERT INTO players (discord_id, name) VALUES (?, ?)", (discord_id, discord_name)) + connection.commit() + + # Den frisch angelegten Spieler direkt wieder auslesen, um seine neue ID zu bekommen + cursor.execute("SELECT id, name, mmr, points, games FROM players WHERE discord_id = ?", (discord_id,)) + player = cursor.fetchone() + + # 4. Verbindung schließen + connection.close() + + # 5. Daten zurückgeben + return player + +if __name__ == "__main__": + init_db() + + +def get_all_players(): + connection = sqlite3.connect("warhammer_league.db") + cursor = connection.cursor() + + # Alle Spieler laden, absteigend sortiert nach MMR + cursor.execute("SELECT id, name, mmr, points, games FROM players ORDER BY mmr DESC") + players = cursor.fetchall() + + connection.close() + + # Die Daten für das Web-GUI in eine lesbare Form umwandeln (Liste von Dictionaries) + result = [] + for player in players: + result.append({ + 'id': player[0], + 'name': player[1], + 'mmr': player[2], + 'points': player[3], + 'games': player[4] + }) + + return result diff --git a/gui/main_gui.py b/gui/main_gui.py index e69de29..dab3685 100644 --- a/gui/main_gui.py +++ b/gui/main_gui.py @@ -0,0 +1,110 @@ +import os +import urllib.parse +import requests +from nicegui import ui, app + +# NEU: Wir importieren unsere eigene Datenbank-Datei +import database + +def setup_routes(): + client_id = os.getenv("DISCORD_CLIENT_ID") + client_secret = os.getenv("DISCORD_CLIENT_SECRET") + app_url = os.getenv("APP_URL") + + redirect_uri = f"{app_url}/login/discord" + encoded_redirect_uri = urllib.parse.quote(redirect_uri, safe="") + + discord_auth_url = f"https://discord.com/api/oauth2/authorize?client_id={client_id}&redirect_uri={encoded_redirect_uri}&response_type=code&scope=identify" + + # --- HOME PAGE --- + @ui.page('/') + + def home_page(): + ui.dark_mode(True) + with ui.card().classes('w-full items-center mt-10'): + ui.label('Warhammer League').classes('text-2xl font-bold') + + # --- LOGIN BEREICH --- + if app.storage.user.get('authenticated', False): + discord_name = app.storage.user.get('discord_name') + db_id = app.storage.user.get('db_id') + + ui.label(f'Welcome back, {discord_name}!').classes('text-green-500 font-bold') + + ui.button('Enter Match', on_click=lambda: ui.navigate.to('/add-match')).classes('mt-4 bg-blue-500 text-white') + + def logout(): + app.storage.user.clear() + ui.navigate.to('/') + + ui.button('Logout', on_click=logout).classes('mt-4 bg-red-500 text-white') + else: + ui.button('Login with Discord', on_click=lambda: ui.navigate.to(discord_auth_url)) + + # --- LEADERBOARD BEREICH --- + with ui.card().classes('w-full items-center mt-6'): + ui.label('Leaderboard').classes('text-xl font-bold mb-4') + + # 1. Spalten (Columns) für die Tabelle definieren + table_columns = [ + {'name': 'name', 'label': 'Player', 'field': 'name', 'align': 'left'}, + {'name': 'mmr', 'label': 'MMR', 'field': 'mmr', 'sortable': True}, + {'name': 'points', 'label': 'Points', 'field': 'points', 'sortable': True}, + {'name': 'games', 'label': 'Games Played', 'field': 'games', 'sortable': True}, + ] + + # 2. Daten (Rows) aus der Datenbank holen + player_data = database.get_all_players() + + # 3. Tabelle zeichnen + ui.table(columns=table_columns, rows=player_data, row_key='name').classes('w-full max-w-4xl') + + + + # --- DISCORD CALLBACK PAGE --- + @ui.page('/login/discord') + def discord_callback(code: str = None): + if not code: + ui.label('Fehler: Kein Code erhalten.').classes('text-red-500') + return + + token_data = { + 'client_id': client_id, + 'client_secret': client_secret, + 'grant_type': 'authorization_code', + 'code': code, + 'redirect_uri': redirect_uri + } + + token_response = requests.post('https://discord.com/api/oauth2/token', data=token_data) + token_json = token_response.json() + + if 'access_token' in token_json: + access_token = token_json['access_token'] + + user_headers = { + 'Authorization': f"Bearer {access_token}" + } + user_response = requests.get('https://discord.com/api/users/@me', headers=user_headers) + user_json = user_response.json() + + # --- NEU: DATENBANK INTEGRATION --- + discord_id = user_json['id'] + discord_name = user_json['username'] + + # 1. Spieler in der DB suchen oder neu anlegen + player = database.get_or_create_player(discord_id, discord_name) + + # 2. Session-Daten aktualisieren + app.storage.user['authenticated'] = True + app.storage.user['discord_id'] = discord_id + app.storage.user['discord_name'] = discord_name + + # player ist ein Tuple aus der DB, z.B.: (1, "123456", "Daniel", 1000, 0, 0) + # player[0] ist die interne Datenbank-ID. Diese merken wir uns in der Session! + app.storage.user['db_id'] = player[0] + + ui.navigate.to('/') + else: + ui.label('Fehler beim Login!').classes('text-red-500') + ui.label(str(token_json)) diff --git a/gui/match_gui.py b/gui/match_gui.py new file mode 100644 index 0000000..744c975 --- /dev/null +++ b/gui/match_gui.py @@ -0,0 +1,23 @@ +from nicegui import ui, app + +def setup_match_routes(): + + # Unsere neue Unterseite für das Eintragen der Spiele + @ui.page('/add-match') + def add_match_page(): + with ui.card().classes('w-full items-center mt-10'): + ui.label('Enter New Match').classes('text-2xl font-bold') + + # --- SICHERHEITS-CHECK --- + # Prüfen, ob der User wirklich eingeloggt ist. + # Wenn nicht, brechen wir hier sofort ab! + if not app.storage.user.get('authenticated', False): + ui.label('Access Denied. Please log in first.').classes('text-red-500') + ui.button('Back to Home', on_click=lambda: ui.navigate.to('/')) + return + + # --- PLATZHALTER --- + ui.label('Hier kommt im nächsten Schritt das Eingabe-Formular hin!').classes('text-gray-500 my-4') + + # Ein Button, um wieder zurück zur Startseite zu kommen + ui.button('Cancel', on_click=lambda: ui.navigate.to('/')).classes('bg-gray-500 text-white') diff --git a/main.py b/main.py index e69de29..45485c2 100644 --- a/main.py +++ b/main.py @@ -0,0 +1,19 @@ +import os +from dotenv import load_dotenv +from nicegui import ui +from gui import main_gui +from gui import match_gui + +# 1. Lade die geheimen Variablen aus der .env Datei in den Speicher +load_dotenv() + +# 2. Variablen abrufen +client_id = os.getenv("DISCORD_CLIENT_ID") +client_secret = os.getenv("DISCORD_CLIENT_SECRET") + +# 3. Wir rufen unsere Funktion aus der main_gui.py auf +main_gui.setup_routes() +match_gui.setup_match_routes() + +# 4. Wir starten die NiceGUI App +ui.run(title="Warhammer Liga", port=9000, storage_secret="ein_sehr_geheimes_passwort_fuer_die_cookies")