diff --git a/app.py b/app.py index 52c6a8c..9878a95 100644 --- a/app.py +++ b/app.py @@ -385,6 +385,485 @@ def mindmap(): 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}") +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +from datetime import datetime, timedelta, timezone +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 flask_wtf import FlaskForm +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 +from openai import OpenAI +from dotenv import load_dotenv +from flask_socketio import SocketIO, emit +from flask_migrate import Migrate +import sqlalchemy + +# 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, ForumCategory, ForumPost +) + +# 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')) +app.config['WTF_CSRF_ENABLED'] = False + +# OpenAI API-Konfiguration +api_key = "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" +# api_key = os.environ.get("OPENAI_API_KEY") +# if not api_key: +# print("WARNUNG: Kein OPENAI_API_KEY in Umgebungsvariablen gefunden. KI-Funktionalität wird nicht verfügbar sein.") + +client = OpenAI(api_key=api_key) + +# 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) + +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-music"}, + {"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 create_forum_categories(): + """Erstellt Forum-Kategorien basierend auf Hauptknotenpunkten der Mindmap""" + # Hauptknotenpunkte abrufen (nur die, die keine Elternknoten haben) + main_nodes = MindMapNode.query.filter(~MindMapNode.id.in_( + db.session.query(node_relationship.c.child_id) + )).all() + + for node in main_nodes: + # Prüfen, ob eine Forum-Kategorie für diesen Knoten bereits existiert + existing_category = ForumCategory.query.filter_by(node_id=node.id).first() + if existing_category: + continue + + # Neue Kategorie erstellen + forum_category = ForumCategory( + node_id=node.id, + title=node.name, + description=node.description, + is_active=True + ) + db.session.add(forum_category) + + db.session.commit() + print("Forum-Kategorien wurden für alle Hauptknotenpunkte 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!") + + # Forum-Kategorien erstellen + create_forum_categories() + + 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 + +@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