modified: app.py

This commit is contained in:
SimolZimol
2025-08-24 01:03:36 +02:00
parent b718779bbf
commit 3939442236

277
app.py
View File

@@ -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,16 +126,141 @@ 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(
"""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
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,6 +383,9 @@ 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 = None
cursor = None
try:
connection = get_db_connection()
cursor = connection.cursor(dictionary=True)
@@ -224,15 +393,38 @@ def load_user_data():
user_guilds = session.get("discord_guilds", [])
user_guild_ids = [guild["id"] for guild in user_guilds]
if user_guild_ids:
# 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)
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}]
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)