diff --git a/.nicegui/storage-user-83ffc178-0f94-4ada-8ca6-1c51b99b4b9c.json b/.nicegui/storage-user-83ffc178-0f94-4ada-8ca6-1c51b99b4b9c.json index 9e26dfe..e0bc8fd 100644 --- a/.nicegui/storage-user-83ffc178-0f94-4ada-8ca6-1c51b99b4b9c.json +++ b/.nicegui/storage-user-83ffc178-0f94-4ada-8ca6-1c51b99b4b9c.json @@ -1 +1 @@ -{} \ No newline at end of file +{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":1,"avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"} \ No newline at end of file diff --git a/database.py b/database.py index a314e18..180b221 100644 --- a/database.py +++ b/database.py @@ -86,29 +86,28 @@ def seed_gamesystems(): print("Spielsysteme wurden angelegt!") -def get_or_create_player(discord_id, discord_name): - # 1. Verbindung öffnen + +def get_or_create_player(discord_id, discord_name, avatar_url): 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,)) + # REPARIERT: Wir fragen nur noch id, name und avatar_url aus 'players' ab + cursor.execute("SELECT id, name, avatar_url 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)) + cursor.execute("INSERT INTO players (discord_id, name, avatar_url) VALUES (?, ?, ?)", (discord_id, discord_name, avatar_url)) 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,)) + cursor.execute("SELECT id, name, avatar_url FROM players WHERE discord_id = ?", (discord_id,)) + player = cursor.fetchone() + else: + # Falls sich Name oder Bild auf Discord geändert haben, machen wir ein Update + cursor.execute("UPDATE players SET name = ?, avatar_url = ? WHERE discord_id = ?", (discord_name, avatar_url, discord_id)) + connection.commit() + cursor.execute("SELECT id, name, avatar_url FROM players WHERE discord_id = ?", (discord_id,)) player = cursor.fetchone() - # 4. Verbindung schließen connection.close() - - # 5. Daten zurückgeben return player diff --git a/gui/discord_login.py b/gui/discord_login.py new file mode 100644 index 0000000..b5496bb --- /dev/null +++ b/gui/discord_login.py @@ -0,0 +1,69 @@ + +import os +import urllib.parse +import requests +from nicegui import ui, app +import database + +def get_auth_url(): + client_id = os.getenv("DISCORD_CLIENT_ID") + app_url = os.getenv("APP_URL") + redirect_uri = f"{app_url}/login/discord" + encoded_redirect_uri = urllib.parse.quote(redirect_uri, safe="") + + # NEU: scope=identify%20guilds fragt Profilbild UND Server ab + return f"https://discord.com/api/oauth2/authorize?client_id={client_id}&redirect_uri={encoded_redirect_uri}&response_type=code&scope=identify%20guilds" + +def setup_login_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" + + @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() + + discord_id = user_json['id'] + discord_name = user_json['username'] + + # --- BILD ABFANGEN --- + avatar_hash = user_json.get('avatar') + if avatar_hash: + avatar_url = f"https://cdn.discordapp.com/avatars/{discord_id}/{avatar_hash}.png" + else: + avatar_url = "https://cdn.discordapp.com/embed/avatars/0.png" + + # In die Datenbank eintragen + player = database.get_or_create_player(discord_id, discord_name, avatar_url) + + # Session updaten + app.storage.user['authenticated'] = True + app.storage.user['discord_id'] = discord_id + app.storage.user['discord_name'] = discord_name + app.storage.user['db_id'] = player[0] + app.storage.user['avatar_url'] = player[2] # Bild speichern! + + ui.navigate.to('/') + else: + ui.label('Fehler beim Login!').classes('text-red-500') \ No newline at end of file diff --git a/gui/league_statistic.py b/gui/league_statistic.py new file mode 100644 index 0000000..6cbed41 --- /dev/null +++ b/gui/league_statistic.py @@ -0,0 +1,28 @@ +from nicegui import ui, app + +def setup_statistic_routes(): + + # 1. Die {}-Klammern definieren eine dynamische Variable in der URL + @ui.page('/statistic/{systemname}') + def gamesystem_statistic_page(systemname: str): # <--- WICHTIG: Hier fangen wir das Wort aus der URL auf! + + # Sicherheitscheck: Ist der User eingeloggt? + if not app.storage.user.get('authenticated', False): + ui.navigate.to('/') + return + + ui.dark_mode(True) + + # 2. Seite aufbauen + with ui.column().classes('w-full items-center mt-10'): + # Hier nutzen wir die Variable 'systemname', um den Titel anzupassen + ui.label(f'Deine Statistik in {systemname}').classes('text-3xl font-bold text-blue-400') + + with ui.card().classes('w-full max-w-2xl mt-6 p-6 items-center'): + ui.label('Hier laden wir im nächsten Schritt die Daten aus der player_game_statistic Tabelle!').classes('text-gray-400') + + # Platzhalter für später: + ui.label('Winrate: -- %').classes('text-xl mt-4') + ui.label('Durchschnittspunkte: --').classes('text-xl') + + ui.button('Zurück zur Übersicht', on_click=lambda: ui.navigate.to('/')).classes('mt-8 bg-gray-600 text-white') diff --git a/gui/main_gui.py b/gui/main_gui.py index 9bff4c7..80218c1 100644 --- a/gui/main_gui.py +++ b/gui/main_gui.py @@ -1,108 +1,50 @@ -import os -import urllib.parse -import requests from nicegui import ui, app - -# NEU: Wir importieren unsere eigene Datenbank-Datei -import database +from gui import discord_login 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) - # --- Profil Block Oben --- - with ui.card().classes('w-full items-center mt-10'): + # --- NAVIGATIONSLEISTE (HEADER) --- + with ui.header().classes('items-center justify-between bg-zinc-900 p-4 shadow-lg'): + ui.label('Diceghost Liga').classes('text-2xl font-bold text-white') + if app.storage.user.get('authenticated', False): - with ui.row(): - discord_name = app.storage.user.get('discord_name') - db_id = app.storage.user.get('db_id') + with ui.row().classes('items-center gap-4'): + ui.label(app.storage.user.get('discord_name')).classes('text-lg text-gray-300') + avatar = app.storage.user.get('avatar_url') + if avatar: + ui.image(avatar).classes('w-12 h-12 rounded-full border-2 border-blue-500') - 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)) - + auth_url = discord_login.get_auth_url() + ui.button('Login with Discord', on_click=lambda: ui.navigate.to(auth_url)) + + + # --- Spielsysteme --- if app.storage.user.get('authenticated', False): with ui.card().classes("w-full"): - # 1. Die Row muss wissen, dass sie die volle Breite nutzen darf (w-full) - # und wir geben ihr einen kleinen Abstand zwischen den Karten (gap-4) + ui.button('Enter Match', on_click=lambda: ui.navigate.to('/add-match')).classes('mt-4 bg-blue-500 text-white') + ui.label(text="Meine Ligaplätze").classes("font-bold text-white") with ui.row().classes("w-full h-30 gap-4 items-center"): + #Einfügen: Eine UI Card PRO Spielsystem des Spielers. + with ui.card().classes("flex-1 h-24 items-center justify-center cursor-pointer hover:bg-zinc-800 transition-colors").on('click', lambda: ui.navigate.to('/statistic/Warhammer 40k')): + ui.label(text="Warhammer 40k").classes('text-xl font-bold') - with ui.card().classes("flex-1 h-full items-center"): - ui.label(text="40k") + with ui.card().classes("flex-1 h-24 items-center justify-center cursor-pointer hover:bg-zinc-800 transition-colors").on('click', lambda: ui.navigate.to('/statistic/Age of Sigmar')): + ui.label(text="Age of Sigmar").classes('text-xl font-bold') - with ui.card().classes("flex-1 h-full items-center"): - ui.label(text="AoS") + with ui.card().classes("flex-1 h-24 items-center justify-center cursor-pointer hover:bg-zinc-800 transition-colors").on('click', lambda: ui.navigate.to('/statistic/Spearhead')): + ui.label(text="Spearhead").classes('text-xl font-bold') + + with ui.card().classes("w-full"): + ui.label(text="Meine Statistik").classes("font-bold text-white") - with ui.card().classes("flex-1 h-full items-center"): - ui.label(text="Spearhead") - - - - # --- 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 index 744c975..6c355dd 100644 --- a/gui/match_gui.py +++ b/gui/match_gui.py @@ -5,12 +5,12 @@ def setup_match_routes(): # Unsere neue Unterseite für das Eintragen der Spiele @ui.page('/add-match') def add_match_page(): + ui.dark_mode(True) 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('/')) diff --git a/main.py b/main.py index 641c6fa..af57ed3 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,12 @@ import os from dotenv import load_dotenv from nicegui import ui -from gui import main_gui -from gui import match_gui + +from gui import main_gui, match_gui, discord_login, league_statistic + import database + # 1. Lade die geheimen Variablen aus der .env Datei in den Speicher load_dotenv() @@ -33,6 +35,8 @@ 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() +discord_login.setup_login_routes() +league_statistic.setup_statistic_routes() # 4. Wir starten die NiceGUI App ui.run(title="Warhammer Liga", port=9000, storage_secret="ein_sehr_geheimes_passwort_fuer_die_cookies")