dev von master #30

Closed
daniel wants to merge 20 commits from master into dev
87 changed files with 391 additions and 533 deletions

View File

@ -1,100 +1,120 @@
import sqlite3
import random
from wood import logger
from data.setup_database import DB_PATH
from data.database import db_connection
# -----------------------------------------------------
# User / Session
# -----------------------------------------------------
def validate_user_session(db_id, discord_id):
"""Prüft, ob das Cookie noch zur aktuellen Datenbank passt."""
connection = sqlite3.connect(DB_PATH)
connection = db_connection()
cursor = connection.cursor()
cursor.execute("SELECT discord_id FROM players WHERE id = ?", (db_id,))
cursor.execute("SELECT discord_id FROM players WHERE id = %s", (db_id,))
result = cursor.fetchone()
cursor.close()
connection.close()
# 1. Fall: Die ID gibt es gar nicht mehr in der Datenbank
if result is None:
logger.log(f"Player not found in database. Discord:{discord_id}")
return False
# 2. Fall: Die ID gehört jetzt einem anderen Discord-Account (Datenbank wurde resettet)
if str(result[0]) != str(discord_id):
logger.log(f"Player with false coockies logged in! {discord_id} doesnt belong to {db_id}")
return False
# 3. Fall: Alles ist korrekt!
return True
def change_display_name(player_id, new_name):
connection = sqlite3.connect(DB_PATH)
connection = db_connection()
cursor = connection.cursor()
cursor.execute("UPDATE players SET display_name = ? WHERE id = ?", (new_name, player_id))
cursor.execute("UPDATE players SET display_name = %s WHERE id = %s", (new_name, player_id))
connection.commit()
cursor.close()
connection.close()
def generate_silly_name():
adjectives = ["Verwirrter", "Blinder", "Heulender", "Zorniger", "Chaos", "Verzweifelter", "Schreiender", "Stolpernder", "Schwitzender"]
nouns = ["Grot", "Kultist", "Servitor", "Snotling", "Guardmen", "Würfellecker", "Regelvergesser", "Meta-Chaser", "Klebschnüffler"]
adj = random.choice(adjectives)
noun = random.choice(nouns)
return f"{adj} {noun}"
adjectives = ["Verwirrter", "Blinder", "Heulender", "Zorniger", "Chaos",
"Verzweifelter", "Schreiender", "Stolpernder", "Schwitzender"]
nouns = ["Grot", "Kultist", "Servitor", "Snotling", "Guardmen",
"Würfellecker", "Regelvergesser", "Meta-Chaser", "Klebschnüffler"]
return f"{random.choice(adjectives)} {random.choice(nouns)}"
def get_or_create_player(discord_id, discord_name, avatar_url):
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
connection = db_connection()
cursor = connection.cursor(dictionary=True)
# fragen 4 Dinge ab (id, discord_name, display_name, discord_avatar_url)
cursor.execute("SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = ?", (discord_id,))
cursor.execute(
"SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = %s",
(discord_id,)
)
player = cursor.fetchone()
if player is None:
# Random Silly Name Generator für neue Spieler. Damit sie angeregt werden ihren richtigen Namen einzutragen.
silly_name = generate_silly_name()
cursor.execute("INSERT INTO players (discord_id, discord_name, display_name, discord_avatar_url) VALUES (?, ?, ?, ?)", (discord_id, discord_name, silly_name, avatar_url))
logger.log(f"new player added. Discord:{discord_name}")
cursor.execute(
"INSERT INTO players (discord_id, discord_name, display_name, discord_avatar_url) "
"VALUES (%s, %s, %s, %s)",
(discord_id, discord_name, silly_name, avatar_url)
)
connection.commit()
cursor.execute("SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = ?", (discord_id,))
logger.log(f"new player added. Discord:{discord_name}")
cursor.execute(
"SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = %s",
(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 discord_name = ?, discord_avatar_url = ? WHERE discord_id = ?", (discord_name, avatar_url, discord_id))
cursor.execute(
"UPDATE players SET discord_name = %s, discord_avatar_url = %s WHERE discord_id = %s",
(discord_name, avatar_url, discord_id)
)
connection.commit()
cursor.execute("SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = ?", (discord_id,))
cursor.execute(
"SELECT id, discord_name, display_name, discord_avatar_url FROM players WHERE discord_id = %s",
(discord_id,)
)
player = cursor.fetchone()
cursor.close()
connection.close()
return player
# -----------------------------------------------------
# Player-Listen
# -----------------------------------------------------
def get_all_players():
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
"""Alle Spieler Basisdaten."""
connection = db_connection()
cursor = connection.cursor(dictionary=True)
# Alle Spieler laden, absteigend sortiert nach MMR
cursor.execute("SELECT id, name, mmr, points, games FROM players ORDER BY mmr DESC")
players = cursor.fetchall()
cursor.execute(
"SELECT id, display_name, discord_name, discord_avatar_url FROM players "
"ORDER BY display_name ASC"
)
rows = cursor.fetchall()
cursor.close()
connection.close()
# Die Daten für das Web-GUI in eine lesbare Form umwandeln (Liste von Dictionaries)
result = []
return result
return rows
def get_all_players_from_system(system_name):
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
connection = db_connection()
cursor = connection.cursor(dictionary=True)
# ID und Namen der Spieler.
# DISTINCT stellt sicher, dass jeder Spieler nur einmal vorkommt.
query = """
SELECT DISTINCT
p.id AS player_id,
@ -103,47 +123,37 @@ def get_all_players_from_system(system_name):
FROM players p
JOIN player_game_statistic stat ON p.id = stat.player_id
JOIN gamesystems sys ON stat.gamesystem_id = sys.id
WHERE sys.name = ?
WHERE sys.name = %s
ORDER BY p.display_name ASC
"""
cursor.execute(query, (system_name,))
rows = cursor.fetchall()
cursor.close()
connection.close()
result = []
for row in rows:
result.append(dict(row))
return result
return rows
def get_gamesystem_id_by_name(system_name):
"""Holt die interne ID eines Spielsystems anhand seines Namens (z.B. 'Warhammer 40k' -> 1)."""
connection = sqlite3.connect(DB_PATH)
connection = db_connection()
cursor = connection.cursor()
# Wir suchen exakt nach dem Namen
cursor.execute("SELECT id FROM gamesystems WHERE name = ?", (system_name,))
cursor.execute("SELECT id FROM gamesystems WHERE name = %s", (system_name,))
row = cursor.fetchone()
cursor.close()
connection.close()
# Wenn wir einen Treffer haben, geben wir die erste Spalte (die ID) zurück
if row:
return row[0]
# Wenn das System nicht existiert
return None
return row[0] if row else None
# -----------------------------------------------------
# Player Statistics
# -----------------------------------------------------
def get_player_statistics(player_id):
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
connection = db_connection()
cursor = connection.cursor(dictionary=True)
query = """
SELECT
@ -162,42 +172,71 @@ def get_player_statistics(player_id):
stat.nemesis_id
FROM gamesystems sys
LEFT JOIN player_game_statistic stat
ON sys.id = stat.gamesystem_id AND stat.player_id = ?
ON sys.id = stat.gamesystem_id AND stat.player_id = %s
"""
cursor.execute(query, (player_id,))
rows = cursor.fetchall()
cursor.close()
connection.close()
result = []
for row in rows:
result.append(dict(row))
return result
return rows
def get_player_statistic(player_id, system_id):
connection = db_connection()
cursor = connection.cursor(dictionary=True)
query = """
SELECT
sys.id AS gamesystem_id,
sys.name AS gamesystem_name,
sys.*,
stat.mmr,
stat.games_in_system,
stat.points,
stat.avv_points,
stat.last_played,
stat.win_rate,
stat.trend,
stat.tyrann_id,
stat.pushover_id,
stat.nemesis_id
FROM gamesystems sys
LEFT JOIN player_game_statistic stat
ON sys.id = stat.gamesystem_id AND stat.player_id = %s
WHERE sys.id = %s
"""
cursor.execute(query, (player_id, system_id))
row = cursor.fetchone()
cursor.close()
connection.close()
return row
def join_league(player_id, gamesystem_id):
connection = sqlite3.connect(DB_PATH)
connection = db_connection()
cursor = connection.cursor()
# Wir fügen nur die beiden IDs ein, der Rest wird von den DEFAULT-Werten der DB erledigt
query = """
INSERT INTO player_game_statistic (player_id, gamesystem_id)
VALUES (?, ?)
VALUES (%s, %s)
"""
logger.log(f"{get_player_name(player_id)} joined {gamesystem_id}")
cursor.execute(query, (player_id, gamesystem_id))
connection.commit()
cursor.close()
connection.close()
# -----------------------------------------------------
# Matches: Lesen
# -----------------------------------------------------
def get_recent_matches_for_player(player_id):
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
connection = db_connection()
cursor = connection.cursor(dictionary=True)
query = """
SELECT
@ -216,69 +255,62 @@ def get_recent_matches_for_player(player_id):
JOIN gamesystems sys ON m.gamesystem_id = sys.id
JOIN players p1 ON m.player1_id = p1.id
JOIN players p2 ON m.player2_id = p2.id
WHERE m.player1_id = ? OR m.player2_id = ?
WHERE m.player1_id = %s OR m.player2_id = %s
ORDER BY m.played_at DESC
LIMIT 10
"""
cursor.execute(query, (player_id, player_id))
rows = cursor.fetchall()
cursor.close()
connection.close()
result = []
for row in rows:
result.append(dict(row))
return result
return rows
def add_new_match(system_name, player1_id, player2_id, score_p1, score_p2):
connection = sqlite3.connect(DB_PATH)
connection = db_connection()
cursor = connection.cursor()
# 1. Wir suchen die interne ID des Spielsystems anhand des Namens (z.B. "Warhammer 40k" -> 1)
cursor.execute("SELECT id FROM gamesystems WHERE name = ?", (system_name,))
cursor.execute("SELECT id FROM gamesystems WHERE name = %s", (system_name,))
sys_row = cursor.fetchone()
# Sicherheitscheck (sollte eigentlich nie passieren)
if not sys_row:
cursor.close()
connection.close()
return None
return None
sys_id = sys_row[0]
# 2. Das Match eintragen! (Datum 'played_at' macht SQLite durch DEFAULT CURRENT_TIMESTAMP automatisch)
query = """
INSERT INTO matches (gamesystem_id, player1_id, player2_id, score_player1, score_player2)
VALUES (?, ?, ?, ?, ?)
VALUES (%s, %s, %s, %s, %s)
"""
cursor.execute(query, (sys_id, player1_id, player2_id, score_p1, score_p2))
new_match_id = cursor.lastrowid
logger.log(f"{get_player_name(player1_id)}:({score_p1}) posted Match. System: {system_name}, {get_player_name(player2_id)}:({score_p2})")
logger.log(
f"{get_player_name(player1_id)}:({score_p1}) posted Match. "
f"System: {system_name}, {get_player_name(player2_id)}:({score_p2})"
)
connection.commit()
cursor.close()
connection.close()
return new_match_id
def save_calculated_match(calc_results: dict):
connection = sqlite3.connect(DB_PATH)
connection = db_connection()
cursor = connection.cursor()
try:
match_id = calc_results["match_id"]
winner_id = calc_results["winner_id"]
looser_id = calc_results["looser_id"]
match_id = calc_results["match_id"]
winner_id = calc_results["winner_id"]
looser_id = calc_results["looser_id"]
# Match aus DB lesen
cursor.execute(
"SELECT player1_id, player2_id, gamesystem_id FROM matches WHERE id = ?",
"SELECT player1_id, player2_id, gamesystem_id FROM matches WHERE id = %s",
(match_id,)
)
row = cursor.fetchone()
@ -287,21 +319,19 @@ def save_calculated_match(calc_results: dict):
player1_id, player2_id, gamesystem_id = row
# Daten der Spieler aus calc_results holen (Key = player_id als int/str)
p1 = calc_results[player1_id]
p2 = calc_results[player2_id]
# 1. Match-Tabelle updaten
match_query = """
UPDATE matches
SET
player1_base_change = ?, player1_khorne = ?, player1_slaanesh = ?,
player1_tzeentch = ?, player1_mmr_change = ?,
player2_base_change = ?, player2_khorne = ?, player2_slaanesh = ?,
player2_tzeentch = ?, player2_mmr_change = ?,
rust_factor = ?, elo_factor = ?, point_inflation = ?,
player1_base_change = %s, player1_khorne = %s, player1_slaanesh = %s,
player1_tzeentch = %s, player1_mmr_change = %s,
player2_base_change = %s, player2_khorne = %s, player2_slaanesh = %s,
player2_tzeentch = %s, player2_mmr_change = %s,
rust_factor = %s, elo_factor = %s, point_inflation = %s,
match_is_counted = 1
WHERE id = ?
WHERE id = %s
"""
cursor.execute(match_query, (
p1["base"], p1["khorne"], p1["slaanesh"], p1["tzeentch"], p1["total"],
@ -310,36 +340,29 @@ def save_calculated_match(calc_results: dict):
match_id
))
# 2. Scores holen
cursor.execute(
"SELECT score_player1, score_player2 FROM matches WHERE id = ?",
"SELECT score_player1, score_player2 FROM matches WHERE id = %s",
(match_id,)
)
score_row = cursor.fetchone()
score_p1, score_p2 = score_row if score_row else (0, 0)
# 3. Statistik-Query
stat_query = """
UPDATE player_game_statistic
SET
mmr = mmr + ?,
mmr = mmr + %s,
games_in_system = games_in_system + 1,
points = points + ?,
avv_points = (points + ?) / (games_in_system + 1),
points = points + %s,
avv_points = (points + %s) / (games_in_system + 1),
last_played = CURRENT_TIMESTAMP
WHERE player_id = ? AND gamesystem_id = ?
WHERE player_id = %s AND gamesystem_id = %s
"""
# 4. Statistik Spieler 1
cursor.execute(stat_query, (
p1["total"], score_p1, score_p1,
player1_id, gamesystem_id
p1["total"], score_p1, score_p1, player1_id, gamesystem_id
))
# 5. Statistik Spieler 2
cursor.execute(stat_query, (
p2["total"], score_p2, score_p2,
player2_id, gamesystem_id
p2["total"], score_p2, score_p2, player2_id, gamesystem_id
))
connection.commit()
@ -352,76 +375,63 @@ def save_calculated_match(calc_results: dict):
return False
finally:
cursor.close()
connection.close()
# -----------------------------------------------------
# Get Data Funktionen
# -----------------------------------------------------
# -----------------------------------------------------
def get_gamesystem_data(system_id):
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
cursor.execute("SELECT * FROM gamesystems WHERE id = ?", (system_id,))
connection = db_connection()
cursor = connection.cursor(dictionary=True)
cursor.execute("SELECT * FROM gamesystems WHERE id = %s", (system_id,))
row = cursor.fetchone()
cursor.close()
connection.close()
return dict(zip([col[0] for col in cursor.description], row)) if row else None
return row
def get_gamesystems_data():
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
connection = db_connection()
cursor = connection.cursor(dictionary=True)
cursor.execute("SELECT * FROM gamesystems")
rows = cursor.fetchall()
cursor.close()
connection.close()
# SQLite-Rows in normale Python-Dictionaries um
result = []
for row in rows:
result.append(dict(row))
return result
return rows
def get_leaderboard(system_name):
"""Holt alle Spieler eines Systems sortiert nach MMR für die Rangliste."""
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
connection = db_connection()
cursor = connection.cursor(dictionary=True)
# WIR HABEN HIER EINE BEDINGUNG HINZUGEFÜGT: AND stat.games_in_system > 0
# Dadurch filtert die Datenbank direkt auf dem Server schon alle "0-Spiele"-Accounts raus.
query = """
SELECT p.id, p.display_name, p.discord_name, stat.mmr
FROM players p
JOIN player_game_statistic stat ON p.id = stat.player_id
JOIN gamesystems sys ON stat.gamesystem_id = sys.id
WHERE sys.name = ? AND stat.games_in_system > 0
WHERE sys.name = %s AND stat.games_in_system > 0
ORDER BY stat.mmr DESC
"""
cursor.execute(query, (system_name,))
rows = cursor.fetchall()
cursor.close()
connection.close()
result = []
for row in rows:
result.append(dict(row))
return result
return rows
def get_match_by_id(match_id: int) -> dict | None:
"""Gibt alle Match-Daten inkl. Gamesystem-Name als Dict zurück."""
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
connection = db_connection()
cursor = connection.cursor(dictionary=True)
try:
cursor.execute("""
@ -436,99 +446,86 @@ def get_match_by_id(match_id: int) -> dict | None:
m.played_at
FROM matches m
JOIN gamesystems g ON m.gamesystem_id = g.id
WHERE m.id = ?
WHERE m.id = %s
""", (match_id,))
row = cursor.fetchone()
if not row:
return None
columns = [desc[0] for desc in cursor.description]
return dict(zip(columns, row))
return cursor.fetchone()
except Exception as e:
logger.log(f"Fehler beim Laden des Matches: {e}")
return None
finally:
cursor.close()
connection.close()
def get_player_name(player_id):
"""Gibt den Namen eines Spielers im Format 'Anzeigename (Discordname)' zurück."""
# Sicherheits-Check: Falls aus Versehen gar keine ID übergeben wird
if player_id is None:
return "Unbekannter Spieler"
connection = sqlite3.connect(DB_PATH)
connection = db_connection()
cursor = connection.cursor()
cursor.execute("SELECT display_name, discord_name FROM players WHERE id = ?", (player_id,))
cursor.execute("SELECT display_name, discord_name FROM players WHERE id = %s", (player_id,))
row = cursor.fetchone()
cursor.close()
connection.close()
# Wenn die Datenbank den Spieler gefunden hat:
if row:
display_name = row[0]
discord_name = row[1]
return f"{display_name} ({discord_name})"
else:
return "Gelöschter Spieler"
return f"{row[0]} ({row[1]})"
return "Gelöschter Spieler"
def get_system_name(sys_id):
if sys_id is None:
return "Unbekanntes System"
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
cursor.execute("SELECT name FROM gamesystems WHERE id = ?", (sys_id,))
connection = db_connection()
cursor = connection.cursor()
cursor.execute("SELECT name FROM gamesystems WHERE id = %s", (sys_id,))
row = cursor.fetchone()
cursor.close()
connection.close()
if row:
return row[0]
else:
return "Gelöschtes System"
return row[0] if row else "Gelöschtes System"
def get_days_since_last_system_game(player_id, gamesystem_id):
"""Gibt zurück, wie viele Tage das letzte Spiel in einem bestimmten System her ist."""
connection = sqlite3.connect(DB_PATH)
connection = db_connection()
cursor = connection.cursor()
# Julianday() berechnet in SQLite die Differenz in Tagen zwischen zwei Daten
# MariaDB: DATEDIFF statt julianday()
query = """
SELECT CAST(julianday('now') - julianday(last_played) AS INTEGER)
SELECT DATEDIFF(NOW(), last_played)
FROM player_game_statistic
WHERE player_id = ? AND gamesystem_id = ?
WHERE player_id = %s AND gamesystem_id = %s
"""
cursor.execute(query, (player_id, gamesystem_id))
result = cursor.fetchone()
cursor.close()
connection.close()
# Wenn er gefunden wurde und ein Datum hat, gib die Tage zurück. Sonst (Erstes Spiel) 0.
if result and result[0] is not None:
return result[0]
return int(result[0])
return 0
# -----------------------------------------------------
# Matches Bestätigen, Löschen, Berechnen, ...
# -----------------------------------------------------
# -----------------------------------------------------
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()
connection = db_connection()
cursor = connection.cursor(dictionary=True)
# 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,
@ -536,58 +533,58 @@ def get_unconfirmed_matches(player_id):
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
WHERE m.player2_id = %s 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]
cursor.close()
connection.close()
return rows
def delete_match(match_id, player_id):
"""Löscht ein Match anhand seiner ID komplett aus der Datenbank."""
connection = sqlite3.connect(DB_PATH)
connection = db_connection()
cursor = connection.cursor()
# DELETE FROM löscht die gesamte Zeile, bei der die ID übereinstimmt.
cursor.execute("DELETE FROM matches WHERE id = ?", (match_id,))
cursor.execute("DELETE FROM matches WHERE id = %s", (match_id,))
logger.log(f"Match ID{match_id} deleted from user {get_player_name(player_id)}(ID:{player_id})")
connection.commit()
cursor.close()
connection.close()
def confirm_match(match_id):
connection = sqlite3.connect(DB_PATH)
connection = db_connection()
cursor = connection.cursor()
# Ändert nur die Spalte player2_check auf 1 (True)
cursor.execute("UPDATE matches SET player2_check = 1 WHERE id = ?", (match_id,))
cursor.execute("UPDATE matches SET player2_check = 1 WHERE id = %s", (match_id,))
logger.log(f"Match mit ID{match_id} von Player2 bestätigt.")
connection.commit()
cursor.close()
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)
connection = db_connection()
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,))
cursor.execute("UPDATE matches SET match_is_counted = 1 WHERE id = %s", (match_id,))
connection.commit()
cursor.close()
connection.close()
def get_submitted_matches(player_id):
"""Holt alle offenen Matches, die der Spieler selbst eingetragen hat, aber vom Gegner noch nicht bestätigt wurden."""
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
"""Holt alle offenen Matches, die der Spieler selbst eingetragen hat."""
connection = db_connection()
cursor = connection.cursor(dictionary=True)
# ACHTUNG: Wir joinen hier p2 (player2), weil wir den Namen des Gegners anzeigen wollen!
query = """
SELECT m.id AS match_id, m.score_player1, m.score_player2, m.played_at,
sys.name AS system_name,
@ -595,21 +592,20 @@ def get_submitted_matches(player_id):
FROM matches m
JOIN gamesystems sys ON m.gamesystem_id = sys.id
JOIN players p2 ON m.player2_id = p2.id
WHERE m.player1_id = ? AND m.player2_check = 0
WHERE m.player1_id = %s AND m.player2_check = 0
"""
cursor.execute(query, (player_id,))
rows = cursor.fetchall()
connection.close()
return [dict(row) for row in rows]
cursor.close()
connection.close()
return rows
def get_match_history_log(player_id):
"""Holt ALLE Matches eines Spielers inklusive der MMR-Änderungen und Faktoren für das Log."""
connection = sqlite3.connect(DB_PATH)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
"""Holt ALLE Matches eines Spielers inkl. MMR-Änderungen und Faktoren."""
connection = db_connection()
cursor = connection.cursor(dictionary=True)
query = """
SELECT m.played_at, sys.name AS gamesystem_name,
@ -625,54 +621,12 @@ def get_match_history_log(player_id):
JOIN gamesystems sys ON m.gamesystem_id = sys.id
JOIN players p1 ON m.player1_id = p1.id
JOIN players p2 ON m.player2_id = p2.id
WHERE m.player1_id = ? OR m.player2_id = ?
WHERE m.player1_id = %s OR m.player2_id = %s
ORDER BY m.played_at DESC
"""
cursor.execute(query, (player_id, player_id))
rows = cursor.fetchall()
cursor.close()
connection.close()
return [dict(row) for row in rows]
# -----------------------------------------------------
# Testing and Prototyping
# -----------------------------------------------------
def create_random_dummy_match(player_id):
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# 1. Die ID des Dummys suchen (Wir suchen einfach nach dem Namen 'Dummy')
cursor.execute("SELECT id FROM players WHERE display_name LIKE '%Dummy%' OR discord_name LIKE '%Dummy%' LIMIT 1")
dummy_row = cursor.fetchone()
if not dummy_row:
connection.close()
print("Fehler: Kein Dummy in der Datenbank gefunden!")
return False
dummy_id = dummy_row[0]
# 2. Zufällige Punkte generieren (z.B. zwischen 0 und 100)
score_p1 = random.randint(0, 100)
score_p2 = random.randint(0, 100)
# 3. Das Match hart in System 1 (gamesystem_id = 1) eintragen
query = """
INSERT INTO matches (gamesystem_id, player2_id, player1_id, score_player1, score_player2, player2_check)
VALUES (?, ?, ?, ?, ?, 0)
"""
cursor.execute(query, (1, player_id, dummy_id, score_p1, score_p2))
connection.commit()
connection.close()
p1_name = get_player_name(player_id)
dummy_name = get_player_name(dummy_id)
sys_name = get_system_name(1)
logger.log(f"Zufallsspiel generiert. [{sys_name}]: {p1_name} ({score_p1}) vs. {dummy_name} ({score_p2})")
return True
return rows

