Main Page GUI, Statistik Seiten je Spielsystem, Login prozess Verbessert.
This commit is contained in:
parent
bfdbb9b99a
commit
06c5a4377f
|
|
@ -1 +1 @@
|
||||||
{}
|
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":1,"avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||||
25
database.py
25
database.py
|
|
@ -86,29 +86,28 @@ def seed_gamesystems():
|
||||||
print("Spielsysteme wurden angelegt!")
|
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")
|
connection = sqlite3.connect("warhammer_league.db")
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
# 2. Suchen, ob der Spieler schon existiert
|
# REPARIERT: Wir fragen nur noch id, name und avatar_url aus 'players' ab
|
||||||
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()
|
player = cursor.fetchone()
|
||||||
|
|
||||||
# 3. Wenn der Spieler nicht gefunden wurde (ist "None")
|
|
||||||
if player is None:
|
if player is None:
|
||||||
# Neu anlegen
|
cursor.execute("INSERT INTO players (discord_id, name, avatar_url) VALUES (?, ?, ?)", (discord_id, discord_name, avatar_url))
|
||||||
cursor.execute("INSERT INTO players (discord_id, name) VALUES (?, ?)", (discord_id, discord_name))
|
|
||||||
connection.commit()
|
connection.commit()
|
||||||
|
cursor.execute("SELECT id, name, avatar_url FROM players WHERE discord_id = ?", (discord_id,))
|
||||||
# Den frisch angelegten Spieler direkt wieder auslesen, um seine neue ID zu bekommen
|
player = cursor.fetchone()
|
||||||
cursor.execute("SELECT id, name, mmr, points, games FROM players WHERE discord_id = ?", (discord_id,))
|
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()
|
player = cursor.fetchone()
|
||||||
|
|
||||||
# 4. Verbindung schließen
|
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
# 5. Daten zurückgeben
|
|
||||||
return player
|
return player
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
69
gui/discord_login.py
Normal file
69
gui/discord_login.py
Normal file
|
|
@ -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')
|
||||||
28
gui/league_statistic.py
Normal file
28
gui/league_statistic.py
Normal file
|
|
@ -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')
|
||||||
112
gui/main_gui.py
112
gui/main_gui.py
|
|
@ -1,108 +1,50 @@
|
||||||
import os
|
|
||||||
import urllib.parse
|
|
||||||
import requests
|
|
||||||
from nicegui import ui, app
|
from nicegui import ui, app
|
||||||
|
from gui import discord_login
|
||||||
# NEU: Wir importieren unsere eigene Datenbank-Datei
|
|
||||||
import database
|
|
||||||
|
|
||||||
def setup_routes():
|
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('/')
|
@ui.page('/')
|
||||||
|
|
||||||
def home_page():
|
def home_page():
|
||||||
ui.dark_mode(True)
|
ui.dark_mode(True)
|
||||||
|
|
||||||
# --- Profil Block Oben ---
|
# --- NAVIGATIONSLEISTE (HEADER) ---
|
||||||
with ui.card().classes('w-full items-center mt-10'):
|
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):
|
if app.storage.user.get('authenticated', False):
|
||||||
with ui.row():
|
with ui.row().classes('items-center gap-4'):
|
||||||
discord_name = app.storage.user.get('discord_name')
|
ui.label(app.storage.user.get('discord_name')).classes('text-lg text-gray-300')
|
||||||
db_id = app.storage.user.get('db_id')
|
avatar = app.storage.user.get('avatar_url')
|
||||||
|
if avatar:
|
||||||
ui.label(f'Welcome back, {discord_name}!').classes('text-green-500 font-bold')
|
ui.image(avatar).classes('w-12 h-12 rounded-full border-2 border-blue-500')
|
||||||
ui.button('Enter Match', on_click=lambda: ui.navigate.to('/add-match')).classes('mt-4 bg-blue-500 text-white')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def logout():
|
def logout():
|
||||||
app.storage.user.clear()
|
app.storage.user.clear()
|
||||||
ui.navigate.to('/')
|
ui.navigate.to('/')
|
||||||
|
|
||||||
ui.button('Logout', on_click=logout).classes('mt-4 bg-red-500 text-white')
|
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))
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
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):
|
if app.storage.user.get('authenticated', False):
|
||||||
with ui.card().classes("w-full"):
|
with ui.card().classes("w-full"):
|
||||||
# 1. Die Row muss wissen, dass sie die volle Breite nutzen darf (w-full)
|
ui.button('Enter Match', on_click=lambda: ui.navigate.to('/add-match')).classes('mt-4 bg-blue-500 text-white')
|
||||||
# und wir geben ihr einen kleinen Abstand zwischen den Karten (gap-4)
|
ui.label(text="Meine Ligaplätze").classes("font-bold text-white")
|
||||||
with ui.row().classes("w-full h-30 gap-4 items-center"):
|
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"):
|
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="40k")
|
ui.label(text="Age of Sigmar").classes('text-xl font-bold')
|
||||||
|
|
||||||
with ui.card().classes("flex-1 h-full items-center"):
|
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="AoS")
|
ui.label(text="Spearhead").classes('text-xl font-bold')
|
||||||
|
|
||||||
with ui.card().classes("flex-1 h-full items-center"):
|
with ui.card().classes("w-full"):
|
||||||
ui.label(text="Spearhead")
|
ui.label(text="Meine Statistik").classes("font-bold text-white")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# --- 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))
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ def setup_match_routes():
|
||||||
# Unsere neue Unterseite für das Eintragen der Spiele
|
# Unsere neue Unterseite für das Eintragen der Spiele
|
||||||
@ui.page('/add-match')
|
@ui.page('/add-match')
|
||||||
def add_match_page():
|
def add_match_page():
|
||||||
|
ui.dark_mode(True)
|
||||||
with ui.card().classes('w-full items-center mt-10'):
|
with ui.card().classes('w-full items-center mt-10'):
|
||||||
ui.label('Enter New Match').classes('text-2xl font-bold')
|
ui.label('Enter New Match').classes('text-2xl font-bold')
|
||||||
|
|
||||||
# --- SICHERHEITS-CHECK ---
|
# --- SICHERHEITS-CHECK ---
|
||||||
# Prüfen, ob der User wirklich eingeloggt ist.
|
# Prüfen, ob der User wirklich eingeloggt ist.
|
||||||
# Wenn nicht, brechen wir hier sofort ab!
|
|
||||||
if not app.storage.user.get('authenticated', False):
|
if not app.storage.user.get('authenticated', False):
|
||||||
ui.label('Access Denied. Please log in first.').classes('text-red-500')
|
ui.label('Access Denied. Please log in first.').classes('text-red-500')
|
||||||
ui.button('Back to Home', on_click=lambda: ui.navigate.to('/'))
|
ui.button('Back to Home', on_click=lambda: ui.navigate.to('/'))
|
||||||
|
|
|
||||||
8
main.py
8
main.py
|
|
@ -1,10 +1,12 @@
|
||||||
import os
|
import os
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from nicegui import ui
|
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
|
import database
|
||||||
|
|
||||||
|
|
||||||
# 1. Lade die geheimen Variablen aus der .env Datei in den Speicher
|
# 1. Lade die geheimen Variablen aus der .env Datei in den Speicher
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
|
@ -33,6 +35,8 @@ client_secret = os.getenv("DISCORD_CLIENT_SECRET")
|
||||||
# 3. Wir rufen unsere Funktion aus der main_gui.py auf
|
# 3. Wir rufen unsere Funktion aus der main_gui.py auf
|
||||||
main_gui.setup_routes()
|
main_gui.setup_routes()
|
||||||
match_gui.setup_match_routes()
|
match_gui.setup_match_routes()
|
||||||
|
discord_login.setup_login_routes()
|
||||||
|
league_statistic.setup_statistic_routes()
|
||||||
|
|
||||||
# 4. Wir starten die NiceGUI App
|
# 4. Wir starten die NiceGUI App
|
||||||
ui.run(title="Warhammer Liga", port=9000, storage_secret="ein_sehr_geheimes_passwort_fuer_die_cookies")
|
ui.run(title="Warhammer Liga", port=9000, storage_secret="ein_sehr_geheimes_passwort_fuer_die_cookies")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user