📝 MIGRATION_LOG.md: Renamed backend/utils/test_korrekturen.py to MIGRATION_LOG.md

This commit is contained in:
2025-06-11 12:49:58 +02:00
parent 6be0d6ee88
commit 6961732fc8
28 changed files with 933 additions and 1359 deletions

View File

@ -0,0 +1 @@

View File

@ -1765,7 +1765,7 @@ def main():
# Performance-Monitoring aktivieren # Performance-Monitoring aktivieren
if getattr(ProductionConfig, 'ENABLE_PERFORMANCE_MONITORING', False): if getattr(ProductionConfig, 'ENABLE_PERFORMANCE_MONITORING', False):
try: try:
from utils.performance_monitor import init_performance_monitoring from utils.system_utilities import init_performance_monitoring
init_performance_monitoring(app) init_performance_monitoring(app)
app_logger.info("[PRODUCTION] ✅ Performance-Monitoring aktiviert") app_logger.info("[PRODUCTION] ✅ Performance-Monitoring aktiviert")
except ImportError: except ImportError:

View File

@ -1,359 +0,0 @@
"""
Benutzer-Blueprint für das 3D-Druck-Management-System
Dieses Modul enthält alle Benutzer-spezifischen Routen und Funktionen,
einschließlich Profilverwaltung, Einstellungen und Passwort-Änderung.
"""
import json
from datetime import datetime
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, flash, make_response
from flask_login import login_required, current_user
from werkzeug.security import check_password_hash
from models import User, get_db_session
from utils.logging_config import get_logger
# Blueprint erstellen
user_blueprint = Blueprint('user', __name__, url_prefix='/user')
# Logger initialisieren
user_logger = get_logger("user")
@user_blueprint.route("/profile", methods=["GET"])
@login_required
def profile():
"""Benutzerprofil anzeigen"""
return render_template('user/profile.html', user=current_user)
@user_blueprint.route("/settings", methods=["GET"])
@login_required
def settings():
"""Benutzereinstellungen anzeigen"""
return render_template('user/settings.html', user=current_user)
@user_blueprint.route("/update-profile", methods=["POST"])
@login_required
def update_profile():
"""Benutzerprofil aktualisieren (Form-basiert)"""
try:
db_session = get_db_session()
user = db_session.query(User).filter(User.id == current_user.id).first()
if not user:
db_session.close()
flash("Benutzer nicht gefunden", "error")
return redirect(url_for('user.profile'))
# Aktualisierbare Felder aus dem Formular
user.name = request.form.get('name', user.name)
user.email = request.form.get('email', user.email)
user.department = request.form.get('department', user.department)
user.position = request.form.get('position', user.position)
user.phone = request.form.get('phone', user.phone)
user.bio = request.form.get('bio', user.bio)
user.updated_at = datetime.now()
db_session.commit()
user_logger.info(f"Profil aktualisiert für Benutzer {user.username}")
flash("Profil erfolgreich aktualisiert", "success")
db_session.close()
return redirect(url_for('user.profile'))
except Exception as e:
user_logger.error(f"Fehler beim Aktualisieren des Profils: {str(e)}")
flash("Fehler beim Aktualisieren des Profils", "error")
return redirect(url_for('user.profile'))
@user_blueprint.route("/api/update-settings", methods=["POST"])
@login_required
def api_update_settings():
"""API-Endpunkt für Einstellungen-Updates"""
try:
data = request.get_json()
db_session = get_db_session()
user = db_session.query(User).filter(User.id == current_user.id).first()
if not user:
db_session.close()
return jsonify({"error": "Benutzer nicht gefunden"}), 404
# Einstellungen JSON aktualisieren
current_settings = user.settings or {}
if isinstance(current_settings, str):
try:
current_settings = json.loads(current_settings)
except json.JSONDecodeError:
current_settings = {}
# Neue Einstellungen hinzufügen/aktualisieren
for key, value in data.items():
current_settings[key] = value
user.settings = json.dumps(current_settings)
user.updated_at = datetime.now()
db_session.commit()
user_logger.info(f"Einstellungen aktualisiert für Benutzer {user.username}")
db_session.close()
return jsonify({
"success": True,
"message": "Einstellungen erfolgreich aktualisiert"
})
except Exception as e:
user_logger.error(f"Fehler beim Aktualisieren der Einstellungen: {str(e)}")
return jsonify({"error": "Fehler beim Aktualisieren der Einstellungen"}), 500
@user_blueprint.route("/update-settings", methods=["POST"])
@login_required
def update_settings():
"""Benutzereinstellungen aktualisieren (Form-basiert)"""
try:
db_session = get_db_session()
user = db_session.query(User).filter(User.id == current_user.id).first()
if not user:
db_session.close()
flash("Benutzer nicht gefunden", "error")
return redirect(url_for('user.settings'))
# Einstellungen aus dem Formular sammeln
settings = {}
# Theme-Einstellungen
settings['theme'] = request.form.get('theme', 'light')
settings['language'] = request.form.get('language', 'de')
# Benachrichtigungseinstellungen
settings['email_notifications'] = request.form.get('email_notifications') == 'on'
settings['push_notifications'] = request.form.get('push_notifications') == 'on'
settings['job_completion_notifications'] = request.form.get('job_completion_notifications') == 'on'
settings['printer_error_notifications'] = request.form.get('printer_error_notifications') == 'on'
# Dashboard-Einstellungen
settings['default_dashboard_view'] = request.form.get('default_dashboard_view', 'overview')
settings['auto_refresh_interval'] = int(request.form.get('auto_refresh_interval', 30))
# Privacy-Einstellungen
settings['show_profile_publicly'] = request.form.get('show_profile_publicly') == 'on'
settings['allow_job_sharing'] = request.form.get('allow_job_sharing') == 'on'
user.settings = json.dumps(settings)
user.updated_at = datetime.now()
db_session.commit()
user_logger.info(f"Einstellungen aktualisiert für Benutzer {user.username}")
flash("Einstellungen erfolgreich aktualisiert", "success")
db_session.close()
return redirect(url_for('user.settings'))
except Exception as e:
user_logger.error(f"Fehler beim Aktualisieren der Einstellungen: {str(e)}")
flash("Fehler beim Aktualisieren der Einstellungen", "error")
return redirect(url_for('user.settings'))
@user_blueprint.route("/change-password", methods=["POST"])
@login_required
def change_password():
"""Passwort ändern"""
try:
# Daten aus Form oder JSON extrahieren
if request.is_json:
data = request.get_json()
current_password = data.get('current_password')
new_password = data.get('new_password')
confirm_password = data.get('confirm_password')
else:
current_password = request.form.get('current_password')
new_password = request.form.get('new_password')
confirm_password = request.form.get('confirm_password')
# Validierung
if not all([current_password, new_password, confirm_password]):
error_msg = "Alle Passwort-Felder sind erforderlich"
if request.is_json:
return jsonify({"error": error_msg}), 400
flash(error_msg, "error")
return redirect(url_for('user.settings'))
if new_password != confirm_password:
error_msg = "Neue Passwörter stimmen nicht überein"
if request.is_json:
return jsonify({"error": error_msg}), 400
flash(error_msg, "error")
return redirect(url_for('user.settings'))
if len(new_password) < 8:
error_msg = "Das neue Passwort muss mindestens 8 Zeichen lang sein"
if request.is_json:
return jsonify({"error": error_msg}), 400
flash(error_msg, "error")
return redirect(url_for('user.settings'))
db_session = get_db_session()
user = db_session.query(User).filter(User.id == current_user.id).first()
if not user:
db_session.close()
error_msg = "Benutzer nicht gefunden"
if request.is_json:
return jsonify({"error": error_msg}), 404
flash(error_msg, "error")
return redirect(url_for('user.settings'))
# Aktuelles Passwort überprüfen
if not user.check_password(current_password):
db_session.close()
error_msg = "Aktuelles Passwort ist falsch"
if request.is_json:
return jsonify({"error": error_msg}), 401
flash(error_msg, "error")
return redirect(url_for('user.settings'))
# Neues Passwort setzen
user.set_password(new_password)
user.updated_at = datetime.now()
db_session.commit()
user_logger.info(f"Passwort geändert für Benutzer {user.username}")
db_session.close()
success_msg = "Passwort erfolgreich geändert"
if request.is_json:
return jsonify({"success": True, "message": success_msg})
flash(success_msg, "success")
return redirect(url_for('user.settings'))
except Exception as e:
user_logger.error(f"Fehler beim Ändern des Passworts: {str(e)}")
error_msg = "Fehler beim Ändern des Passworts"
if request.is_json:
return jsonify({"error": error_msg}), 500
flash(error_msg, "error")
return redirect(url_for('user.settings'))
@user_blueprint.route("/export", methods=["GET"])
@login_required
def export_data():
"""Benutzerdaten exportieren (DSGVO-Compliance)"""
try:
db_session = get_db_session()
user = db_session.query(User).filter(User.id == current_user.id).first()
if not user:
db_session.close()
flash("Benutzer nicht gefunden", "error")
return redirect(url_for('user.settings'))
# Benutzerdaten sammeln
user_data = {
"personal_information": {
"id": user.id,
"username": user.username,
"email": user.email,
"name": user.name,
"department": user.department,
"position": user.position,
"phone": user.phone,
"bio": user.bio,
"role": user.role,
"created_at": user.created_at.isoformat() if user.created_at else None,
"last_login": user.last_login.isoformat() if user.last_login else None,
"updated_at": user.updated_at.isoformat() if user.updated_at else None,
"last_activity": user.last_activity.isoformat() if user.last_activity else None
},
"settings": json.loads(user.settings) if user.settings else {},
"jobs": [],
"export_date": datetime.now().isoformat(),
"export_note": "Dies ist ein Export Ihrer persönlichen Daten gemäß DSGVO Art. 20"
}
# Benutzer-Jobs sammeln (falls verfügbar)
try:
from models import Job
user_jobs = db_session.query(Job).filter(Job.user_id == user.id).all()
for job in user_jobs:
user_data["jobs"].append({
"id": job.id,
"filename": job.filename,
"status": job.status,
"created_at": job.created_at.isoformat() if job.created_at else None,
"estimated_duration": job.estimated_duration,
"material_used": job.material_used,
"notes": job.notes
})
except Exception as job_error:
user_logger.warning(f"Fehler beim Sammeln der Job-Daten: {str(job_error)}")
db_session.close()
# JSON-Response erstellen
response = make_response(jsonify(user_data))
response.headers['Content-Disposition'] = f'attachment; filename=user_data_{user.username}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json'
response.headers['Content-Type'] = 'application/json'
user_logger.info(f"Datenexport erstellt für Benutzer {user.username}")
return response
except Exception as e:
user_logger.error(f"Fehler beim Datenexport: {str(e)}")
flash("Fehler beim Erstellen des Datenexports", "error")
return redirect(url_for('user.settings'))
@user_blueprint.route("/profile", methods=["PUT"])
@login_required
def update_profile_api():
"""API-Endpunkt für Profil-Updates"""
try:
data = request.get_json()
db_session = get_db_session()
user = db_session.query(User).filter(User.id == current_user.id).first()
if not user:
db_session.close()
return jsonify({"error": "Benutzer nicht gefunden"}), 404
# Aktualisierbare Felder (ohne sensitive Daten)
updatable_fields = ['name', 'email', 'department', 'position', 'phone', 'bio']
for field in updatable_fields:
if field in data:
setattr(user, field, data[field])
user.updated_at = datetime.now()
db_session.commit()
user_logger.info(f"Profil über API aktualisiert für Benutzer {user.username}")
# Aktuelle Benutzerdaten zurückgeben
user_data = {
"id": user.id,
"username": user.username,
"email": user.email,
"name": user.name,
"department": user.department,
"position": user.position,
"phone": user.phone,
"bio": user.bio,
"role": user.role,
"updated_at": user.updated_at.isoformat()
}
db_session.close()
return jsonify({
"success": True,
"message": "Profil erfolgreich aktualisiert",
"user": user_data
})
except Exception as e:
user_logger.error(f"Fehler beim API-Profil-Update: {str(e)}")
return jsonify({"error": "Fehler beim Aktualisieren des Profils"}), 500