View File

@ -1,27 +1,23 @@
import sqlite3
import mariadb
import os
from data import setup_database, data_api
from dotenv import load_dotenv
from data.setup_database import DB_PATH
load_dotenv()
def db_connection():
try:
connection = mariadb.connect(
host=os.getenv("DB_HOST", "localhost"),
port=int(os.getenv("DB_PORT", 3306)),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASSWORD"),
database=os.getenv("DB_NAME")
)
return connection
except mariadb.Error as e:
raise RuntimeError(f"Fehler beim Verbinden mit DB: {e}")
if __name__ == "__main__":
setup_database.init_db()
def check_db():
# Prüfen, ob die Datei existiert
db_file = DB_PATH
if not os.path.exists(db_file):
print(f"WARNUNG: '{db_file}' nicht gefunden!")
print("Starte Datenbank-Einrichtung...")
# 1. Erstellt die leere Datei und alle Tabellen-Strukturen
setup_database.init_db()
print("Datenbank aufgebaut!")
else:
print(f"OK: Datenbank '{db_file}' gefunden. Lade System...")

View File

@ -1,222 +0,0 @@
import sqlite3
import os
import json
from dotenv import load_dotenv
load_dotenv()
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "league_database.db")
dummy_is_in = True
def init_db():
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# Fremdschlüssel (Foreign Keys) in SQLite aktivieren
cursor.execute('PRAGMA foreign_keys = ON;')
# 1. Tabelle: players (Stammdaten)
cursor.execute('''
CREATE TABLE IF NOT EXISTS players (
id INTEGER PRIMARY KEY AUTOINCREMENT,
discord_id TEXT UNIQUE,
discord_name TEXT NOT NULL,
discord_avatar_url TEXT,
display_name TEXT
)
''')
# 2. Tabelle: gamesystems (Globale Spielsysteme)
cursor.execute('''
CREATE TABLE IF NOT EXISTS gamesystems (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
picture TEXT,
description TEXT,
active_players INTEGER DEFAULT 0,
min_score INTEGER DEFAULT 0,
max_score INTEGER DEFAULT 100
)
''')
# 3. Tabelle: player_game_statistic (Welcher Spieler hat in welchem System welche Stats?)
cursor.execute('''
CREATE TABLE IF NOT EXISTS player_game_statistic (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_id INTEGER,
gamesystem_id INTEGER,
mmr INTEGER DEFAULT 1000,
games_in_system INTEGER DEFAULT 0,
points INTEGER DEFAULT 0,
avv_points INTEGER DEFAULT 0,
last_played TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
win_rate REAL DEFAULT 0,
win_streak INTEGER DEFAULT 0,
trend INTEGER DEFAULT 0,
tyrann_id INTEGER,
pushover_id INTEGER,
nemesis_id INTEGER,
FOREIGN KEY (player_id) REFERENCES players (id),
FOREIGN KEY (tyrann_id) REFERENCES players (id),
FOREIGN KEY (pushover_id) REFERENCES players (id),
FOREIGN KEY (nemesis_id) REFERENCES players (id),
FOREIGN KEY (gamesystem_id) REFERENCES gamesystems (id)
)
''')
# 4. Tabelle: matches (Wer hat wann gegen wen in welchem System gespielt?)
cursor.execute('''
CREATE TABLE IF NOT EXISTS matches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
gamesystem_id INTEGER,
player1_id INTEGER,
score_player1 INTEGER,
player2_id INTEGER,
score_player2 INTEGER,
played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
player1_base_change INTEGER,
player1_khorne INTEGER,
player1_slaanesh INTEGER,
player1_tzeentch INTEGER,
player1_mmr_change INTEGER,
player2_base_change INTEGER,
player2_khorne INTEGER,
player2_slaanesh INTEGER,
player2_tzeentch INTEGER,
player2_mmr_change INTEGER,
rust_factor REAL,
elo_factor REAL,
point_inflation REAL,
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)
)
''')
# 5. Tabelle: achievements (Der globale Katalog aller möglichen Erfolge - Stammdaten)
cursor.execute('''
CREATE TABLE IF NOT EXISTS achievements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT NOT NULL,
icon TEXT
)
''')
# 6. Tabelle: player_achievements. Wer hat welchen Erfolg wann bekommen
cursor.execute('''
CREATE TABLE IF NOT EXISTS player_achievements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_id INTEGER,
achievement_id INTEGER,
earned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
earned_in INTEGER,
FOREIGN KEY (earned_in) REFERENCES gamesystems (id),
FOREIGN KEY (player_id) REFERENCES players (id),
FOREIGN KEY (achievement_id) REFERENCES achievements (id)
)
''')
connection.commit()
connection.close()
print("Datenbank Struktur aufgebaut")
#nächster Schritt: Standard Spielsysteme eintragen
seed_gamesystems()
# --------------------
# --- Start Setup der DB Daten. Damit sie nicht GANZ leer sind.
# --------------------
def seed_gamesystems():
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
systems = [
("Warhammer 40k", "40k_logo.png", "Die Schlacht um die Galaxie in einer entfernten Zukunft.", 0, 100),
("Warhammer Age of Sigmar", "aos_logo.png", "Der ewige Krieg um die Reiche der Sterblichen in einer epischen Fantasy-Welt.", 0, 50),
("Spearhead", "aos_logo.png", "Schnelle und taktische Scharmützel für actiongeladene Kämpfe.", 0, 50)
]
# executemany ist eine extrem schnelle SQL For-Schleife für Inserts
cursor.executemany("INSERT INTO gamesystems (name, picture, description, min_score, max_score) VALUES (?, ?, ?, ?, ?)", systems)
connection.commit()
connection.close()
print("Spielsysteme angelegt!")
#Nächster Schritt: Standard Achievments eintragen.
seed_achievements()
def seed_achievements():
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# Unsere Start-Stückliste an Erfolgen (Name, Beschreibung, Icon)
achievements = [
("Kingslayer", "Hat den Spieler auf Platz 1 besiegt!", "👑"),
("Unstoppable", "3 Spiele in Folge gewonnen.", "🔥"),
("First Blood", "Das allererste Ligaspiel absolviert.", "🩸")
]
cursor.executemany("INSERT INTO achievements (name, description, icon) VALUES (?, ?, ?)", achievements)
connection.commit()
connection.close()
print("Achievements angelegt.")
if dummy_is_in:
seed_dummy_player()
def seed_dummy_player():
connection = sqlite3.connect(DB_PATH)
cursor = connection.cursor()
# 1. Dummy-Spieler anlegen (falls noch nicht vorhanden)
query_player = """
INSERT OR IGNORE INTO players
(discord_id, discord_name, display_name, discord_avatar_url)
VALUES (?, ?, ?, ?)
"""
cursor.execute(query_player, ("dummy_001", "dummy_user", "Dummy Mc DummDumm", ""))
# 2. Wir holen uns die ID des Dummys (egal ob neu oder alt)
cursor.execute("SELECT id FROM players WHERE discord_id = 'dummy_001'")
dummy_row = cursor.fetchone()
if dummy_row:
dummy_id = dummy_row[0]
# 3. Wir holen alle IDs der aktuellen Spielsysteme
cursor.execute("SELECT id FROM gamesystems")
systems = cursor.fetchall()
# 4. Wir gehen jedes System durch und prüfen, ob er schon drin ist
for sys in systems:
sys_id = sys[0]
cursor.execute("SELECT id FROM player_game_statistic WHERE player_id = ? AND gamesystem_id = ?", (dummy_id, sys_id))
is_in_league = cursor.fetchone()
if not is_in_league:
# Er ist noch nicht drin -> Eintragen! (MMR startet durch DEFAULT automatisch bei 1000)
cursor.execute("""
INSERT INTO player_game_statistic (player_id, gamesystem_id)
VALUES (?, ?)
""", (dummy_id, sys_id))
connection.commit()
connection.close()
print("Test-Gegner 'Dummy Mc DummDumm' ist bereit und in allen Ligen angemeldet!")

