diff --git a/.nicegui/storage-user-47e8cea6-65ad-4725-abd9-23cf84587a03.json b/.nicegui/storage-user-47e8cea6-65ad-4725-abd9-23cf84587a03.json index c9a1563..6b89cb8 100644 --- a/.nicegui/storage-user-47e8cea6-65ad-4725-abd9-23cf84587a03.json +++ b/.nicegui/storage-user-47e8cea6-65ad-4725-abd9-23cf84587a03.json @@ -1 +1 @@ -{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":1,"display_name":"Zorniger Klebschnüffler","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"} \ No newline at end of file +{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"r","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"} \ No newline at end of file diff --git a/data/data_api.py b/data/data_api.py index 99fde59..771787c 100644 --- a/data/data_api.py +++ b/data/data_api.py @@ -475,3 +475,64 @@ def get_leaderboard(system_name): result.append(dict(row)) return result + + +def get_unconfirmed_matches(player_id): + """Holt alle offenen Matches, die der Spieler noch bestätigen muss.""" + connection = sqlite3.connect(DB_PATH) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + + # Wir joinen players (für den Namen des Gegners) und gamesystems (für den Systemnamen) + query = """ + SELECT m.id AS match_id, m.score_player1, m.score_player2, m.played_at, + sys.name AS system_name, + p1.display_name AS p1_name + FROM matches m + JOIN gamesystems sys ON m.gamesystem_id = sys.id + JOIN players p1 ON m.player1_id = p1.id + WHERE m.player2_id = ? AND m.player2_check = 0 + """ + + cursor.execute(query, (player_id,)) + rows = cursor.fetchall() + connection.close() + + # Wir geben eine Liste mit Dictionaries zurück + return [dict(row) for row in rows] + + +def delete_match(match_id): + """Löscht ein Match anhand seiner ID komplett aus der Datenbank.""" + connection = sqlite3.connect(DB_PATH) + cursor = connection.cursor() + + # DELETE FROM löscht die gesamte Zeile, bei der die ID übereinstimmt. + cursor.execute("DELETE FROM matches WHERE id = ?", (match_id,)) + + connection.commit() + connection.close() + + +def confirm_match(match_id): + connection = sqlite3.connect(DB_PATH) + cursor = connection.cursor() + + # Ändert nur die Spalte player2_check auf 1 (True) + cursor.execute("UPDATE matches SET player2_check = 1 WHERE id = ?", (match_id,)) + + connection.commit() + connection.close() + + +def set_match_counted(match_id): + """Setzt den Haken (1), dass das Match erfolgreich in die MMR eingeflossen ist.""" + connection = sqlite3.connect(DB_PATH) + cursor = connection.cursor() + + # Ändert nur die Spalte match_is_counted auf 1 (True) + cursor.execute("UPDATE matches SET match_is_counted = 1 WHERE id = ?", (match_id,)) + + connection.commit() + connection.close() + diff --git a/data/setup_database.py b/data/setup_database.py index b42f6ec..96e9535 100644 --- a/data/setup_database.py +++ b/data/setup_database.py @@ -2,8 +2,12 @@ import sqlite3 import os import json +dummy_is_in = False -DB_PATH = "data/warhammer_league.db" +# 1. Sucht den exakten, absoluten Pfad zu diesem Skript (z.B. /srv/Diceghost-Liga-System/data/) +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +# 2. Klebt den Datenbank-Namen an diesen Ordner +DB_PATH = os.path.join(BASE_DIR, "league_database.db") def init_db(): connection = sqlite3.connect(DB_PATH) @@ -63,7 +67,9 @@ def init_db(): score_player2 INTEGER, played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, player1_mmr_change INTEGER, - player2_mmr_change INTEGER, + player2_mmr_change INTEGER, + player2_check INTEGER DEFAULT 0, + match_is_counted INTEGER DEFAULT 0, FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id), FOREIGN KEY (player1_id) REFERENCES players (id), FOREIGN KEY (player2_id) REFERENCES players (id) @@ -141,7 +147,9 @@ def seed_achievements(): connection.commit() connection.close() print("Achievements angelegt.") - #seed_dummy_player() + + if dummy_is_in: + seed_dummy_player() def seed_dummy_player(): @@ -215,7 +223,7 @@ def generate_default_mmr_rules(): # Das ist unsere Standard-Vorlage (Faktor 10) default_rules = { "system_info": f"Balancing für {sys_name}", - "draw_point_difference": 5, + "draw_point_difference": 3, "rank_matrix": { "10": {"win": 10, "draw": 30}, "9": {"win": 10, "draw": 30}, diff --git a/gui/admin_gui.py b/gui/admin_gui.py new file mode 100644 index 0000000..4636cb9 --- /dev/null +++ b/gui/admin_gui.py @@ -0,0 +1,8 @@ +from nicegui import ui, app +from data import database, data_api +from gui import gui_style + +def setup_routes(): + @ui.page('/admin') + def home_page(): + gui_style.apply_design() \ No newline at end of file diff --git a/gui/main_gui.py b/gui/main_gui.py index dc827be..e4d48af 100644 --- a/gui/main_gui.py +++ b/gui/main_gui.py @@ -1,8 +1,9 @@ from nicegui import ui, app from data import database, data_api from gui import discord_login, gui_style +from match_calculations import calc_match -def setup_routes(): +def setup_routes(admin_discord_id): @ui.page('/') def home_page(): gui_style.apply_design() @@ -19,9 +20,15 @@ def setup_routes(): ui.image("gui/pictures/wsdg.png").classes('w-20 h-20 rounded-full') ui.label('Diceghost Liga').classes('text-2xl font-bold text-white') + # --- RECHTE SEITE --- + discord_id = app.storage.user.get("discord_id") + if discord_id == admin_discord_id: + ui.button('Admin Panel', on_click=lambda: ui.navigate.to('/admin')) + # --- RECHTE SEITE --- if app.storage.user.get('authenticated', False): with ui.row().classes('items-center gap-4'): + discord_name = app.storage.user.get('discord_name') display_name = app.storage.user.get('display_name') player_id = app.storage.user.get('db_id') @@ -77,6 +84,49 @@ def setup_routes(): ui.button('Login with Discord', on_click=lambda: ui.navigate.to(auth_url)) + # --------------------------- + # --- Match Bestätigung --- + # --------------------------- + # --- Bestätigungs-Bereich für offene Spiele --- Der "Marian Balken !!!1!11!" + if app.storage.user.get('authenticated', False): + unconfirmed_matches = data_api.get_unconfirmed_matches(player_id) + + if len(unconfirmed_matches) > 0: + # Eine auffällige, rote Karte über die volle Breite + with ui.card().classes('w-full bg-red-900/80 border-2 border-red-500 mb-6'): + ui.label(f"Aktion erforderlich: Du hast {len(unconfirmed_matches)} unbestätigte(s) Spiel(e)!").classes('text-2xl font-bold text-white mb-2') + + for match in unconfirmed_matches: + + # --- NEU: Die Funktion, die beim Klick ausgeführt wird --- + def reject_match(m_id): + data_api.delete_match(m_id) + ui.notify("Spiel abgelehnt und gelöscht!", color="warning") + ui.navigate.reload() # Lädt die Seite neu, um die Karte zu aktualisieren + + def acccept_match(m_id): + ui.notify("Spiel akzeptiert. Wird Berechnet.") + ui.navigate.reload() # Lädt die Seite neu, um die Karte zu aktualisieren + calc_match.calculate_match(m_id) + + + + # Für jedes Match machen wir eine kleine Reihe + with ui.row().classes('w-full items-center justify-between bg-zinc-900 p-3 rounded shadow-inner'): + # Info-Text: Was wurde eingetragen? + info_text = f"[{match['system_name']}] {match['p1_name']} behauptet: Er hat {match['score_player1']} : {match['score_player2']} gegen dich gespielt." + ui.label(info_text).classes('text-lg text-gray-200') + + # Die Buttons (Funktion machen wir im nächsten Schritt!) + with ui.row().classes('gap-2'): + # ABLEHNEN und Spiel löschen + ui.button("Ablehnen", color="negative", icon="close", on_click=lambda e, m_id=match['match_id']: reject_match(m_id)) + ui.space() + # BESTÄTIGEN und spiel berechnen lassen + ui.button("Bestätigen", color="positive", icon="check", on_click=lambda e, m_id=match['match_id']: acccept_match(m_id)) + + + # --------------------------- # --- Spielsysteme --- # --------------------------- diff --git a/gui/match_gui.py b/gui/match_gui.py index b99aec6..65a4d7a 100644 --- a/gui/match_gui.py +++ b/gui/match_gui.py @@ -69,8 +69,7 @@ def setup_routes(): # 4. Erfolgsmeldung und Berechnung ui.notify("Match erfolgreich eingetragen!", color="green") - calc_match.calculate_inserted_match(systemname, match_id) - ui.navigate.to('/') + ui.navigate.to(f'/statistic/{systemname}') # Buttons ganz unten in einer Reihe with ui.row().classes("w-full items-center justify-between mt-8"): diff --git a/main.py b/main.py index 8f489fd..9e0b150 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ import os from dotenv import load_dotenv from nicegui import ui -from gui import main_gui, match_gui, discord_login, league_statistic +from gui import main_gui, match_gui, discord_login, league_statistic, admin_gui from data import database @@ -10,18 +10,19 @@ from data import database # 1. Lade die geheimen Variablen aus der .env Datei in den Speicher load_dotenv() -database.check_db() -# ---------------------- - # 2. Variablen abrufen client_id = os.getenv("DISCORD_CLIENT_ID") client_secret = os.getenv("DISCORD_CLIENT_SECRET") +admin_discord_id = os.getenv("ADMIN") + +database.check_db() # 3. Seitenrouten aufbauen -main_gui.setup_routes() +main_gui.setup_routes(admin_discord_id) discord_login.setup_login_routes() league_statistic.setup_routes() match_gui.setup_routes() +admin_gui.setup_routes() # 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="Westside Diceghost Liga", port=9000, storage_secret="ein_sehr_geheimes_passwort_fuer_die_cookies") diff --git a/match_calculations/calc_match.py b/match_calculations/calc_match.py index 7ad1ef5..fc7fa8d 100644 --- a/match_calculations/calc_match.py +++ b/match_calculations/calc_match.py @@ -4,11 +4,15 @@ import json import os # Mach die DB abfrage für die Relevanten Daten. Von hier aus werden die "Aufgaben" und Daten dann an die kleineren Berechnungs Funktionen verteilt. -def calculate_inserted_match (gamesystem, match_id): +def calculate_match (match_id): match_data = data_api.get_match_by_id(match_id) + if not match_data: print("Fehler: Match nicht gefunden!") return + + data_api.confirm_match(match_id) + # Laden und umsetzen der Match Daten p1_id = match_data['player1_id'] p2_id = match_data['player2_id'] @@ -60,6 +64,7 @@ def calculate_inserted_match (gamesystem, match_id): data_api.apply_match_to_player_statistic (winner_id, sys_id, mmr_change_winner, winner_score) data_api.apply_match_to_player_statistic (looser_id, sys_id, mmr_change_looser, looser_score) + data_api.set_match_counted(match_id) diff --git a/match_calculations/mmr_rules_spearhead.json b/match_calculations/mmr_rules_spearhead.json index 4140846..bd619ba 100644 --- a/match_calculations/mmr_rules_spearhead.json +++ b/match_calculations/mmr_rules_spearhead.json @@ -1,6 +1,6 @@ { "system_info": "Balancing für Spearhead", - "draw_point_difference": 5, + "draw_point_difference": 3, "rank_matrix": { "10": { "win": 10, @@ -109,5 +109,4 @@ "bonus": 0 } ] - } \ No newline at end of file diff --git a/match_calculations/mmr_rules_warhammer_40k.json b/match_calculations/mmr_rules_warhammer_40k.json index 892fe7d..a7163db 100644 --- a/match_calculations/mmr_rules_warhammer_40k.json +++ b/match_calculations/mmr_rules_warhammer_40k.json @@ -1,6 +1,6 @@ { "system_info": "Balancing für Warhammer 40k", - "draw_point_difference": 5, + "draw_point_difference": 3, "rank_matrix": { "10": { "win": 10, diff --git a/match_calculations/mmr_rules_warhammer_age_of_sigmar.json b/match_calculations/mmr_rules_warhammer_age_of_sigmar.json index cd836e3..a4bfbbf 100644 --- a/match_calculations/mmr_rules_warhammer_age_of_sigmar.json +++ b/match_calculations/mmr_rules_warhammer_age_of_sigmar.json @@ -1,6 +1,6 @@ { "system_info": "Balancing für Warhammer Age of Sigmar", - "draw_point_difference": 5, + "draw_point_difference": 3, "rank_matrix": { "10": { "win": 10, @@ -109,5 +109,4 @@ "bonus": 0 } ] - } \ No newline at end of file