""" Datenbankmanager für den Social Media Account Generator. """ import os import json import sqlite3 import logging from datetime import datetime from typing import Dict, List, Any, Optional, Tuple, Union logger = logging.getLogger("db_manager") class DatabaseManager: """Klasse zur Verwaltung der Datenbank für Account-Informationen.""" def __init__(self, db_path: str = "database/accounts.db"): """ Initialisiert den DatabaseManager. Args: db_path: Pfad zur Datenbank-Datei """ self.db_path = db_path # Stelle sicher, dass das Datenbankverzeichnis existiert os.makedirs(os.path.dirname(self.db_path), exist_ok=True) # Datenbank initialisieren self.init_db() def init_db(self) -> None: """Initialisiert die Datenbank und erstellt die benötigten Tabellen, wenn sie nicht existieren.""" try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # Accounts-Tabelle erstellen cursor.execute(''' CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, platform TEXT NOT NULL, username TEXT NOT NULL, password TEXT NOT NULL, email TEXT, phone TEXT, full_name TEXT, created_at TEXT NOT NULL, last_login TEXT, notes TEXT, cookies TEXT, status TEXT ) ''') # Settings-Tabelle erstellen cursor.execute(''' CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL ) ''') conn.commit() conn.close() logger.info("Datenbank initialisiert") except sqlite3.Error as e: logger.error(f"Fehler bei der Datenbankinitialisierung: {e}") def add_account(self, account_data: Dict[str, Any]) -> int: """ Fügt einen Account zur Datenbank hinzu. Args: account_data: Dictionary mit Account-Daten Returns: ID des hinzugefügten Accounts oder -1 im Fehlerfall """ try: # Prüfe, ob erforderliche Felder vorhanden sind required_fields = ["platform", "username", "password"] for field in required_fields: if field not in account_data: logger.error(f"Fehlendes Pflichtfeld: {field}") return -1 # Sicherstellen, dass created_at vorhanden ist if "created_at" not in account_data: account_data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # SQL-Anweisung vorbereiten fields = ", ".join(account_data.keys()) placeholders = ", ".join(["?" for _ in account_data]) query = f"INSERT INTO accounts ({fields}) VALUES ({placeholders})" # Anweisung ausführen cursor.execute(query, list(account_data.values())) # ID des hinzugefügten Datensatzes abrufen account_id = cursor.lastrowid conn.commit() conn.close() logger.info(f"Account hinzugefügt: {account_data['username']} (ID: {account_id})") return account_id except sqlite3.Error as e: logger.error(f"Fehler beim Hinzufügen des Accounts: {e}") return -1 def get_account(self, account_id: int) -> Optional[Dict[str, Any]]: """ Gibt einen Account anhand seiner ID zurück. Args: account_id: ID des Accounts Returns: Dictionary mit Account-Daten oder None, wenn der Account nicht gefunden wurde """ try: conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row # Für dict-like Zugriff auf Zeilen cursor = conn.cursor() cursor.execute("SELECT * FROM accounts WHERE id = ?", (account_id,)) row = cursor.fetchone() conn.close() if row: # Konvertiere Row in Dictionary account = dict(row) logger.debug(f"Account gefunden: {account['username']} (ID: {account_id})") return account else: logger.warning(f"Account nicht gefunden: ID {account_id}") return None except sqlite3.Error as e: logger.error(f"Fehler beim Abrufen des Accounts: {e}") return None def get_all_accounts(self) -> List[Dict[str, Any]]: """ Gibt alle Accounts zurück. Returns: Liste von Dictionaries mit Account-Daten """ try: conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute("SELECT * FROM accounts ORDER BY id DESC") rows = cursor.fetchall() conn.close() # Konvertiere Rows in Dictionaries accounts = [dict(row) for row in rows] logger.info(f"{len(accounts)} Accounts abgerufen") return accounts except sqlite3.Error as e: logger.error(f"Fehler beim Abrufen aller Accounts: {e}") return [] def get_accounts_by_platform(self, platform: str) -> List[Dict[str, Any]]: """ Gibt alle Accounts einer bestimmten Plattform zurück. Args: platform: Plattformname (z.B. "instagram") Returns: Liste von Dictionaries mit Account-Daten """ try: conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute("SELECT * FROM accounts WHERE platform = ? ORDER BY id DESC", (platform.lower(),)) rows = cursor.fetchall() conn.close() # Konvertiere Rows in Dictionaries accounts = [dict(row) for row in rows] logger.info(f"{len(accounts)} Accounts für Plattform '{platform}' abgerufen") return accounts except sqlite3.Error as e: logger.error(f"Fehler beim Abrufen der Accounts für Plattform '{platform}': {e}") return [] def update_account(self, account_id: int, update_data: Dict[str, Any]) -> bool: """ Aktualisiert einen Account in der Datenbank. Args: account_id: ID des zu aktualisierenden Accounts update_data: Dictionary mit zu aktualisierenden Feldern Returns: True bei Erfolg, False im Fehlerfall """ if not update_data: logger.warning("Keine Aktualisierungsdaten bereitgestellt") return False try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # SQL-Anweisung vorbereiten set_clause = ", ".join([f"{field} = ?" for field in update_data.keys()]) values = list(update_data.values()) values.append(account_id) query = f"UPDATE accounts SET {set_clause} WHERE id = ?" # Anweisung ausführen cursor.execute(query, values) conn.commit() conn.close() logger.info(f"Account aktualisiert: ID {account_id}") return True except sqlite3.Error as e: logger.error(f"Fehler beim Aktualisieren des Accounts: {e}") return False def delete_account(self, account_id: int) -> bool: """ Löscht einen Account aus der Datenbank. Args: account_id: ID des zu löschenden Accounts Returns: True bei Erfolg, False im Fehlerfall """ try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("DELETE FROM accounts WHERE id = ?", (account_id,)) conn.commit() conn.close() logger.info(f"Account gelöscht: ID {account_id}") return True except sqlite3.Error as e: logger.error(f"Fehler beim Löschen des Accounts: {e}") return False def search_accounts(self, query: str, platform: Optional[str] = None) -> List[Dict[str, Any]]: """ Sucht nach Accounts in der Datenbank. Args: query: Suchbegriff platform: Optional, Plattform für die Einschränkung der Suche Returns: Liste von Dictionaries mit gefundenen Account-Daten """ try: conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row cursor = conn.cursor() # Suchbegriff für LIKE-Operator vorbereiten search_term = f"%{query}%" if platform: query_sql = """ SELECT * FROM accounts WHERE (username LIKE ? OR email LIKE ? OR phone LIKE ? OR full_name LIKE ?) AND platform = ? ORDER BY id DESC """ cursor.execute(query_sql, (search_term, search_term, search_term, search_term, platform.lower())) else: query_sql = """ SELECT * FROM accounts WHERE username LIKE ? OR email LIKE ? OR phone LIKE ? OR full_name LIKE ? ORDER BY id DESC """ cursor.execute(query_sql, (search_term, search_term, search_term, search_term)) rows = cursor.fetchall() conn.close() # Konvertiere Rows in Dictionaries accounts = [dict(row) for row in rows] logger.info(f"{len(accounts)} Accounts gefunden für Suchbegriff '{query}'") return accounts except sqlite3.Error as e: logger.error(f"Fehler bei der Suche nach Accounts: {e}") return [] def get_account_count(self, platform: Optional[str] = None) -> int: """ Gibt die Anzahl der Accounts zurück. Args: platform: Optional, Plattform für die Einschränkung der Zählung Returns: Anzahl der Accounts """ try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() if platform: cursor.execute("SELECT COUNT(*) FROM accounts WHERE platform = ?", (platform.lower(),)) else: cursor.execute("SELECT COUNT(*) FROM accounts") count = cursor.fetchone()[0] conn.close() return count except sqlite3.Error as e: logger.error(f"Fehler beim Zählen der Accounts: {e}") return 0 def get_setting(self, key: str, default: Any = None) -> Any: """ Gibt einen Einstellungswert zurück. Args: key: Schlüssel der Einstellung default: Standardwert, falls die Einstellung nicht gefunden wurde Returns: Wert der Einstellung oder der Standardwert """ try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("SELECT value FROM settings WHERE key = ?", (key,)) row = cursor.fetchone() conn.close() if row: # Versuche, den Wert als JSON zu parsen try: return json.loads(row[0]) except json.JSONDecodeError: # Wenn kein gültiges JSON, gib den Rohwert zurück return row[0] else: return default except sqlite3.Error as e: logger.error(f"Fehler beim Abrufen der Einstellung '{key}': {e}") return default def set_setting(self, key: str, value: Any) -> bool: """ Setzt einen Einstellungswert. Args: key: Schlüssel der Einstellung value: Wert der Einstellung (wird als JSON gespeichert, wenn es kein String ist) Returns: True bei Erfolg, False im Fehlerfall """ try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # Wert als JSON speichern, wenn es kein String ist if not isinstance(value, str): value = json.dumps(value) # Prüfen, ob die Einstellung bereits existiert cursor.execute("SELECT COUNT(*) FROM settings WHERE key = ?", (key,)) exists = cursor.fetchone()[0] > 0 if exists: cursor.execute("UPDATE settings SET value = ? WHERE key = ?", (value, key)) else: cursor.execute("INSERT INTO settings (key, value) VALUES (?, ?)", (key, value)) conn.commit() conn.close() logger.info(f"Einstellung gespeichert: {key}") return True except sqlite3.Error as e: logger.error(f"Fehler beim Speichern der Einstellung '{key}': {e}") return False def delete_setting(self, key: str) -> bool: """ Löscht eine Einstellung. Args: key: Schlüssel der zu löschenden Einstellung Returns: True bei Erfolg, False im Fehlerfall """ try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("DELETE FROM settings WHERE key = ?", (key,)) conn.commit() conn.close() logger.info(f"Einstellung gelöscht: {key}") return True except sqlite3.Error as e: logger.error(f"Fehler beim Löschen der Einstellung '{key}': {e}") return False def backup_database(self, backup_path: Optional[str] = None) -> bool: """ Erstellt ein Backup der Datenbank. Args: backup_path: Optional, Pfad für das Backup Returns: True bei Erfolg, False im Fehlerfall """ if not backup_path: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") backup_path = f"database/backup/accounts_{timestamp}.db" # Stelle sicher, dass das Backup-Verzeichnis existiert os.makedirs(os.path.dirname(backup_path), exist_ok=True) try: # SQLite-Backup-API verwenden conn = sqlite3.connect(self.db_path) backup_conn = sqlite3.connect(backup_path) conn.backup(backup_conn) conn.close() backup_conn.close() logger.info(f"Datenbank-Backup erstellt: {backup_path}") return True except sqlite3.Error as e: logger.error(f"Fehler beim Erstellen des Datenbank-Backups: {e}") return False