import logging from datetime import datetime from zoneinfo import ZoneInfo from flask import Blueprint, request, jsonify, session import config from auth.decorators import login_required from utils.audit import log_audit from utils.network import get_client_ip from utils.license import generate_license_key from db import get_connection, get_db_connection, get_db_cursor from models import get_license_by_id, get_customers # Create Blueprint api_bp = Blueprint('api', __name__, url_prefix='/api') @api_bp.route("/customers", methods=["GET"]) @login_required def api_customers(): """API endpoint for customer search (used by Select2)""" search = request.args.get('q', '').strip() page = int(request.args.get('page', 1)) per_page = 20 try: # Get all customers (with optional search) customers = get_customers(show_fake=True, search=search) # Pagination start = (page - 1) * per_page end = start + per_page paginated_customers = customers[start:end] # Format for Select2 results = [] for customer in paginated_customers: results.append({ 'id': customer['id'], 'text': f"{customer['name']} ({customer['email'] or 'keine E-Mail'})", 'is_fake': customer.get('is_fake', False) # Include the is_fake field }) return jsonify({ 'results': results, 'pagination': { 'more': len(customers) > end } }) except Exception as e: logging.error(f"Error in api_customers: {str(e)}") return jsonify({'error': 'Fehler beim Laden der Kunden'}), 500 @api_bp.route("/license//toggle", methods=["POST"]) @login_required def toggle_license(license_id): """Toggle license is_active status""" conn = get_connection() cur = conn.cursor() try: # Get current status license_data = get_license_by_id(license_id) if not license_data: return jsonify({'error': 'Lizenz nicht gefunden'}), 404 new_status = not license_data['is_active'] # Update status cur.execute("UPDATE licenses SET is_active = %s WHERE id = %s", (new_status, license_id)) conn.commit() # Log change log_audit('TOGGLE', 'license', license_id, old_values={'is_active': license_data['is_active']}, new_values={'is_active': new_status}) return jsonify({'success': True, 'is_active': new_status}) except Exception as e: conn.rollback() logging.error(f"Fehler beim Umschalten der Lizenz: {str(e)}", exc_info=True) return jsonify({'error': 'Fehler beim Umschalten der Lizenz'}), 500 finally: cur.close() conn.close() @api_bp.route("/licenses/bulk-activate", methods=["POST"]) @login_required def bulk_activate_licenses(): """Aktiviere mehrere Lizenzen gleichzeitig""" data = request.get_json() license_ids = data.get('license_ids', []) if not license_ids: return jsonify({'error': 'Keine Lizenzen ausgewählt'}), 400 conn = get_connection() cur = conn.cursor() try: # Update all selected licenses cur.execute(""" UPDATE licenses SET is_active = true WHERE id = ANY(%s) AND is_active = false RETURNING id """, (license_ids,)) updated_ids = [row[0] for row in cur.fetchall()] conn.commit() # Log changes for license_id in updated_ids: log_audit('BULK_ACTIVATE', 'license', license_id, new_values={'is_active': True}) return jsonify({ 'success': True, 'updated_count': len(updated_ids) }) except Exception as e: conn.rollback() logging.error(f"Fehler beim Bulk-Aktivieren: {str(e)}") return jsonify({'error': 'Fehler beim Aktivieren der Lizenzen'}), 500 finally: cur.close() conn.close() @api_bp.route("/licenses/bulk-deactivate", methods=["POST"]) @login_required def bulk_deactivate_licenses(): """Deaktiviere mehrere Lizenzen gleichzeitig""" data = request.get_json() license_ids = data.get('license_ids', []) if not license_ids: return jsonify({'error': 'Keine Lizenzen ausgewählt'}), 400 conn = get_connection() cur = conn.cursor() try: # Update all selected licenses cur.execute(""" UPDATE licenses SET is_active = false WHERE id = ANY(%s) AND is_active = true RETURNING id """, (license_ids,)) updated_ids = [row[0] for row in cur.fetchall()] conn.commit() # Log changes for license_id in updated_ids: log_audit('BULK_DEACTIVATE', 'license', license_id, new_values={'is_active': False}) return jsonify({ 'success': True, 'updated_count': len(updated_ids) }) except Exception as e: conn.rollback() logging.error(f"Fehler beim Bulk-Deaktivieren: {str(e)}") return jsonify({'error': 'Fehler beim Deaktivieren der Lizenzen'}), 500 finally: cur.close() conn.close() @api_bp.route("/license//devices") @login_required def get_license_devices(license_id): """Hole alle Geräte einer Lizenz""" conn = get_connection() cur = conn.cursor() try: # Hole Lizenz-Info license_data = get_license_by_id(license_id) if not license_data: return jsonify({'error': 'Lizenz nicht gefunden'}), 404 # Hole registrierte Geräte cur.execute(""" SELECT dr.id, dr.hardware_id, dr.device_name, dr.device_type, dr.first_seen as registration_date, dr.last_seen, dr.is_active, dr.operating_system, dr.ip_address, (SELECT COUNT(*) FROM sessions s WHERE s.license_key = l.license_key AND s.hardware_id = dr.hardware_id AND s.is_active = true) as active_sessions FROM device_registrations dr JOIN licenses l ON dr.license_id = l.id WHERE l.license_key = %s ORDER BY dr.first_seen DESC """, (license_data['license_key'],)) devices = [] for row in cur.fetchall(): devices.append({ 'id': row[0], 'hardware_id': row[1], 'device_name': row[2], 'device_type': row[3], 'registration_date': row[4].isoformat() if row[4] else None, 'last_seen': row[5].isoformat() if row[5] else None, 'is_active': row[6], 'operating_system': row[7] or 'Unknown', 'ip_address': row[8] or 'Unknown', 'active_sessions': row[9], 'first_seen': row[4].isoformat() if row[4] else None }) return jsonify({ 'success': True, 'license_key': license_data['license_key'], 'device_limit': license_data['device_limit'], 'devices': devices, 'device_count': len(devices), 'active_count': len([d for d in devices if d['is_active']]) }) except Exception as e: logging.error(f"Fehler beim Abrufen der Geräte: {str(e)}") return jsonify({'error': 'Fehler beim Abrufen der Geräte'}), 500 finally: cur.close() conn.close() @api_bp.route("/license//register-device", methods=["POST"]) @login_required def register_device(license_id): """Registriere ein neues Gerät für eine Lizenz""" data = request.get_json() hardware_id = data.get('hardware_id') device_name = data.get('device_name') device_type = data.get('device_type', 'unknown') if not hardware_id or not device_name: return jsonify({'error': 'Geräte-ID und Name erforderlich'}), 400 conn = get_connection() cur = conn.cursor() try: # Hole Lizenz-Info license_data = get_license_by_id(license_id) if not license_data: return jsonify({'error': 'Lizenz nicht gefunden'}), 404 # Prüfe Gerätelimit cur.execute(""" SELECT COUNT(*) FROM device_registrations dr JOIN licenses l ON dr.license_id = l.id WHERE l.license_key = %s AND dr.is_active = true """, (license_data['license_key'],)) active_device_count = cur.fetchone()[0] if active_device_count >= license_data['device_limit']: return jsonify({'error': 'Gerätelimit erreicht'}), 400 # Prüfe ob Gerät bereits registriert cur.execute(""" SELECT dr.id, dr.is_active FROM device_registrations dr JOIN licenses l ON dr.license_id = l.id WHERE l.license_key = %s AND dr.hardware_id = %s """, (license_data['license_key'], hardware_id)) existing = cur.fetchone() if existing: if existing[1]: # is_active return jsonify({'error': 'Gerät bereits registriert'}), 400 else: # Reaktiviere Gerät cur.execute(""" UPDATE device_registrations SET is_active = true, last_seen = CURRENT_TIMESTAMP WHERE id = %s """, (existing[0],)) else: # Registriere neues Gerät cur.execute(""" INSERT INTO device_registrations (license_id, hardware_id, device_name, device_type, is_active) VALUES (%s, %s, %s, %s, true) """, (license_id, hardware_id, device_name, device_type)) conn.commit() # Audit-Log log_audit('DEVICE_REGISTER', 'license', license_id, additional_info=f"Gerät {device_name} ({hardware_id}) registriert") return jsonify({'success': True}) except Exception as e: conn.rollback() logging.error(f"Fehler beim Registrieren des Geräts: {str(e)}") return jsonify({'error': 'Fehler beim Registrieren des Geräts'}), 500 finally: cur.close() conn.close() @api_bp.route("/license//deactivate-device/", methods=["POST"]) @login_required def deactivate_device(license_id, device_id): """Deaktiviere ein Gerät einer Lizenz""" conn = get_connection() cur = conn.cursor() try: # Prüfe ob Gerät zur Lizenz gehört cur.execute(""" SELECT dr.device_name, dr.hardware_id, l.license_key FROM device_registrations dr JOIN licenses l ON dr.license_id = l.id WHERE dr.id = %s AND l.id = %s """, (device_id, license_id)) device = cur.fetchone() if not device: return jsonify({'error': 'Gerät nicht gefunden'}), 404 # Deaktiviere Gerät cur.execute(""" UPDATE device_registrations SET is_active = false WHERE id = %s """, (device_id,)) # Beende aktive Sessions cur.execute(""" UPDATE sessions SET is_active = false, ended_at = CURRENT_TIMESTAMP WHERE license_key = %s AND hardware_id = %s AND is_active = true """, (device[2], device[1])) conn.commit() # Audit-Log log_audit('DEVICE_DEACTIVATE', 'license', license_id, additional_info=f"Gerät {device[0]} ({device[1]}) deaktiviert") return jsonify({'success': True}) except Exception as e: conn.rollback() logging.error(f"Fehler beim Deaktivieren des Geräts: {str(e)}") return jsonify({'error': 'Fehler beim Deaktivieren des Geräts'}), 500 finally: cur.close() conn.close() @api_bp.route("/licenses/bulk-delete", methods=["POST"]) @login_required def bulk_delete_licenses(): """Lösche mehrere Lizenzen gleichzeitig mit Sicherheitsprüfungen""" data = request.get_json() # Accept both 'ids' (from frontend) and 'license_ids' for compatibility license_ids = data.get('ids', data.get('license_ids', [])) force_delete = data.get('force', False) if not license_ids: return jsonify({'error': 'Keine Lizenzen ausgewählt'}), 400 conn = get_connection() cur = conn.cursor() try: deleted_count = 0 skipped_licenses = [] active_licenses = [] recently_used_licenses = [] for license_id in license_ids: # Hole vollständige Lizenz-Info cur.execute(""" SELECT l.id, l.license_key, l.is_active, l.is_fake, c.name as customer_name FROM licenses l LEFT JOIN customers c ON l.customer_id = c.id WHERE l.id = %s """, (license_id,)) result = cur.fetchone() if not result: continue license_id, license_key, is_active, is_fake, customer_name = result # Safety check: Don't delete active licenses unless forced if is_active and not force_delete: active_licenses.append(f"{license_key} ({customer_name})") skipped_licenses.append(license_id) continue # Check for recent activity (heartbeats in last 24 hours) if not force_delete: try: cur.execute(""" SELECT COUNT(*) FROM license_heartbeats WHERE license_id = %s AND timestamp > NOW() - INTERVAL '24 hours' """, (license_id,)) recent_heartbeats = cur.fetchone()[0] if recent_heartbeats > 0: recently_used_licenses.append(f"{license_key} ({recent_heartbeats} activities)") skipped_licenses.append(license_id) continue except: # If heartbeats table doesn't exist, continue pass # Check for active devices if not force_delete: try: cur.execute(""" SELECT COUNT(*) FROM activations WHERE license_id = %s AND is_active = true """, (license_id,)) active_devices = cur.fetchone()[0] if active_devices > 0: recently_used_licenses.append(f"{license_key} ({active_devices} active devices)") skipped_licenses.append(license_id) continue except: # If activations table doesn't exist, continue pass # Delete associated data cur.execute("DELETE FROM sessions WHERE license_key = %s", (license_key,)) try: cur.execute("DELETE FROM device_registrations WHERE license_id = %s", (license_id,)) except: pass try: cur.execute("DELETE FROM license_heartbeats WHERE license_id = %s", (license_id,)) except: pass try: cur.execute("DELETE FROM activations WHERE license_id = %s", (license_id,)) except: pass # Delete the license cur.execute("DELETE FROM licenses WHERE id = %s", (license_id,)) # Audit-Log log_audit('BULK_DELETE', 'license', license_id, old_values={ 'license_key': license_key, 'customer_name': customer_name, 'was_active': is_active, 'forced': force_delete }) deleted_count += 1 conn.commit() # Build response message message = f"{deleted_count} Lizenz(en) gelöscht." warnings = [] if active_licenses: warnings.append(f"Aktive Lizenzen übersprungen: {', '.join(active_licenses[:3])}{'...' if len(active_licenses) > 3 else ''}") if recently_used_licenses: warnings.append(f"Kürzlich genutzte Lizenzen übersprungen: {', '.join(recently_used_licenses[:3])}{'...' if len(recently_used_licenses) > 3 else ''}") return jsonify({ 'success': True, 'deleted_count': deleted_count, 'skipped_count': len(skipped_licenses), 'message': message, 'warnings': warnings }) except Exception as e: conn.rollback() logging.error(f"Fehler beim Bulk-Löschen: {str(e)}") return jsonify({'error': 'Fehler beim Löschen der Lizenzen'}), 500 finally: cur.close() conn.close() @api_bp.route("/license//quick-edit", methods=['POST']) @login_required def quick_edit_license(license_id): """Schnellbearbeitung einer Lizenz""" data = request.get_json() conn = get_connection() cur = conn.cursor() try: # Hole aktuelle Lizenz für Vergleich current_license = get_license_by_id(license_id) if not current_license: return jsonify({'error': 'Lizenz nicht gefunden'}), 404 # Update nur die übergebenen Felder updates = [] params = [] old_values = {} new_values = {} if 'device_limit' in data: updates.append("device_limit = %s") params.append(int(data['device_limit'])) old_values['device_limit'] = current_license['device_limit'] new_values['device_limit'] = int(data['device_limit']) if 'valid_until' in data: updates.append("valid_until = %s") params.append(data['valid_until']) old_values['valid_until'] = str(current_license['valid_until']) new_values['valid_until'] = data['valid_until'] if 'is_active' in data: updates.append("is_active = %s") params.append(bool(data['is_active'])) old_values['is_active'] = current_license['is_active'] new_values['is_active'] = bool(data['is_active']) if not updates: return jsonify({'error': 'Keine Änderungen angegeben'}), 400 # Führe Update aus params.append(license_id) cur.execute(f""" UPDATE licenses SET {', '.join(updates)} WHERE id = %s """, params) conn.commit() # Audit-Log log_audit('QUICK_EDIT', 'license', license_id, old_values=old_values, new_values=new_values) return jsonify({'success': True}) except Exception as e: conn.rollback() logging.error(f"Fehler bei Schnellbearbeitung: {str(e)}") return jsonify({'error': 'Fehler bei der Bearbeitung'}), 500 finally: cur.close() conn.close() @api_bp.route("/license//resources") @login_required def get_license_resources(license_id): """Hole alle Ressourcen einer Lizenz""" conn = get_connection() cur = conn.cursor() try: # Hole Lizenz-Info license_data = get_license_by_id(license_id) if not license_data: return jsonify({'error': 'Lizenz nicht gefunden'}), 404 # Hole zugewiesene Ressourcen cur.execute(""" SELECT rp.id, rp.resource_type, rp.resource_value, rp.is_fake, rp.status_changed_at, lr.assigned_at, lr.assigned_by FROM resource_pools rp JOIN license_resources lr ON rp.id = lr.resource_id WHERE lr.license_id = %s ORDER BY rp.resource_type, rp.resource_value """, (license_id,)) resources = [] for row in cur.fetchall(): resources.append({ 'id': row[0], 'type': row[1], 'value': row[2], 'is_fake': row[3], 'status_changed_at': row[4].isoformat() if row[4] else None, 'assigned_at': row[5].isoformat() if row[5] else None, 'assigned_by': row[6] }) # Gruppiere nach Typ grouped = {} for resource in resources: res_type = resource['type'] if res_type not in grouped: grouped[res_type] = [] grouped[res_type].append(resource) return jsonify({ 'success': True, 'license_key': license_data['license_key'], 'resources': resources, 'grouped': grouped, 'total_count': len(resources) }) except Exception as e: logging.error(f"Fehler beim Abrufen der Ressourcen: {str(e)}") return jsonify({'error': 'Fehler beim Abrufen der Ressourcen'}), 500 finally: cur.close() conn.close() @api_bp.route("/resources/allocate", methods=['POST']) @login_required def allocate_resources(): """Weise Ressourcen einer Lizenz zu""" data = request.get_json() license_id = data.get('license_id') resource_ids = data.get('resource_ids', []) if not license_id or not resource_ids: return jsonify({'error': 'Lizenz-ID und Ressourcen erforderlich'}), 400 conn = get_connection() cur = conn.cursor() try: # Prüfe Lizenz license_data = get_license_by_id(license_id) if not license_data: return jsonify({'error': 'Lizenz nicht gefunden'}), 404 allocated_count = 0 errors = [] for resource_id in resource_ids: try: # Prüfe ob Ressource verfügbar ist cur.execute(""" SELECT resource_value, status, is_fake FROM resource_pools WHERE id = %s """, (resource_id,)) resource = cur.fetchone() if not resource: errors.append(f"Ressource {resource_id} nicht gefunden") continue if resource[1] != 'available': errors.append(f"Ressource {resource[0]} ist nicht verfügbar") continue # Prüfe Test/Produktion Kompatibilität if resource[2] != license_data['is_fake']: errors.append(f"Ressource {resource[0]} ist {'Test' if resource[2] else 'Produktion'}, Lizenz ist {'Test' if license_data['is_fake'] else 'Produktion'}") continue # Weise Ressource zu cur.execute(""" UPDATE resource_pools SET status = 'allocated', allocated_to_license = %s, status_changed_at = CURRENT_TIMESTAMP, status_changed_by = %s WHERE id = %s """, (license_id, session['username'], resource_id)) # Erstelle Verknüpfung cur.execute(""" INSERT INTO license_resources (license_id, resource_id, assigned_by) VALUES (%s, %s, %s) """, (license_id, resource_id, session['username'])) # History-Eintrag cur.execute(""" INSERT INTO resource_history (resource_id, license_id, action, action_by, ip_address) VALUES (%s, %s, 'allocated', %s, %s) """, (resource_id, license_id, session['username'], get_client_ip())) allocated_count += 1 except Exception as e: errors.append(f"Fehler bei Ressource {resource_id}: {str(e)}") conn.commit() # Audit-Log if allocated_count > 0: log_audit('RESOURCE_ALLOCATE', 'license', license_id, additional_info=f"{allocated_count} Ressourcen zugewiesen") return jsonify({ 'success': True, 'allocated_count': allocated_count, 'errors': errors }) except Exception as e: conn.rollback() logging.error(f"Fehler beim Zuweisen der Ressourcen: {str(e)}") return jsonify({'error': 'Fehler beim Zuweisen der Ressourcen'}), 500 finally: cur.close() conn.close() @api_bp.route("/resources/check-availability", methods=['GET']) @login_required def check_resource_availability(): """Prüfe Verfügbarkeit von Ressourcen""" # Einzelne Ressource prüfen (alte API) resource_type = request.args.get('type') if resource_type: count = int(request.args.get('count', 1)) is_fake = request.args.get('is_fake', 'false') == 'true' show_fake = request.args.get('show_fake', 'false') == 'true' conn = get_connection() cur = conn.cursor() try: # Hole verfügbare Ressourcen mit Details if show_fake: # Zeige alle verfügbaren Ressourcen (Test und Produktion) cur.execute(""" SELECT id, resource_value, is_fake FROM resource_pools WHERE resource_type = %s AND status = 'available' ORDER BY is_fake, resource_value LIMIT %s """, (resource_type, count)) else: # Zeige nur Produktions-Ressourcen cur.execute(""" SELECT id, resource_value, is_fake FROM resource_pools WHERE resource_type = %s AND status = 'available' AND is_fake = false ORDER BY resource_value LIMIT %s """, (resource_type, count)) available_resources = [] for row in cur.fetchall(): available_resources.append({ 'id': row[0], 'value': row[1], 'is_fake': row[2] }) return jsonify({ 'resource_type': resource_type, 'requested': count, 'available': available_resources, 'sufficient': len(available_resources) >= count, 'show_fake': show_fake }) except Exception as e: logging.error(f"Fehler beim Prüfen der Verfügbarkeit: {str(e)}") return jsonify({'error': 'Fehler beim Prüfen der Verfügbarkeit'}), 500 finally: cur.close() conn.close() # Mehrere Ressourcen gleichzeitig prüfen (für Batch) domain_count = int(request.args.get('domain', 0)) ipv4_count = int(request.args.get('ipv4', 0)) phone_count = int(request.args.get('phone', 0)) is_fake = request.args.get('is_fake', 'false') == 'true' conn = get_connection() cur = conn.cursor() try: # Zähle verfügbare Ressourcen für jeden Typ result = {} # Domains cur.execute(""" SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'domain' AND status = 'available' AND is_fake = %s """, (is_fake,)) domain_available = cur.fetchone()[0] # IPv4 cur.execute(""" SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'ipv4' AND status = 'available' AND is_fake = %s """, (is_fake,)) ipv4_available = cur.fetchone()[0] # Phones cur.execute(""" SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'phone' AND status = 'available' AND is_fake = %s """, (is_fake,)) phone_available = cur.fetchone()[0] return jsonify({ 'domain_requested': domain_count, 'domain_available': domain_available, 'domain_sufficient': domain_available >= domain_count, 'ipv4_requested': ipv4_count, 'ipv4_available': ipv4_available, 'ipv4_sufficient': ipv4_available >= ipv4_count, 'phone_requested': phone_count, 'phone_available': phone_available, 'phone_sufficient': phone_available >= phone_count, 'all_sufficient': ( domain_available >= domain_count and ipv4_available >= ipv4_count and phone_available >= phone_count ), 'is_fake': is_fake }) except Exception as e: logging.error(f"Fehler beim Prüfen der Verfügbarkeit: {str(e)}") return jsonify({'error': 'Fehler beim Prüfen der Verfügbarkeit'}), 500 finally: cur.close() conn.close() @api_bp.route("/global-search", methods=['GET']) @login_required def global_search(): """Globale Suche über alle Entitäten""" query = request.args.get('q', '').strip() if not query or len(query) < 3: return jsonify({'error': 'Suchbegriff muss mindestens 3 Zeichen haben'}), 400 conn = get_connection() cur = conn.cursor() results = { 'licenses': [], 'customers': [], 'resources': [], 'sessions': [] } try: # Suche in Lizenzen cur.execute(""" SELECT id, license_key, customer_name, is_active FROM licenses WHERE license_key ILIKE %s OR customer_name ILIKE %s OR customer_email ILIKE %s LIMIT 10 """, (f'%{query}%', f'%{query}%', f'%{query}%')) for row in cur.fetchall(): results['licenses'].append({ 'id': row[0], 'license_key': row[1], 'customer_name': row[2], 'is_active': row[3] }) # Suche in Kunden cur.execute(""" SELECT id, name, email, is_fake FROM customers WHERE name ILIKE %s OR email ILIKE %s LIMIT 10 """, (f'%{query}%', f'%{query}%')) for row in cur.fetchall(): results['customers'].append({ 'id': row[0], 'name': row[1], 'email': row[2], 'is_fake': row[3] }) # Suche in Ressourcen cur.execute(""" SELECT id, resource_type, resource_value, status FROM resource_pools WHERE resource_value ILIKE %s LIMIT 10 """, (f'%{query}%',)) for row in cur.fetchall(): results['resources'].append({ 'id': row[0], 'type': row[1], 'value': row[2], 'status': row[3] }) # Suche in Sessions cur.execute(""" SELECT id, license_key, username, hardware_id, is_active FROM sessions WHERE username ILIKE %s OR hardware_id ILIKE %s ORDER BY started_at DESC LIMIT 10 """, (f'%{query}%', f'%{query}%')) for row in cur.fetchall(): results['sessions'].append({ 'id': row[0], 'license_key': row[1], 'username': row[2], 'hardware_id': row[3], 'is_active': row[4] }) return jsonify(results) except Exception as e: logging.error(f"Fehler bei der globalen Suche: {str(e)}") return jsonify({'error': 'Fehler bei der Suche'}), 500 finally: cur.close() conn.close() @api_bp.route("/generate-license-key", methods=['POST']) @login_required def api_generate_key(): """API Endpoint zur Generierung eines neuen Lizenzschlüssels""" try: # Lizenztyp aus Request holen (default: full) data = request.get_json() or {} license_type = data.get('type', 'full') # Key generieren key = generate_license_key(license_type) # Prüfen ob Key bereits existiert (sehr unwahrscheinlich aber sicher ist sicher) conn = get_connection() cur = conn.cursor() # Wiederhole bis eindeutiger Key gefunden attempts = 0 while attempts < 10: # Max 10 Versuche cur.execute("SELECT 1 FROM licenses WHERE license_key = %s", (key,)) if not cur.fetchone(): break # Key ist eindeutig key = generate_license_key(license_type) attempts += 1 cur.close() conn.close() # Log für Audit log_audit('GENERATE_KEY', 'license', additional_info={'type': license_type, 'key': key}) return jsonify({ 'success': True, 'key': key, 'type': license_type }) except Exception as e: logging.error(f"Fehler bei Key-Generierung: {str(e)}") return jsonify({ 'success': False, 'error': 'Fehler bei der Key-Generierung' }), 500