Files
website/app.py

1195 lines
39 KiB
Python
Executable File

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from datetime import datetime, timedelta
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session
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
# Modelle importieren
from models import (
db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating,
RelationType, Category, UserMindmap, UserMindmapNode, MindmapNote,
node_thought_association, user_thought_bookmark
)
# 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
# OpenAI API-Konfiguration
client = OpenAI(api_key="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")
# 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
}
# Kontext-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'
# 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):
return User.query.get(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.utcnow()
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)
# Erstelle eine Standard-Mindmap für den neuen Benutzer
default_mindmap = UserMindmap(
name='Meine Mindmap',
description='Meine persönliche Wissenslandschaft',
user=user
)
db.session.add(default_mindmap)
db.session.commit()
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 öffentliche Mindmap an."""
# Sicherstellen, dass wir Kategorien haben
with app.app_context():
if Category.query.count() == 0:
create_default_categories()
# Hole alle Kategorien der obersten Ebene
categories = Category.query.filter_by(parent_id=None).all()
return render_template('mindmap.html', categories=categories)
# Route for user profile
@app.route('/profile')
@login_required
def profile():
# Lade Benutzer-Mindmaps
user_mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all()
# Lade Statistiken
thought_count = Thought.query.filter_by(user_id=current_user.id).count()
bookmark_count = db.session.query(func.count()).select_from(user_thought_bookmark).filter(
user_thought_bookmark.c.user_id == current_user.id
).scalar()
return render_template('profile.html',
user_mindmaps=user_mindmaps,
thought_count=thought_count,
bookmark_count=bookmark_count)
# Route für Benutzereinstellungen
@app.route('/settings', methods=['GET', 'POST'])
@login_required
def settings():
if request.method == 'POST':
action = request.form.get('action')
if action == 'update_profile':
current_user.bio = request.form.get('bio')
# Update avatar if provided
avatar_url = request.form.get('avatar_url')
if avatar_url:
current_user.avatar = avatar_url
db.session.commit()
flash('Profil erfolgreich aktualisiert!', 'success')
elif action == 'update_password':
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):
flash('Aktuelles Passwort ist nicht korrekt', 'error')
elif new_password != confirm_password:
flash('Neue Passwörter stimmen nicht überein', 'error')
else:
current_user.set_password(new_password)
db.session.commit()
flash('Passwort erfolgreich aktualisiert!', 'success')
return redirect(url_for('settings'))
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')
# 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 Mindmap-Daten
@app.route('/api/mindmap/public')
def get_public_mindmap():
"""Liefert die öffentliche Mindmap-Struktur."""
# Hole alle Kategorien der obersten Ebene
root_categories = Category.query.filter_by(parent_id=None).all()
# Baue Baumstruktur auf
result = []
for category in root_categories:
result.append(build_category_tree(category))
return jsonify(result)
def build_category_tree(category):
"""Rekursive Funktion zum Aufbau der Kategoriestruktur."""
nodes = []
# Hole alle Knoten in dieser Kategorie
for node in category.nodes:
if node.is_public:
nodes.append({
'id': node.id,
'name': node.name,
'description': node.description,
'color_code': node.color_code,
'thought_count': len(node.thoughts)
})
# Rekursiv durch Unterkaterorien
children = []
for child in category.children:
children.append(build_category_tree(child))
return {
'id': category.id,
'name': category.name,
'description': category.description,
'color_code': category.color_code,
'icon': category.icon,
'nodes': nodes,
'children': children
}
@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/<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')
x_pos = data.get('x', 0)
y_pos = data.get('y', 0)
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)
db.session.commit()
return jsonify({
'success': True,
'node_id': node_id,
'x': x_pos,
'y': y_pos
})
@app.route('/api/mindmap/<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/<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.utcnow()
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():
"""API-Endpunkt zur Bereitstellung der Mindmap-Daten in hierarchischer Form."""
# Alle root-Nodes (ohne parent) abrufen
root_nodes = MindMapNode.query.filter_by(parent_id=None).all()
if not root_nodes:
# Wenn keine Nodes existieren, rufen wir initialize_database direkt auf
# anstatt create_sample_mindmap zu verwenden
with app.app_context():
initialize_database()
root_nodes = MindMapNode.query.filter_by(parent_id=None).all()
# Ergebnisse in hierarchischer Struktur zurückgeben
result = []
for node in root_nodes:
node_data = build_node_tree(node)
result.append(node_data)
return jsonify({"nodes": result})
def build_node_tree(node):
"""Erzeugt eine hierarchische Darstellung eines Knotens inkl. seiner Kindknoten."""
# Gedankenzähler abrufen von der many-to-many Beziehung
thought_count = len(node.thoughts)
# Daten für aktuellen Knoten
node_data = {
"id": node.id,
"name": node.name,
"description": f"Knoten mit {thought_count} Gedanken",
"thought_count": thought_count,
"children": []
}
# Rekursiv Kinder hinzufügen
child_nodes = MindMapNode.query.filter_by(parent_id=node.id).all()
for child_node in child_nodes:
child_data = build_node_tree(child_node)
node_data["children"].append(child_data)
return node_data
@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.utcnow()
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():
"""Liefert alle verfügbaren Kategorien."""
categories = Category.query.all()
return jsonify([{
'id': category.id,
'name': category.name,
'description': category.description,
'color_code': category.color_code,
'icon': category.icon,
'parent_id': category.parent_id
} for category in categories])
@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):
return render_template('errors/404.html'), 404
@app.errorhandler(403)
def forbidden(e):
return render_template('errors/403.html'), 403
@app.errorhandler(500)
def internal_server_error(e):
return render_template('errors/500.html'), 500
@app.errorhandler(429)
def too_many_requests(e):
return render_template('errors/429.html'), 429
# OpenAI-Integration für KI-Assistenz
@app.route('/api/assistant', methods=['POST'])
def chat_with_assistant():
"""Chatbot-API mit OpenAI Integration."""
data = request.json
# Prüfen, ob wir ein einzelnes Prompt oder ein messages-Array haben
if 'messages' in data:
messages = data.get('messages', [])
if not messages:
return jsonify({
'error': 'Keine Nachrichten vorhanden.'
}), 400
# Extrahiere Systemnachricht falls vorhanden, sonst Standard-Systemnachricht
system_message = next((msg['content'] for msg in messages if msg['role'] == 'system'),
"Du bist ein hilfreicher Assistent, der Menschen dabei hilft, "
"Wissen zu organisieren und zu verknüpfen. Liefere informative, "
"sachliche und gut strukturierte Antworten.")
# Formatiere Nachrichten für OpenAI API
api_messages = [{"role": "system", "content": system_message}]
# Füge Benutzer- und Assistenten-Nachrichten hinzu
for msg in messages:
if msg['role'] in ['user', 'assistant']:
api_messages.append({"role": msg['role'], "content": msg['content']})
else:
# Alte Implementierung für direktes Prompt
prompt = data.get('prompt', '')
context = data.get('context', '')
if not prompt:
return jsonify({
'error': 'Prompt darf nicht leer sein.'
}), 400
# Zusammenfassen mehrerer Gedanken oder Analyse anfordern
system_message = (
"Du bist ein hilfreicher Assistent, der Menschen dabei hilft, "
"Wissen zu organisieren und zu verknüpfen. Liefere informative, "
"sachliche und gut strukturierte Antworten."
)
if context:
system_message += f"\n\nKontext: {context}"
api_messages = [
{"role": "system", "content": system_message},
{"role": "user", "content": prompt}
]
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=api_messages,
max_tokens=300,
temperature=0.7
)
answer = response.choices[0].message.content
# Für das neue Format erwarten wir response statt answer
return jsonify({
'response': answer
})
except Exception as e:
return jsonify({
'error': f'Fehler bei der OpenAI-Anfrage: {str(e)}'
}), 500
# App-Kontext-Funktion für Initialisierung der Datenbank
def create_default_categories():
"""Erstellt die Standard-Kategorien und wissenschaftlichen Bereiche."""
categories = [
{
'name': 'Naturwissenschaften',
'description': 'Empirische Untersuchung und Erklärung natürlicher Phänomene',
'color_code': '#4CAF50',
'icon': 'flask',
'children': [
{
'name': 'Physik',
'description': 'Studium der Materie, Energie und deren Wechselwirkungen',
'color_code': '#81C784',
'icon': 'atom'
},
{
'name': 'Biologie',
'description': 'Wissenschaft des Lebens und lebender Organismen',
'color_code': '#66BB6A',
'icon': 'leaf'
},
{
'name': 'Chemie',
'description': 'Wissenschaft der Materie, ihrer Eigenschaften und Reaktionen',
'color_code': '#A5D6A7',
'icon': 'vial'
}
]
},
{
'name': 'Sozialwissenschaften',
'description': 'Untersuchung von Gesellschaft und menschlichem Verhalten',
'color_code': '#2196F3',
'icon': 'users',
'children': [
{
'name': 'Psychologie',
'description': 'Wissenschaftliches Studium des Geistes und Verhaltens',
'color_code': '#64B5F6',
'icon': 'brain'
},
{
'name': 'Soziologie',
'description': 'Studium sozialer Beziehungen und Institutionen',
'color_code': '#42A5F5',
'icon': 'network-wired'
}
]
},
{
'name': 'Geisteswissenschaften',
'description': 'Studium menschlicher Kultur und Kreativität',
'color_code': '#9C27B0',
'icon': 'book',
'children': [
{
'name': 'Philosophie',
'description': 'Untersuchung grundlegender Fragen über Existenz, Wissen und Ethik',
'color_code': '#BA68C8',
'icon': 'lightbulb'
},
{
'name': 'Geschichte',
'description': 'Studium der Vergangenheit und ihres Einflusses auf die Gegenwart',
'color_code': '#AB47BC',
'icon': 'landmark'
},
{
'name': 'Literatur',
'description': 'Studium literarischer Werke und ihrer Bedeutung',
'color_code': '#CE93D8',
'icon': 'feather'
}
]
},
{
'name': 'Technologie',
'description': 'Anwendung wissenschaftlicher Erkenntnisse für praktische Zwecke',
'color_code': '#FF9800',
'icon': 'microchip',
'children': [
{
'name': 'Informatik',
'description': 'Studium von Computern und Berechnungssystemen',
'color_code': '#FFB74D',
'icon': 'laptop-code'
},
{
'name': 'Künstliche Intelligenz',
'description': 'Entwicklung intelligenter Maschinen und Software',
'color_code': '#FFA726',
'icon': 'robot'
}
]
}
]
# Kategorien in die Datenbank einfügen
for category_data in categories:
children_data = category_data.pop('children', [])
category = Category(**category_data)
db.session.add(category)
db.session.flush() # Um die ID zu generieren
# Unterkategorien hinzufügen
for child_data in children_data:
child = Category(**child_data, parent_id=category.id)
db.session.add(child)
db.session.commit()
print("Standard-Kategorien wurden erstellt!")
def initialize_database():
"""Initialisiert die Datenbank, falls sie noch nicht existiert."""
db.create_all()
# Überprüfe, ob bereits Kategorien existieren
if Category.query.count() == 0:
create_default_categories()
# Führe die Datenbankinitialisierung beim Starten der App aus
with app.app_context():
initialize_database()
@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():
"""Stellt einen Fallback für alte Netzwerk-Hintergrund-Anfragen bereit."""
return redirect(url_for('static', filename='img/backgrounds/network-bg.jpg'))
@app.route('/static/css/src/cybernetwork-bg.css')
def serve_cybernetwork_css():
"""Stellt das CSS für den cybertechnischen Netzwerk-Hintergrund bereit."""
return app.send_static_file('css/src/cybernetwork-bg.css')
@app.route('/static/js/modules/cyber-network.js')
def serve_cybernetwork_js():
"""Stellt das JavaScript-Modul für den cybertechnischen Netzwerk-Hintergrund bereit."""
return app.send_static_file('js/modules/cyber-network.js')
@app.route('/static/js/modules/cyber-network-init.js')
def serve_cybernetwork_init_js():
"""Stellt das Initialisierungs-JavaScript für den cybertechnischen Netzwerk-Hintergrund bereit."""
return app.send_static_file('js/modules/cyber-network-init.js')
@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
# Flask starten
if __name__ == '__main__':
with app.app_context():
# Make sure tables exist
db.create_all()
app.run(host="0.0.0.0", debug=True)