import json from datetime import datetime, timedelta from flask import Blueprint, render_template, request, jsonify, redirect, url_for, abort, session, flash from flask_login import current_user, login_required from functools import wraps from sqlalchemy import desc from models import GuestRequest, Job, Printer, User, UserPermission, Notification, get_cached_session from utils.logging_config import get_logger guest_blueprint = Blueprint('guest', __name__) logger = get_logger("guest") # Hilfsfunktionen def can_approve_jobs(user_id): """Prüft, ob ein Benutzer Anfragen genehmigen darf.""" with get_cached_session() as db_session: permission = db_session.query(UserPermission).filter_by(user_id=user_id).first() if not permission: return False return permission.can_approve_jobs def approver_required(f): """Decorator zur Prüfung der Genehmigungsberechtigung.""" @wraps(f) @login_required def decorated_function(*args, **kwargs): if not can_approve_jobs(current_user.id): abort(403, "Keine Berechtigung zum Genehmigen von Anfragen") return f(*args, **kwargs) return decorated_function # Gast-Routen @guest_blueprint.route('/request', methods=['GET']) def guest_request_form(): """Formular für Gastanfragen anzeigen.""" with get_cached_session() as db_session: printers = db_session.query(Printer).filter_by(active=True).all() return render_template('guest_request.html', printers=printers) @guest_blueprint.route('/request/', methods=['GET']) def guest_request_status(request_id): """Status einer Gastanfrage anzeigen.""" with get_cached_session() as db_session: guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first() if not guest_request: abort(404, "Anfrage nicht gefunden") # Nur wenn Status "approved" ist, OTP generieren und anzeigen otp_code = None if guest_request.status == "approved" and not guest_request.otp_code: otp_code = guest_request.generate_otp() db_session.commit() # Zugehörigen Job laden, falls vorhanden job = None if guest_request.job_id: job = db_session.query(Job).filter_by(id=guest_request.job_id).first() return render_template('guest_status.html', request=guest_request, job=job, otp_code=otp_code) # API-Endpunkte @guest_blueprint.route('/api/guest/requests', methods=['POST']) def api_create_guest_request(): """Neue Gastanfrage erstellen.""" data = request.get_json() if not data: return jsonify({"error": "Keine Daten erhalten"}), 400 # Pflichtfelder prüfen name = data.get('name') if not name: return jsonify({"error": "Name ist erforderlich"}), 400 # Optionale Felder email = data.get('email') reason = data.get('reason') duration_min = data.get('duration_min', 60) # Standard: 1 Stunde printer_id = data.get('printer_id') # IP-Adresse erfassen author_ip = request.remote_addr try: with get_cached_session() as db_session: # Drucker prüfen if printer_id: printer = db_session.query(Printer).filter_by(id=printer_id, active=True).first() if not printer: return jsonify({"error": "Ungültiger Drucker ausgewählt"}), 400 # Neue Anfrage erstellen guest_request = GuestRequest( name=name, email=email, reason=reason, duration_min=duration_min, printer_id=printer_id, author_ip=author_ip ) db_session.add(guest_request) db_session.commit() # Benachrichtigung für Genehmiger erstellen Notification.create_for_approvers( notification_type="guest_request", payload={ "request_id": guest_request.id, "name": guest_request.name, "created_at": guest_request.created_at.isoformat(), "status": guest_request.status } ) logger.info(f"Neue Gastanfrage erstellt: ID {guest_request.id}, Name: {name}") return jsonify({ "success": True, "request_id": guest_request.id, "status": guest_request.status, "redirect_url": url_for('guest.guest_request_status', request_id=guest_request.id) }) except Exception as e: logger.error(f"Fehler beim Erstellen der Gastanfrage: {str(e)}") return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500 @guest_blueprint.route('/api/guest/requests/', methods=['GET']) def api_get_guest_request(request_id): """Status einer Gastanfrage abrufen.""" try: with get_cached_session() as db_session: guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first() if not guest_request: return jsonify({"error": "Anfrage nicht gefunden"}), 404 # OTP wird nie über die API zurückgegeben response_data = guest_request.to_dict() response_data.pop("otp_code", None) return jsonify(response_data) except Exception as e: logger.error(f"Fehler beim Abrufen der Gastanfrage: {str(e)}") return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500 @guest_blueprint.route('/api/requests//approve', methods=['POST']) @approver_required def api_approve_request(request_id): """Gastanfrage genehmigen.""" try: with get_cached_session() as db_session: guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first() if not guest_request: return jsonify({"error": "Anfrage nicht gefunden"}), 404 if guest_request.status != "pending": return jsonify({"error": "Anfrage wurde bereits bearbeitet"}), 400 # Anfrage genehmigen guest_request.status = "approved" # OTP generieren otp_plain = guest_request.generate_otp() # Zugehörigen Job erstellen start_time = datetime.now() + timedelta(minutes=5) # Start in 5 Minuten end_time = start_time + timedelta(minutes=guest_request.duration_min) # Admin-Benutzer als Eigentümer verwenden admin_user = db_session.query(User).filter_by(role="admin").first() job = Job( name=f"Gastauftrag: {guest_request.name}", description=guest_request.reason or "Gastauftrag", user_id=admin_user.id, printer_id=guest_request.printer_id, start_at=start_time, end_at=end_time, status="scheduled", duration_minutes=guest_request.duration_min, owner_id=admin_user.id ) db_session.add(job) db_session.flush() # ID generieren # Job-ID in Gastanfrage speichern guest_request.job_id = job.id db_session.commit() logger.info(f"Gastanfrage {request_id} genehmigt von Benutzer {current_user.id}") return jsonify({ "success": True, "status": "approved", "job_id": job.id, "otp": otp_plain # Nur in dieser Antwort wird der OTP-Klartext zurückgegeben }) except Exception as e: logger.error(f"Fehler beim Genehmigen der Gastanfrage: {str(e)}") return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500 @guest_blueprint.route('/api/requests//deny', methods=['POST']) @approver_required def api_deny_request(request_id): """Gastanfrage ablehnen.""" try: data = request.get_json() or {} reason = data.get('reason', '') with get_cached_session() as db_session: guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first() if not guest_request: return jsonify({"error": "Anfrage nicht gefunden"}), 404 if guest_request.status != "pending": return jsonify({"error": "Anfrage wurde bereits bearbeitet"}), 400 # Anfrage ablehnen guest_request.status = "denied" db_session.commit() logger.info(f"Gastanfrage {request_id} abgelehnt von Benutzer {current_user.id}") return jsonify({ "success": True, "status": "denied" }) except Exception as e: logger.error(f"Fehler beim Ablehnen der Gastanfrage: {str(e)}") return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500 @guest_blueprint.route('/api/jobs/start/', methods=['POST']) def api_start_job_with_otp(otp): """Job mit OTP-Code starten.""" if not otp: return jsonify({"error": "Kein OTP-Code angegeben"}), 400 try: with get_cached_session() as db_session: # Alle Gastanfragen mit approved-Status durchsuchen guest_requests = db_session.query(GuestRequest).filter_by(status="approved").all() valid_request = None for req in guest_requests: if req.verify_otp(otp): valid_request = req break if not valid_request: return jsonify({"error": "Ungültiger OTP-Code"}), 400 # Zugehörigen Job laden job = db_session.query(Job).filter_by(id=valid_request.job_id).first() if not job: return jsonify({"error": "Kein Job für diese Anfrage gefunden"}), 404 # Grace-Period prüfen (5 Minuten nach geplantem Start) now = datetime.now() grace_end = job.start_at + timedelta(minutes=5) if now > job.end_at: return jsonify({"error": "Der Job ist bereits abgelaufen"}), 400 # Job starten job.status = "running" # OTP-Code nach Verwendung löschen valid_request.otp_code = None db_session.commit() logger.info(f"Job {job.id} mit OTP-Code gestartet") return jsonify({ "success": True, "job_id": job.id, "status": "running" }) except Exception as e: logger.error(f"Fehler beim Starten des Jobs mit OTP: {str(e)}") return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500 @guest_blueprint.route('/api/notifications', methods=['GET']) @login_required def api_get_notifications(): """Benachrichtigungen für den aktuellen Benutzer abrufen.""" try: # Zeitstempel für Filter (nur neue Benachrichtigungen) since = request.args.get('since') if since: try: since_date = datetime.fromisoformat(since) except ValueError: return jsonify({"error": "Ungültiges Datumsformat"}), 400 else: since_date = None with get_cached_session() as db_session: query = db_session.query(Notification).filter_by( user_id=current_user.id, read=False ) if since_date: query = query.filter(Notification.created_at > since_date) notifications = query.order_by(desc(Notification.created_at)).all() return jsonify({ "count": len(notifications), "notifications": [n.to_dict() for n in notifications] }) except Exception as e: logger.error(f"Fehler beim Abrufen der Benachrichtigungen: {str(e)}") return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500 @guest_blueprint.route('/api/notifications//read', methods=['POST']) @login_required def api_mark_notification_read(notification_id): """Benachrichtigung als gelesen markieren.""" try: with get_cached_session() as db_session: notification = db_session.query(Notification).filter_by( id=notification_id, user_id=current_user.id ).first() if not notification: return jsonify({"error": "Benachrichtigung nicht gefunden"}), 404 notification.read = True db_session.commit() return jsonify({"success": True}) except Exception as e: logger.error(f"Fehler beim Markieren der Benachrichtigung als gelesen: {str(e)}") return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500