189 lines
6.9 KiB
Python
189 lines
6.9 KiB
Python
import sqlite3
|
|
import logging
|
|
from threading import local
|
|
from typing import Optional, Dict, Any
|
|
from .interface import DatabaseInterface
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SQLiteDatabase(DatabaseInterface):
|
|
"""Thread-Safe SQLite-Implementierung der Database-Interface"""
|
|
|
|
def __init__(self, db_path: str):
|
|
self.db_path = db_path
|
|
# Thread-local storage für Verbindungen
|
|
self._local = local()
|
|
|
|
def _get_connection(self) -> sqlite3.Connection:
|
|
"""Holt oder erstellt eine thread-lokale Verbindung"""
|
|
if not hasattr(self._local, 'connection') or self._local.connection is None:
|
|
self._local.connection = sqlite3.connect(
|
|
self.db_path,
|
|
check_same_thread=False, # Erlaubt thread-übergreifende Nutzung
|
|
timeout=30.0,
|
|
detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
|
|
)
|
|
self._local.connection.row_factory = sqlite3.Row
|
|
# Optimierungen für bessere Performance
|
|
self._local.connection.execute("PRAGMA journal_mode=WAL")
|
|
self._local.connection.execute("PRAGMA synchronous=NORMAL")
|
|
self._local.connection.execute("PRAGMA cache_size=1000")
|
|
self._local.connection.execute("PRAGMA temp_store=MEMORY")
|
|
logger.debug(f"New SQLite connection created for thread")
|
|
|
|
return self._local.connection
|
|
|
|
def connect(self) -> None:
|
|
"""Initialisiert die thread-lokale Verbindung"""
|
|
try:
|
|
conn = self._get_connection()
|
|
logger.info(f"Connected to SQLite database: {self.db_path}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to connect to SQLite database: {e}")
|
|
raise
|
|
|
|
def disconnect(self) -> None:
|
|
"""Schließt die thread-lokale Verbindung"""
|
|
if hasattr(self._local, 'connection') and self._local.connection:
|
|
try:
|
|
self._local.connection.close()
|
|
self._local.connection = None
|
|
logger.debug("SQLite connection closed for thread")
|
|
except Exception as e:
|
|
logger.warning(f"Error closing SQLite connection: {e}")
|
|
|
|
def create_user_table(self) -> None:
|
|
"""User-Tabelle erstellen"""
|
|
conn = self._get_connection()
|
|
cursor = conn.cursor()
|
|
try:
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS users
|
|
(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL UNIQUE,
|
|
email TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )
|
|
""")
|
|
conn.commit()
|
|
logger.info("User table created/verified")
|
|
except Exception as e:
|
|
logger.error(f"Failed to create user table: {e}")
|
|
raise
|
|
finally:
|
|
cursor.close()
|
|
|
|
def create_user(self, username: str, email: str, password_hash: str) -> bool:
|
|
"""Neuen User erstellen"""
|
|
conn = self._get_connection()
|
|
cursor = conn.cursor()
|
|
try:
|
|
cursor.execute(
|
|
"INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)",
|
|
(username, email, password_hash)
|
|
)
|
|
conn.commit()
|
|
logger.info(f"User created successfully: {email}")
|
|
return True
|
|
except sqlite3.IntegrityError as e:
|
|
logger.warning(f"User creation failed (duplicate): {e}")
|
|
conn.rollback()
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"Failed to create user: {e}")
|
|
conn.rollback()
|
|
raise
|
|
finally:
|
|
cursor.close()
|
|
|
|
def get_user_by_email(self, email: str) -> Optional[Dict[str, Any]]:
|
|
"""User anhand Email suchen"""
|
|
conn = self._get_connection()
|
|
cursor = conn.cursor()
|
|
try:
|
|
cursor.execute(
|
|
"SELECT id, username, email, password_hash, created_at FROM users WHERE email = ?",
|
|
(email,)
|
|
)
|
|
row = cursor.fetchone()
|
|
return dict(row) if row else None
|
|
except Exception as e:
|
|
logger.error(f"Failed to get user by email: {e}")
|
|
raise
|
|
finally:
|
|
cursor.close()
|
|
|
|
def get_user_by_username(self, username: str) -> Optional[Dict[str, Any]]:
|
|
"""User anhand Username suchen"""
|
|
conn = self._get_connection()
|
|
cursor = conn.cursor()
|
|
try:
|
|
cursor.execute(
|
|
"SELECT id, username, email, password_hash, created_at FROM users WHERE username = ?",
|
|
(username,)
|
|
)
|
|
row = cursor.fetchone()
|
|
return dict(row) if row else None
|
|
except Exception as e:
|
|
logger.error(f"Failed to get user by username: {e}")
|
|
raise
|
|
finally:
|
|
cursor.close()
|
|
|
|
def get_password_hash_by_email(self, email: str) -> Optional[str]:
|
|
"""Password-Hash für Email abrufen"""
|
|
conn = self._get_connection()
|
|
cursor = conn.cursor()
|
|
try:
|
|
cursor.execute(
|
|
"SELECT password_hash FROM users WHERE email = ?",
|
|
(email,)
|
|
)
|
|
row = cursor.fetchone()
|
|
return row if row else None
|
|
except Exception as e:
|
|
logger.error(f"Failed to get password hash by email: {e}")
|
|
raise
|
|
finally:
|
|
cursor.close()
|
|
|
|
def update_user_password(self, email: str, new_password_hash: str) -> bool:
|
|
"""Passwort aktualisieren"""
|
|
conn = self._get_connection()
|
|
cursor = conn.cursor()
|
|
try:
|
|
cursor.execute(
|
|
"UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE email = ?",
|
|
(new_password_hash, email)
|
|
)
|
|
conn.commit()
|
|
success = cursor.rowcount > 0
|
|
if success:
|
|
logger.info(f"Password updated for user: {email}")
|
|
return success
|
|
except Exception as e:
|
|
logger.error(f"Failed to update password: {e}")
|
|
conn.rollback()
|
|
raise
|
|
finally:
|
|
cursor.close()
|
|
|
|
def delete_user(self, email: str) -> bool:
|
|
"""User löschen"""
|
|
conn = self._get_connection()
|
|
cursor = conn.cursor()
|
|
try:
|
|
cursor.execute("DELETE FROM users WHERE email = ?", (email,))
|
|
conn.commit()
|
|
success = cursor.rowcount > 0
|
|
if success:
|
|
logger.info(f"User deleted: {email}")
|
|
return success
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete user: {e}")
|
|
conn.rollback()
|
|
raise
|
|
finally:
|
|
cursor.close() |