View File

@ -89,9 +89,16 @@ def setup_login_routes():
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['display_name'] = player[2]
app.storage.user['discord_avatar_url'] = player[3]
# Kompatibel mit dict (MariaDB) UND tuple (SQLite)
if isinstance(player, dict):
app.storage.user['db_id'] = player['id'] # oder der echte Spaltenname
app.storage.user['display_name'] = player['display_name']
app.storage.user['discord_avatar_url'] = player['discord_avatar_url']
else:
app.storage.user['db_id'] = player[0]
app.storage.user['display_name'] = player[2]
app.storage.user['discord_avatar_url'] = player['discord_avatar_url']
ui.navigate.to('/')
else:

View File

@ -5,7 +5,7 @@
],
"mmr_info": [
"**MMR Punkte** sind die Liga Punkte um die gespielt wird.",
"Verliert man ein Spiel, verliert man Punkte. Und umgekehrt."
"Auf der Hauptseite findest du eine detailierte Übersicht über deine letzen Spiele und die Punkte die du verloren oder gewonnen hast."
],
"match_form_info": [
"Um ein Spiel einzutragen gibt einfach deine Punkte ein. Wähle deinen Gegner aus. Und gibt seine Punkte ein.",
@ -25,5 +25,29 @@
"Verlierer: (-l_base + l_khorne - slaanesh - tzeentch) * rust_factor)"
],
"tyrann_info": [],
"prügelknabe_info": []
"prügelknabe_info": [],
"rang_info": [
"Der Rang ergibt sich aus den MMR Punkten. Je mehr Punkte du hast, desto höher bist du wahrscheinlich in der Rangliste.",
"MMR Punkte werden nach jedem Spiel errechnet."
],
"khorne_rule_info": [
"Khorne mag es wenn Blut fließt!",
"Deshalb schenkt er 8 Punkte den Spielern die oft Spielen. Ist dein letztes Spiel in dieser Liga noch keine 16 Tage her, kriegst du 8 Bonus Punkte. Egal ob du gewonnen oder verloren hast."
],
"tzeentch_rule_info": [
"Der Gebieter der Intriegen mag es wenn geschmiedete Pläne aufgehen. Wenn du deinen Gegner mit einem großen Punkte abstand besiegst, gewährt er dir extra Puntke.",
"Sieger Punkte - Verlierer Punkte / Maximal Punkte im Spielsystem. Ergibt 0-9 Punkte, je nach Abstand."
],
"slaanesh_rule_info": [
"Diese Regel wird noch entworfen. Irgend eine Funktion mit dem ***Rivale*** und ***Prügelknabe***. Updates folgen!"
],
"rust_factor_info": [
"Hat ein Spieler schon länger als 30 Tage in einem System nicht mehr gespielt, wird ein Multiplikator auf die gewonnenen oder verlorenen MMR Punkte angewandt.",
"Bei 30 Tagen startet der Rost-Faktor bei 0,8 (80%) und erhöht sich bis 0,1 (10%) bei 90 Tagen.",
"Der Rost Faktor soll verhindern dass Gelegenheitsspieler mit einem Spiel die ganze Rangliste umkrempeln. Oder als ***Schutz*** für Wiedereinsteiger."
],
"elo_factor_info": [
"Der Elo Faktor errechnet sich aus den Sieger MMR Punkte und des Verlierer MMR Punkte. Für genauere Infos: Google=>'Schach Elo'",
"im Prinzip gibt er an wie wahrscheinlich es war dass der Sieger gewonnen hat. Elo 0,5 entspricht 50%. Je höher der Elo Faktor desto **weniger** Base Punkte kriegt der Sieger. Oder verliert der Verlierer."
]
}

