"Refactor app. Implementing new feature for user authentication (feat): Add API endpoint enhancement)"
This commit is contained in:
901
app.py
901
app.py
@@ -2472,710 +2472,217 @@ def admin_update_database():
|
||||
return render_template('admin/update_database.html', message=message, success=success)
|
||||
|
||||
@app.route('/api/mindmap/<node_id>')
|
||||
def get_mindmap_node(node_id):
|
||||
"""Liefert die Mindmap-Daten für einen bestimmten Knoten und seine Subthemen."""
|
||||
def get_mindmap_data(node_id):
|
||||
"""
|
||||
Stellt Mindmap-Daten für das Frontend bereit.
|
||||
Liefert für 'root' die Hauptebene und für andere Node-IDs die entsprechenden Unterknoten.
|
||||
"""
|
||||
app.logger.info(f"Mindmap-Daten werden für Node '{node_id}' angefordert.")
|
||||
|
||||
try:
|
||||
# Erkennen besonderer Knotennamen
|
||||
if node_id == 'root':
|
||||
# Hauptknoten (Wissen) abrufen - dieser sollte durch die Datenbank-Initialisierung bereits existieren
|
||||
wissen_node = MindMapNode.query.filter_by(name="Wissen").first()
|
||||
if not wissen_node:
|
||||
# Fallback-Erstellung des Wissen-Knotens, falls er wider Erwarten nicht existiert
|
||||
wissen_node = MindMapNode(
|
||||
name="Wissen",
|
||||
description="Zentrale Wissensbasis",
|
||||
color_code="#4299E1",
|
||||
is_public=True
|
||||
)
|
||||
db.session.add(wissen_node)
|
||||
db.session.commit()
|
||||
app.logger.warning("'Wissen'-Knoten musste während einer API-Anfrage erstellt werden. "
|
||||
"Dies sollte normalerweise nicht vorkommen, da er bei der Initialisierung erstellt werden sollte.")
|
||||
# Hauptebene der Mindmap
|
||||
nodes = [
|
||||
{
|
||||
"id": "center",
|
||||
"name": "Wissenskarte",
|
||||
"description": "Zentrale Wissenskarte mit allen Hauptthemen",
|
||||
"is_center": True,
|
||||
"color_code": "#f5f5f5",
|
||||
"has_children": True
|
||||
},
|
||||
{
|
||||
"id": "philosophy",
|
||||
"name": "Philosophie",
|
||||
"description": "Die Lehre vom Denken und der Erkenntnis",
|
||||
"category": "Philosophie",
|
||||
"has_children": True,
|
||||
"color_code": "#9F7AEA"
|
||||
},
|
||||
{
|
||||
"id": "science",
|
||||
"name": "Wissenschaft",
|
||||
"description": "Systematische Erforschung der Natur und Gesellschaft",
|
||||
"category": "Wissenschaft",
|
||||
"has_children": True,
|
||||
"color_code": "#f4b400"
|
||||
},
|
||||
{
|
||||
"id": "technology",
|
||||
"name": "Technologie",
|
||||
"description": "Anwendung wissenschaftlicher Erkenntnisse",
|
||||
"category": "Technologie",
|
||||
"has_children": True,
|
||||
"color_code": "#0d47a1"
|
||||
},
|
||||
{
|
||||
"id": "arts",
|
||||
"name": "Künste",
|
||||
"description": "Kreativer Ausdruck und künstlerische Gestaltung",
|
||||
"category": "Künste",
|
||||
"has_children": True,
|
||||
"color_code": "#c2185b"
|
||||
}
|
||||
]
|
||||
|
||||
edges = [
|
||||
{"source_id": "center", "target_id": "philosophy", "strength": 0.9},
|
||||
{"source_id": "center", "target_id": "science", "strength": 0.9},
|
||||
{"source_id": "center", "target_id": "technology", "strength": 0.9},
|
||||
{"source_id": "center", "target_id": "arts", "strength": 0.9}
|
||||
]
|
||||
|
||||
elif node_id == 'philosophy':
|
||||
nodes = [
|
||||
{
|
||||
"id": "epistemology",
|
||||
"name": "Erkenntnistheorie",
|
||||
"description": "Untersuchung der Natur und Grenzen menschlicher Erkenntnis",
|
||||
"category": "Philosophie",
|
||||
"has_children": True,
|
||||
"color_code": "#9F7AEA"
|
||||
},
|
||||
{
|
||||
"id": "ethics",
|
||||
"name": "Ethik",
|
||||
"description": "Lehre vom moralisch richtigen Handeln",
|
||||
"category": "Philosophie",
|
||||
"has_children": True,
|
||||
"color_code": "#9F7AEA"
|
||||
},
|
||||
{
|
||||
"id": "metaphysics",
|
||||
"name": "Metaphysik",
|
||||
"description": "Grundfragen des Seins und der Wirklichkeit",
|
||||
"category": "Philosophie",
|
||||
"has_children": True,
|
||||
"color_code": "#9F7AEA"
|
||||
}
|
||||
]
|
||||
|
||||
edges = [
|
||||
{"source_id": "philosophy", "target_id": "epistemology", "strength": 0.8},
|
||||
{"source_id": "philosophy", "target_id": "ethics", "strength": 0.8},
|
||||
{"source_id": "philosophy", "target_id": "metaphysics", "strength": 0.8}
|
||||
]
|
||||
|
||||
elif node_id == 'science':
|
||||
nodes = [
|
||||
{
|
||||
"id": "physics",
|
||||
"name": "Physik",
|
||||
"description": "Lehre von der Materie und ihren Wechselwirkungen",
|
||||
"category": "Wissenschaft",
|
||||
"has_children": True,
|
||||
"color_code": "#f4b400"
|
||||
},
|
||||
{
|
||||
"id": "biology",
|
||||
"name": "Biologie",
|
||||
"description": "Lehre von den Lebewesen und ihren Lebensprozessen",
|
||||
"category": "Wissenschaft",
|
||||
"has_children": True,
|
||||
"color_code": "#f4b400"
|
||||
},
|
||||
{
|
||||
"id": "chemistry",
|
||||
"name": "Chemie",
|
||||
"description": "Wissenschaft von den Stoffen und ihren Reaktionen",
|
||||
"category": "Wissenschaft",
|
||||
"has_children": True,
|
||||
"color_code": "#f4b400"
|
||||
}
|
||||
]
|
||||
|
||||
edges = [
|
||||
{"source_id": "science", "target_id": "physics", "strength": 0.8},
|
||||
{"source_id": "science", "target_id": "biology", "strength": 0.8},
|
||||
{"source_id": "science", "target_id": "chemistry", "strength": 0.8}
|
||||
]
|
||||
|
||||
elif node_id == 'technology':
|
||||
nodes = [
|
||||
{
|
||||
"id": "ai",
|
||||
"name": "Künstliche Intelligenz",
|
||||
"description": "Maschinelles Lernen und intelligente Systeme",
|
||||
"category": "Technologie",
|
||||
"has_children": True,
|
||||
"color_code": "#0d47a1"
|
||||
},
|
||||
{
|
||||
"id": "robotics",
|
||||
"name": "Robotik",
|
||||
"description": "Entwicklung und Steuerung von Robotern",
|
||||
"category": "Technologie",
|
||||
"has_children": True,
|
||||
"color_code": "#0d47a1"
|
||||
},
|
||||
{
|
||||
"id": "quantum_computing",
|
||||
"name": "Quantencomputing",
|
||||
"description": "Computer basierend auf Quantenmechanik",
|
||||
"category": "Technologie",
|
||||
"has_children": True,
|
||||
"color_code": "#0d47a1"
|
||||
}
|
||||
]
|
||||
|
||||
edges = [
|
||||
{"source_id": "technology", "target_id": "ai", "strength": 0.8},
|
||||
{"source_id": "technology", "target_id": "robotics", "strength": 0.8},
|
||||
{"source_id": "technology", "target_id": "quantum_computing", "strength": 0.8}
|
||||
]
|
||||
|
||||
elif node_id == 'arts':
|
||||
nodes = [
|
||||
{
|
||||
"id": "visual_arts",
|
||||
"name": "Bildende Kunst",
|
||||
"description": "Malerei, Bildhauerei und andere visuelle Kunstformen",
|
||||
"category": "Künste",
|
||||
"has_children": True,
|
||||
"color_code": "#c2185b"
|
||||
},
|
||||
{
|
||||
"id": "music",
|
||||
"name": "Musik",
|
||||
"description": "Tonkunst und musikalische Komposition",
|
||||
"category": "Künste",
|
||||
"has_children": True,
|
||||
"color_code": "#c2185b"
|
||||
},
|
||||
{
|
||||
"id": "literature",
|
||||
"name": "Literatur",
|
||||
"description": "Schriftliche Kunstwerke und Poesie",
|
||||
"category": "Künste",
|
||||
"has_children": True,
|
||||
"color_code": "#c2185b"
|
||||
}
|
||||
]
|
||||
|
||||
edges = [
|
||||
{"source_id": "arts", "target_id": "visual_arts", "strength": 0.8},
|
||||
{"source_id": "arts", "target_id": "music", "strength": 0.8},
|
||||
{"source_id": "arts", "target_id": "literature", "strength": 0.8}
|
||||
]
|
||||
|
||||
# Alle direkten Kinder des Wissen-Knotens holen
|
||||
nodes = wissen_node.children.all()
|
||||
parent_node = wissen_node
|
||||
elif node_id.isdigit():
|
||||
# Bestimmten Knoten und seine Kinder abrufen
|
||||
parent_node = MindMapNode.query.get_or_404(int(node_id))
|
||||
nodes = parent_node.children.all()
|
||||
else:
|
||||
# Versuche, einen Knoten mit diesem Namen zu finden
|
||||
parent_node = MindMapNode.query.filter_by(name=node_id.capitalize()).first()
|
||||
# Für jede andere Node-ID geben wir eine leere Struktur zurück
|
||||
nodes = []
|
||||
edges = []
|
||||
|
||||
if not parent_node:
|
||||
# Versuche, eine Kategorie mit diesem Namen zu finden
|
||||
category = Category.query.filter(func.lower(Category.name) == func.lower(node_id)).first()
|
||||
if category:
|
||||
# Finde oder erstelle einen Knoten für diese Kategorie
|
||||
parent_node = MindMapNode.query.filter_by(category_id=category.id).first()
|
||||
if not parent_node:
|
||||
parent_node = MindMapNode(
|
||||
name=category.name,
|
||||
description=category.description,
|
||||
color_code=category.color_code,
|
||||
category_id=category.id,
|
||||
is_public=True
|
||||
)
|
||||
db.session.add(parent_node)
|
||||
db.session.commit()
|
||||
|
||||
if not parent_node:
|
||||
# Fallback zum Wissen-Knoten
|
||||
parent_node = MindMapNode.query.filter_by(name="Wissen").first()
|
||||
if not parent_node:
|
||||
return jsonify({'error': 'Knoten nicht gefunden'}), 404
|
||||
|
||||
nodes = parent_node.children.all()
|
||||
|
||||
# Ergebnisdaten vorbereiten
|
||||
nodes_data = []
|
||||
edges_data = []
|
||||
|
||||
# Hauptknoten hinzufügen
|
||||
nodes_data.append({
|
||||
'id': parent_node.id,
|
||||
'name': parent_node.name,
|
||||
'description': parent_node.description or '',
|
||||
'color_code': parent_node.color_code or '#4299E1',
|
||||
'is_center': True,
|
||||
'has_children': len(nodes) > 0
|
||||
})
|
||||
|
||||
# Kinder 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',
|
||||
'is_center': False,
|
||||
'has_children': len(node.children.all()) > 0
|
||||
})
|
||||
|
||||
# Verbindung zum Elternknoten hinzufügen
|
||||
edges_data.append({
|
||||
'source_id': parent_node.id,
|
||||
'target_id': node.id,
|
||||
'strength': 0.8
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'nodes': nodes_data,
|
||||
'edges': edges_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Abrufen der Mindmap-Daten: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Mindmap-Daten konnten nicht geladen werden'
|
||||
}), 500
|
||||
|
||||
# API-Endpunkte für Notizen und Layout-Speicherung
|
||||
|
||||
@app.route('/api/mindmap/node/<int:node_id>/notes', methods=['GET'])
|
||||
@login_required
|
||||
def get_node_notes(node_id):
|
||||
"""Ruft die Notizen für einen Mindmap-Knoten ab"""
|
||||
try:
|
||||
# Prüfen, ob der Knoten existiert
|
||||
node = MindMapNode.query.get_or_404(node_id)
|
||||
|
||||
# Prüfen, ob der Knoten in einer Mindmap des Benutzers ist
|
||||
user_node = UserMindmapNode.query.filter_by(node_id=node_id).join(
|
||||
UserMindmap, UserMindmapNode.user_mindmap_id == UserMindmap.id
|
||||
).filter(UserMindmap.user_id == current_user.id).first()
|
||||
|
||||
if not user_node and not current_user.is_admin:
|
||||
return jsonify({'success': False, 'message': 'Keine Berechtigung'}), 403
|
||||
|
||||
# Prüfen, ob eine Notiz für diesen Knoten existiert
|
||||
note = MindmapNote.query.filter_by(
|
||||
user_id=current_user.id,
|
||||
node_id=node_id
|
||||
).first()
|
||||
|
||||
if note:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'notes': note.content,
|
||||
'color_code': note.color_code
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'notes': '',
|
||||
'color_code': '#FFF59D' # Standard-Gelb
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler in get_node_notes: {str(e)}")
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/mindmap/node/<int:node_id>/notes', methods=['POST'])
|
||||
@login_required
|
||||
def update_node_notes(node_id):
|
||||
"""Aktualisiert die Notizen für einen Mindmap-Knoten"""
|
||||
try:
|
||||
# Prüfen, ob der Knoten existiert
|
||||
node = MindMapNode.query.get_or_404(node_id)
|
||||
|
||||
# Prüfen, ob der Knoten in einer Mindmap des Benutzers ist
|
||||
user_node = UserMindmapNode.query.filter_by(node_id=node_id).join(
|
||||
UserMindmap, UserMindmapNode.user_mindmap_id == UserMindmap.id
|
||||
).filter(UserMindmap.user_id == current_user.id).first()
|
||||
|
||||
if not user_node and not current_user.is_admin:
|
||||
return jsonify({'success': False, 'message': 'Keine Berechtigung'}), 403
|
||||
|
||||
# Daten aus dem Request abrufen
|
||||
data = request.json
|
||||
notes_content = data.get('notes', '')
|
||||
color_code = data.get('color_code', '#FFF59D') # Standard-Gelb
|
||||
|
||||
# Prüfen, ob bereits eine Notiz für diesen Knoten existiert
|
||||
note = MindmapNote.query.filter_by(
|
||||
user_id=current_user.id,
|
||||
node_id=node_id
|
||||
).first()
|
||||
|
||||
if note:
|
||||
# Vorhandene Notiz aktualisieren
|
||||
note.content = notes_content
|
||||
note.color_code = color_code
|
||||
else:
|
||||
# Neue Notiz erstellen - hier brauchen wir die Mindmap-ID
|
||||
mindmap_id = user_node.user_mindmap_id
|
||||
note = MindmapNote(
|
||||
user_id=current_user.id,
|
||||
mindmap_id=mindmap_id,
|
||||
node_id=node_id,
|
||||
content=notes_content,
|
||||
color_code=color_code
|
||||
)
|
||||
db.session.add(note)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Notizen erfolgreich gespeichert'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Fehler in update_node_notes: {str(e)}")
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/mindmap/<int:mindmap_id>/layout', methods=['POST'])
|
||||
@login_required
|
||||
def save_mindmap_layout(mindmap_id):
|
||||
"""Speichert das Layout (Positionen der Knoten) einer Mindmap"""
|
||||
try:
|
||||
# Prüfe, ob die Mindmap dem Benutzer gehört
|
||||
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
||||
if mindmap.user_id != current_user.id and not current_user.is_admin:
|
||||
return jsonify({'success': False, 'message': 'Keine Berechtigung'}), 403
|
||||
|
||||
# Daten aus dem Request abrufen
|
||||
data = request.json
|
||||
nodes_data = data.get('nodes', [])
|
||||
|
||||
# Positionen aller Knoten aktualisieren
|
||||
for node_data in nodes_data:
|
||||
node_id = node_data.get('id')
|
||||
x_pos = node_data.get('x')
|
||||
y_pos = node_data.get('y')
|
||||
|
||||
if node_id and x_pos is not None and y_pos is not None:
|
||||
# UserMindmapNode-Eintrag aktualisieren
|
||||
user_node = UserMindmapNode.query.filter_by(
|
||||
user_mindmap_id=mindmap_id,
|
||||
node_id=node_id
|
||||
).first()
|
||||
|
||||
if user_node:
|
||||
user_node.x_position = x_pos
|
||||
user_node.y_position = y_pos
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Layout erfolgreich gespeichert'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Fehler in save_mindmap_layout: {str(e)}")
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/public-mindmap', methods=['GET'])
|
||||
def get_public_mindmap_nodes():
|
||||
"""Liefert die öffentliche Mindmap für die Knotenübernahme"""
|
||||
try:
|
||||
# Alle öffentlichen Knoten abrufen
|
||||
public_nodes = MindMapNode.query.filter_by(is_public=True).all()
|
||||
|
||||
# Knoten formatieren
|
||||
nodes = []
|
||||
for node in public_nodes:
|
||||
nodes.append({
|
||||
'id': node.id,
|
||||
'name': node.name,
|
||||
'description': node.description or '',
|
||||
'color_code': node.color_code or '#9F7AEA'
|
||||
})
|
||||
|
||||
# Alle Beziehungen zwischen Knoten abrufen
|
||||
edges = []
|
||||
for node in public_nodes:
|
||||
for child in node.children:
|
||||
if child.is_public:
|
||||
edges.append({
|
||||
'source': node.id,
|
||||
'target': child.id
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'nodes': nodes,
|
||||
'edges': edges
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler in get_public_mindmap_nodes: {str(e)}")
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
# Suchfunktion für Mindmap-Knoten
|
||||
@app.route('/api/search/mindmap', methods=['GET'])
|
||||
@handle_api_exception
|
||||
def search_mindmap_nodes():
|
||||
"""
|
||||
Durchsucht Mindmap-Knoten nach einem Suchbegriff.
|
||||
|
||||
Query-Parameter:
|
||||
- q: Suchbegriff
|
||||
- user_only: Wenn "true", werden nur Knoten aus den Mindmaps des Benutzers durchsucht
|
||||
- include_public: Wenn "true", werden auch öffentliche Knoten einbezogen (Standard: true)
|
||||
"""
|
||||
try:
|
||||
# Parameter auslesen
|
||||
query = request.args.get('q', '').strip()
|
||||
user_only = request.args.get('user_only', 'false').lower() == 'true'
|
||||
include_public = request.args.get('include_public', 'true').lower() == 'true'
|
||||
|
||||
if not query:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Kein Suchbegriff angegeben',
|
||||
'results': []
|
||||
}), 400
|
||||
|
||||
# Basisfunktion für die Suche
|
||||
results = []
|
||||
|
||||
# Erstelle Such-Pattern (beide Suchbegriffe werden verwendet)
|
||||
search_pattern = f"%{query}%"
|
||||
|
||||
# 1. Knoten aus Benutzer-Mindmaps suchen, falls Benutzer angemeldet ist
|
||||
if current_user.is_authenticated:
|
||||
# Suche in allen Knoten, die in den Mindmaps des Benutzers sind
|
||||
user_nodes_query = db.session.query(
|
||||
MindMapNode, UserMindmapNode
|
||||
).join(
|
||||
UserMindmapNode, UserMindmapNode.node_id == MindMapNode.id
|
||||
).join(
|
||||
UserMindmap, UserMindmapNode.user_mindmap_id == UserMindmap.id
|
||||
).filter(
|
||||
UserMindmap.user_id == current_user.id,
|
||||
db.or_(
|
||||
MindMapNode.name.ilike(search_pattern),
|
||||
MindMapNode.description.ilike(search_pattern)
|
||||
)
|
||||
).all()
|
||||
|
||||
# Formatiere die Ergebnisse
|
||||
for node, user_node in user_nodes_query:
|
||||
# Hole den Mindmap-Namen für diesen Knoten
|
||||
mindmap = UserMindmap.query.get(user_node.user_mindmap_id)
|
||||
|
||||
results.append({
|
||||
'id': node.id,
|
||||
'name': node.name,
|
||||
'description': node.description or '',
|
||||
'color_code': node.color_code or '#9F7AEA',
|
||||
'source': 'user_mindmap',
|
||||
'mindmap_id': user_node.user_mindmap_id,
|
||||
'mindmap_name': mindmap.name if mindmap else 'Unbekannte Mindmap',
|
||||
'position': {
|
||||
'x': user_node.x_position,
|
||||
'y': user_node.y_position
|
||||
}
|
||||
})
|
||||
|
||||
# 2. Öffentliche Knoten suchen, falls gewünscht und nicht nur Benutzer-Mindmaps
|
||||
if include_public and not user_only:
|
||||
public_nodes_query = MindMapNode.query.filter(
|
||||
MindMapNode.is_public == True,
|
||||
db.or_(
|
||||
MindMapNode.name.ilike(search_pattern),
|
||||
MindMapNode.description.ilike(search_pattern)
|
||||
)
|
||||
).all()
|
||||
|
||||
# Prüfen, ob die öffentlichen Knoten bereits in den Ergebnissen sind
|
||||
existing_node_ids = [node['id'] for node in results]
|
||||
|
||||
for node in public_nodes_query:
|
||||
if node.id not in existing_node_ids:
|
||||
results.append({
|
||||
'id': node.id,
|
||||
'name': node.name,
|
||||
'description': node.description or '',
|
||||
'color_code': node.color_code or '#9F7AEA',
|
||||
'source': 'public',
|
||||
'mindmap_id': None,
|
||||
'mindmap_name': 'Öffentliche Mindmap'
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'{len(results)} Ergebnisse gefunden',
|
||||
'results': results
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler bei der Mindmap-Suche: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'Fehler bei der Suche: {str(e)}',
|
||||
'results': []
|
||||
}), 500
|
||||
|
||||
# Export/Import-Funktionen für Mindmaps
|
||||
@app.route('/api/mindmap/<int:mindmap_id>/export', methods=['GET'])
|
||||
@login_required
|
||||
@handle_api_exception
|
||||
def export_mindmap(mindmap_id):
|
||||
"""
|
||||
Exportiert eine Mindmap im angegebenen Format.
|
||||
|
||||
Query-Parameter:
|
||||
- format: Format der Exportdatei (json, xml, csv)
|
||||
"""
|
||||
try:
|
||||
# Sicherheitscheck: Nur eigene Mindmaps oder Mindmaps, auf die der Benutzer Zugriff hat
|
||||
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
||||
|
||||
# Prüfen, ob der Benutzer Zugriff auf diese Mindmap hat
|
||||
can_access = mindmap.user_id == current_user.id
|
||||
|
||||
if not can_access and mindmap.is_private:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Keine Berechtigung für den Zugriff auf diese Mindmap'
|
||||
}), 403
|
||||
|
||||
# Format aus Query-Parameter holen
|
||||
export_format = request.args.get('format', 'json')
|
||||
|
||||
# Alle Knoten und ihre Positionen in dieser Mindmap holen
|
||||
nodes_data = db.session.query(
|
||||
MindMapNode, UserMindmapNode
|
||||
).join(
|
||||
UserMindmapNode, UserMindmapNode.node_id == MindMapNode.id
|
||||
).filter(
|
||||
UserMindmapNode.user_mindmap_id == mindmap_id
|
||||
).all()
|
||||
|
||||
# Beziehungen zwischen Knoten holen
|
||||
relationships = []
|
||||
for node1, user_node1 in nodes_data:
|
||||
for node2, user_node2 in nodes_data:
|
||||
if node1.id != node2.id and node2 in node1.children:
|
||||
relationships.append({
|
||||
'source': node1.id,
|
||||
'target': node2.id
|
||||
})
|
||||
|
||||
# Exportdaten vorbereiten
|
||||
export_data = {
|
||||
'mindmap': {
|
||||
'id': mindmap.id,
|
||||
'name': mindmap.name,
|
||||
'description': mindmap.description,
|
||||
'created_at': mindmap.created_at.isoformat(),
|
||||
'last_modified': mindmap.last_modified.isoformat()
|
||||
},
|
||||
'nodes': [{
|
||||
'id': node.id,
|
||||
'name': node.name,
|
||||
'description': node.description or '',
|
||||
'color_code': node.color_code or '#9F7AEA',
|
||||
'x_position': user_node.x_position,
|
||||
'y_position': user_node.y_position,
|
||||
'scale': user_node.scale or 1.0
|
||||
} for node, user_node in nodes_data],
|
||||
'relationships': relationships
|
||||
# Antwort zusammenstellen
|
||||
response = {
|
||||
"nodes": nodes,
|
||||
"edges": edges
|
||||
}
|
||||
|
||||
# Exportieren im angeforderten Format
|
||||
if export_format == 'json':
|
||||
response = app.response_class(
|
||||
response=json.dumps(export_data, indent=2),
|
||||
status=200,
|
||||
mimetype='application/json'
|
||||
)
|
||||
response.headers["Content-Disposition"] = f"attachment; filename=mindmap_{mindmap_id}.json"
|
||||
return response
|
||||
|
||||
elif export_format == 'xml':
|
||||
import dicttoxml
|
||||
xml_data = dicttoxml.dicttoxml(export_data)
|
||||
response = app.response_class(
|
||||
response=xml_data,
|
||||
status=200,
|
||||
mimetype='application/xml'
|
||||
)
|
||||
response.headers["Content-Disposition"] = f"attachment; filename=mindmap_{mindmap_id}.xml"
|
||||
return response
|
||||
|
||||
elif export_format == 'csv':
|
||||
import io
|
||||
import csv
|
||||
|
||||
# CSV kann nicht die gesamte Struktur darstellen, daher nur die Knotenliste
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output)
|
||||
|
||||
# Schreibe Header
|
||||
writer.writerow(['id', 'name', 'description', 'color_code', 'x_position', 'y_position', 'scale'])
|
||||
|
||||
# Schreibe Knotendaten
|
||||
for node, user_node in nodes_data:
|
||||
writer.writerow([
|
||||
node.id,
|
||||
node.name,
|
||||
node.description or '',
|
||||
node.color_code or '#9F7AEA',
|
||||
user_node.x_position,
|
||||
user_node.y_position,
|
||||
user_node.scale or 1.0
|
||||
])
|
||||
|
||||
output.seek(0)
|
||||
response = app.response_class(
|
||||
response=output.getvalue(),
|
||||
status=200,
|
||||
mimetype='text/csv'
|
||||
)
|
||||
response.headers["Content-Disposition"] = f"attachment; filename=mindmap_{mindmap_id}_nodes.csv"
|
||||
return response
|
||||
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'Nicht unterstütztes Format: {export_format}'
|
||||
}), 400
|
||||
return jsonify(response)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Exportieren der Mindmap: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'Fehler beim Exportieren: {str(e)}'
|
||||
}), 500
|
||||
app.logger.error(f"Fehler beim Abrufen der Mindmap-Daten: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Abrufen der Mindmap-Daten"}), 500
|
||||
|
||||
@app.route('/api/mindmap/<int:mindmap_id>/import', methods=['POST'])
|
||||
@login_required
|
||||
@handle_api_exception
|
||||
def import_mindmap(mindmap_id):
|
||||
"""
|
||||
Importiert Daten in eine bestehende Mindmap.
|
||||
|
||||
Die Daten können in verschiedenen Formaten (JSON, XML, CSV) hochgeladen werden.
|
||||
"""
|
||||
try:
|
||||
# Sicherheitscheck: Nur eigene Mindmaps können bearbeitet werden
|
||||
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
||||
|
||||
if mindmap.user_id != current_user.id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Keine Berechtigung zum Bearbeiten dieser Mindmap'
|
||||
}), 403
|
||||
|
||||
# Prüfen, ob eine Datei hochgeladen wurde
|
||||
if 'file' not in request.files:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Keine Datei ausgewählt'
|
||||
}), 400
|
||||
|
||||
file = request.files['file']
|
||||
|
||||
if file.filename == '':
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Keine Datei ausgewählt'
|
||||
}), 400
|
||||
|
||||
# Format anhand der Dateiendung erkennen
|
||||
file_ext = file.filename.rsplit('.', 1)[1].lower() if '.' in file.filename else None
|
||||
|
||||
if file_ext not in ['json', 'xml', 'csv']:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'Nicht unterstütztes Dateiformat: {file_ext}'
|
||||
}), 400
|
||||
|
||||
# Datei einlesen
|
||||
import_data = None
|
||||
|
||||
if file_ext == 'json':
|
||||
import_data = json.loads(file.read().decode('utf-8'))
|
||||
elif file_ext == 'xml':
|
||||
import xml.etree.ElementTree as ET
|
||||
import xmltodict
|
||||
xml_data = file.read().decode('utf-8')
|
||||
import_data = xmltodict.parse(xml_data)
|
||||
elif file_ext == 'csv':
|
||||
import io
|
||||
import csv
|
||||
csv_data = file.read().decode('utf-8')
|
||||
reader = csv.DictReader(io.StringIO(csv_data))
|
||||
nodes = []
|
||||
for row in reader:
|
||||
nodes.append(row)
|
||||
import_data = {'nodes': nodes}
|
||||
|
||||
# Daten in die Mindmap importieren
|
||||
if 'nodes' in import_data:
|
||||
# Bestehende Knoten in der Mindmap für Referenz
|
||||
existing_nodes = db.session.query(
|
||||
UserMindmapNode.node_id
|
||||
).filter_by(
|
||||
user_mindmap_id=mindmap_id
|
||||
).all()
|
||||
existing_node_ids = [n[0] for n in existing_nodes]
|
||||
|
||||
# Mapping von alten zu neuen Knoten-IDs für importierte Knoten
|
||||
id_mapping = {}
|
||||
|
||||
# Knoten importieren
|
||||
for node_data in import_data.get('nodes', []):
|
||||
# Prüfen, ob es sich um Stringkeys (aus CSV) oder Dict (aus JSON) handelt
|
||||
if isinstance(node_data, dict):
|
||||
node_name = node_data.get('name')
|
||||
node_desc = node_data.get('description', '')
|
||||
node_color = node_data.get('color_code', '#9F7AEA')
|
||||
x_pos = float(node_data.get('x_position', 0))
|
||||
y_pos = float(node_data.get('y_position', 0))
|
||||
node_scale = float(node_data.get('scale', 1.0))
|
||||
old_id = node_data.get('id')
|
||||
else:
|
||||
# Fallback für andere Formate
|
||||
continue
|
||||
|
||||
# Neuen Knoten erstellen, wenn nötig
|
||||
new_node = MindMapNode(
|
||||
name=node_name,
|
||||
description=node_desc,
|
||||
color_code=node_color
|
||||
)
|
||||
db.session.add(new_node)
|
||||
db.session.flush() # ID generieren
|
||||
|
||||
# Verknüpfung zur Mindmap erstellen
|
||||
user_node = UserMindmapNode(
|
||||
user_mindmap_id=mindmap_id,
|
||||
node_id=new_node.id,
|
||||
x_position=x_pos,
|
||||
y_position=y_pos,
|
||||
scale=node_scale
|
||||
)
|
||||
db.session.add(user_node)
|
||||
|
||||
# ID-Mapping für Beziehungen speichern
|
||||
if old_id:
|
||||
id_mapping[old_id] = new_node.id
|
||||
|
||||
# Beziehungen zwischen Knoten importieren
|
||||
for rel in import_data.get('relationships', []):
|
||||
source_id = rel.get('source')
|
||||
target_id = rel.get('target')
|
||||
|
||||
if source_id in id_mapping and target_id in id_mapping:
|
||||
# Knoten-Objekte holen
|
||||
source_node = MindMapNode.query.get(id_mapping[source_id])
|
||||
target_node = MindMapNode.query.get(id_mapping[target_id])
|
||||
|
||||
if source_node and target_node:
|
||||
# Beziehung erstellen
|
||||
source_node.children.append(target_node)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'{len(import_data.get("nodes", []))} Knoten erfolgreich importiert'
|
||||
})
|
||||
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Keine Knotendaten in der Importdatei gefunden'
|
||||
}), 400
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Fehler beim Importieren der Mindmap: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'Fehler beim Importieren: {str(e)}'
|
||||
}), 500
|
||||
|
||||
# Automatische Datenbankinitialisierung - Aktualisiert für Flask 2.2+ Kompatibilität
|
||||
def initialize_app():
|
||||
"""Initialisierung der Anwendung"""
|
||||
print("Initialisierung der Anwendung...")
|
||||
with app.app_context():
|
||||
# Prüfen, ob die Datenbank existiert und initialisiert ist
|
||||
try:
|
||||
# Prüfen, ob Tabellen existieren
|
||||
db.create_all()
|
||||
|
||||
# Prüfen, ob Stammdaten vorhanden sind
|
||||
if User.query.count() == 0:
|
||||
print("Erstelle Standardbenutzer...")
|
||||
create_default_users()
|
||||
|
||||
# Wir nutzen die initialize_database Funktion für Kategorien und Mindmap
|
||||
# Diese Funktionalität ist bereits dort implementiert
|
||||
initialize_database()
|
||||
|
||||
# Zusätzliche Sicherheitsprüfung: 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 nachträglich erstellt")
|
||||
|
||||
print("Datenbank wurde erfolgreich initialisiert.")
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"Fehler bei der Datenbankinitialisierung: {e}")
|
||||
print(traceback.format_exc())
|
||||
|
||||
# Moderne Methode für die Datenbankinitialisierung in Flask 2.2+
|
||||
with app.app_context():
|
||||
initialize_app()
|
||||
# ... existing code ...
|
||||
Reference in New Issue
Block a user