From 39394422363425cdd6f981d8643742e5360b5cb8 Mon Sep 17 00:00:00 2001 From: SimolZimol <70102430+SimolZimol@users.noreply.github.com> Date: Sun, 24 Aug 2025 01:03:36 +0200 Subject: [PATCH] modified: app.py --- app.py | 311 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 289 insertions(+), 22 deletions(-) diff --git a/app.py b/app.py index 6738a60..92d5b99 100644 --- a/app.py +++ b/app.py @@ -8,9 +8,11 @@ import os import subprocess import psutil import mysql.connector +from mysql.connector import pooling from datetime import datetime, timedelta from flask_session import Session import logging +import time app = Flask(__name__) app.secret_key = os.getenv("FLASK_SECRET_KEY") @@ -50,6 +52,45 @@ DB_USER = os.getenv("DB_USER") DB_PASS = os.getenv("DB_PASSWORD") DB_NAME = os.getenv("DB_DATABASE") +# Connection Pool Konfiguration +DB_POOL_CONFIG = { + 'pool_name': 'multus_pool', + 'pool_size': 5, + 'pool_reset_session': True, + 'host': DB_HOST, + 'port': DB_PORT, + 'user': DB_USER, + 'password': DB_PASS, + 'database': DB_NAME, + 'autocommit': True, + 'connection_timeout': 20, + 'charset': 'utf8mb4', + 'collation': 'utf8mb4_unicode_ci', + 'buffered': True, + 'raise_on_warnings': False, + 'use_unicode': True, + 'sql_mode': '', + 'max_allowed_packet': 16777216, + 'auth_plugin': 'mysql_native_password' +} + +# Globaler Connection Pool +db_pool = None + +def initialize_db_pool(): + """Initialisiert den Datenbankverbindungspool.""" + global db_pool + try: + db_pool = pooling.MySQLConnectionPool(**DB_POOL_CONFIG) + print("✅ Datenbankverbindungspool erfolgreich initialisiert") + return True + except mysql.connector.Error as err: + print(f"❌ Fehler beim Initialisieren des Datenbankpools: {err}") + return False + except Exception as err: + print(f"❌ Unerwarteter Fehler beim Pool-Setup: {err}") + return False + DISCORD_CLIENT_ID = os.getenv("DISCORD_CLIENT_ID") DISCORD_CLIENT_SECRET = os.getenv("DISCORD_CLIENT_SECRET") DISCORD_REDIRECT_URI = os.getenv("DISCORD_REDIRECT_URI") @@ -85,15 +126,140 @@ def stop_bot(): else: print("Bot läuft nicht.") +def test_db_connection(): + """Testet die Datenbankverbindung.""" + try: + connection = get_db_connection() + if connection.is_connected(): + print("Datenbankverbindung erfolgreich!") + connection.close() + return True + except Exception as err: + print(f"Datenbankverbindungstest fehlgeschlagen: {err}") + return False + return False + +def safe_db_operation(operation_func, *args, **kwargs): + """Führt eine Datenbankoperation sicher aus mit Fehlerbehandlung und automatischem Connection-Management.""" + connection = None + cursor = None + try: + connection = get_db_connection() + cursor = connection.cursor(dictionary=True) + + result = operation_func(cursor, *args, **kwargs) + + # Commit falls nötig (auch wenn autocommit=True ist) + if connection.in_transaction: + connection.commit() + + return result + + except mysql.connector.Error as err: + print(f"Datenbankfehler in safe_db_operation: {err}") + if connection and connection.in_transaction: + try: + connection.rollback() + except: + pass + return None + + except Exception as err: + print(f"Unerwarteter Fehler bei Datenbankoperation: {err}") + if connection and connection.in_transaction: + try: + connection.rollback() + except: + pass + return None + + finally: + # Ressourcen sauber freigeben + if cursor: + try: + cursor.close() + except: + pass + if connection: + try: + if connection.is_connected(): + connection.close() + except: + pass + def get_db_connection(): - """Stellt eine Verbindung zur MySQL-Datenbank her.""" - return mysql.connector.connect( - host=DB_HOST, - port=DB_PORT, - user=DB_USER, - password=DB_PASS, - database=DB_NAME - ) + """Holt eine Verbindung aus dem Connection Pool mit Fallback.""" + global db_pool + max_retries = 3 + retry_delay = 1 + + for attempt in range(max_retries): + try: + # Versuche zuerst den Pool zu verwenden + if db_pool: + connection = db_pool.get_connection() + if connection.is_connected(): + # Test die Verbindung + cursor = connection.cursor() + cursor.execute("SELECT 1") + cursor.fetchone() + cursor.close() + return connection + + # Fallback: Direkte Verbindung ohne Pool + print(f"Pool nicht verfügbar, verwende direkte Verbindung (Versuch {attempt + 1})") + connection = mysql.connector.connect( + host=DB_HOST, + port=DB_PORT, + user=DB_USER, + password=DB_PASS, + database=DB_NAME, + autocommit=True, + connection_timeout=20, + reconnect=True, + charset='utf8mb4', + collation='utf8mb4_unicode_ci', + buffered=True, + raise_on_warnings=False, + use_unicode=True, + sql_mode='', + max_allowed_packet=16777216, + auth_plugin='mysql_native_password' + ) + + if connection.is_connected(): + cursor = connection.cursor() + cursor.execute("SELECT 1") + cursor.fetchone() + cursor.close() + return connection + + except mysql.connector.Error as err: + print(f"Datenbankverbindung Versuch {attempt + 1} fehlgeschlagen: {err}") + if err.errno == 2013: + print("⚠️ Spezifischer Fehler: Kommunikationspakete verloren - Netzwerkproblem") + elif err.errno == 2003: + print("⚠️ Spezifischer Fehler: Kann MySQL-Server nicht erreichen") + elif err.errno == 1045: + print("⚠️ Spezifischer Fehler: Authentifizierung fehlgeschlagen") + + if attempt < max_retries - 1: + print(f"Warte {retry_delay} Sekunden vor nächstem Versuch...") + time.sleep(retry_delay) + retry_delay *= 2 + else: + print("❌ Alle Datenbankverbindungsversuche fehlgeschlagen") + raise + + except Exception as err: + print(f"Unerwarteter Fehler bei Datenbankverbindung: {err}") + if attempt < max_retries - 1: + time.sleep(retry_delay) + retry_delay *= 2 + else: + raise + + raise mysql.connector.Error("Konnte keine Datenbankverbindung herstellen") def token_updater(token): session['oauth_token'] = token @@ -217,22 +383,48 @@ def load_user_data(): g.bot_running = bot_status() # Lädt den Bot-Status in g # Hole die Liste der Gilden aus der Datenbank - connection = get_db_connection() - cursor = connection.cursor(dictionary=True) - - # Lade die Gilden des Nutzers - user_guilds = session.get("discord_guilds", []) - user_guild_ids = [guild["id"] for guild in user_guilds] + connection = None + cursor = None + try: + connection = get_db_connection() + cursor = connection.cursor(dictionary=True) + + # Lade die Gilden des Nutzers + user_guilds = session.get("discord_guilds", []) + user_guild_ids = [guild["id"] for guild in user_guilds] - # Finde nur die Gilden, die auch in der Datenbank existieren - cursor.execute("SELECT guild_id FROM guilds WHERE guild_id IN (%s)" % ','.join(['%s'] * len(user_guild_ids)), user_guild_ids) - existing_guilds = cursor.fetchall() + if user_guild_ids: + # Finde nur die Gilden, die auch in der Datenbank existieren + placeholders = ','.join(['%s'] * len(user_guild_ids)) + query = f"SELECT guild_id FROM guilds WHERE guild_id IN ({placeholders})" + cursor.execute(query, user_guild_ids) + existing_guilds = cursor.fetchall() - # Filtere die Gilden des Nutzers basierend auf der Existenz in der Datenbank - g.guilds = [guild for guild in user_guilds if int(guild["id"]) in {g["guild_id"] for g in existing_guilds}] - - cursor.close() - connection.close() + # Filtere die Gilden des Nutzers basierend auf der Existenz in der Datenbank + existing_guild_ids = {g["guild_id"] for g in existing_guilds} + g.guilds = [guild for guild in user_guilds if int(guild["id"]) in existing_guild_ids] + else: + g.guilds = [] + + except mysql.connector.Error as err: + print(f"Datenbankfehler in load_user_data: {err}") + g.guilds = [] # Fallback auf leere Liste + except Exception as err: + print(f"Unerwarteter Fehler in load_user_data: {err}") + g.guilds = [] # Fallback auf leere Liste + finally: + # Ressourcen sauber freigeben + if cursor: + try: + cursor.close() + except: + pass + if connection: + try: + if connection.is_connected(): + connection.close() + except: + pass else: # Falls der Benutzer nicht eingeloggt ist, keine Daten setzen g.user_info = None @@ -807,11 +999,86 @@ def stop(): return redirect(url_for("global_admin_dashboard")) return redirect(url_for("landing_page")) +@app.route("/health") +def health_check(): + """Health-Check-Endpunkt für die Anwendung und Datenbank.""" + global db_pool + health_status = { + "status": "healthy", + "timestamp": datetime.now().isoformat(), + "database": "disconnected", + "connection_pool": "not_initialized", + "version": __version__ + } + + # Prüfe Connection Pool Status + if db_pool: + try: + health_status["connection_pool"] = "active" + health_status["pool_size"] = db_pool.pool_size + except: + health_status["connection_pool"] = "error" + + # Teste die Datenbankverbindung + if test_db_connection(): + health_status["database"] = "connected" + return jsonify(health_status), 200 + else: + health_status["status"] = "unhealthy" + health_status["database"] = "error" + return jsonify(health_status), 503 + +@app.route("/db_stats") +def db_stats(): + """Zeigt Datenbankstatistiken an (nur für Admins).""" + if not g.get('is_admin', False): + return jsonify({"error": "Unauthorized"}), 403 + + global db_pool + stats = { + "timestamp": datetime.now().isoformat(), + "pool_status": "not_initialized", + "version": __version__ + } + + if db_pool: + try: + stats.update({ + "pool_status": "active", + "pool_name": db_pool.pool_name, + "pool_size": db_pool.pool_size, + "pool_reset_session": DB_POOL_CONFIG.get('pool_reset_session', False) + }) + except Exception as e: + stats["pool_status"] = f"error: {str(e)}" + + return jsonify(stats) + if __name__ == "__main__": # Disable default Flask logging for static files app.logger.disabled = True log = logging.getLogger('werkzeug') log.disabled = True + # Initialisiere Datenbankverbindungspool + print("Initialisiere Datenbankverbindungspool...") + if initialize_db_pool(): + print("✅ Connection Pool erfolgreich erstellt!") + else: + print("⚠️ Connection Pool konnte nicht erstellt werden, verwende direkte Verbindungen") + + # Test database connection on startup + print("Teste Datenbankverbindung beim Start...") + if test_db_connection(): + print("✅ Datenbankverbindung erfolgreich!") + else: + print("❌ Datenbankverbindung fehlgeschlagen! Überprüfe die Konfiguration.") + print("Die Anwendung wird trotzdem gestartet, aber Datenbankfunktionen sind nicht verfügbar.") + print("Häufige Ursachen:") + print(" - MySQL-Server ist nicht erreichbar") + print(" - Firewall blockiert die Verbindung") + print(" - Falsche Verbindungsparameter") + print(" - Netzwerkprobleme zwischen Client und Server") + # Start app with minimal logging app.run(host="0.0.0.0", port=5000, debug=True)