View File

@ -70,7 +70,10 @@ def setup_routes():
ui.label(str(mmr)).classes('text-4xl font-bold text-accent')
with ui.card().classes("w-full items-center justify-center text-center"):
ui.label("Rang: ").classes('text-2xl font-bold')
with ui.row().classes("w-full items-center text-center"):
ui.label("Rang: ").classes('justify-center text-2xl font-bold text-normaltext')
ui.space()
info_system.create_info_button("rang_info")
ui.label(str(my_rank)).classes('text-4xl font-bold text-blue-100')
with ui.card().classes("w-full lg:col-span-2"):

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -23,10 +23,8 @@ client_secret = os.getenv("DISCORD_CLIENT_SECRET")
admin_discord_id = os.getenv("ADMIN")
url = os.getenv("APP_URL")
database.check_db()
logger.setup_log_db()
# 3. Seitenrouten aufbauen
main_gui.setup_routes(admin_discord_id)
discord_login.setup_login_routes()

View File

@ -0,0 +1,25 @@
from data import data_api
from wood import logger
import sqlite3
def check_player_achievments(player_id):
# Schauen ob der Spieler sich nach dem Match (function wird aufgerufen NACHDEM ein Match berechnet wurde) sich ein Achievment verdient hat.
game_stats = data_api.get_player_statistic(player_id, system_id)
# Checken ob die Gamestats da sind und geladen sind.
if not game_stats:
logger.log(f"no game_stats could be loaded for PlayerID {player_id}.")
return
else:
if game_stats["mmr"] is None:
logger.log(f"PlayerID {player_id} has no game statistics AFTER match got calculated.")
return
played_games_achievment(player_id)
def played_games_achievment(player_id):
return