View File

@ -1,168 +0,0 @@
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, abort
from flask_login import current_user, login_required
from sqlalchemy.exc import SQLAlchemyError
from functools import wraps
from models import User, UserPermission, get_cached_session
from utils.logging_config import get_logger
users_blueprint = Blueprint('users', __name__)
logger = get_logger("users")
def users_admin_required(f):
"""Decorator zur Prüfung der Admin-Berechtigung für Users Blueprint."""
@wraps(f)
@login_required
def users_decorated_function(*args, **kwargs):
if not current_user.is_admin:
abort(403, "Nur Administratoren haben Zugriff auf diese Seite")
return f(*args, **kwargs)
return users_decorated_function
@users_blueprint.route('/admin/users/<int:user_id>/permissions', methods=['GET'])
@users_admin_required
def admin_user_permissions(user_id):
"""Benutzerberechtigungen anzeigen und bearbeiten."""
with get_cached_session() as db_session:
user = db_session.query(User).filter_by(id=user_id).first()
if not user:
abort(404, "Benutzer nicht gefunden")
# Berechtigungen laden oder erstellen, falls nicht vorhanden
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
permission = UserPermission(user_id=user_id)
db_session.add(permission)
db_session.commit()
return render_template('admin_user_permissions.html', user=user, permission=permission)
@users_blueprint.route('/api/users/<int:user_id>/permissions', methods=['GET'])
@login_required
def api_get_user_permissions(user_id):
"""Benutzerberechtigungen als JSON zurückgeben."""
# Nur Admins oder der Benutzer selbst darf seine Berechtigungen sehen
if not current_user.is_admin and current_user.id != user_id:
return jsonify({"error": "Keine Berechtigung"}), 403
try:
with get_cached_session() as db_session:
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
# Falls keine Berechtigungen existieren, Standard-Werte zurückgeben
return jsonify({
"user_id": user_id,
"can_start_jobs": False,
"needs_approval": True,
"can_approve_jobs": False
})
return jsonify(permission.to_dict())
except Exception as e:
logger.error(f"Fehler beim Abrufen der Benutzerberechtigungen: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@users_blueprint.route('/api/users/<int:user_id>/permissions', methods=['PUT'])
@users_admin_required
def api_update_user_permissions(user_id):
"""Benutzerberechtigungen aktualisieren."""
try:
data = request.get_json()
if not data:
return jsonify({"error": "Keine Daten erhalten"}), 400
with get_cached_session() as db_session:
# Benutzer prüfen
user = db_session.query(User).filter_by(id=user_id).first()
if not user:
return jsonify({"error": "Benutzer nicht gefunden"}), 404
# Berechtigungen laden oder erstellen
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
permission = UserPermission(user_id=user_id)
db_session.add(permission)
# Berechtigungen aktualisieren
if 'can_start_jobs' in data:
permission.can_start_jobs = bool(data['can_start_jobs'])
if 'needs_approval' in data:
permission.needs_approval = bool(data['needs_approval'])
if 'can_approve_jobs' in data:
permission.can_approve_jobs = bool(data['can_approve_jobs'])
db_session.commit()
logger.info(f"Berechtigungen für Benutzer {user_id} aktualisiert durch Admin {current_user.id}")
return jsonify({
"success": True,
"permissions": permission.to_dict()
})
except SQLAlchemyError as e:
logger.error(f"Datenbankfehler beim Aktualisieren der Benutzerberechtigungen: {str(e)}")
return jsonify({"error": "Datenbankfehler beim Verarbeiten der Anfrage"}), 500
except Exception as e:
logger.error(f"Fehler beim Aktualisieren der Benutzerberechtigungen: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@users_blueprint.route('/admin/users/<int:user_id>/permissions/update', methods=['POST'])
@users_admin_required
def admin_update_user_permissions(user_id):
"""Benutzerberechtigungen über Formular aktualisieren."""
try:
# Formularfelder auslesen
can_start_jobs = request.form.get('can_start_jobs') == 'on'
needs_approval = request.form.get('needs_approval') == 'on'
can_approve_jobs = request.form.get('can_approve_jobs') == 'on'
with get_cached_session() as db_session:
# Benutzer prüfen
user = db_session.query(User).filter_by(id=user_id).first()
if not user:
abort(404, "Benutzer nicht gefunden")
# Berechtigungen laden oder erstellen
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
permission = UserPermission(user_id=user_id)
db_session.add(permission)
# Berechtigungen aktualisieren
permission.can_start_jobs = can_start_jobs
permission.needs_approval = needs_approval
permission.can_approve_jobs = can_approve_jobs
db_session.commit()
logger.info(f"Berechtigungen für Benutzer {user_id} aktualisiert durch Admin {current_user.id} (Formular)")
return redirect(url_for('users.admin_user_permissions', user_id=user_id))
except Exception as e:
logger.error(f"Fehler beim Aktualisieren der Benutzerberechtigungen: {str(e)}")
abort(500, "Fehler beim Verarbeiten der Anfrage")
# Erweiterung des bestehenden Benutzer-Bearbeitungsformulars
@users_blueprint.route('/admin/users/<int:user_id>/edit/permissions', methods=['GET'])
@users_admin_required
def admin_edit_user_permissions_section(user_id):
"""Rendert nur den Berechtigungsteil für das Benutzer-Edit-Formular."""
with get_cached_session() as db_session:
user = db_session.query(User).filter_by(id=user_id).first()
if not user:
abort(404, "Benutzer nicht gefunden")
# Berechtigungen laden oder erstellen, falls nicht vorhanden
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
permission = UserPermission(user_id=user_id)
db_session.add(permission)
db_session.commit()
return render_template('_user_permissions_form.html', user=user, permission=permission)

View File

@ -5154,3 +5154,9 @@ WHERE users.id = ?
2025-06-11 10:52:44 - [app] app - [DEBUG] DEBUG - Response: 200 2025-06-11 10:52:44 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-11 10:52:45 - [app] app - [INFO] INFO - ✅ Admin API: System-Health abgerufen 2025-06-11 10:52:45 - [app] app - [INFO] INFO - ✅ Admin API: System-Health abgerufen
2025-06-11 10:52:45 - [app] app - [DEBUG] DEBUG - Response: 200 2025-06-11 10:52:45 - [app] app - [DEBUG] DEBUG - Response: 200
2025-06-11 12:43:22 - [app] app - [WARNING] WARNING - DatabaseCleanupManager nicht verfügbar - Fallback auf Legacy-Cleanup
2025-06-11 12:43:22 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\instance\printer_manager.db
2025-06-11 12:43:29 - [app] app - [WARNING] WARNING - DatabaseCleanupManager nicht verfügbar - Fallback auf Legacy-Cleanup
2025-06-11 12:43:29 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\instance\printer_manager.db
2025-06-11 12:43:36 - [app] app - [WARNING] WARNING - DatabaseCleanupManager nicht verfügbar - Fallback auf Legacy-Cleanup
2025-06-11 12:43:36 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\instance\printer_manager.db

View File

@ -195,3 +195,4 @@
2025-06-11 10:51:39 - [scheduler] scheduler - [INFO] INFO - ✅ Drucker Tapo P110 (192.168.0.106): Status auf 'online' gesetzt (Steckdose ausgeschaltet - bereit) 2025-06-11 10:51:39 - [scheduler] scheduler - [INFO] INFO - ✅ Drucker Tapo P110 (192.168.0.106): Status auf 'online' gesetzt (Steckdose ausgeschaltet - bereit)
2025-06-11 10:51:39 - [scheduler] scheduler - [INFO] INFO - ✅ Status für Drucker Tapo P110 (192.168.0.106) erfolgreich aktualisiert 2025-06-11 10:51:39 - [scheduler] scheduler - [INFO] INFO - ✅ Status für Drucker Tapo P110 (192.168.0.106) erfolgreich aktualisiert
2025-06-11 10:51:39 - [scheduler] scheduler - [INFO] INFO - 💤 Drucker Tapo P110 (192.168.0.106) nach 7 Min Leerlauf ausgeschaltet 2025-06-11 10:51:39 - [scheduler] scheduler - [INFO] INFO - 💤 Drucker Tapo P110 (192.168.0.106) nach 7 Min Leerlauf ausgeschaltet
2025-06-11 12:43:22 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True

View File

@ -528,3 +528,4 @@
2025-06-11 10:51:50 - [tapo_controller] tapo_controller - [INFO] INFO - 🔍 teste ip 6/6: 192.168.0.105 2025-06-11 10:51:50 - [tapo_controller] tapo_controller - [INFO] INFO - 🔍 teste ip 6/6: 192.168.0.105
2025-06-11 10:51:53 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ steckdosen-erkennung abgeschlossen: 5/6 steckdosen gefunden in 18.0s 2025-06-11 10:51:53 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ steckdosen-erkennung abgeschlossen: 5/6 steckdosen gefunden in 18.0s
2025-06-11 10:51:56 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ steckdosen-erkennung abgeschlossen: 5/6 steckdosen gefunden in 18.7s 2025-06-11 10:51:56 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ steckdosen-erkennung abgeschlossen: 5/6 steckdosen gefunden in 18.7s
2025-06-11 12:43:22 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert

View File

@ -1,37 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from models import get_db_session, Printer
def aktiviere_alle_drucker():
"""Aktiviert alle Drucker in der Datenbank."""
try:
session = get_db_session()
drucker = session.query(Printer).all()
if not drucker:
print("Keine Drucker in der Datenbank gefunden.")
session.close()
return
print(f"Anzahl Drucker: {len(drucker)}")
print("Aktiviere alle Drucker...")
for d in drucker:
d.active = True
print(f"Drucker {d.id}: {d.name} - IP: {d.plug_ip} - Aktiv: {d.active}")
session.commit()
print("Alle Drucker wurden erfolgreich aktiviert!")
session.close()
except Exception as e:
print(f"Fehler: {str(e)}")
try:
session.rollback()
session.close()
except:
pass
if __name__ == "__main__":
aktiviere_alle_drucker()

View File

@ -1,336 +0,0 @@
#!/usr/bin/env python3
"""
Robuste Datenbank-Cleanup-Utilities
Verhindert "database is locked" Fehler durch intelligente Retry-Logik und Verbindungsmanagement
"""
import os
import time
import sqlite3
import threading
from datetime import datetime
from typing import Optional, Tuple, List
from contextlib import contextmanager
from sqlalchemy import text, create_engine
from sqlalchemy.engine import Engine
from sqlalchemy.pool import StaticPool
from utils.settings import DATABASE_PATH
from utils.logging_config import get_logger
logger = get_logger("database_cleanup")
class DatabaseCleanupManager:
"""
Verwaltet sichere Datenbank-Cleanup-Operationen mit Retry-Logik
Verhindert "database is locked" Fehler durch intelligente Session-Verwaltung
"""
def __init__(self):
self._cleanup_lock = threading.Lock()
self._cleanup_completed = False
self._active_engines = []
def register_engine(self, engine: Engine):
"""Registriert eine Engine für das Cleanup"""
with self._cleanup_lock:
if engine not in self._active_engines:
self._active_engines.append(engine)
def force_close_all_connections(self, max_wait_seconds: int = 10) -> bool:
"""
Schließt alle aktiven Datenbankverbindungen forciert
Args:
max_wait_seconds: Maximale Wartezeit für graceful shutdown
Returns:
bool: True wenn erfolgreich
"""
try:
logger.info("🔄 Schließe alle aktiven Datenbankverbindungen...")
# Alle registrierten Engines disposen
with self._cleanup_lock:
for engine in self._active_engines:
try:
logger.debug(f"Disposing Engine: {engine}")
engine.dispose()
except Exception as e:
logger.warning(f"Fehler beim Engine Dispose: {e}")
self._active_engines.clear()
# Kurz warten damit alle Verbindungen sich schließen können
time.sleep(1)
# Prüfe ob noch WAL-Locks bestehen
wal_path = DATABASE_PATH + "-wal"
shm_path = DATABASE_PATH + "-shm"
start_time = time.time()
while time.time() - start_time < max_wait_seconds:
try:
# Teste kurze Verbindung
test_conn = sqlite3.connect(DATABASE_PATH, timeout=2)
test_conn.execute("BEGIN IMMEDIATE") # Teste exklusiven Zugriff
test_conn.rollback()
test_conn.close()
logger.info("✅ Alle Datenbankverbindungen erfolgreich geschlossen")
return True
except sqlite3.OperationalError as e:
if "database is locked" in str(e):
logger.debug(f"Warte auf Verbindungsschließung... ({time.time() - start_time:.1f}s)")
time.sleep(0.5)
continue
else:
raise
logger.warning(f"⚠️ Timeout beim Warten auf Verbindungsschließung ({max_wait_seconds}s)")
return False
except Exception as e:
logger.error(f"❌ Fehler beim Schließen der Verbindungen: {e}")
return False
def safe_wal_checkpoint(self, retry_attempts: int = 5) -> Tuple[bool, Optional[str]]:
"""
Führt sicheren WAL-Checkpoint mit Retry-Logik durch
Args:
retry_attempts: Anzahl der Wiederholungsversuche
Returns:
Tuple[bool, Optional[str]]: (Erfolg, Fehlermeldung)
"""
for attempt in range(retry_attempts):
try:
# Kurze, direkte SQLite-Verbindung für Checkpoint
conn = sqlite3.connect(DATABASE_PATH, timeout=10)
# WAL-Checkpoint mit verschiedenen Strategien
strategies = ["TRUNCATE", "RESTART", "FULL", "PASSIVE"]
for strategy in strategies:
try:
cursor = conn.execute(f"PRAGMA wal_checkpoint({strategy})")
result = cursor.fetchone()
if result and result[0] == 0: # Erfolg (0 = success)
pages_transferred = result[1] if len(result) > 1 else 0
pages_reset = result[2] if len(result) > 2 else 0
if pages_transferred > 0:
logger.info(f"✅ WAL-Checkpoint ({strategy}): {pages_transferred} Seiten übertragen, {pages_reset} Seiten zurückgesetzt")
else:
logger.debug(f"WAL-Checkpoint ({strategy}): Keine Seiten zu übertragen")
conn.close()
return True, None
else:
logger.warning(f"WAL-Checkpoint ({strategy}) unvollständig: {result}")
except Exception as strategy_error:
logger.warning(f"WAL-Checkpoint ({strategy}) fehlgeschlagen: {strategy_error}")
continue
conn.close()
# Wenn alle Strategien fehlschlagen, versuche VACUUM als Fallback
if attempt == 0: # Nur beim ersten Versuch
logger.info("Versuche VACUUM als Fallback...")
conn = sqlite3.connect(DATABASE_PATH, timeout=10)
conn.execute("VACUUM")
conn.close()
logger.info("✅ VACUUM erfolgreich")
return True, None
except sqlite3.OperationalError as e:
if "database is locked" in str(e):
wait_time = (2 ** attempt) * 0.5 # Exponential backoff
logger.warning(f"Database locked - Versuch {attempt + 1}/{retry_attempts}, warte {wait_time}s...")
time.sleep(wait_time)
continue
else:
return False, f"SQLite-Fehler: {e}"
except Exception as e:
return False, f"Unerwarteter Fehler: {e}"
return False, f"Database nach {retry_attempts} Versuchen immer noch gesperrt"
def safe_journal_mode_switch(self, target_mode: str = "DELETE", retry_attempts: int = 3) -> Tuple[bool, Optional[str]]:
"""
Führt sicheren Journal-Mode-Switch mit Retry-Logik durch
Args:
target_mode: Ziel-Journal-Mode (DELETE, WAL, etc.)
retry_attempts: Anzahl der Wiederholungsversuche
Returns:
Tuple[bool, Optional[str]]: (Erfolg, Fehlermeldung)
"""
for attempt in range(retry_attempts):
try:
conn = sqlite3.connect(DATABASE_PATH, timeout=15)
# Prüfe aktuellen Journal-Mode
current_mode = conn.execute("PRAGMA journal_mode").fetchone()[0]
logger.debug(f"Aktueller Journal-Mode: {current_mode}")
if current_mode.upper() == target_mode.upper():
logger.info(f"Journal-Mode bereits auf {target_mode}")
conn.close()
return True, None
# Mode-Switch durchführen
result = conn.execute(f"PRAGMA journal_mode={target_mode}").fetchone()
new_mode = result[0] if result else None
conn.close()
if new_mode and new_mode.upper() == target_mode.upper():
logger.info(f"✅ Journal-Mode erfolgreich auf {new_mode} umgeschaltet")
return True, None
else:
logger.warning(f"Journal-Mode-Switch unvollständig: {new_mode} != {target_mode}")
except sqlite3.OperationalError as e:
if "database is locked" in str(e):
wait_time = (2 ** attempt) * 1.0 # Exponential backoff
logger.warning(f"Database locked bei Mode-Switch - Versuch {attempt + 1}/{retry_attempts}, warte {wait_time}s...")
time.sleep(wait_time)
continue
else:
return False, f"SQLite-Fehler: {e}"
except Exception as e:
return False, f"Unerwarteter Fehler: {e}"
return False, f"Journal-Mode-Switch nach {retry_attempts} Versuchen fehlgeschlagen"
def comprehensive_cleanup(self, force_mode_switch: bool = True) -> dict:
"""
Führt umfassendes, sicheres Datenbank-Cleanup durch
Args:
force_mode_switch: Ob Journal-Mode forciert umgeschaltet werden soll
Returns:
dict: Cleanup-Ergebnis mit Details
"""
with self._cleanup_lock:
if self._cleanup_completed:
logger.info("Datenbank-Cleanup bereits durchgeführt")
return {"success": True, "message": "Bereits durchgeführt", "operations": []}
logger.info("🧹 Starte umfassendes Datenbank-Cleanup...")
operations = []
errors = []
try:
# Schritt 1: Alle Verbindungen schließen
logger.info("📝 Schritt 1: Schließe alle Datenbankverbindungen...")
connection_success = self.force_close_all_connections(max_wait_seconds=15)
if connection_success:
operations.append("Alle Verbindungen geschlossen")
else:
errors.append("Timeout beim Verbindungsschließen")
# Schritt 2: WAL-Checkpoint
logger.info("📝 Schritt 2: Führe WAL-Checkpoint durch...")
checkpoint_success, checkpoint_error = self.safe_wal_checkpoint(retry_attempts=5)
if checkpoint_success:
operations.append("WAL-Checkpoint erfolgreich")
else:
errors.append(f"WAL-Checkpoint fehlgeschlagen: {checkpoint_error}")
# Schritt 3: Journal-Mode-Switch (nur wenn gewünscht und Checkpoint erfolgreich)
if force_mode_switch and checkpoint_success:
logger.info("📝 Schritt 3: Schalte Journal-Mode um...")
mode_success, mode_error = self.safe_journal_mode_switch("DELETE", retry_attempts=3)
if mode_success:
operations.append("Journal-Mode auf DELETE umgeschaltet")
else:
errors.append(f"Journal-Mode-Switch fehlgeschlagen: {mode_error}")
logger.warning(f"Journal-Mode-Switch fehlgeschlagen, aber WAL-Checkpoint war erfolgreich")
# Schritt 4: Finale Optimierungen (nur bei Erfolg)
if checkpoint_success:
logger.info("📝 Schritt 4: Finale Optimierungen...")
try:
conn = sqlite3.connect(DATABASE_PATH, timeout=5)
conn.execute("PRAGMA optimize")
conn.close()
operations.append("Datenbank optimiert")
except Exception as opt_error:
logger.warning(f"Optimierung fehlgeschlagen: {opt_error}")
# Schritt 5: Prüfe Ergebnis
wal_path = DATABASE_PATH + "-wal"
shm_path = DATABASE_PATH + "-shm"
wal_exists = os.path.exists(wal_path)
shm_exists = os.path.exists(shm_path)
if not wal_exists and not shm_exists:
operations.append("WAL/SHM-Dateien erfolgreich entfernt")
logger.info("✅ WAL- und SHM-Dateien erfolgreich entfernt")
elif force_mode_switch:
errors.append(f"WAL/SHM-Dateien bestehen noch (WAL: {wal_exists}, SHM: {shm_exists})")
else:
logger.info("WAL/SHM-Dateien bleiben bestehen (kein Mode-Switch angefordert)")
self._cleanup_completed = True
# Erfolgsstatus bestimmen
success = len(operations) > 0 and (not force_mode_switch or not wal_exists)
result = {
"success": success,
"operations": operations,
"errors": errors,
"timestamp": datetime.now().isoformat(),
"wal_files_removed": not wal_exists and not shm_exists
}
if success:
logger.info(f"✅ Datenbank-Cleanup erfolgreich: {', '.join(operations)}")
else:
logger.error(f"❌ Datenbank-Cleanup mit Fehlern: {', '.join(errors)}")
return result
except Exception as e:
error_msg = f"Kritischer Fehler beim Datenbank-Cleanup: {e}"
logger.error(f"{error_msg}")
return {
"success": False,
"operations": operations,
"errors": errors + [error_msg],
"timestamp": datetime.now().isoformat()
}
# Globale Instanz
cleanup_manager = DatabaseCleanupManager()
def get_cleanup_manager() -> DatabaseCleanupManager:
"""Gibt die globale Cleanup-Manager-Instanz zurück"""
return cleanup_manager
def safe_database_cleanup(force_mode_switch: bool = True) -> dict:
"""
Convenience-Funktion für sicheres Datenbank-Cleanup
Args:
force_mode_switch: Ob Journal-Mode forciert umgeschaltet werden soll
Returns:
dict: Cleanup-Ergebnis
"""
return cleanup_manager.comprehensive_cleanup(force_mode_switch=force_mode_switch)

View File

@ -1,133 +0,0 @@
import os
import logging
from typing import List, Optional, Any
from datetime import datetime
from sqlalchemy import create_engine, func
from sqlalchemy.orm import sessionmaker, Session, joinedload
from models import User, Printer, Job, Stats, Base
from utils.settings import DATABASE_PATH, ensure_database_directory
logger = logging.getLogger(__name__)
class DatabaseManager:
"""Database manager class to handle database operations."""
def __init__(self):
"""Initialize the database manager."""
ensure_database_directory()
self.engine = create_engine(f"sqlite:///{DATABASE_PATH}")
self.Session = sessionmaker(bind=self.engine)
def get_session(self) -> Session:
"""Get a new database session.
Returns:
Session: A new SQLAlchemy session.
"""
return self.Session()
def test_connection(self) -> bool:
"""Test the database connection.
Returns:
bool: True if the connection is successful, False otherwise.
"""
try:
session = self.get_session()
session.execute("SELECT 1")
session.close()
return True
except Exception as e:
logger.error(f"Database connection test failed: {str(e)}")
return False
def get_all_jobs(self) -> List[Job]:
"""Get all jobs with eager loading of relationships.
Returns:
List[Job]: A list of all jobs.
"""
session = self.get_session()
try:
jobs = session.query(Job).options(
joinedload(Job.user),
joinedload(Job.printer)
).all()
return jobs
finally:
session.close()
def get_jobs_by_status(self, status: str) -> List[Job]:
"""Get jobs by status with eager loading of relationships.
Args:
status: The job status to filter by.
Returns:
List[Job]: A list of jobs with the specified status.
"""
session = self.get_session()
try:
jobs = session.query(Job).options(
joinedload(Job.user),
joinedload(Job.printer)
).filter(Job.status == status).all()
return jobs
finally:
session.close()
def get_job_by_id(self, job_id: int) -> Optional[Job]:
"""Get a job by ID with eager loading of relationships.
Args:
job_id: The job ID to find.
Returns:
Optional[Job]: The job if found, None otherwise.
"""
session = self.get_session()
try:
job = session.query(Job).options(
joinedload(Job.user),
joinedload(Job.printer)
).filter(Job.id == job_id).first()
return job
finally:
session.close()
def get_available_printers(self) -> List[Printer]:
"""Get all available printers.
Returns:
List[Printer]: A list of available printers.
"""
session = self.get_session()
try:
printers = session.query(Printer).filter(
Printer.active == True,
Printer.status != "busy"
).all()
return printers
finally:
session.close()
def get_jobs_since(self, since_date: datetime) -> List[Job]:
"""Get jobs created since a specific date.
Args:
since_date: The date to filter jobs from.
Returns:
List[Job]: A list of jobs created since the specified date.
"""
session = self.get_session()
try:
jobs = session.query(Job).options(
joinedload(Job.user),
joinedload(Job.printer)
).filter(Job.created_at >= since_date).all()
return jobs
finally:
session.close()

View File

@ -0,0 +1,405 @@
#!/usr/bin/env python3.11
"""
Development Utilities - Konsolidierte Entwicklungs- und Test-Hilfsfunktionen
Zusammenfassung von Datenbank-Tests, Session-Fixes und anderen Entwicklungstools
"""
import re
import os
import sys
from utils.logging_config import get_logger
# Logger initialisieren
logger = get_logger("development_utilities")
# ===== DATENBANK-TESTS =====
def test_database_connectivity():
"""
Testet die grundlegende Datenbank-Konnektivität.
Returns:
dict: Test-Ergebnisse
"""
try:
from models import get_cached_session, User, Printer, Job
logger.info("=== DATENBANK-KONNEKTIVITÄTS-TEST ===")
results = {
'success': True,
'tests': {},
'errors': []
}
with get_cached_session() as session:
# Test User-Query
try:
users = session.query(User).limit(5).all()
results['tests']['users'] = {
'success': True,
'count': len(users),
'sample': users[0].username if users else None
}
logger.info(f"✓ User-Abfrage erfolgreich - {len(users)} Benutzer gefunden")
if users:
user = users[0]
logger.info(f"✓ Test-User: {user.username} ({user.email})")
logger.info(f"✓ updated_at-Feld: {user.updated_at}")
except Exception as e:
results['tests']['users'] = {'success': False, 'error': str(e)}
results['errors'].append(f"User-Test: {str(e)}")
logger.error(f"❌ User-Test fehlgeschlagen: {str(e)}")
# Test Printer-Query
try:
printers = session.query(Printer).limit(5).all()
results['tests']['printers'] = {
'success': True,
'count': len(printers),
'sample': printers[0].name if printers else None
}
logger.info(f"✓ Printer-Abfrage erfolgreich - {len(printers)} Drucker gefunden")
except Exception as e:
results['tests']['printers'] = {'success': False, 'error': str(e)}
results['errors'].append(f"Printer-Test: {str(e)}")
logger.error(f"❌ Printer-Test fehlgeschlagen: {str(e)}")
# Test Job-Query
try:
jobs = session.query(Job).limit(5).all()
results['tests']['jobs'] = {
'success': True,
'count': len(jobs),
'sample': jobs[0].title if jobs else None
}
logger.info(f"✓ Job-Abfrage erfolgreich - {len(jobs)} Jobs gefunden")
except Exception as e:
results['tests']['jobs'] = {'success': False, 'error': str(e)}
results['errors'].append(f"Job-Test: {str(e)}")
logger.error(f"❌ Job-Test fehlgeschlagen: {str(e)}")
if results['errors']:
results['success'] = False
logger.error("❌ DATENBANK-TESTS TEILWEISE FEHLGESCHLAGEN")
else:
logger.info("🎉 ALLE DATENBANK-TESTS ERFOLGREICH!")
logger.info("Die Anwendung sollte jetzt ohne Fehler starten.")
return results
except Exception as e:
logger.error(f"❌ KRITISCHER DATENBANK-TEST-FEHLER: {str(e)}")
return {
'success': False,
'tests': {},
'errors': [f"Kritischer Fehler: {str(e)}"]
}
def test_database_schema():
"""
Testet die Datenbank-Schema-Integrität.
Returns:
dict: Schema-Test-Ergebnisse
"""
try:
from models import get_cached_session, User, Printer, Job, GuestRequest
from sqlalchemy import inspect
logger.info("=== DATENBANK-SCHEMA-TEST ===")
results = {
'success': True,
'tables': {},
'errors': []
}
with get_cached_session() as session:
inspector = inspect(session.bind)
# Erwartete Tabellen
expected_tables = ['users', 'printers', 'jobs', 'guest_requests', 'system_logs']
for table_name in expected_tables:
try:
if inspector.has_table(table_name):
columns = inspector.get_columns(table_name)
results['tables'][table_name] = {
'exists': True,
'columns': len(columns),
'column_names': [col['name'] for col in columns]
}
logger.info(f"✓ Tabelle '{table_name}': {len(columns)} Spalten")
else:
results['tables'][table_name] = {'exists': False}
results['errors'].append(f"Tabelle '{table_name}' nicht gefunden")
logger.error(f"❌ Tabelle '{table_name}' nicht gefunden")
except Exception as e:
results['tables'][table_name] = {'exists': False, 'error': str(e)}
results['errors'].append(f"Tabelle '{table_name}': {str(e)}")
logger.error(f"❌ Fehler bei Tabelle '{table_name}': {str(e)}")
if results['errors']:
results['success'] = False
logger.error("❌ SCHEMA-TESTS TEILWEISE FEHLGESCHLAGEN")
else:
logger.info("🎉 ALLE SCHEMA-TESTS ERFOLGREICH!")
return results
except Exception as e:
logger.error(f"❌ KRITISCHER SCHEMA-TEST-FEHLER: {str(e)}")
return {
'success': False,
'tables': {},
'errors': [f"Kritischer Fehler: {str(e)}"]
}
# ===== SESSION-FIXES =====
def fix_session_usage_in_file(file_path):
"""
Behebt Session-Usage in einer Datei durch Konvertierung zu Context Manager Pattern.
Args:
file_path (str): Pfad zur zu reparierenden Datei
Returns:
dict: Reparatur-Ergebnisse
"""
try:
if not os.path.exists(file_path):
return {
'success': False,
'message': f'Datei nicht gefunden: {file_path}',
'changes_made': False
}
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Pattern für direkte Session-Aufrufe
patterns = [
# session = get_cached_session() -> with get_cached_session() as session:
(r'(\s+)session = get_cached_session\(\)', r'\1with get_cached_session() as session:'),
# session.close() entfernen (wird automatisch durch Context Manager gemacht)
(r'\s+session\.close\(\)\s*\n', '\n'),
]
original_content = content
changes_count = 0
for pattern, replacement in patterns:
new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
if new_content != content:
changes_count += 1
content = new_content
# Nur schreiben wenn sich etwas geändert hat
if content != original_content:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
logger.info(f"{file_path} wurde aktualisiert ({changes_count} Änderungen)")
return {
'success': True,
'message': f'Datei erfolgreich aktualisiert',
'changes_made': True,
'changes_count': changes_count
}
else:
logger.info(f" {file_path} benötigt keine Änderungen")
return {
'success': True,
'message': 'Keine Änderungen erforderlich',
'changes_made': False,
'changes_count': 0
}
except Exception as e:
logger.error(f"❌ Fehler beim Reparieren von {file_path}: {str(e)}")
return {
'success': False,
'message': f'Fehler: {str(e)}',
'changes_made': False
}
def fix_session_usage_bulk(directory_path=None):
"""
Repariert Session-Usage in mehreren Dateien.
Args:
directory_path (str): Verzeichnis zum Durchsuchen (Standard: blueprints/)
Returns:
dict: Bulk-Reparatur-Ergebnisse
"""
if directory_path is None:
backend_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
directory_path = os.path.join(backend_dir, 'blueprints')
results = {
'success': True,
'files_processed': 0,
'files_changed': 0,
'total_changes': 0,
'errors': []
}
try:
for root, dirs, files in os.walk(directory_path):
for file in files:
if file.endswith('.py'):
file_path = os.path.join(root, file)
result = fix_session_usage_in_file(file_path)
results['files_processed'] += 1
if result['success']:
if result['changes_made']:
results['files_changed'] += 1
results['total_changes'] += result.get('changes_count', 0)
else:
results['errors'].append(f"{file_path}: {result['message']}")
results['success'] = False
logger.info(f"Bulk-Reparatur abgeschlossen: {results['files_processed']} Dateien verarbeitet, {results['files_changed']} geändert")
except Exception as e:
logger.error(f"❌ Fehler bei Bulk-Reparatur: {str(e)}")
results['success'] = False
results['errors'].append(f"Bulk-Fehler: {str(e)}")
return results
# ===== CODE-QUALITÄT =====
def analyze_code_quality(file_path):
"""
Analysiert die Code-Qualität einer Python-Datei.
Args:
file_path (str): Pfad zur zu analysierenden Datei
Returns:
dict: Code-Qualitäts-Analyse
"""
try:
if not os.path.exists(file_path):
return {
'success': False,
'message': f'Datei nicht gefunden: {file_path}'
}
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
lines = content.split('\n')
analysis = {
'success': True,
'file_path': file_path,
'metrics': {
'total_lines': len(lines),
'code_lines': len([line for line in lines if line.strip() and not line.strip().startswith('#')]),
'comment_lines': len([line for line in lines if line.strip().startswith('#')]),
'empty_lines': len([line for line in lines if not line.strip()]),
'functions': len(re.findall(r'^\s*def\s+\w+', content, re.MULTILINE)),
'classes': len(re.findall(r'^\s*class\s+\w+', content, re.MULTILINE)),
'imports': len(re.findall(r'^\s*(import|from)\s+', content, re.MULTILINE))
},
'issues': []
}
# Potentielle Probleme identifizieren
if analysis['metrics']['total_lines'] > 1000:
analysis['issues'].append('Datei sehr groß (>1000 Zeilen) - Aufteilung empfohlen')
if analysis['metrics']['functions'] > 50:
analysis['issues'].append('Viele Funktionen (>50) - Modularisierung empfohlen')
# TODO-Kommentare finden
todos = re.findall(r'#.*TODO.*', content, re.IGNORECASE)
if todos:
analysis['metrics']['todos'] = len(todos)
analysis['issues'].append(f'{len(todos)} TODO-Kommentare gefunden')
return analysis
except Exception as e:
logger.error(f"❌ Fehler bei Code-Qualitäts-Analyse von {file_path}: {str(e)}")
return {
'success': False,
'message': f'Fehler: {str(e)}'
}
# ===== CLI INTERFACE =====
if __name__ == "__main__":
if len(sys.argv) > 1:
command = sys.argv[1]
if command == "test-db":
result = test_database_connectivity()
if result['success']:
print("🎉 Datenbank-Tests erfolgreich!")
else:
print("❌ Datenbank-Tests fehlgeschlagen:")
for error in result['errors']:
print(f" - {error}")
elif command == "test-schema":
result = test_database_schema()
if result['success']:
print("🎉 Schema-Tests erfolgreich!")
else:
print("❌ Schema-Tests fehlgeschlagen:")
for error in result['errors']:
print(f" - {error}")
elif command == "fix-sessions":
if len(sys.argv) > 2:
file_path = sys.argv[2]
result = fix_session_usage_in_file(file_path)
print(f"{'' if result['success'] else ''} {result['message']}")
else:
result = fix_session_usage_bulk()
print(f"Bulk-Reparatur: {result['files_changed']}/{result['files_processed']} Dateien geändert")
elif command == "analyze":
if len(sys.argv) > 2:
file_path = sys.argv[2]
result = analyze_code_quality(file_path)
if result['success']:
metrics = result['metrics']
print(f"=== Code-Analyse: {os.path.basename(file_path)} ===")
print(f"Zeilen gesamt: {metrics['total_lines']}")
print(f"Code-Zeilen: {metrics['code_lines']}")
print(f"Funktionen: {metrics['functions']}")
print(f"Klassen: {metrics['classes']}")
if result['issues']:
print("Probleme:")
for issue in result['issues']:
print(f" ⚠️ {issue}")
else:
print(f"{result['message']}")
else:
print("Verwendung: python3.11 development_utilities.py analyze <datei>")
else:
print("Verfügbare Kommandos:")
print(" test-db - Testet Datenbank-Konnektivität")
print(" test-schema - Testet Datenbank-Schema")
print(" fix-sessions [datei] - Repariert Session-Usage")
print(" analyze <datei> - Analysiert Code-Qualität")
else:
print("Verwendung: python3.11 development_utilities.py <command>")
print("Verfügbare Kommandos: test-db, test-schema, fix-sessions, analyze")

View File

@ -1,22 +0,0 @@
#!/usr/bin/env python3
"""Entferne problematischen CSRF-Error-Handler aus app.py"""
import re
# Lese die Backup-Datei
with open('app_backup.py', 'r', encoding='utf-8') as f:
content = f.read()
# Entferne den CSRF-Error-Handler-Block
# Suche nach @csrf.error_handler bis zum ersten leeren Zeilen-Block
pattern = r'@csrf\.error_handler.*?(?=\n\n|\n# [A-Z])'
content = re.sub(pattern, '', content, flags=re.DOTALL)
# Entferne auch mögliche doppelte Leerzeilen
content = re.sub(r'\n\n\n+', '\n\n', content)
# Schreibe die bereinigte Version
with open('app.py', 'w', encoding='utf-8') as f:
f.write(content)
print("CSRF-Error-Handler erfolgreich entfernt!")

View File

@ -1,29 +0,0 @@
#!/usr/bin/env python3.11
"""
Skript zur Behebung von Einrückungsproblemen in user_management.py
"""
def fix_indentation():
file_path = 'blueprints/user_management.py'
with open(file_path, 'r') as f:
content = f.read()
lines = content.split('\n')
fixed_lines = []
for line in lines:
# Behebe die falsche Einrückung nach 'with get_cached_session() as session:'
if line.startswith(' ') and not line.strip().startswith('#'):
# 7 Leerzeichen entfernen (von 15 auf 8)
fixed_lines.append(' ' + line[15:])
else:
fixed_lines.append(line)
with open(file_path, 'w') as f:
f.write('\n'.join(fixed_lines))
print('✅ Einrückung behoben')
if __name__ == "__main__":
fix_indentation()

View File

@ -1,62 +0,0 @@
#!/usr/bin/env python3.11
"""
Skript zur automatischen Behebung von get_cached_session() Aufrufen
Konvertiert direkte Session-Aufrufe zu Context Manager Pattern.
Autor: MYP Team
Datum: 2025-06-09
"""
import re
import os
def fix_session_usage(file_path):
"""Behebt Session-Usage in einer Datei"""
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Pattern für direkte Session-Aufrufe
patterns = [
# session = get_cached_session() -> with get_cached_session() as session:
(r'(\s+)session = get_cached_session\(\)', r'\1with get_cached_session() as session:'),
# session.close() entfernen (wird automatisch durch Context Manager gemacht)
(r'\s+session\.close\(\)\s*\n', '\n'),
# Einrückung nach with-Statement anpassen
(r'(with get_cached_session\(\) as session:\s*\n)(\s+)([^\s])',
lambda m: m.group(1) + m.group(2) + ' ' + m.group(3))
]
original_content = content
for pattern, replacement in patterns:
if callable(replacement):
content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
else:
content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
# Nur schreiben wenn sich etwas geändert hat
if content != original_content:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"{file_path} wurde aktualisiert")
return True
else:
print(f" {file_path} benötigt keine Änderungen")
return False
def main():
"""Hauptfunktion"""
backend_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
user_mgmt_file = os.path.join(backend_dir, 'blueprints', 'user_management.py')
if os.path.exists(user_mgmt_file):
print(f"Bearbeite {user_mgmt_file}...")
fix_session_usage(user_mgmt_file)
else:
print(f"❌ Datei nicht gefunden: {user_mgmt_file}")
if __name__ == "__main__":
main()

View File

@ -1,25 +0,0 @@
#!/usr/bin/env python3.11
from models import init_database, create_initial_admin
if __name__ == "__main__":
print("Initialisiere Datenbank...")
init_database()
print("Erstelle initialen Admin-Benutzer...")
success = create_initial_admin(
email="admin@mercedes-benz.com",
password="744563017196A",
name="System Administrator",
username="admin"
)
if success:
print("Admin-Benutzer erfolgreich erstellt.")
print("Login-Daten:")
print(" Benutzername: admin")
print(" Passwort: 744563017196A")
else:
print("Admin-Benutzer konnte nicht erstellt werden (existiert bereits?).")
print("Datenbank-Initialisierung abgeschlossen.")

View File

@ -1,30 +0,0 @@
"""
Performance Monitor für Production Environment
Minimale Implementierung für Mercedes-Benz TBA Marienfelde
"""
from utils.logging_config import get_logger
logger = get_logger("performance_monitor")
def init_performance_monitoring(app):
"""
Initialisiert Performance-Monitoring für die Flask-App
Args:
app: Flask-App-Instanz
"""
try:
# Basic Performance-Monitoring Setup
logger.info("[PERF] Performance-Monitoring wird initialisiert...")
# Optional: Hier könnten weitere Performance-Monitoring-Tools integriert werden
# Für Air-Gapped Environment halten wir es minimal
app.config['PERFORMANCE_MONITORING_ENABLED'] = True
logger.info("[PERF] ✅ Performance-Monitoring erfolgreich initialisiert")
except Exception as e:
logger.error(f"[PERF] ❌ Fehler bei Performance-Monitoring-Initialisierung: {str(e)}")
app.config['PERFORMANCE_MONITORING_ENABLED'] = False

View File

@ -0,0 +1,360 @@
#!/usr/bin/env python3.11
"""
Printer Utilities - Konsolidierte Drucker-Management-Hilfsfunktionen
Zusammenfassung von Drucker-Aktivierung und Standort-Updates
"""
from models import get_db_session, Printer
from utils.logging_config import get_logger
from datetime import datetime
# Logger initialisieren
logger = get_logger("printer_utilities")
# ===== DRUCKER AKTIVIERUNG =====
def aktiviere_alle_drucker():
"""
Aktiviert alle Drucker in der Datenbank.
Returns:
dict: Ergebnis der Aktivierung mit Statistiken
"""
try:
session = get_db_session()
drucker = session.query(Printer).all()
if not drucker:
logger.warning("Keine Drucker in der Datenbank gefunden.")
session.close()
return {
'success': False,
'message': 'Keine Drucker gefunden',
'activated_count': 0
}
logger.info(f"Anzahl Drucker: {len(drucker)}")
logger.info("Aktiviere alle Drucker...")
activated_count = 0
for d in drucker:
if not d.active:
d.active = True
activated_count += 1
logger.info(f"Drucker {d.id}: {d.name} - IP: {d.plug_ip} - Aktiviert")
else:
logger.debug(f"Drucker {d.id}: {d.name} - Bereits aktiv")
session.commit()
session.close()
logger.info(f"{activated_count} Drucker wurden erfolgreich aktiviert!")
return {
'success': True,
'message': f'{activated_count} Drucker aktiviert',
'activated_count': activated_count,
'total_count': len(drucker)
}
except Exception as e:
logger.error(f"Fehler bei Drucker-Aktivierung: {str(e)}")
try:
session.rollback()
session.close()
except:
pass
return {
'success': False,
'message': f'Fehler: {str(e)}',
'activated_count': 0
}
def deaktiviere_alle_drucker():
"""
Deaktiviert alle Drucker in der Datenbank.
Returns:
dict: Ergebnis der Deaktivierung mit Statistiken
"""
try:
session = get_db_session()
drucker = session.query(Printer).all()
if not drucker:
logger.warning("Keine Drucker in der Datenbank gefunden.")
session.close()
return {
'success': False,
'message': 'Keine Drucker gefunden',
'deactivated_count': 0
}
logger.info(f"Anzahl Drucker: {len(drucker)}")
logger.info("Deaktiviere alle Drucker...")
deactivated_count = 0
for d in drucker:
if d.active:
d.active = False
deactivated_count += 1
logger.info(f"Drucker {d.id}: {d.name} - IP: {d.plug_ip} - Deaktiviert")
else:
logger.debug(f"Drucker {d.id}: {d.name} - Bereits inaktiv")
session.commit()
session.close()
logger.info(f"{deactivated_count} Drucker wurden erfolgreich deaktiviert!")
return {
'success': True,
'message': f'{deactivated_count} Drucker deaktiviert',
'deactivated_count': deactivated_count,
'total_count': len(drucker)
}
except Exception as e:
logger.error(f"Fehler bei Drucker-Deaktivierung: {str(e)}")
try:
session.rollback()
session.close()
except:
pass
return {
'success': False,
'message': f'Fehler: {str(e)}',
'deactivated_count': 0
}
# ===== STANDORT-MANAGEMENT =====
def update_printer_locations(new_location="Werk 040 - Berlin - TBA"):
"""
Aktualisiert alle Drucker-Standorte zu einem neuen Standort.
Args:
new_location (str): Neuer Standort für alle Drucker
Returns:
dict: Ergebnis der Standort-Aktualisierung mit Statistiken
"""
try:
session = get_db_session()
# Alle Drucker abrufen
all_printers = session.query(Printer).all()
logger.info(f"Gefundene Drucker: {len(all_printers)}")
if not all_printers:
logger.warning("Keine Drucker in der Datenbank gefunden.")
session.close()
return {
'success': False,
'message': 'Keine Drucker gefunden',
'updated_count': 0
}
updated_count = 0
location_changes = []
# Alle Drucker durchgehen und Standort aktualisieren
for printer in all_printers:
old_location = printer.location
if old_location != new_location:
printer.location = new_location
location_changes.append({
'printer_id': printer.id,
'printer_name': printer.name,
'old_location': old_location,
'new_location': new_location
})
logger.info(f"{printer.name}: '{old_location}''{new_location}'")
updated_count += 1
else:
logger.debug(f"Drucker {printer.name}: Standort bereits korrekt")
# Änderungen speichern
session.commit()
session.close()
logger.info(f"{updated_count} Drucker-Standorte erfolgreich aktualisiert")
logger.info(f"Neuer Standort: {new_location}")
return {
'success': True,
'message': f'{updated_count} Standorte aktualisiert',
'updated_count': updated_count,
'total_count': len(all_printers),
'new_location': new_location,
'changes': location_changes
}
except Exception as e:
logger.error(f"❌ Fehler bei der Standort-Aktualisierung: {e}")
try:
session.rollback()
session.close()
except:
pass
return {
'success': False,
'message': f'Fehler: {str(e)}',
'updated_count': 0
}
def get_printer_locations():
"""
Gibt eine Übersicht aller Drucker-Standorte zurück.
Returns:
dict: Standort-Statistiken
"""
try:
session = get_db_session()
all_printers = session.query(Printer).all()
session.close()
if not all_printers:
return {
'success': False,
'message': 'Keine Drucker gefunden',
'locations': {}
}
# Standorte gruppieren
locations = {}
for printer in all_printers:
location = printer.location or 'Unbekannt'
if location not in locations:
locations[location] = []
locations[location].append({
'id': printer.id,
'name': printer.name,
'active': printer.active,
'plug_ip': printer.plug_ip
})
return {
'success': True,
'total_printers': len(all_printers),
'locations': locations,
'location_count': len(locations)
}
except Exception as e:
logger.error(f"Fehler beim Abrufen der Standorte: {str(e)}")
return {
'success': False,
'message': f'Fehler: {str(e)}',
'locations': {}
}
# ===== STATUS UND STATISTIKEN =====
def get_printer_status_summary():
"""
Gibt eine Zusammenfassung des Drucker-Status zurück.
Returns:
dict: Status-Zusammenfassung
"""
try:
session = get_db_session()
all_printers = session.query(Printer).all()
session.close()
if not all_printers:
return {
'success': False,
'message': 'Keine Drucker gefunden',
'summary': {}
}
active_count = sum(1 for p in all_printers if p.active)
inactive_count = len(all_printers) - active_count
# Standort-Verteilung
location_distribution = {}
for printer in all_printers:
location = printer.location or 'Unbekannt'
location_distribution[location] = location_distribution.get(location, 0) + 1
return {
'success': True,
'summary': {
'total_printers': len(all_printers),
'active_printers': active_count,
'inactive_printers': inactive_count,
'locations': location_distribution,
'last_updated': datetime.now().isoformat()
}
}
except Exception as e:
logger.error(f"Fehler beim Abrufen der Status-Zusammenfassung: {str(e)}")
return {
'success': False,
'message': f'Fehler: {str(e)}',
'summary': {}
}
# ===== CLI INTERFACE =====
if __name__ == "__main__":
import sys
if len(sys.argv) > 1:
command = sys.argv[1]
if command == "activate-all":
result = aktiviere_alle_drucker()
print(f"{result['message']}")
elif command == "deactivate-all":
result = deaktiviere_alle_drucker()
print(f"{result['message']}")
elif command == "update-locations":
new_location = sys.argv[2] if len(sys.argv) > 2 else "Werk 040 - Berlin - TBA"
result = update_printer_locations(new_location)
print(f"{result['message']}")
elif command == "locations":
result = get_printer_locations()
if result['success']:
print("=== Drucker-Standorte ===")
for location, printers in result['locations'].items():
print(f"\n📍 {location} ({len(printers)} Drucker):")
for printer in printers:
status = "🟢" if printer['active'] else "🔴"
print(f" {status} {printer['name']} (ID: {printer['id']}, IP: {printer['plug_ip']})")
else:
print(f"{result['message']}")
elif command == "status":
result = get_printer_status_summary()
if result['success']:
summary = result['summary']
print("=== Drucker-Status ===")
print(f"Gesamt: {summary['total_printers']}")
print(f"Aktiv: {summary['active_printers']} 🟢")
print(f"Inaktiv: {summary['inactive_printers']} 🔴")
print(f"Standorte: {len(summary['locations'])}")
print(f"Letzte Aktualisierung: {summary['last_updated']}")
else:
print(f"{result['message']}")
else:
print("Verfügbare Kommandos:")
print(" activate-all - Aktiviert alle Drucker")
print(" deactivate-all - Deaktiviert alle Drucker")
print(" update-locations [STANDORT] - Aktualisiert alle Standorte")
print(" locations - Zeigt Standort-Übersicht")
print(" status - Zeigt Status-Zusammenfassung")
else:
print("Verwendung: python3.11 printer_utilities.py <command>")
print("Verfügbare Kommandos: activate-all, deactivate-all, update-locations, locations, status")

View File

@ -1,50 +0,0 @@
"""
Scheduler utility functions for the admin panel.
"""
from utils.job_scheduler import scheduler
def scheduler_is_running():
"""
Überprüft, ob der Job-Scheduler läuft.
Returns:
bool: True wenn der Scheduler aktiv ist, sonst False
"""
return scheduler.is_running()
def start_scheduler():
"""
Startet den Job-Scheduler.
Returns:
bool: True wenn erfolgreich gestartet, False wenn bereits läuft
"""
return scheduler.start()
def stop_scheduler():
"""
Stoppt den Job-Scheduler.
Returns:
bool: True wenn erfolgreich gestoppt, False wenn nicht läuft
"""
return scheduler.stop()
def get_scheduler_uptime():
"""
Gibt die Laufzeit des Schedulers zurück.
Returns:
str: Formatierte Laufzeit oder None, wenn der Scheduler nicht läuft
"""
return scheduler.get_uptime()
def get_scheduler_tasks():
"""
Gibt alle registrierten Tasks im Scheduler zurück.
Returns:
dict: Dictionary mit Task-IDs als Schlüssel und Task-Konfigurationen als Werte
"""
return scheduler.get_tasks()

View File

@ -0,0 +1,158 @@
#!/usr/bin/env python3.11
"""
System Utilities - Konsolidierte System-Hilfsfunktionen
Zusammenfassung von performance_monitor, scheduler und init_db Funktionalitäten
"""
from utils.logging_config import get_logger
from utils.job_scheduler import scheduler
from models import init_database, create_initial_admin
# Logger initialisieren
logger = get_logger("system_utilities")
# ===== PERFORMANCE MONITORING =====
def init_performance_monitoring(app):
"""
Initialisiert Performance-Monitoring für die Flask-App
Args:
app: Flask-App-Instanz
"""
try:
# Basic Performance-Monitoring Setup
logger.info("[PERF] Performance-Monitoring wird initialisiert...")
# Optional: Hier könnten weitere Performance-Monitoring-Tools integriert werden
# Für Air-Gapped Environment halten wir es minimal
app.config['PERFORMANCE_MONITORING_ENABLED'] = True
logger.info("[PERF] ✅ Performance-Monitoring erfolgreich initialisiert")
except Exception as e:
logger.error(f"[PERF] ❌ Fehler bei Performance-Monitoring-Initialisierung: {str(e)}")
app.config['PERFORMANCE_MONITORING_ENABLED'] = False
# ===== SCHEDULER UTILITIES =====
def scheduler_is_running():
"""
Überprüft, ob der Job-Scheduler läuft.
Returns:
bool: True wenn der Scheduler aktiv ist, sonst False
"""
return scheduler.is_running()
def start_scheduler():
"""
Startet den Job-Scheduler.
Returns:
bool: True wenn erfolgreich gestartet, False wenn bereits läuft
"""
return scheduler.start()
def stop_scheduler():
"""
Stoppt den Job-Scheduler.
Returns:
bool: True wenn erfolgreich gestoppt, False wenn nicht läuft
"""
return scheduler.stop()
def get_scheduler_uptime():
"""
Gibt die Laufzeit des Schedulers zurück.
Returns:
str: Formatierte Laufzeit oder None, wenn der Scheduler nicht läuft
"""
return scheduler.get_uptime()
def get_scheduler_tasks():
"""
Gibt alle registrierten Tasks im Scheduler zurück.
Returns:
dict: Dictionary mit Task-IDs als Schlüssel und Task-Konfigurationen als Werte
"""
return scheduler.get_tasks()
# ===== DATABASE INITIALIZATION =====
def initialize_database_with_admin():
"""
Initialisiert die Datenbank und erstellt einen initialen Admin-Benutzer.
Returns:
bool: True wenn erfolgreich, False bei Fehlern
"""
try:
logger.info("Initialisiere Datenbank...")
init_database()
logger.info("Erstelle initialen Admin-Benutzer...")
success = create_initial_admin(
email="admin@mercedes-benz.com",
password="744563017196A",
name="System Administrator",
username="admin"
)
if success:
logger.info("Admin-Benutzer erfolgreich erstellt.")
logger.info("Login-Daten: Benutzername: admin, Passwort: 744563017196A")
else:
logger.warning("Admin-Benutzer konnte nicht erstellt werden (existiert bereits?).")
logger.info("Datenbank-Initialisierung abgeschlossen.")
return True
except Exception as e:
logger.error(f"Fehler bei Datenbank-Initialisierung: {str(e)}")
return False
# ===== SYSTEM STATUS =====
def get_system_status():
"""
Gibt den aktuellen System-Status zurück.
Returns:
dict: System-Status-Informationen
"""
return {
'scheduler_running': scheduler_is_running(),
'scheduler_uptime': get_scheduler_uptime(),
'scheduler_tasks': len(get_scheduler_tasks()) if get_scheduler_tasks() else 0,
'performance_monitoring': True # Immer aktiviert in dieser Version
}
# ===== CLI INTERFACE =====
if __name__ == "__main__":
import sys
if len(sys.argv) > 1:
command = sys.argv[1]
if command == "init-db":
initialize_database_with_admin()
elif command == "status":
status = get_system_status()
print("=== System Status ===")
print(f"Scheduler läuft: {'' if status['scheduler_running'] else ''}")
print(f"Scheduler Uptime: {status['scheduler_uptime'] or 'N/A'}")
print(f"Scheduler Tasks: {status['scheduler_tasks']}")
print(f"Performance Monitoring: {'' if status['performance_monitoring'] else ''}")
else:
print("Verfügbare Kommandos:")
print(" init-db - Initialisiert Datenbank mit Admin-Benutzer")
print(" status - Zeigt System-Status an")
else:
print("Verwendung: python3.11 system_utilities.py <command>")
print("Verfügbare Kommandos: init-db, status")

View File

@ -1,47 +0,0 @@
#!/usr/bin/env python3
"""
Test-Script für die Datenbank-Reparatur
"""
import sys
import os
# Pfad zur App hinzufügen
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def test_database_fix():
"""Testet ob die Datenbank-Reparatur erfolgreich war."""
try:
from models import get_cached_session, User, Printer, Job
print("=== DATENBANK-TEST NACH REPARATUR ===")
with get_cached_session() as session:
# Test User-Query (das war das ursprüngliche Problem)
users = session.query(User).limit(5).all()
print(f"✓ User-Abfrage erfolgreich - {len(users)} Benutzer gefunden")
# Details des ersten Users anzeigen (falls vorhanden)
if users:
user = users[0]
print(f"✓ Test-User: {user.username} ({user.email})")
print(f"✓ updated_at-Feld: {user.updated_at}")
# Test Printer-Query
printers = session.query(Printer).limit(5).all()
print(f"✓ Printer-Abfrage erfolgreich - {len(printers)} Drucker gefunden")
# Test Job-Query
jobs = session.query(Job).limit(5).all()
print(f"✓ Job-Abfrage erfolgreich - {len(jobs)} Jobs gefunden")
print("\n🎉 ALLE DATENBANK-TESTS ERFOLGREICH!")
print("Die Anwendung sollte jetzt ohne Fehler starten.")
return True
except Exception as e:
print(f"\n❌ DATENBANK-TEST FEHLGESCHLAGEN: {str(e)}")
return False
if __name__ == "__main__":
test_database_fix()

View File

@ -1,60 +0,0 @@
#!/usr/bin/env python3.11
"""
Skript zur Aktualisierung der Drucker-Standorte in der Datenbank.
Ändert alle Standorte von "Labor" zu "Werk 040 - Berlin - TBA".
"""
import sys
import os
sys.path.append('.')
from database.db_manager import DatabaseManager
from models import Printer
from datetime import datetime
def update_printer_locations():
"""Aktualisiert alle Drucker-Standorte zu 'Werk 040 - Berlin - TBA'."""
print("=== Drucker-Standorte aktualisieren ===")
try:
db = DatabaseManager()
session = db.get_session()
# Alle Drucker abrufen
all_printers = session.query(Printer).all()
print(f"Gefundene Drucker: {len(all_printers)}")
if not all_printers:
print("Keine Drucker in der Datenbank gefunden.")
session.close()
return
# Neue Standort-Bezeichnung
new_location = "Werk 040 - Berlin - TBA"
updated_count = 0
# Alle Drucker durchgehen und Standort aktualisieren
for printer in all_printers:
old_location = printer.location
printer.location = new_location
print(f"{printer.name}: '{old_location}''{new_location}'")
updated_count += 1
# Änderungen speichern
session.commit()
session.close()
print(f"\n{updated_count} Drucker-Standorte erfolgreich aktualisiert")
print(f"Neuer Standort: {new_location}")
print("Standort-Aktualisierung abgeschlossen!")
except Exception as e:
print(f"❌ Fehler bei der Standort-Aktualisierung: {e}")
if 'session' in locals():
session.rollback()
session.close()
if __name__ == "__main__":
update_printer_locations()