Files
website/app.py.bak

2527 lines
90 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging
import traceback
from datetime import datetime, timedelta, timezone
from functools import wraps
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session, g
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
import json
from enum import Enum
from wtforms import StringField, PasswordField, BooleanField, TextAreaField, SelectField, HiddenField
from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError
from functools import wraps
import secrets
from sqlalchemy.sql import func
import openai
from openai import OpenAI
from dotenv import load_dotenv
from flask_socketio import SocketIO, emit
from flask_migrate import Migrate
import sqlalchemy
import ssl
import certifi
import os
from sqlalchemy.orm import joinedload
# Modelle importieren
from models import (
db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating,
RelationType, Category, UserMindmap, UserMindmapNode, MindmapNote,
node_thought_association, user_thought_bookmark, node_relationship,
MindmapShare, PermissionType
)
# Lade .env-Datei
load_dotenv() # force=True erzwingt die Synchronisierung
# Bestimme den absoluten Pfad zur Datenbank
basedir = os.path.abspath(os.path.dirname(__file__))
db_path = os.path.join(basedir, 'database', 'systades.db')
# Stellen Sie sicher, dass das Verzeichnis existiert
os.makedirs(os.path.dirname(db_path), exist_ok=True)
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-dev-key')
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung
app.config['UPLOAD_FOLDER'] = os.getenv('UPLOAD_FOLDER', os.path.join(os.getcwd(), 'uploads'))
# Logger-Konfiguration
log_level = os.environ.get('LOG_LEVEL', 'INFO').upper()
log_dir = os.path.join(basedir, 'logs')
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, 'app.log')
# Handler für Datei-Logs
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(getattr(logging, log_level))
# Handler für Konsolen-Logs
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
console_handler.setLevel(getattr(logging, log_level))
# Konfiguriere App-Logger
app.logger.addHandler(file_handler)
app.logger.addHandler(console_handler)
app.logger.setLevel(getattr(logging, log_level))
app.logger.info('Anwendung gestartet')
# Einheitliches Fehlerbehandlungssystem
class ErrorHandler:
"""Zentralisierte Fehlerbehandlung für die gesamte Anwendung"""
@staticmethod
def log_exception(e, endpoint=None, code=500):
"""Protokolliert eine Ausnahme mit Kontext-Informationen"""
# Stack-Trace für Debugging
trace = traceback.format_exc()
# Benutzer-Informationen (wenn verfügbar)
user_info = f"User: {current_user.id} ({current_user.username})" if current_user and current_user.is_authenticated else "Nicht angemeldet"
# Request-Informationen
request_info = f"Endpoint: {endpoint or request.path}, Method: {request.method}, IP: {request.remote_addr}"
# Vollständige Log-Nachricht
app.logger.error(f"Fehler {code}: {str(e)}\n{request_info}\n{user_info}\n{trace}")
@staticmethod
def api_error(message, code=400, details=None):
"""Erstellt eine standardisierte API-Fehlerantwort"""
response = {
'success': False,
'error': {
'code': code,
'message': message
}
}
if details:
response['error']['details'] = details
return jsonify(response), code
@staticmethod
def handle_exception(e, is_api_request=False):
"""Behandelt eine Ausnahme basierend auf ihrem Typ"""
# Bestimme, ob es sich um eine API-Anfrage handelt
if is_api_request is None:
is_api_request = request.path.startswith('/api/') or request.headers.get('Accept') == 'application/json'
# Protokolliere die Ausnahme
ErrorHandler.log_exception(e)
# Erstelle benutzerfreundliche Fehlermeldung
user_message = "Ein unerwarteter Fehler ist aufgetreten."
if is_api_request:
# API-Antwort für Ausnahme
return ErrorHandler.api_error(user_message, 500)
else:
# Web-Antwort für Ausnahme (mit Flash-Nachricht)
flash(user_message, 'error')
return render_template('errors/500.html'), 500
# Globaler Exception-Handler
@app.errorhandler(Exception)
def handle_unhandled_exception(e):
"""Fängt alle unbehandelten Ausnahmen ab"""
return ErrorHandler.handle_exception(e)
# OpenAI API-Konfiguration
api_key = os.environ.get("OPENAI_API_KEY", "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")
# Variable zur Überprüfung, ob OpenAI verfügbar ist
openai_available = True
# Client mit SSL-Verify-Deaktivierung für problematische Umgebungen
try:
# Versuche, den OpenAI-Client zu initialisieren und zu testen
client = OpenAI(api_key=api_key)
# Deaktiviere SSL-Verifizierung, falls in Windows-Umgebungen Probleme auftreten
client._client.proxies = {}
client._client.verify = False
# Testanfrage, um zu prüfen, ob die API funktioniert
try:
# Einfache Testanfrage
resp = client.models.list()
openai_available = True
print("OpenAI API-Verbindung erfolgreich hergestellt.")
except Exception as e:
print(f"OpenAI API-Verbindungstest fehlgeschlagen: {e}")
openai_available = False
except Exception as e:
print(f"Fehler bei der Initialisierung des OpenAI-Clients: {e}")
openai_available = False
client = None
# Dark Mode Einstellung in Session speichern
@app.before_request
def handle_dark_mode():
if 'dark_mode' not in session:
session['dark_mode'] = False # Standardmäßig Light Mode
# Context processor für Dark Mode
@app.context_processor
def inject_dark_mode():
return {'dark_mode': session.get('dark_mode', False)}
# Route zum Umschalten des Dark Mode
@app.route('/toggle-dark-mode', methods=['POST'])
def toggle_dark_mode():
session['dark_mode'] = not session.get('dark_mode', False)
return jsonify({'success': True, 'dark_mode': session['dark_mode']})
# Context processor für globale Template-Variablen
@app.context_processor
def inject_globals():
"""Inject global variables into all templates."""
return {
'current_year': datetime.now().year
}
# Context-Prozessor für alle Templates
@app.context_processor
def inject_current_year():
return {'current_year': datetime.now().year}
# Initialisiere die Datenbank
db.init_app(app)
# Initialisiere den Login-Manager
login_manager = LoginManager(app)
login_manager.login_view = 'login'
# Erst nach der App-Initialisierung die DB-Check-Funktionen importieren
from utils.db_check import check_db_connection, initialize_db_if_needed
# SocketIO initialisieren
socketio = SocketIO(app)
migrate = Migrate(app, db)
# Funktion zum Erstellen von Standardbenutzern
def create_default_users():
"""Erstellt Standardbenutzer für die Anwendung"""
users = [
{
'username': 'admin',
'email': 'admin@example.com',
'password': 'admin',
'role': 'admin'
},
{
'username': 'user',
'email': 'user@example.com',
'password': 'user',
'role': 'user'
}
]
for user_data in users:
password = user_data.pop('password')
user = User(**user_data)
user.set_password(password)
db.session.add(user)
db.session.commit()
print(f"{len(users)} Benutzer wurden erstellt.")
def create_default_categories():
"""Erstellt die Standardkategorien für die Mindmap"""
# Hauptkategorien
main_categories = [
{
"name": "Philosophie",
"description": "Philosophisches Denken und Konzepte",
"color_code": "#9F7AEA",
"icon": "fa-brain",
"subcategories": [
{"name": "Ethik", "description": "Moralische Grundsätze", "icon": "fa-balance-scale"},
{"name": "Logik", "description": "Gesetze des Denkens", "icon": "fa-project-diagram"},
{"name": "Erkenntnistheorie", "description": "Natur des Wissens", "icon": "fa-lightbulb"}
]
},
{
"name": "Wissenschaft",
"description": "Wissenschaftliche Disziplinen und Forschung",
"color_code": "#48BB78",
"icon": "fa-flask",
"subcategories": [
{"name": "Physik", "description": "Gesetze der Materie und Energie", "icon": "fa-atom"},
{"name": "Biologie", "description": "Wissenschaft des Lebens", "icon": "fa-dna"},
{"name": "Mathematik", "description": "Abstrakte Strukturen", "icon": "fa-calculator"},
{"name": "Informatik", "description": "Wissenschaft der Datenverarbeitung", "icon": "fa-laptop-code"}
]
},
{
"name": "Technologie",
"description": "Technologische Entwicklungen und Anwendungen",
"color_code": "#ED8936",
"icon": "fa-microchip",
"subcategories": [
{"name": "Künstliche Intelligenz", "description": "Intelligente Maschinen", "icon": "fa-robot"},
{"name": "Programmierung", "description": "Softwareentwicklung", "icon": "fa-code"},
{"name": "Elektronik", "description": "Elektronische Systeme", "icon": "fa-memory"}
]
},
{
"name": "Künste",
"description": "Kunstformen und kulturelle Ausdrucksweisen",
"color_code": "#ED64A6",
"icon": "fa-palette",
"subcategories": [
{"name": "Literatur", "description": "Schriftliche Werke", "icon": "fa-book"},
{"name": "Musik", "description": "Klangkunst", "icon": "fa-musik"},
{"name": "Bildende Kunst", "description": "Visuelle Kunstformen", "icon": "fa-paint-brush"}
]
},
{
"name": "Psychologie",
"description": "Menschliches Verhalten und Geist",
"color_code": "#4299E1",
"icon": "fa-comments",
"subcategories": [
{"name": "Kognition", "description": "Denken und Wahrnehmen", "icon": "fa-brain"},
{"name": "Emotionen", "description": "Gefühlswelt", "icon": "fa-heart"},
{"name": "Persönlichkeit", "description": "Charaktereigenschaften", "icon": "fa-user"}
]
}
]
# Kategorien erstellen
for main_cat_data in main_categories:
# Prüfen, ob die Kategorie bereits existiert
existing_cat = Category.query.filter_by(name=main_cat_data["name"]).first()
if existing_cat:
continue
# Hauptkategorie erstellen
main_category = Category(
name=main_cat_data["name"],
description=main_cat_data["description"],
color_code=main_cat_data["color_code"],
icon=main_cat_data["icon"]
)
db.session.add(main_category)
db.session.flush() # Um die ID zu generieren
# Unterkategorien erstellen
for sub_cat_data in main_cat_data.get("subcategories", []):
sub_category = Category(
name=sub_cat_data["name"],
description=sub_cat_data["description"],
color_code=main_cat_data["color_code"],
icon=sub_cat_data.get("icon", main_cat_data["icon"]),
parent_id=main_category.id
)
db.session.add(sub_category)
db.session.commit()
print("Standard-Kategorien wurden erstellt!")
def initialize_database():
"""Initialisiert die Datenbank mit Grunddaten, falls diese leer ist"""
try:
print("Initialisiere die Datenbank...")
# Erstelle alle Tabellen
db.create_all()
# Prüfen, ob bereits Kategorien existieren
categories_count = Category.query.count()
users_count = User.query.count()
# Erstelle Standarddaten, wenn es keine Kategorien gibt
if categories_count == 0:
create_default_categories()
# Admin-Benutzer erstellen, wenn keine Benutzer vorhanden sind
if users_count == 0:
admin_user = User(
username="admin",
email="admin@example.com",
role="admin",
is_active=True
)
admin_user.set_password("admin123") # Sicheres Passwort in der Produktion verwenden!
db.session.add(admin_user)
db.session.commit()
print("Admin-Benutzer wurde erstellt!")
# Prüfe, ob der "Wissen"-Knoten existiert, falls nicht, erstelle ihn
wissen_node = MindMapNode.query.filter_by(name="Wissen").first()
if not wissen_node:
wissen_node = MindMapNode(
name="Wissen",
description="Zentrale Wissensbasis",
color_code="#4299E1",
is_public=True
)
db.session.add(wissen_node)
db.session.commit()
print("'Wissen'-Knoten wurde erstellt")
return True
except Exception as e:
print(f"Fehler bei Datenbank-Initialisierung: {e}")
return False
# Instead of before_first_request, which is deprecated in newer Flask versions
# Use a function to initialize the database that will be called during app creation
def init_app_database(app_instance):
"""Initialisiert die Datenbank für die Flask-App"""
with app_instance.app_context():
# Überprüfe und initialisiere die Datenbank bei Bedarf
if not initialize_db_if_needed(db, initialize_database):
print("WARNUNG: Datenbankinitialisierung fehlgeschlagen. Einige Funktionen könnten eingeschränkt sein.")
# Call the function to initialize the database
init_app_database(app)
# Benutzerdefinierter Decorator für Admin-Zugriff
def admin_required(f):
@wraps(f)
@login_required
def decorated_function(*args, **kwargs):
if not current_user.is_admin:
flash('Zugriff verweigert. Nur Administratoren dürfen diese Seite aufrufen.', 'error')
return redirect(url_for('index'))
return f(*args, **kwargs)
return decorated_function
# Hilfsfunktion zur Fehlerbehandlung in API-Endpunkten
def handle_api_exception(func):
"""Decorator für API-Endpunkte zur einheitlichen Fehlerbehandlung"""
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
# Log und API-Fehler zurückgeben
return ErrorHandler.handle_exception(e, is_api_request=True)
return wrapper
@login_manager.user_loader
def load_user(id):
# Verwende session.get() anstelle von query.get() für SQLAlchemy 2.0 Kompatibilität
return db.session.get(User, int(id))
# Routes for authentication
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
login_user(user)
# Aktualisiere letzten Login-Zeitpunkt
user.last_login = datetime.now(timezone.utc)
db.session.commit()
next_page = request.args.get('next')
return redirect(next_page or url_for('index'))
flash('Ungültiger Benutzername oder Passwort')
return render_template('login.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
if User.query.filter_by(username=username).first():
flash('Benutzername existiert bereits')
return redirect(url_for('register'))
if User.query.filter_by(email=email).first():
flash('E-Mail ist bereits registriert')
return redirect(url_for('register'))
user = User(username=username, email=email)
user.set_password(password)
db.session.add(user)
db.session.commit() # Commit, um eine ID für den Benutzer zu erhalten
# Erstelle eine Standard-Mindmap für den neuen Benutzer
try:
default_mindmap = UserMindmap(
name='Meine Mindmap',
description='Meine persönliche Wissenslandschaft',
user_id=user.id
)
db.session.add(default_mindmap)
db.session.commit()
except Exception as e:
print(f"Fehler beim Erstellen der Standard-Mindmap: {e}")
# Stelle sicher, dass wir trotzdem weitermachen können
db.session.rollback()
login_user(user)
flash('Dein Konto wurde erfolgreich erstellt!', 'success')
return redirect(url_for('index'))
return render_template('register.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
# Route for the homepage
@app.route('/')
def index():
return render_template('index.html')
# Route for the mindmap page
@app.route('/mindmap')
def mindmap():
"""Zeigt die Mindmap-Seite an."""
# Benutzer-Mindmaps, falls angemeldet
user_mindmaps = []
if current_user.is_authenticated:
user_mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all()
# Stelle sicher, dass der "Wissen"-Knoten existiert
wissen_node = MindMapNode.query.filter_by(name="Wissen").first()
if not wissen_node:
wissen_node = MindMapNode(
name="Wissen",
description="Zentrale Wissensbasis",
color_code="#4299E1",
is_public=True
)
db.session.add(wissen_node)
db.session.commit()
print("'Wissen'-Knoten wurde erstellt")
# Überprüfe, ob es Kategorien gibt, sonst erstelle sie
if Category.query.count() == 0:
create_default_categories()
print("Kategorien wurden erstellt")
# Stelle sicher, dass die Route für statische Dateien korrekt ist
mindmap_js_path = url_for('static', filename='js/mindmap-init.js')
return render_template('mindmap.html', user_mindmaps=user_mindmaps, mindmap_js_path=mindmap_js_path)
# Route for user profile
@app.route('/profile')
@login_required
def profile():
try:
# Versuche auf die neue Benutzermodellstruktur zuzugreifen
_ = current_user.bio # Dies wird fehlschlagen, wenn die Spalte nicht existiert
# Wenn keine Ausnahme, fahre mit normalem Profil fort
# Lade Benutzer-Mindmaps
user_mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all()
# Prüfe, ob der Benutzer eine Standard-Mindmap hat, sonst erstelle eine
if not user_mindmaps:
try:
default_mindmap = UserMindmap(
name='Meine Mindmap',
description='Meine persönliche Wissenslandschaft',
user_id=current_user.id
)
db.session.add(default_mindmap)
db.session.commit()
# Aktualisiere die Liste nach dem Erstellen
user_mindmaps = [default_mindmap]
except Exception as e:
print(f"Fehler beim Erstellen der Standard-Mindmap in Profil: {e}")
# Flash-Nachricht für den Benutzer
flash('Es gab ein Problem beim Laden deiner Mindmaps. Bitte versuche es später erneut.', 'warning')
# Lade Statistiken
thought_count = Thought.query.filter_by(user_id=current_user.id).count()
bookmark_count = db.session.query(user_thought_bookmark).filter(
user_thought_bookmark.c.user_id == current_user.id).count()
# Berechne tatsächliche Werte für Benutzerstatistiken
contributions_count = Comment.query.filter_by(user_id=current_user.id).count()
# Berechne Verbindungen (Anzahl der Gedankenverknüpfungen)
connections_count = ThoughtRelation.query.filter(
(ThoughtRelation.source_id.in_(
db.session.query(Thought.id).filter_by(user_id=current_user.id)
)) |
(ThoughtRelation.target_id.in_(
db.session.query(Thought.id).filter_by(user_id=current_user.id)
))
).count()
# Berechne durchschnittliche Bewertung der Gedanken des Benutzers
avg_rating = db.session.query(func.avg(ThoughtRating.relevance_score)).join(
Thought, Thought.id == ThoughtRating.thought_id
).filter(Thought.user_id == current_user.id).scalar() or 0
# Sammle alle Statistiken in einem Wörterbuch
stats = {
'thought_count': thought_count,
'bookmark_count': bookmark_count,
'connections_count': connections_count,
'contributions_count': contributions_count,
'followers_count': 0, # Platzhalter für zukünftige Funktionalität
'rating': round(avg_rating, 1)
}
# Hole die letzten Gedanken des Benutzers
thoughts = Thought.query.filter_by(user_id=current_user.id).order_by(Thought.created_at.desc()).limit(5).all()
# Hole den Standort des Benutzers aus der Datenbank, falls vorhanden
location = "Deutschland" # Standardwert
return render_template('profile.html',
user=current_user,
user_mindmaps=user_mindmaps,
stats=stats,
thoughts=thoughts,
location=location)
except (AttributeError, sqlalchemy.exc.OperationalError) as e:
# Die Spalte existiert nicht, verwende stattdessen das einfache Profil
print(f"Verwende einfaches Profil wegen Datenbankfehler: {e}")
flash('Dein Profil wird im einfachen Modus angezeigt, bis die Datenbank aktualisiert wird.', 'warning')
# Lade nur die grundlegenden Informationen
user_mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all()
thoughts = Thought.query.filter_by(user_id=current_user.id).order_by(Thought.created_at.desc()).limit(5).all()
return render_template('simple_profile.html',
user=current_user,
user_mindmaps=user_mindmaps,
thoughts=thoughts)
except Exception as e:
# Eine andere Ausnahme ist aufgetreten
print(f"Fehler beim Laden des Profils: {e}")
flash('Beim Laden Ihres Benutzerprofils ist ein Fehler aufgetreten. Wir zeigen Ihnen die Standard-Ansicht.', 'error')
# Erstelle grundlegende stats
stats = {
'thought_count': 0,
'bookmark_count': 0,
'connections_count': 0,
'contributions_count': 0,
'followers_count': 0,
'rating': 0.0
}
# Leere Listen für Mindmaps und Gedanken
user_mindmaps = []
thoughts = []
location = "Deutschland"
# Zeige das normale Profil mit minimalen Daten an
return render_template('profile.html',
user=current_user,
user_mindmaps=user_mindmaps,
stats=stats,
thoughts=thoughts,
location=location)
# Route für Benutzereinstellungen
@app.route('/settings', methods=['GET', 'POST'])
@login_required
def settings():
if request.method == 'POST':
action = request.form.get('action')
# Bestimme, ob es eine AJAX-Anfrage ist
is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest' or request.content_type and 'multipart/form-data' in request.content_type
if action == 'update_profile':
try:
current_user.bio = request.form.get('bio', '')
current_user.location = request.form.get('location', '')
current_user.website = request.form.get('website', '')
# Update avatar if provided
avatar_url = request.form.get('avatar_url')
if avatar_url:
current_user.avatar = avatar_url
db.session.commit()
if is_ajax:
return jsonify({
'success': True,
'message': 'Profil erfolgreich aktualisiert!'
})
else:
flash('Profil erfolgreich aktualisiert!', 'success')
except Exception as e:
db.session.rollback()
app.logger.error(f"Fehler beim Aktualisieren des Profils: {str(e)}")
if is_ajax:
return jsonify({
'success': False,
'message': 'Fehler beim Aktualisieren des Profils'
}), 500
else:
flash('Fehler beim Aktualisieren des Profils', 'error')
elif action == 'update_password':
try:
current_password = request.form.get('current_password')
new_password = request.form.get('new_password')
confirm_password = request.form.get('confirm_password')
if not current_user.check_password(current_password):
if is_ajax:
return jsonify({
'success': False,
'message': 'Aktuelles Passwort ist nicht korrekt'
}), 400
else:
flash('Aktuelles Passwort ist nicht korrekt', 'error')
elif new_password != confirm_password:
if is_ajax:
return jsonify({
'success': False,
'message': 'Neue Passwörter stimmen nicht überein'
}), 400
else:
flash('Neue Passwörter stimmen nicht überein', 'error')
else:
current_user.set_password(new_password)
db.session.commit()
if is_ajax:
return jsonify({
'success': True,
'message': 'Passwort erfolgreich aktualisiert!'
})
else:
flash('Passwort erfolgreich aktualisiert!', 'success')
except Exception as e:
db.session.rollback()
app.logger.error(f"Fehler beim Aktualisieren des Passworts: {str(e)}")
if is_ajax:
return jsonify({
'success': False,
'message': 'Fehler beim Aktualisieren des Passworts'
}), 500
else:
flash('Fehler beim Aktualisieren des Passworts', 'error')
if not is_ajax:
return redirect(url_for('settings'))
else:
# Standardantwort für AJAX, falls keine spezifische Antwort zurückgegeben wurde
return jsonify({
'success': True,
'message': 'Einstellungen aktualisiert'
})
return render_template('settings.html')
# API-Endpunkt für Flash-Nachrichten
@app.route('/api/get_flash_messages')
def get_flash_messages():
"""Liefert aktuelle Flash-Nachrichten für API/JS-Clients."""
# Hole alle gespeicherten Flash-Nachrichten
messages = []
flashed_messages = session.get('_flashes', [])
# Formatierung der Nachrichten für die API-Antwort
for category, message in flashed_messages:
messages.append({
'category': category,
'message': message
})
# Lösche die Nachrichten aus der Session, nachdem sie abgerufen wurden
session.pop('_flashes', None)
return jsonify(messages)
# Routes für rechtliche Seiten
@app.route('/impressum/')
def impressum():
return render_template('impressum.html')
@app.route('/datenschutz/')
def datenschutz():
return render_template('datenschutz.html')
@app.route('/agb/')
def agb():
return render_template('agb.html')
@app.route('/ueber-uns/')
def ueber_uns():
return render_template('ueber_uns.html')
# Benutzer-Mindmap-Funktionalität
@app.route('/my-mindmap/<int:mindmap_id>')
@login_required
def user_mindmap(mindmap_id):
mindmap = UserMindmap.query.get_or_404(mindmap_id)
# Sicherheitscheck: Nur eigene Mindmaps oder öffentliche Mindmaps
if mindmap.user_id != current_user.id and mindmap.is_private:
flash("Du hast keinen Zugriff auf diese Mindmap.", "error")
return redirect(url_for('profile'))
return render_template('user_mindmap.html', mindmap=mindmap)
@app.route('/mindmap/create', methods=['GET', 'POST'])
@login_required
def create_mindmap():
if request.method == 'POST':
name = request.form.get('name')
description = request.form.get('description')
is_private = request.form.get('is_private') == 'on'
new_mindmap = UserMindmap(
name=name,
description=description,
user_id=current_user.id,
is_private=is_private
)
db.session.add(new_mindmap)
db.session.commit()
flash('Neue Mindmap erfolgreich erstellt!', 'success')
return redirect(url_for('user_mindmap', mindmap_id=new_mindmap.id))
return render_template('create_mindmap.html')
@app.route('/mindmap/<int:mindmap_id>/edit', methods=['GET', 'POST'])
@login_required
def edit_mindmap(mindmap_id):
mindmap = UserMindmap.query.get_or_404(mindmap_id)
# Sicherheitscheck
if mindmap.user_id != current_user.id:
flash("Du kannst nur deine eigenen Mindmaps bearbeiten.", "error")
return redirect(url_for('profile'))
if request.method == 'POST':
mindmap.name = request.form.get('name')
mindmap.description = request.form.get('description')
mindmap.is_private = request.form.get('is_private') == 'on'
db.session.commit()
flash('Mindmap erfolgreich aktualisiert!', 'success')
return redirect(url_for('user_mindmap', mindmap_id=mindmap.id))
return render_template('edit_mindmap.html', mindmap=mindmap)
@app.route('/mindmap/<int:mindmap_id>/delete', methods=['POST'])
@login_required
def delete_mindmap(mindmap_id):
mindmap = UserMindmap.query.get_or_404(mindmap_id)
# Sicherheitscheck
if mindmap.user_id != current_user.id:
flash("Du kannst nur deine eigenen Mindmaps löschen.", "error")
return redirect(url_for('profile'))
db.session.delete(mindmap)
db.session.commit()
flash('Mindmap erfolgreich gelöscht!', 'success')
return redirect(url_for('profile'))
# API-Endpunkte für UserMindmap CRUD-Operationen
@app.route('/api/mindmaps', methods=['POST'])
@login_required
def api_create_user_mindmap():
data = request.get_json()
name = data.get('name')
description = data.get('description')
is_private = data.get('is_private', True)
if not name:
return jsonify({'error': 'Name ist erforderlich'}), 400
new_mindmap = UserMindmap(
name=name,
description=description,
user_id=current_user.id,
is_private=is_private
)
db.session.add(new_mindmap)
db.session.commit()
return jsonify({
'id': new_mindmap.id,
'name': new_mindmap.name,
'description': new_mindmap.description,
'is_private': new_mindmap.is_private,
'user_id': new_mindmap.user_id,
'created_at': new_mindmap.created_at.isoformat(),
'last_modified': new_mindmap.last_modified.isoformat()
}), 201
@app.route('/api/mindmaps', methods=['GET'])
@login_required
def api_get_user_mindmaps():
mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all()
return jsonify([{
'id': m.id,
'name': m.name,
'description': m.description,
'is_private': m.is_private,
'created_at': m.created_at.isoformat(),
'last_modified': m.last_modified.isoformat()
} for m in mindmaps])
@app.route('/api/mindmaps/<int:mindmap_id>', methods=['GET'])
@login_required
@handle_api_exception
def api_get_user_mindmap_detail(mindmap_id):
# Berechtigungsprüfung (mindestens READ-Zugriff)
has_permission, error_msg = check_mindmap_permission(mindmap_id, "READ")
if not has_permission:
return ErrorHandler.api_error(error_msg, 403)
mindmap = UserMindmap.query.get_or_404(mindmap_id)
# Logik für eine detaillierte Ansicht
result = {
'id': mindmap.id,
'name': mindmap.name,
'description': mindmap.description,
'is_private': mindmap.is_private,
'user_id': mindmap.user_id,
'created_at': mindmap.created_at.isoformat(),
'last_modified': mindmap.last_modified.isoformat(),
'is_owner': mindmap.user_id == current_user.id
}
# Berechtigungsinformationen hinzufügen, falls nicht der Eigentümer
if mindmap.user_id != current_user.id:
share = MindmapShare.query.filter_by(
mindmap_id=mindmap_id,
shared_with_id=current_user.id
).first()
if share:
result['permission'] = share.permission_type.name
result['owner'] = {
'id': mindmap.user_id,
'username': User.query.get(mindmap.user_id).username
}
return jsonify(result)
@app.route('/api/mindmaps/<int:mindmap_id>', methods=['PUT'])
@login_required
@handle_api_exception
def api_update_user_mindmap(mindmap_id):
# Berechtigungsprüfung (mindestens EDIT-Zugriff)
has_permission, error_msg = check_mindmap_permission(mindmap_id, "EDIT")
if not has_permission:
return ErrorHandler.api_error(error_msg, 403)
mindmap = UserMindmap.query.get_or_404(mindmap_id)
data = request.get_json()
# Grundlegende Informationen aktualisieren
mindmap.name = data.get('name', mindmap.name)
mindmap.description = data.get('description', mindmap.description)
# is_private kann nur vom Eigentümer geändert werden
if mindmap.user_id == current_user.id and 'is_private' in data:
mindmap.is_private = data.get('is_private')
db.session.commit()
return jsonify({
'id': mindmap.id,
'name': mindmap.name,
'description': mindmap.description,
'is_private': mindmap.is_private,
'last_modified': mindmap.last_modified.isoformat(),
'success': True
})
@app.route('/api/mindmaps/<int:mindmap_id>', methods=['DELETE'])
@login_required
@handle_api_exception
def api_delete_user_mindmap(mindmap_id):
# Nur der Eigentümer kann eine Mindmap löschen
mindmap = UserMindmap.query.get_or_404(mindmap_id)
if mindmap.user_id != current_user.id:
return ErrorHandler.api_error("Nur der Eigentümer kann eine Mindmap löschen.", 403)
# Alle Freigaben löschen
MindmapShare.query.filter_by(mindmap_id=mindmap_id).delete()
# Mindmap löschen
db.session.delete(mindmap)
db.session.commit()
return jsonify({
'success': True,
'message': 'Mindmap erfolgreich gelöscht'
}), 200
# API-Endpunkte für Mindmap-Daten (öffentlich und benutzerspezifisch)
@app.route('/api/mindmap/public')
def get_public_mindmap():
"""Liefert die Standard-Mindmap-Struktur basierend auf Kategorien."""
try:
# Hole alle Hauptkategorien
categories = Category.query.filter_by(parent_id=None).all()
# Transformiere zu einer Baumstruktur
category_tree = [build_category_tree(cat) for cat in categories]
return jsonify(category_tree)
except Exception as e:
print(f"Fehler beim Abrufen der Mindmap: {str(e)}")
return jsonify({
'success': False,
'error': 'Mindmap konnte nicht geladen werden'
}), 500
@app.route('/api/mindmap/public/add_node', methods=['POST'])
@login_required
def add_node_to_public_mindmap():
"""Fügt einen neuen Knoten zur öffentlichen Mindmap hinzu."""
try:
data = request.json
name = data.get('name')
description = data.get('description', '')
color_code = data.get('color_code', '#8b5cf6')
x_position = data.get('x_position', 0)
y_position = data.get('y_position', 0)
if not name:
return jsonify({
'success': False,
'error': 'Knotenname ist erforderlich'
}), 400
# Neuen Knoten erstellen
new_node = MindMapNode(
name=name,
description=description,
color_code=color_code
)
db.session.add(new_node)
db.session.flush() # ID generieren
# Als Beitrag des aktuellen Benutzers markieren
new_node.contributed_by = current_user.id
db.session.commit()
return jsonify({
'success': True,
'node_id': new_node.id,
'message': 'Knoten erfolgreich hinzugefügt'
})
except Exception as e:
db.session.rollback()
print(f"Fehler beim Hinzufügen des Knotens: {str(e)}")
return jsonify({
'success': False,
'error': f'Fehler beim Hinzufügen des Knotens: {str(e)}'
}), 500
@app.route('/api/mindmap/public/update_node/<int:node_id>', methods=['PUT'])
@login_required
def update_public_node(node_id):
"""Aktualisiert einen Knoten in der öffentlichen Mindmap."""
try:
node = MindMapNode.query.get_or_404(node_id)
data = request.json
# Aktualisiere Knotendaten
if 'name' in data:
node.name = data['name']
if 'description' in data:
node.description = data['description']
if 'color_code' in data:
node.color_code = data['color_code']
# Als bearbeitet markieren
node.last_modified = datetime.now(timezone.utc)
node.last_modified_by = current_user.id
db.session.commit()
return jsonify({
'success': True,
'message': 'Knoten erfolgreich aktualisiert'
})
except Exception as e:
db.session.rollback()
print(f"Fehler beim Aktualisieren des Knotens: {str(e)}")
return jsonify({
'success': False,
'error': f'Fehler beim Aktualisieren des Knotens: {str(e)}'
}), 500
@app.route('/api/mindmap/public/remove_node/<int:node_id>', methods=['DELETE'])
@login_required
def remove_node_from_public_mindmap(node_id):
"""Entfernt einen Knoten aus der öffentlichen Mindmap."""
try:
node = MindMapNode.query.get_or_404(node_id)
# Lösche den Knoten
db.session.delete(node)
db.session.commit()
return jsonify({
'success': True,
'message': 'Knoten erfolgreich entfernt'
})
except Exception as e:
db.session.rollback()
print(f"Fehler beim Entfernen des Knotens: {str(e)}")
return jsonify({
'success': False,
'error': f'Fehler beim Entfernen des Knotens: {str(e)}'
}), 500
@app.route('/api/mindmap/public/update_layout', methods=['POST'])
@login_required
def update_public_layout():
"""Aktualisiert die Positionen der Knoten in der öffentlichen Mindmap."""
try:
data = request.json
positions = data.get('positions', [])
for pos in positions:
node_id = pos.get('node_id')
node = MindMapNode.query.get(node_id)
if node:
# Position aktualisieren
node.x_position = pos.get('x_position', 0)
node.y_position = pos.get('y_position', 0)
db.session.commit()
return jsonify({
'success': True,
'message': 'Layout erfolgreich aktualisiert'
})
except Exception as e:
db.session.rollback()
print(f"Fehler beim Aktualisieren des Layouts: {str(e)}")
return jsonify({
'success': False,
'error': f'Fehler beim Aktualisieren des Layouts: {str(e)}'
}), 500
def build_category_tree(category):
"""
Erstellt eine Baumstruktur für eine Kategorie mit all ihren Unterkategorien
und dazugehörigen Knoten
Args:
category: Ein Category-Objekt
Returns:
dict: Eine JSON-serialisierbare Darstellung der Kategoriestruktur
"""
# Kategorie-Basisinformationen
category_dict = {
'id': category.id,
'name': category.name,
'description': category.description,
'color_code': category.color_code,
'icon': category.icon,
'nodes': [],
'children': []
}
# Knoten zur Kategorie hinzufügen
if category.nodes:
for node in category.nodes:
category_dict['nodes'].append({
'id': node.id,
'name': node.name,
'description': node.description or '',
'color_code': node.color_code or '#9F7AEA',
'thought_count': len(node.thoughts) if hasattr(node, 'thoughts') else 0
})
# Rekursiv Unterkategorien hinzufügen
if category.children:
for child in category.children:
category_dict['children'].append(build_category_tree(child))
return category_dict
@app.route('/api/mindmap/user/<int:mindmap_id>')
@login_required
def get_user_mindmap(mindmap_id):
"""Liefert die benutzerdefinierte Mindmap-Struktur."""
mindmap = UserMindmap.query.get_or_404(mindmap_id)
# Sicherheitscheck
if mindmap.user_id != current_user.id and mindmap.is_private:
return jsonify({'error': 'Nicht autorisiert'}), 403
# Hole alle verknüpften Knoten mit Position
nodes_data = db.session.query(
MindMapNode, UserMindmapNode
).join(
UserMindmapNode, UserMindmapNode.node_id == MindMapNode.id
).filter(
UserMindmapNode.user_mindmap_id == mindmap_id
).all()
# Knoten formatieren
nodes = []
for node, user_node in nodes_data:
nodes.append({
'id': node.id,
'name': node.name,
'description': node.description,
'color_code': node.color_code,
'x': user_node.x_position,
'y': user_node.y_position,
'scale': user_node.scale,
'thought_count': len(node.thoughts)
})
# Hole Notizen zu dieser Mindmap
notes = MindmapNote.query.filter_by(
mindmap_id=mindmap_id,
user_id=current_user.id
).all()
notes_data = [{
'id': note.id,
'content': note.content,
'node_id': note.node_id,
'thought_id': note.thought_id,
'color_code': note.color_code,
'created_at': note.created_at.isoformat()
} for note in notes]
return jsonify({
'id': mindmap.id,
'name': mindmap.name,
'description': mindmap.description,
'is_private': mindmap.is_private,
'nodes': nodes,
'notes': notes_data
})
@app.route('/api/mindmap/id/<int:mindmap_id>/add_node', methods=['POST'])
@login_required
def add_node_to_mindmap(mindmap_id):
"""Fügt einen öffentlichen Knoten zur Benutzer-Mindmap hinzu."""
data = request.json
mindmap = UserMindmap.query.get_or_404(mindmap_id)
# Sicherheitscheck
if mindmap.user_id != current_user.id:
return jsonify({'error': 'Nicht autorisiert'}), 403
node_id = data.get('node_id')
parent_id = data.get('parent_id') # Optional: Elternknoten für die Verknüpfung
x_pos = data.get('x', 0)
y_pos = data.get('y', 0)
# Knoten abrufen
node = MindMapNode.query.get_or_404(node_id)
# Prüfen, ob der Knoten bereits in der Mindmap existiert
existing = UserMindmapNode.query.filter_by(
user_mindmap_id=mindmap_id,
node_id=node_id
).first()
if existing:
# Update Position
existing.x_position = x_pos
existing.y_position = y_pos
else:
# Neuen Knoten hinzufügen
user_node = UserMindmapNode(
user_mindmap_id=mindmap_id,
node_id=node_id,
x_position=x_pos,
y_position=y_pos
)
db.session.add(user_node)
# Wenn ein Elternknoten angegeben wurde, Verbindung erstellen
if parent_id:
# Existenz des Elternknotens in der Mindmap prüfen
parent_node = MindMapNode.query.get(parent_id)
parent_user_node = UserMindmapNode.query.filter_by(
user_mindmap_id=mindmap_id,
node_id=parent_id
).first()
if parent_node and parent_user_node:
# Beziehung zwischen den Knoten erstellen
try:
parent_node.children.append(node)
db.session.flush()
except Exception as e:
print(f"Warnung: Fehler beim Erstellen der Beziehung: {e}")
db.session.commit()
return jsonify({
'success': True,
'node_id': node_id,
'x': x_pos,
'y': y_pos
})
@app.route('/api/mindmap/id/<int:mindmap_id>/remove_node/<int:node_id>', methods=['DELETE'])
@login_required
def remove_node_from_mindmap(mindmap_id, node_id):
"""Entfernt einen Knoten aus der Benutzer-Mindmap."""
mindmap = UserMindmap.query.get_or_404(mindmap_id)
# Sicherheitscheck
if mindmap.user_id != current_user.id:
return jsonify({'error': 'Nicht autorisiert'}), 403
user_node = UserMindmapNode.query.filter_by(
user_mindmap_id=mindmap_id,
node_id=node_id
).first_or_404()
db.session.delete(user_node)
db.session.commit()
return jsonify({'success': True})
@app.route('/api/mindmap/node/<int:node_id>', methods=['GET'])
@login_required
def get_node_info(node_id):
"""Liefert Detailinformationen zu einem einzelnen Knoten"""
try:
# Knoten abrufen
node = MindMapNode.query.get_or_404(node_id)
return jsonify({
'id': node.id,
'name': node.name,
'description': node.description or '',
'color_code': node.color_code or '#9F7AEA'
})
except Exception as e:
print(f"Fehler in get_node_info: {str(e)}")
return jsonify({'success': False, 'message': str(e)}), 500
@app.route('/api/mindmap/id/<int:mindmap_id>/update_node_position', methods=['POST'])
@login_required
def update_node_position(mindmap_id):
"""Aktualisiert die Position eines Knotens in der Benutzer-Mindmap."""
data = request.json
mindmap = UserMindmap.query.get_or_404(mindmap_id)
# Sicherheitscheck
if mindmap.user_id != current_user.id:
return jsonify({'error': 'Nicht autorisiert'}), 403
node_id = data.get('node_id')
x_pos = data.get('x')
y_pos = data.get('y')
scale = data.get('scale')
user_node = UserMindmapNode.query.filter_by(
user_mindmap_id=mindmap_id,
node_id=node_id
).first_or_404()
if x_pos is not None:
user_node.x_position = x_pos
if y_pos is not None:
user_node.y_position = y_pos
if scale is not None:
user_node.scale = scale
db.session.commit()
return jsonify({'success': True})
# Notizen-Funktionalität
@app.route('/api/mindmap/<int:mindmap_id>/notes', methods=['GET'])
@login_required
def get_mindmap_notes(mindmap_id):
"""Liefert alle Notizen zu einer Mindmap."""
mindmap = UserMindmap.query.get_or_404(mindmap_id)
# Sicherheitscheck
if mindmap.user_id != current_user.id:
return jsonify({'error': 'Nicht autorisiert'}), 403
notes = MindmapNote.query.filter_by(
mindmap_id=mindmap_id,
user_id=current_user.id
).all()
return jsonify([{
'id': note.id,
'content': note.content,
'node_id': note.node_id,
'thought_id': note.thought_id,
'color_code': note.color_code,
'created_at': note.created_at.isoformat(),
'last_modified': note.last_modified.isoformat()
} for note in notes])
@app.route('/api/mindmap/<int:mindmap_id>/notes', methods=['POST'])
@login_required
def add_mindmap_note(mindmap_id):
"""Fügt eine neue Notiz zur Mindmap hinzu."""
data = request.json
mindmap = UserMindmap.query.get_or_404(mindmap_id)
# Sicherheitscheck
if mindmap.user_id != current_user.id:
return jsonify({'error': 'Nicht autorisiert'}), 403
content = data.get('content')
node_id = data.get('node_id')
thought_id = data.get('thought_id')
color_code = data.get('color_code', "#FFF59D") # Gelber Standard
note = MindmapNote(
user_id=current_user.id,
mindmap_id=mindmap_id,
node_id=node_id,
thought_id=thought_id,
content=content,
color_code=color_code
)
db.session.add(note)
db.session.commit()
return jsonify({
'id': note.id,
'content': note.content,
'node_id': note.node_id,
'thought_id': note.thought_id,
'color_code': note.color_code,
'created_at': note.created_at.isoformat(),
'last_modified': note.last_modified.isoformat()
})
@app.route('/api/notes/<int:note_id>', methods=['PUT'])
@login_required
def update_note(note_id):
"""Aktualisiert eine bestehende Notiz."""
data = request.json
note = MindmapNote.query.get_or_404(note_id)
# Sicherheitscheck
if note.user_id != current_user.id:
return jsonify({'error': 'Nicht autorisiert'}), 403
content = data.get('content')
color_code = data.get('color_code')
if content:
note.content = content
if color_code:
note.color_code = color_code
note.last_modified = datetime.now(timezone.utc)
db.session.commit()
return jsonify({
'id': note.id,
'content': note.content,
'color_code': note.color_code,
'last_modified': note.last_modified.isoformat()
})
@app.route('/api/notes/<int:note_id>', methods=['DELETE'])
@login_required
def delete_note(note_id):
"""Löscht eine Notiz."""
note = MindmapNote.query.get_or_404(note_id)
# Sicherheitscheck
if note.user_id != current_user.id:
return jsonify({'error': 'Nicht autorisiert'}), 403
db.session.delete(note)
db.session.commit()
return jsonify({'success': True})
# API routes for mindmap and thoughts
@app.route('/api/mindmap')
def get_mindmap():
"""Gibt die Mindmap-Struktur zurück"""
try:
# Hauptknoten abrufen (Root-Knoten)
wissen_node = MindMapNode.query.filter_by(name="Wissen").first()
if not wissen_node:
# Wissen-Knoten erstellen, falls nicht vorhanden
wissen_node = MindMapNode(
name="Wissen",
description="Zentrale Wissensbasis",
color_code="#4299E1",
is_public=True
)
db.session.add(wissen_node)
db.session.commit()
# Alle anderen aktiven Knoten holen
nodes = MindMapNode.query.filter(MindMapNode.is_public == True).all()
# Ergebnisdaten vorbereiten
nodes_data = []
edges_data = []
# Knoten hinzufügen
for node in nodes:
nodes_data.append({
'id': node.id,
'name': node.name,
'description': node.description or '',
'color_code': node.color_code or '#9F7AEA'
})
# Wenn es nicht der Wissen-Knoten ist, Verbindung zum Wissen-Knoten hinzufügen
if node.id != wissen_node.id:
edges_data.append({
'source': wissen_node.id,
'target': node.id
})
# Beziehungen zwischen Knoten abfragen und hinzufügen
relationships = db.session.query(node_relationship).all()
for rel in relationships:
if rel.parent_id != wissen_node.id: # Doppelte Kanten vermeiden
edges_data.append({
'source': rel.parent_id,
'target': rel.child_id
})
return jsonify({
'nodes': nodes_data,
'edges': edges_data
})
except Exception as e:
print(f"Fehler beim Abrufen der Mindmap: {str(e)}")
return jsonify({
'success': False,
'error': 'Mindmap konnte nicht geladen werden'
}), 500
@app.route('/api/nodes/<int:node_id>/thoughts')
def get_node_thoughts(node_id):
"""Liefert alle Gedanken, die mit einem Knoten verknüpft sind."""
node = MindMapNode.query.get_or_404(node_id)
thoughts = []
for thought in node.thoughts:
author = thought.author
thoughts.append({
'id': thought.id,
'title': thought.title,
'abstract': thought.abstract,
'content': thought.content[:200] + '...' if len(thought.content) > 200 else thought.content,
'keywords': thought.keywords,
'author': {
'id': author.id,
'username': author.username
},
'created_at': thought.created_at.isoformat(),
'color_code': thought.color_code,
'avg_rating': thought.average_rating,
'bookmarked': current_user.is_authenticated and thought in current_user.bookmarked_thoughts
})
return jsonify(thoughts)
@app.route('/api/nodes/<int:node_id>/thoughts', methods=['POST'])
@login_required
def add_node_thought(node_id):
"""Fügt einen neuen Gedanken zu einem Knoten hinzu."""
data = request.json
node = MindMapNode.query.get_or_404(node_id)
title = data.get('title')
content = data.get('content')
abstract = data.get('abstract', '')
keywords = data.get('keywords', '')
color_code = data.get('color_code', '#B39DDB') # Standard-Lila
# Kategorie des Knotens bestimmen
category_name = node.category.name if node.category else "Allgemein"
thought = Thought(
title=title,
content=content,
abstract=abstract,
keywords=keywords,
color_code=color_code,
branch=category_name,
user_id=current_user.id,
source_type='User Input'
)
node.thoughts.append(thought)
db.session.add(thought)
db.session.commit()
return jsonify({
'id': thought.id,
'title': thought.title,
'success': True
})
@app.route('/api/thoughts/<int:thought_id>', methods=['GET'])
def get_thought(thought_id):
"""Liefert Details zu einem Gedanken."""
thought = Thought.query.get_or_404(thought_id)
author = thought.author
is_bookmarked = False
if current_user.is_authenticated:
is_bookmarked = thought in current_user.bookmarked_thoughts
# Verknüpfte Knoten abrufen
nodes = [{
'id': node.id,
'name': node.name,
'category': node.category.name if node.category else None
} for node in thought.nodes]
return jsonify({
'id': thought.id,
'title': thought.title,
'content': thought.content,
'abstract': thought.abstract,
'keywords': thought.keywords,
'branch': thought.branch,
'color_code': thought.color_code,
'created_at': thought.created_at.isoformat(),
'author': {
'id': author.id,
'username': author.username,
'avatar': author.avatar
},
'avg_rating': thought.average_rating,
'bookmarked': is_bookmarked,
'nodes': nodes,
'source_type': thought.source_type
})
@app.route('/api/thoughts', methods=['POST'])
@login_required
def add_thought():
"""Erstellt einen neuen Gedanken."""
data = request.json
title = data.get('title')
content = data.get('content')
abstract = data.get('abstract', '')
keywords = data.get('keywords', '')
branch = data.get('branch', 'Allgemein')
color_code = data.get('color_code', '#B39DDB')
thought = Thought(
title=title,
content=content,
abstract=abstract,
keywords=keywords,
branch=branch,
color_code=color_code,
user_id=current_user.id,
source_type='User Input'
)
# Knoten-IDs, falls vorhanden
node_ids = data.get('node_ids', [])
for node_id in node_ids:
node = MindMapNode.query.get(node_id)
if node:
thought.nodes.append(node)
db.session.add(thought)
db.session.commit()
return jsonify({
'id': thought.id,
'title': thought.title,
'success': True
})
@app.route('/api/thoughts/<int:thought_id>', methods=['PUT'])
@login_required
def update_thought(thought_id):
"""Aktualisiert einen bestehenden Gedanken."""
thought = Thought.query.get_or_404(thought_id)
# Sicherheitscheck
if thought.user_id != current_user.id and not current_user.is_admin:
return jsonify({'error': 'Nicht autorisiert'}), 403
data = request.json
# Aktualisiere Felder, die gesendet wurden
if 'title' in data:
thought.title = data['title']
if 'content' in data:
thought.content = data['content']
if 'abstract' in data:
thought.abstract = data['abstract']
if 'keywords' in data:
thought.keywords = data['keywords']
if 'color_code' in data:
thought.color_code = data['color_code']
thought.last_modified = datetime.now(timezone.utc)
db.session.commit()
return jsonify({
'id': thought.id,
'success': True
})
@app.route('/api/thoughts/<int:thought_id>', methods=['DELETE'])
@login_required
def delete_thought(thought_id):
"""Löscht einen Gedanken."""
thought = Thought.query.get_or_404(thought_id)
# Sicherheitscheck
if thought.user_id != current_user.id and not current_user.is_admin:
return jsonify({'error': 'Nicht autorisiert'}), 403
db.session.delete(thought)
db.session.commit()
return jsonify({'success': True})
@app.route('/api/thoughts/<int:thought_id>/bookmark', methods=['POST'])
@login_required
def bookmark_thought(thought_id):
"""Fügt einen Gedanken zu den Bookmarks des Benutzers hinzu oder entfernt ihn."""
thought = Thought.query.get_or_404(thought_id)
# Toggle Bookmark-Status
if thought in current_user.bookmarked_thoughts:
current_user.bookmarked_thoughts.remove(thought)
action = 'removed'
else:
current_user.bookmarked_thoughts.append(thought)
action = 'added'
db.session.commit()
return jsonify({
'success': True,
'action': action
})
@app.route('/api/categories')
def get_categories():
"""API-Endpunkt, der alle Kategorien als hierarchische Struktur zurückgibt"""
try:
# Hole alle Kategorien der obersten Ebene
categories = Category.query.filter_by(parent_id=None).all()
# Transformiere zu einer Baumstruktur
category_tree = [build_category_tree(cat) for cat in categories]
return jsonify(category_tree)
except Exception as e:
print(f"Fehler beim Abrufen der Kategorien: {str(e)}")
return jsonify({
'success': False,
'error': 'Kategorien konnten nicht geladen werden'
}), 500
@app.route('/api/set_dark_mode', methods=['POST'])
def set_dark_mode():
"""Speichert die Dark Mode-Einstellung in der Session."""
data = request.json
dark_mode = data.get('darkMode', False)
session['dark_mode'] = 'true' if dark_mode else 'false'
session.permanent = True
return jsonify({
'success': True,
'darkMode': dark_mode
})
@app.route('/api/get_dark_mode', methods=['GET'])
def get_dark_mode():
"""Liefert die aktuelle Dark Mode-Einstellung."""
dark_mode = session.get('dark_mode', 'true') # Standard: Dark Mode aktiviert
return jsonify({
'success': True,
'darkMode': dark_mode
})
# Fehlerhandler
@app.errorhandler(404)
def page_not_found(e):
"""404 Fehler - Seite nicht gefunden"""
ErrorHandler.log_exception(e, code=404)
is_api_request = request.path.startswith('/api/')
if is_api_request:
return ErrorHandler.api_error("Die angeforderte Ressource wurde nicht gefunden.", 404)
return render_template('errors/404.html'), 404
@app.errorhandler(403)
def forbidden(e):
"""403 Fehler - Zugriff verweigert"""
ErrorHandler.log_exception(e, code=403)
is_api_request = request.path.startswith('/api/')
if is_api_request:
return ErrorHandler.api_error("Sie haben keine Berechtigung, auf diese Ressource zuzugreifen.", 403)
return render_template('errors/403.html'), 403
@app.errorhandler(500)
def internal_server_error(e):
"""500 Fehler - Interner Serverfehler"""
ErrorHandler.log_exception(e, code=500)
is_api_request = request.path.startswith('/api/')
if is_api_request:
return ErrorHandler.api_error("Ein interner Serverfehler ist aufgetreten.", 500)
return render_template('errors/500.html'), 500
@app.errorhandler(429)
def too_many_requests(e):
"""429 Fehler - Zu viele Anfragen"""
ErrorHandler.log_exception(e, code=429)
is_api_request = request.path.startswith('/api/')
if is_api_request:
return ErrorHandler.api_error("Zu viele Anfragen. Bitte versuchen Sie es später erneut.", 429)
return render_template('errors/429.html'), 429
@app.errorhandler(400)
def bad_request(e):
"""400 Fehler - Ungültige Anfrage"""
ErrorHandler.log_exception(e, code=400)
is_api_request = request.path.startswith('/api/')
if is_api_request:
return ErrorHandler.api_error("Die Anfrage konnte nicht verarbeitet werden.", 400)
flash("Die Anfrage konnte nicht verarbeitet werden. Bitte überprüfen Sie Ihre Eingaben.", "error")
return render_template('errors/400.html', error=str(e)), 400
@app.errorhandler(401)
def unauthorized(e):
"""401 Fehler - Nicht autorisiert"""
ErrorHandler.log_exception(e, code=401)
is_api_request = request.path.startswith('/api/')
if is_api_request:
return ErrorHandler.api_error("Authentifizierung erforderlich.", 401)
flash("Sie müssen sich anmelden, um auf diese Seite zuzugreifen.", "error")
return redirect(url_for('login'))
# OpenAI-Integration für KI-Assistenz
@app.route('/api/assistant', methods=['POST'])
def chat_with_assistant():
"""Chat mit dem KI-Assistenten"""
data = request.json
user_message = data.get('message', '')
# Überprüfe, ob die OpenAI-API verfügbar ist
if not openai_available or client is None:
# Fallback-Antwort, wenn OpenAI nicht verfügbar ist
fallback_message = {
"response": "Der KI-Assistent ist derzeit nicht verfügbar. Bitte versuchen Sie es später erneut oder kontaktieren Sie den Administrator.",
"thoughts": "Leider konnte keine Verbindung zur OpenAI-API hergestellt werden. Dies kann an SSL-Zertifikatsproblemen, Netzwerkproblemen oder API-Schlüsselproblemen liegen."
}
return jsonify(fallback_message)
# Versuche, eine Antwort von OpenAI zu erhalten
try:
# Check, ob es eine Datenbankanfrage ist
is_db_query, db_query_result = check_database_query(user_message)
if is_db_query:
return jsonify({
"response": db_query_result,
"thoughts": "Ihre Anfrage wurde als Datenbankanfrage erkannt und direkt beantwortet."
})
system_message = """Du bist SysTades, ein intelligenter Assistent in einer Wissensmanagement-Anwendung.
Deine Aufgabe ist es, tiefe, reflektierte Antworten auf Fragen der Benutzer zu geben.
Beziehe dich auf vorhandene Konzepte und betrachte Fragen stets aus mehreren Perspektiven.
Vermeide pauschale Antworten und beziehe immer verschiedene Denkansätze ein.
Antworte stets auf Deutsch und in einem nachdenklichen, philosophischen Ton.
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": user_message}
],
temperature=0.7,
timeout=60 # Erhöht auf 60 Sekunden für bessere Zuverlässigkeit
)
# Extrahiere die Antwort
assistant_response = response.choices[0].message.content
# Generiere zusätzliche "Gedanken" für verbesserte UX
thoughts_response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Gib kurze Einblicke in deine Gedankenprozesse zu dieser Frage. Schreibe 1-3 Sätze zur Reflexion über die Frage und die wichtigsten Aspekte deiner Antwort. Bleibe dabei informell und persönlich. Schreibe auf Deutsch."},
{"role": "user", "content": user_message},
{"role": "assistant", "content": assistant_response}
],
temperature=0.7,
max_tokens=150
)
thoughts = thoughts_response.choices[0].message.content
return jsonify({
"response": assistant_response,
"thoughts": thoughts
})
except Exception as e:
print(f"Fehler bei der OpenAI-Anfrage: {e}")
import traceback
print(f"Stack Trace: {traceback.format_exc()}")
# Fallback-Antwort bei Fehler
fallback_message = {
"response": "Es tut mir leid, aber ich konnte Ihre Anfrage nicht verarbeiten. Bitte versuchen Sie es später erneut.",
"thoughts": "Ein technisches Problem ist aufgetreten. Dies könnte an Netzwerkproblemen, API-Grenzen oder Serverauslastung liegen."
}
return jsonify(fallback_message)
def check_database_query(user_message):
"""
Prüft, ob die Benutzeranfrage nach Datenbankinformationen sucht
und gibt relevante Informationen aus der Datenbank zurück.
Returns:
(bool, str): Tupel aus (ist_db_anfrage, db_antwort)
"""
user_message = user_message.lower()
# Schlüsselwörter, die auf eine Datenbankanfrage hindeuten
db_keywords = [
'wie viele', 'wieviele', 'anzahl', 'liste', 'zeige', 'zeig mir', 'datenbank',
'statistik', 'stats', 'benutzer', 'gedanken', 'kategorien', 'mindmap',
'neueste', 'kürzlich', 'gespeichert', 'zähle', 'suche nach', 'finde'
]
# Überprüfen, ob die Nachricht eine Datenbankanfrage sein könnte
is_db_query = any(keyword in user_message for keyword in db_keywords)
if not is_db_query:
return False, ""
# Datenbank-Antwort vorbereiten
response = "Hier sind Informationen aus der Datenbank:\n\n"
try:
# 1. Benutzer-Statistiken
if any(kw in user_message for kw in ['benutzer', 'user', 'nutzer']):
user_count = User.query.count()
active_users = User.query.filter_by(is_active=True).count()
admins = User.query.filter_by(role='admin').count()
response += f"**Benutzer-Statistiken:**\n"
response += f"- Gesamt: {user_count} Benutzer\n"
response += f"- Aktiv: {active_users} Benutzer\n"
response += f"- Administratoren: {admins} Benutzer\n\n"
# 2. Gedanken-Statistiken
if any(kw in user_message for kw in ['gedanken', 'thought', 'inhalt']):
thought_count = Thought.query.count()
if thought_count > 0:
avg_rating = db.session.query(func.avg(ThoughtRating.relevance_score)).scalar() or 0
avg_rating = round(avg_rating, 1)
recent_thoughts = Thought.query.order_by(Thought.created_at.desc()).limit(5).all()
response += f"**Gedanken-Statistiken:**\n"
response += f"- Gesamt: {thought_count} Gedanken\n"
response += f"- Durchschnittliche Bewertung: {avg_rating}/5\n\n"
if recent_thoughts:
response += "**Neueste Gedanken:**\n"
for thought in recent_thoughts:
response += f"- {thought.title} (von {thought.author.username})\n"
response += "\n"
else:
response += "Es sind noch keine Gedanken in der Datenbank gespeichert.\n\n"
# 3. Kategorien-Statistiken
if any(kw in user_message for kw in ['kategorie', 'category', 'thema']):
category_count = Category.query.filter_by(parent_id=None).count()
subcategory_count = Category.query.filter(Category.parent_id != None).count()
response += f"**Kategorien-Statistiken:**\n"
response += f"- Hauptkategorien: {category_count}\n"
response += f"- Unterkategorien: {subcategory_count}\n\n"
main_categories = Category.query.filter_by(parent_id=None).all()
if main_categories:
response += "**Hauptkategorien:**\n"
for category in main_categories:
subcats = Category.query.filter_by(parent_id=category.id).count()
response += f"- {category.name} ({subcats} Unterkategorien)\n"
response += "\n"
# 4. Mindmap-Statistiken
if any(kw in user_message for kw in ['mindmap', 'karte', 'map', 'knoten']):
public_nodes = MindMapNode.query.count()
user_mindmaps = UserMindmap.query.count()
response += f"**Mindmap-Statistiken:**\n"
response += f"- Öffentliche Knoten: {public_nodes}\n"
response += f"- Benutzerdefinierte Mindmaps: {user_mindmaps}\n\n"
if user_mindmaps > 0:
recent_mindmaps = UserMindmap.query.order_by(UserMindmap.created_at.desc()).limit(3).all()
if recent_mindmaps:
response += "**Neueste Mindmaps:**\n"
for mindmap in recent_mindmaps:
response += f"- {mindmap.name} (von {mindmap.user.username})\n"
response += "\n"
# Wenn keine spezifischen Daten angefordert wurden, allgemeine Übersicht geben
if not any(kw in user_message for kw in ['benutzer', 'user', 'gedanken', 'thought', 'kategorie', 'category', 'mindmap']):
users = User.query.count()
thoughts = Thought.query.count()
categories = Category.query.count()
nodes = MindMapNode.query.count()
user_maps = UserMindmap.query.count()
response += f"**Übersicht über die Datenbank:**\n"
response += f"- Benutzer: {users}\n"
response += f"- Gedanken: {thoughts}\n"
response += f"- Kategorien: {categories}\n"
response += f"- Mindmap-Knoten: {nodes}\n"
response += f"- Benutzerdefinierte Mindmaps: {user_maps}\n"
return True, response
except Exception as e:
import traceback
print(f"Fehler bei der Datenbankabfrage: {e}")
print(traceback.format_exc())
return True, "Es ist ein Fehler bei der Abfrage der Datenbank aufgetreten. Bitte versuchen Sie es später erneut."
@app.route('/search')
def search_thoughts_page():
"""Seite zur Gedankensuche anzeigen."""
return render_template('search.html')
@app.route('/my_account')
def my_account():
"""Zeigt die persönliche Merkliste an."""
if not current_user.is_authenticated:
flash('Bitte melde dich an, um auf deine Merkliste zuzugreifen.', 'warning')
return redirect(url_for('login'))
# Hole die Lesezeichen des Benutzers
bookmarked_thoughts = current_user.bookmarked_thoughts
return render_template('my_account.html', bookmarked_thoughts=bookmarked_thoughts)
# Dummy-Route, um 404-Fehler für fehlende Netzwerk-Hintergrundbilder zu vermeiden
@app.route('/static/network-bg.jpg')
@app.route('/static/network-bg.svg')
def dummy_network_bg():
"""Leere Antwort für die nicht mehr verwendeten Netzwerk-Hintergrundbilder."""
return '', 200
# Route zum expliziten Neu-Laden der Umgebungsvariablen
@app.route('/admin/reload-env', methods=['POST'])
@admin_required
def reload_env():
"""Lädt die Umgebungsvariablen aus der .env-Datei neu."""
try:
# Erzwinge das Neuladen der .env-Datei
load_dotenv(override=True, force=True)
# OpenAI API-Key ist bereits fest kodiert
# client wurde bereits mit festem API-Key initialisiert
# Weitere Umgebungsvariablen hier aktualisieren, falls nötig
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', app.config['SECRET_KEY'])
return jsonify({
'success': True,
'message': 'Umgebungsvariablen wurden erfolgreich neu geladen.'
})
except Exception as e:
return jsonify({
'success': False,
'message': f'Fehler beim Neuladen der Umgebungsvariablen: {str(e)}'
}), 500
# Berechtigungsverwaltung für Mindmaps
@app.route('/api/mindmap/<int:mindmap_id>/shares', methods=['GET'])
@login_required
@handle_api_exception
def get_mindmap_shares(mindmap_id):
"""Listet alle Benutzer auf, mit denen eine Mindmap geteilt wurde."""
# Überprüfen, ob die Mindmap dem Benutzer gehört
mindmap = UserMindmap.query.get_or_404(mindmap_id)
if mindmap.user_id != current_user.id:
return ErrorHandler.api_error("Sie haben keine Berechtigung, die Freigaben dieser Mindmap einzusehen.", 403)
# Alle Freigaben für diese Mindmap abrufen
shares = MindmapShare.query.filter_by(mindmap_id=mindmap_id).all()
result = []
for share in shares:
# Benutzerinformationen abrufen
shared_with_user = User.query.get(share.shared_with_id)
if shared_with_user:
result.append({
'id': share.id,
'user_id': shared_with_user.id,
'username': shared_with_user.username,
'email': shared_with_user.email,
'permission': share.permission_type.name,
'created_at': share.created_at.isoformat(),
'last_accessed': share.last_accessed.isoformat() if share.last_accessed else None
})
return jsonify({
'success': True,
'shares': result
})
@app.route('/api/mindmap/<int:mindmap_id>/share', methods=['POST'])
@login_required
@handle_api_exception
def share_mindmap(mindmap_id):
"""Teilt eine Mindmap mit einem anderen Benutzer."""
# Überprüfen, ob die Mindmap dem Benutzer gehört
mindmap = UserMindmap.query.get_or_404(mindmap_id)
if mindmap.user_id != current_user.id:
return ErrorHandler.api_error("Sie haben keine Berechtigung, diese Mindmap zu teilen.", 403)
data = request.json
if not data or 'email' not in data or 'permission' not in data:
return ErrorHandler.api_error("E-Mail-Adresse und Berechtigungstyp sind erforderlich.", 400)
# Benutzer anhand der E-Mail-Adresse finden
user_to_share_with = User.query.filter_by(email=data['email']).first()
if not user_to_share_with:
return ErrorHandler.api_error("Kein Benutzer mit dieser E-Mail-Adresse gefunden.", 404)
# Prüfen, ob der Benutzer versucht, mit sich selbst zu teilen
if user_to_share_with.id == current_user.id:
return ErrorHandler.api_error("Sie können die Mindmap nicht mit sich selbst teilen.", 400)
# Prüfen, ob die Mindmap bereits mit diesem Benutzer geteilt wurde
existing_share = MindmapShare.query.filter_by(
mindmap_id=mindmap_id,
shared_with_id=user_to_share_with.id
).first()
# Berechtigungstyp validieren und konvertieren
try:
permission_type = PermissionType[data['permission']]
except (KeyError, ValueError):
return ErrorHandler.api_error(
"Ungültiger Berechtigungstyp. Erlaubte Werte sind: READ, EDIT, ADMIN.",
400
)
if existing_share:
# Wenn bereits geteilt, aktualisiere die Berechtigungen
existing_share.permission_type = permission_type
message = "Berechtigungen erfolgreich aktualisiert."
else:
# Wenn noch nicht geteilt, erstelle eine neue Freigabe
new_share = MindmapShare(
mindmap_id=mindmap_id,
shared_by_id=current_user.id,
shared_with_id=user_to_share_with.id,
permission_type=permission_type
)
db.session.add(new_share)
message = "Mindmap erfolgreich geteilt."
# Wenn die Mindmap bisher privat war, mache sie jetzt nicht mehr privat
if mindmap.is_private:
mindmap.is_private = False
db.session.commit()
return jsonify({
'success': True,
'message': message
})
@app.route('/api/mindmap/shares/<int:share_id>', methods=['PUT'])
@login_required
@handle_api_exception
def update_mindmap_share(share_id):
"""Aktualisiert die Berechtigungen für eine geteilte Mindmap."""
# Freigabe finden
share = MindmapShare.query.get_or_404(share_id)
# Prüfen, ob der Benutzer der Eigentümer der Mindmap ist
mindmap = UserMindmap.query.get(share.mindmap_id)
if not mindmap or mindmap.user_id != current_user.id:
return ErrorHandler.api_error("Sie haben keine Berechtigung, diese Freigabe zu ändern.", 403)
data = request.json
if not data or 'permission' not in data:
return ErrorHandler.api_error("Berechtigungstyp ist erforderlich.", 400)
# Berechtigungstyp validieren und konvertieren
try:
permission_type = PermissionType[data['permission']]
except (KeyError, ValueError):
return ErrorHandler.api_error(
"Ungültiger Berechtigungstyp. Erlaubte Werte sind: READ, EDIT, ADMIN.",
400
)
# Berechtigungen aktualisieren
share.permission_type = permission_type
db.session.commit()
return jsonify({
'success': True,
'message': "Berechtigungen erfolgreich aktualisiert."
})
@app.route('/api/mindmap/shares/<int:share_id>', methods=['DELETE'])
@login_required
@handle_api_exception
def revoke_mindmap_share(share_id):
"""Widerruft die Freigabe einer Mindmap für einen Benutzer."""
# Freigabe finden
share = MindmapShare.query.get_or_404(share_id)
# Prüfen, ob der Benutzer der Eigentümer der Mindmap ist
mindmap = UserMindmap.query.get(share.mindmap_id)
if not mindmap or mindmap.user_id != current_user.id:
return ErrorHandler.api_error("Sie haben keine Berechtigung, diese Freigabe zu widerrufen.", 403)
# Freigabe löschen
db.session.delete(share)
# Prüfen, ob dies die letzte Freigabe war und ggf. Mindmap wieder privat setzen
remaining_shares = MindmapShare.query.filter_by(mindmap_id=mindmap.id).count()
if remaining_shares == 0:
mindmap.is_private = True
db.session.commit()
return jsonify({
'success': True,
'message': "Freigabe erfolgreich widerrufen."
})
@app.route('/api/mindmaps/shared-with-me', methods=['GET'])
@login_required
@handle_api_exception
def get_shared_mindmaps():
"""Listet alle Mindmaps auf, die mit dem aktuellen Benutzer geteilt wurden."""
shares = MindmapShare.query.filter_by(shared_with_id=current_user.id).all()
result = []
for share in shares:
mindmap = UserMindmap.query.get(share.mindmap_id)
owner = User.query.get(mindmap.user_id)
if mindmap and owner:
# Zugriffsdatum aktualisieren
share.last_accessed = datetime.now(timezone.utc)
result.append({
'id': mindmap.id,
'name': mindmap.name,
'description': mindmap.description,
'owner': {
'id': owner.id,
'username': owner.username
},
'created_at': mindmap.created_at.isoformat(),
'last_modified': mindmap.last_modified.isoformat(),
'permission': share.permission_type.name
})
db.session.commit() # Speichere die aktualisierten Zugriffsdaten
return jsonify({
'success': True,
'mindmaps': result
})
# Hilfsfunktion zur Überprüfung der Berechtigungen
def check_mindmap_permission(mindmap_id, permission_type=None):
"""
Überprüft, ob der aktuelle Benutzer Zugriff auf eine Mindmap hat.
Args:
mindmap_id: ID der Mindmap
permission_type: Mindestberechtigung, die erforderlich ist (READ, EDIT, ADMIN)
Wenn None, wird nur der Zugriff überprüft.
Returns:
(bool, str): Tupel aus (hat_berechtigung, fehlermeldung)
"""
mindmap = UserMindmap.query.get(mindmap_id)
if not mindmap:
return False, "Mindmap nicht gefunden."
# Wenn der Benutzer der Eigentümer ist, hat er vollen Zugriff
if mindmap.user_id == current_user.id:
return True, ""
# Wenn die Mindmap privat ist und der Benutzer nicht der Eigentümer ist
if mindmap.is_private:
share = MindmapShare.query.filter_by(
mindmap_id=mindmap_id,
shared_with_id=current_user.id
).first()
if not share:
return False, "Sie haben keinen Zugriff auf diese Mindmap."
# Wenn eine bestimmte Berechtigungsstufe erforderlich ist
if permission_type:
required_level = PermissionType[permission_type].value
user_level = share.permission_type.value
if required_level not in [p.value for p in PermissionType]:
return False, f"Ungültiger Berechtigungstyp: {permission_type}"
# Berechtigungshierarchie prüfen
permission_hierarchy = {
PermissionType.READ.name: 1,
PermissionType.EDIT.name: 2,
PermissionType.ADMIN.name: 3
}
if permission_hierarchy[share.permission_type.name] < permission_hierarchy[permission_type]:
return False, f"Sie benötigen {permission_type}-Berechtigungen für diese Aktion."
return True, ""
# Flask starten
if __name__ == '__main__':
with app.app_context():
# Make sure tables exist
db.create_all()
socketio.run(app, debug=True, host='0.0.0.0')
def get_category_mindmap_data(category_name):
"""Generische Funktion zum Abrufen der Mindmap-Daten für eine Kategorie."""
try:
# Kategorie mit allen Unterkategorien in einer Abfrage laden
category = Category.query.filter_by(name=category_name).options(
joinedload(Category.children)
).first_or_404()
# Basis-Knoten erstellen
nodes = [{
'id': f'cat_{category.id}',
'name': category.name,
'description': category.description or '',
'color_code': category.color_code or '#9F7AEA',
'is_center': True,
'has_children': bool(category.children),
'icon': category.icon or 'fa-solid fa-circle'
}]
# Unterkategorien hinzufügen
for subcat in category.children:
nodes.append({
'id': f'cat_{subcat.id}',
'name': subcat.name,
'description': subcat.description or '',
'color_code': subcat.color_code or '#9F7AEA',
'category': category_name,
'has_children': bool(subcat.children),
'icon': subcat.icon or 'fa-solid fa-circle'
})
# Kanten erstellen (vereinheitlichte Schlüssel)
edges = [{
'source': f'cat_{category.id}',
'target': f'cat_{subcat.id}',
'strength': 0.8
} for subcat in category.children]
return jsonify({
'success': True,
'nodes': nodes,
'edges': edges
})
except Exception as e:
print(f"Fehler beim Abrufen der {category_name}-Mindmap: {str(e)}")
return jsonify({
'success': False,
'error': f'{category_name}-Mindmap konnte nicht geladen werden',
'details': str(e)
}), 500
@app.route('/api/mindmap/root')
def get_root_mindmap_data():
"""Liefert die Daten für die Root-Mindmap."""
try:
# Hauptkategorien mit Unterkategorien in einer Abfrage laden
categories = Category.query.filter_by(parent_id=None).options(
joinedload(Category.children)
).all()
# Basis-Knoten erstellen
nodes = [{
'id': 'root',
'name': 'Wissen',
'description': 'Zentrale Wissensbasis',
'color_code': '#4299E1',
'is_center': True,
'has_children': bool(categories),
'icon': 'fa-solid fa-circle'
}]
# Kategorien als Knoten hinzufügen
for category in categories:
nodes.append({
'id': f'cat_{category.id}',
'name': category.name,
'description': category.description or '',
'color_code': category.color_code or '#9F7AEA',
'category': category.name,
'has_children': bool(category.children),
'icon': category.icon or 'fa-solid fa-circle'
})
# Kanten erstellen (vereinheitlichte Schlüssel)
edges = [{
'source': 'root',
'target': f'cat_{category.id}',
'strength': 0.8
} for category in categories]
return jsonify({
'success': True,
'nodes': nodes,
'edges': edges
})
except Exception as e:
print(f"Fehler beim Abrufen der Root-Mindmap: {str(e)}")
return jsonify({
'success': False,
'error': 'Root-Mindmap konnte nicht geladen werden',
'details': str(e)
}), 500
# Spezifische Routen für Kategorien
@app.route('/api/mindmap/philosophy')
def get_philosophy_mindmap_data():
return get_category_mindmap_data('Philosophie')
@app.route('/api/mindmap/science')
def get_science_mindmap_data():
return get_category_mindmap_data('Wissenschaft')
@app.route('/api/mindmap/technology')
def get_technology_mindmap_data():
return get_category_mindmap_data('Technologie')
@app.route('/api/mindmap/arts')
def get_arts_mindmap_data():
return get_category_mindmap_data('Künste')
# Generische Route für spezifische Knoten
@app.route('/api/mindmap/<node_id>')
def get_mindmap_data(node_id):
"""Liefert die Daten für einen spezifischen Mindmap-Knoten."""
try:
# Prüfen, ob es sich um eine spezielle Route handelt
if node_id in ['root', 'philosophy', 'science', 'technology', 'arts']:
return jsonify({
'success': False,
'error': 'Ungültige Knoten-ID',
'details': 'Diese ID ist für spezielle Routen reserviert'
}), 400
# Knoten mit Unterknoten in einer Abfrage laden
node = MindMapNode.query.options(
joinedload(MindMapNode.children)
).get_or_404(node_id)
# Basis-Knoten erstellen
nodes = [{
'id': str(node.id),
'name': node.name,
'description': node.description or '',
'color_code': node.color_code or '#9F7AEA',
'is_center': True,
'has_children': bool(node.children),
'icon': node.icon or 'fa-solid fa-circle'
}]
# Unterknoten hinzufügen
for child in node.children:
nodes.append({
'id': str(child.id),
'name': child.name,
'description': child.description or '',
'color_code': child.color_code or '#9F7AEA',
'category': node.name,
'has_children': bool(child.children),
'icon': child.icon or 'fa-solid fa-circle'
})
# Kanten erstellen (vereinheitlichte Schlüssel)
edges = [{
'source': str(node.id),
'target': str(child.id),
'strength': 0.8
} for child in node.children]
return jsonify({
'success': True,
'nodes': nodes,
'edges': edges
})
except Exception as e:
print(f"Fehler beim Abrufen der Mindmap-Daten für Knoten {node_id}: {str(e)}")
return jsonify({
'success': False,
'error': 'Mindmap-Daten konnten nicht geladen werden',
'details': str(e)
}), 500