View File

@ -3,6 +3,7 @@ from match_calculations import calculation
import json
import os
from wood import logger
from match_calculations.achievments import achievments
point_inflation = 1 # => entspricht %
@ -96,6 +97,9 @@ def calculate_match (match_id):
logger.log(f"Match{match_id}: Winner {data_api.get_player_name(winner_id)}: Base {w_base} + Khorne({w_khorne}) + Slaanesh({slaanesh}) + Tzeentch({tzeentch}) = {calc_results[winner_id]["total"]}")
logger.log(f"Match{match_id}: Looser {data_api.get_player_name(looser_id)}: -Base({l_base}) + Khorne({l_khorne}) - Slaanesh({slaanesh}) - Tzeentch({tzeentch}) = {calc_results[looser_id]["total"]}")
data_api.save_calculated_match(calc_results)
achievments.check_player_achievments(winner_id, system_id)
achievments.check_player_achievments(looser_id, system_id)
def determine_draw_diff(sys_id):

View File

@ -1,3 +0,0 @@
nicegui
requests
python-dotenv

View File

@ -1,15 +1,88 @@
#!/bin/bash
# 1. Erstellt die virtuelle Umgebung im Ordner ".venv" (falls sie nicht existiert)
# Sicherstellen dass das Script abbricht wenn ein Fehler passiert
# -e: Beendet das Script sofort wenn ein Befehl einen Fehlercode zurückgibt (Exit-Code != 0)
set -e
echo "✅ Starte einrichtung der Liga Umgebung. Updates und System Abhängigkeiten werden installiert ..."
# Prüfen ob der Liga-Service auf dem System installiert ist
# systemctl list-unit-files zeigt alle registrierten Service-Dateien
SERVICE_EXISTS=false
if systemctl list-unit-files --type service | grep -q "liga.service"; then
# Service existiert → sicher anhalten bevor Updates kommen
sudo systemctl stop liga
SERVICE_EXISTS=true
echo "✅ Liga Service wurde gestoppt."
else
# Service nicht installiert → kein Fehler, nur Info
echo " Liga Service nicht gefunden. Wird übersprungen."
fi
# 1. System updaten
# apt update: Lädt die aktuellen Paketlisten vom Server herunter
# apt upgrade -y: Installiert alle verfügbaren Updates, -y bestätigt automatisch
sudo apt update && sudo apt upgrade -y
# 2. System-Abhängigkeiten installieren
# -y: Automatisch mit "Ja" bestätigen
sudo apt install -y \
python3 \
python3-venv \
python3-full \
libmariadb3 \
libmariadb-dev
echo "✅ Updates installiert. Überprüfe Softwarestand von Git ..."
# Git Pull: Holt die neuesten Änderungen vom Remote-Repository
# und merged sie in den lokalen Branch
git pull
echo "✅ Softwarestand von Git aktuell. Richte Umgebung ein ..."
# 3. Virtuelle Umgebung erstellen (falls noch nicht vorhanden)
# Wenn .venv schon existiert wird es einfach aktualisiert/überschrieben
python3 -m venv .venv
# 2. Aktiviert die virtuelle Umgebung für dieses Skript
# 4. Virtuelle Umgebung aktivieren
# WICHTIG: Da wir in einem Bash-Script sind, gilt die Aktivierung nur für dieses Script
source .venv/bin/activate
# 3. Aktualisiert das Installationsprogramm "pip" auf die neueste Version
echo "✅ Umgebung eingerichtet! Installiere Pakete ..."
# 5. pip aktualisieren
# pip ist der Paketmanager für Python sollte aktuell sein um Kompatibilitätsprobleme zu vermeiden
pip install --upgrade pip
# 4. Installiert alle Pakete aus deiner "Stückliste"
pip install -r requirements.txt
# 6. Python-Pakete installieren
pip install \
nicegui \
requests \
python-dotenv \
mariadb
echo "✅ Pakete installiert. Starte Liga Service ..."
# Prüfen ob der Service vorher existierte und versuche ihn zu starten
if [ "$SERVICE_EXISTS" = true ]; then
# Service existiert bereits auf dem System → versuchen zu starten
# systemctl start gibt Exit-Code 0 bei Erfolg, != 0 bei Fehler
if sudo systemctl start liga; then
echo "✅ Liga Service erfolgreich gestartet. Alles bereit!"
else
# Service konnte nicht gestartet werden mögliche Ursachen:
# - Konfigurationsfehler in der .service Datei
# - Python-Fehler beim Start (Import-Error, Syntax-Error etc.)
# - Port bereits belegt
# - Fehlende Berechtigungen
echo "❌ FEHLER: Liga Service konnte nicht gestartet werden!"
echo " Prüfe mit: sudo systemctl status liga"
echo " Prüfe mit: sudo journalctl -u liga --no-pager -n 50"
exit 1
fi
else
# Service war vorher auch nicht installiert Info an den User
echo " Liga Service nicht installiert. Manuell starten oder Service erst einrichten."
fi
echo "Umgebung wurde erfolgreich eingerichtet!"

View File

@ -2,7 +2,6 @@ import sqlite3
import os
import inspect
from data.setup_database import DB_PATH
DB_FILE = "wood/log.db"