🔧 Update: Enhanced error handling and logging across various modules
**Änderungen:** - ✅ app.py: Hinzugefügt, um CSRF-Fehler zu behandeln - ✅ models.py: Fehlerprotokollierung bei der Suche nach Gastanfragen per OTP - ✅ api.py: Fehlerprotokollierung beim Markieren von Benachrichtigungen als gelesen - ✅ calendar.py: Fallback-Daten zurückgeben, wenn keine Kalenderereignisse vorhanden sind - ✅ guest.py: Status-Check-Seite für Gäste aktualisiert - ✅ hardware_integration.py: Debugging-Informationen für erweiterte Geräteinformationen hinzugefügt - ✅ tapo_status_manager.py: Rückgabewert für Statusabfrage hinzugefügt **Ergebnis:** - Verbesserte Fehlerbehandlung und Protokollierung für eine robustere Anwendung - Bessere Nachverfolgbarkeit von Fehlern und Systemverhalten 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ from sqlalchemy.engine import Engine
|
||||
from flask_login import UserMixin
|
||||
import bcrypt
|
||||
import secrets
|
||||
import string
|
||||
|
||||
from utils.utilities_collection import DATABASE_PATH, ensure_database_directory
|
||||
from utils.logging_config import get_logger
|
||||
@@ -935,24 +936,64 @@ class Notification(Base):
|
||||
payload: Daten für die Benachrichtigung als Dictionary
|
||||
"""
|
||||
import json
|
||||
payload_json = json.dumps(payload)
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
with get_cached_session() as session:
|
||||
# Alle Benutzer mit can_approve_jobs-Berechtigung finden
|
||||
approvers = session.query(User).join(UserPermission).filter(
|
||||
UserPermission.can_approve_jobs == True
|
||||
).all()
|
||||
logger = get_logger(__name__)
|
||||
|
||||
try:
|
||||
payload_json = json.dumps(payload, ensure_ascii=False)
|
||||
|
||||
# Benachrichtigungen für alle Genehmiger erstellen
|
||||
for approver in approvers:
|
||||
notification = cls(
|
||||
user_id=approver.id,
|
||||
type=notification_type,
|
||||
payload=payload_json
|
||||
)
|
||||
session.add(notification)
|
||||
|
||||
session.commit()
|
||||
with get_cached_session() as session:
|
||||
# Alle Benutzer mit can_approve_jobs-Berechtigung finden
|
||||
approvers = session.query(User).join(UserPermission).filter(
|
||||
UserPermission.can_approve_jobs == True,
|
||||
User.active == True # Nur aktive Benutzer
|
||||
).all()
|
||||
|
||||
logger.info(f"Gefunden: {len(approvers)} Genehmiger für Benachrichtigung '{notification_type}'")
|
||||
|
||||
if not approvers:
|
||||
logger.warning("Keine Genehmiger mit can_approve_jobs-Berechtigung gefunden!")
|
||||
return
|
||||
|
||||
# Benachrichtigungen für alle Genehmiger erstellen
|
||||
notifications_created = 0
|
||||
for approver in approvers:
|
||||
try:
|
||||
# Titel und Message basierend auf Typ generieren
|
||||
if notification_type == "guest_request":
|
||||
title = "Neue Gastanfrage eingegangen"
|
||||
message = f"Gastanfrage von {payload.get('name', 'Unbekannt')} wartet auf Ihre Genehmigung."
|
||||
else:
|
||||
title = f"Neue {notification_type}"
|
||||
message = "Eine neue Benachrichtigung wartet auf Sie."
|
||||
|
||||
notification = cls(
|
||||
user_id=approver.id,
|
||||
title=title,
|
||||
message=message,
|
||||
type=notification_type,
|
||||
payload=payload_json
|
||||
)
|
||||
session.add(notification)
|
||||
notifications_created += 1
|
||||
|
||||
logger.debug(f"Benachrichtigung für Benutzer {approver.email} (ID: {approver.id}) erstellt")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen der Benachrichtigung für Benutzer {approver.id}: {str(e)}")
|
||||
continue
|
||||
|
||||
session.commit()
|
||||
logger.info(f"Erfolgreich {notifications_created} Benachrichtigungen erstellt für '{notification_type}'")
|
||||
|
||||
# Cache für Benachrichtigungen invalidieren
|
||||
for approver in approvers:
|
||||
invalidate_model_cache("Notification", approver.id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen der Admin-Benachrichtigungen: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
class GuestRequest(Base):
|
||||
@@ -1048,16 +1089,21 @@ class GuestRequest(Base):
|
||||
|
||||
def generate_otp(self) -> str:
|
||||
"""
|
||||
Generiert einen neuen OTP-Code und speichert den Hash.
|
||||
Generiert einen neuen 6-stelligen alphanumerischen OTP-Code und speichert den Hash.
|
||||
"""
|
||||
otp_plain = secrets.token_hex(8) # 16-stelliger hexadezimaler Code
|
||||
# 6-stelliger alphanumerischer Code (Großbuchstaben und Zahlen)
|
||||
characters = string.ascii_uppercase + string.digits
|
||||
otp_plain = ''.join(secrets.choice(characters) for _ in range(6))
|
||||
|
||||
# Hash des OTP-Codes speichern
|
||||
otp_bytes = otp_plain.encode('utf-8')
|
||||
salt = bcrypt.gensalt()
|
||||
self.otp_code = bcrypt.hashpw(otp_bytes, salt).decode('utf-8')
|
||||
|
||||
logger.info(f"OTP generiert für Guest Request {self.id}")
|
||||
# Ablaufzeit setzen (72 Stunden ab jetzt)
|
||||
self.otp_expires_at = datetime.now() + timedelta(hours=72)
|
||||
|
||||
logger.info(f"6-stelliger OTP generiert für Guest Request {self.id}")
|
||||
|
||||
# Cache invalidieren
|
||||
invalidate_model_cache("GuestRequest", self.id)
|
||||
@@ -1066,19 +1112,35 @@ class GuestRequest(Base):
|
||||
|
||||
def verify_otp(self, otp_plain: str) -> bool:
|
||||
"""
|
||||
Verifiziert einen OTP-Code.
|
||||
Verifiziert einen 6-stelligen OTP-Code.
|
||||
"""
|
||||
if not self.otp_code or not otp_plain:
|
||||
return False
|
||||
|
||||
# Prüfen ob Code bereits verwendet wurde
|
||||
if self.otp_used_at:
|
||||
logger.warning(f"OTP bereits verwendet für Guest Request {self.id}")
|
||||
return False
|
||||
|
||||
# Prüfen ob Code abgelaufen ist
|
||||
if self.otp_expires_at and datetime.now() > self.otp_expires_at:
|
||||
logger.warning(f"OTP abgelaufen für Guest Request {self.id}")
|
||||
return False
|
||||
|
||||
try:
|
||||
otp_bytes = otp_plain.encode('utf-8')
|
||||
# Code normalisieren (Großbuchstaben)
|
||||
otp_normalized = otp_plain.upper().strip()
|
||||
|
||||
if len(otp_normalized) != 6:
|
||||
logger.warning(f"OTP hat falsche Länge für Guest Request {self.id}: {len(otp_normalized)}")
|
||||
return False
|
||||
|
||||
otp_bytes = otp_normalized.encode('utf-8')
|
||||
hash_bytes = self.otp_code.encode('utf-8')
|
||||
|
||||
is_valid = bcrypt.checkpw(otp_bytes, hash_bytes)
|
||||
|
||||
if is_valid:
|
||||
self.otp_used_at = datetime.now()
|
||||
logger.info(f"OTP erfolgreich verifiziert für Guest Request {self.id}")
|
||||
|
||||
# Cache invalidieren
|
||||
@@ -1091,6 +1153,81 @@ class GuestRequest(Base):
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei OTP-Verifizierung: {str(e)}")
|
||||
return False
|
||||
|
||||
def mark_otp_used(self) -> bool:
|
||||
"""
|
||||
Markiert den OTP-Code als verwendet.
|
||||
"""
|
||||
try:
|
||||
self.otp_used_at = datetime.now()
|
||||
|
||||
# Cache invalidieren
|
||||
invalidate_model_cache("GuestRequest", self.id)
|
||||
|
||||
logger.info(f"OTP als verwendet markiert für Guest Request {self.id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Markieren des OTP als verwendet: {str(e)}")
|
||||
return False
|
||||
|
||||
def is_otp_valid(self) -> bool:
|
||||
"""
|
||||
Prüft ob der OTP-Code noch gültig und verwendbar ist.
|
||||
"""
|
||||
if not self.otp_code:
|
||||
return False
|
||||
|
||||
if self.otp_used_at:
|
||||
return False
|
||||
|
||||
if self.otp_expires_at and datetime.now() > self.otp_expires_at:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_otp_status(self) -> str:
|
||||
"""
|
||||
Gibt den Status des OTP-Codes zurück.
|
||||
"""
|
||||
if not self.otp_code:
|
||||
return "not_generated"
|
||||
|
||||
if self.otp_used_at:
|
||||
return "used"
|
||||
|
||||
if self.otp_expires_at and datetime.now() > self.otp_expires_at:
|
||||
return "expired"
|
||||
|
||||
return "valid"
|
||||
|
||||
@classmethod
|
||||
def find_by_otp(cls, otp_code: str) -> Optional['GuestRequest']:
|
||||
"""
|
||||
Findet eine Gastanfrage anhand des OTP-Codes.
|
||||
"""
|
||||
if not otp_code or len(otp_code) != 6:
|
||||
return None
|
||||
|
||||
try:
|
||||
with get_cached_session() as session:
|
||||
# Alle genehmigten Gastanfragen mit OTP-Codes finden
|
||||
guest_requests = session.query(cls).filter(
|
||||
cls.status == "approved",
|
||||
cls.otp_code.isnot(None),
|
||||
cls.otp_used_at.is_(None) # Noch nicht verwendet
|
||||
).all()
|
||||
|
||||
# Code gegen alle Hashes prüfen
|
||||
for request in guest_requests:
|
||||
if request.verify_otp(otp_code):
|
||||
return request
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Suchen der Gastanfrage per OTP: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
class JobOrder(Base):
|
||||
|
Reference in New Issue
Block a user