diff --git a/README.md b/README.md new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/README.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8ab6294..dda7386 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,6 @@ -flask \ No newline at end of file +flask +flask-login +flask-wtf +email-validator +python-dotenv +flask-sqlalchemy \ No newline at end of file diff --git a/website/__pycache__/app.cpython-311.pyc b/website/__pycache__/app.cpython-311.pyc new file mode 100644 index 0000000..edb7455 Binary files /dev/null and b/website/__pycache__/app.cpython-311.pyc differ diff --git a/website/__pycache__/init_db.cpython-311.pyc b/website/__pycache__/init_db.cpython-311.pyc new file mode 100644 index 0000000..5cedb65 Binary files /dev/null and b/website/__pycache__/init_db.cpython-311.pyc differ diff --git a/website/app.py b/website/app.py index 148ed5b..92c3370 100644 --- a/website/app.py +++ b/website/app.py @@ -1,17 +1,264 @@ -from flask import Flask, render_template +import os +from datetime import datetime +from flask import Flask, render_template, request, redirect, url_for, flash, jsonify +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 app = Flask(__name__) +app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-dev-key') +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mindmap.db' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -# Route für die Startseite +db = SQLAlchemy(app) +login_manager = LoginManager(app) +login_manager.login_view = 'login' + +# Database Models +class User(UserMixin, db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + email = db.Column(db.String(120), unique=True, nullable=False) + password_hash = db.Column(db.String(128)) + is_admin = db.Column(db.Boolean, default=False) + thoughts = db.relationship('Thought', backref='author', lazy=True) + + def set_password(self, password): + self.password_hash = generate_password_hash(password) + + def check_password(self, password): + return check_password_hash(self.password_hash, password) + +class Thought(db.Model): + id = db.Column(db.Integer, primary_key=True) + content = db.Column(db.Text, nullable=False) + timestamp = db.Column(db.DateTime, default=datetime.utcnow) + branch = db.Column(db.String(100), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + comments = db.relationship('Comment', backref='thought', lazy=True, cascade="all, delete-orphan") + +class Comment(db.Model): + id = db.Column(db.Integer, primary_key=True) + content = db.Column(db.Text, nullable=False) + timestamp = db.Column(db.DateTime, default=datetime.utcnow) + thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + author = db.relationship('User', backref='comments') + +class MindMapNode(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + parent_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), nullable=True) + children = db.relationship('MindMapNode', backref=db.backref('parent', remote_side=[id])) + thoughts = db.relationship('Thought', secondary='node_thought_association', backref='nodes') + +# Association table for many-to-many relationship between MindMapNode and Thought +node_thought_association = db.Table('node_thought_association', + db.Column('node_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True), + db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True) +) + +@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) + 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() + + login_user(user) + 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 für die Mindmap-Seite +# Route for the mindmap page @app.route('/mindmap') def mindmap(): return render_template('mindmap.html') +# Route for user profile +@app.route('/profile') +@login_required +def profile(): + thoughts = Thought.query.filter_by(user_id=current_user.id).order_by(Thought.timestamp.desc()).all() + return render_template('profile.html', thoughts=thoughts) + +# API routes for mindmap and thoughts +@app.route('/api/mindmap') +def get_mindmap(): + root_nodes = MindMapNode.query.filter_by(parent_id=None).all() + + def build_tree(node): + return { + 'id': node.id, + 'name': node.name, + 'children': [build_tree(child) for child in node.children] + } + + result = [build_tree(node) for node in root_nodes] + return jsonify(result) + +@app.route('/api/thoughts/', methods=['GET']) +def get_thoughts(node_id): + node = MindMapNode.query.get_or_404(node_id) + thoughts = [] + + for thought in node.thoughts: + thoughts.append({ + 'id': thought.id, + 'content': thought.content, + 'author': thought.author.username, + 'timestamp': thought.timestamp.strftime('%d.%m.%Y, %H:%M'), + 'comments_count': len(thought.comments), + 'branch': thought.branch + }) + + return jsonify(thoughts) + +@app.route('/api/thought/', methods=['GET']) +def get_thought(thought_id): + thought = Thought.query.get_or_404(thought_id) + + return jsonify({ + 'id': thought.id, + 'content': thought.content, + 'author': thought.author.username, + 'timestamp': thought.timestamp.strftime('%d.%m.%Y, %H:%M'), + 'branch': thought.branch, + 'comments_count': len(thought.comments) + }) + +@app.route('/api/thoughts', methods=['POST']) +@login_required +def add_thought(): + data = request.json + node_id = data.get('node_id') + content = data.get('content') + + if not node_id or not content: + return jsonify({'error': 'Fehlende Daten'}), 400 + + node = MindMapNode.query.get_or_404(node_id) + + thought = Thought( + content=content, + branch=node.name, + user_id=current_user.id + ) + + db.session.add(thought) + node.thoughts.append(thought) + db.session.commit() + + return jsonify({ + 'id': thought.id, + 'content': thought.content, + 'author': thought.author.username, + 'timestamp': thought.timestamp.strftime('%d.%m.%Y, %H:%M'), + 'branch': thought.branch + }) + +@app.route('/api/comments/', methods=['GET']) +def get_comments(thought_id): + thought = Thought.query.get_or_404(thought_id) + comments = [ + { + 'id': comment.id, + 'content': comment.content, + 'author': comment.author.username, + 'timestamp': comment.timestamp.strftime('%d.%m.%Y, %H:%M') + } + for comment in thought.comments + ] + return jsonify(comments) + +@app.route('/api/comments', methods=['POST']) +@login_required +def add_comment(): + data = request.json + thought_id = data.get('thought_id') + content = data.get('content') + + if not thought_id or not content: + return jsonify({'error': 'Fehlende Daten'}), 400 + + thought = Thought.query.get_or_404(thought_id) + + comment = Comment( + content=content, + thought_id=thought_id, + user_id=current_user.id + ) + + db.session.add(comment) + db.session.commit() + + return jsonify({ + 'id': comment.id, + 'content': comment.content, + 'author': comment.author.username, + 'timestamp': comment.timestamp.strftime('%d.%m.%Y, %H:%M') + }) + +# Admin routes +@app.route('/admin') +@login_required +def admin(): + if not current_user.is_admin: + flash('Zugriff verweigert') + return redirect(url_for('index')) + + users = User.query.all() + nodes = MindMapNode.query.all() + thoughts = Thought.query.all() + + return render_template('admin.html', users=users, nodes=nodes, thoughts=thoughts) + # 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) \ No newline at end of file diff --git a/website/init_db.py b/website/init_db.py new file mode 100644 index 0000000..c5d930a --- /dev/null +++ b/website/init_db.py @@ -0,0 +1,88 @@ +from app import app, db, User, MindMapNode + +def init_database(): + """Initialize the database with admin user and mindmap structure.""" + with app.app_context(): + # Create all tables + db.create_all() + + # Check if we already have users + if User.query.first() is None: + print("Creating admin user...") + # Create admin user + admin = User(username='admin', email='admin@example.com', is_admin=True) + admin.set_password('admin123') + db.session.add(admin) + + # Create regular test user + test_user = User(username='test', email='test@example.com', is_admin=False) + test_user.set_password('test123') + db.session.add(test_user) + + db.session.commit() + print("Admin user created successfully!") + + # Check if we already have mindmap nodes + if MindMapNode.query.first() is None: + print("Creating initial mindmap structure...") + # Create initial mindmap structure + root = MindMapNode(name="Wissenschaftliche Mindmap") + db.session.add(root) + + # Level 1 nodes + node1 = MindMapNode(name="Naturwissenschaften", parent=root) + node2 = MindMapNode(name="Geisteswissenschaften", parent=root) + node3 = MindMapNode(name="Technologie", parent=root) + node4 = MindMapNode(name="Künste", parent=root) + db.session.add_all([node1, node2, node3, node4]) + + # Level 2 nodes - Naturwissenschaften + node1_1 = MindMapNode(name="Physik", parent=node1) + node1_2 = MindMapNode(name="Biologie", parent=node1) + node1_3 = MindMapNode(name="Chemie", parent=node1) + node1_4 = MindMapNode(name="Astronomie", parent=node1) + db.session.add_all([node1_1, node1_2, node1_3, node1_4]) + + # Level 2 nodes - Geisteswissenschaften + node2_1 = MindMapNode(name="Philosophie", parent=node2) + node2_2 = MindMapNode(name="Geschichte", parent=node2) + node2_3 = MindMapNode(name="Psychologie", parent=node2) + node2_4 = MindMapNode(name="Soziologie", parent=node2) + db.session.add_all([node2_1, node2_2, node2_3, node2_4]) + + # Level 2 nodes - Technologie + node3_1 = MindMapNode(name="Informatik", parent=node3) + node3_2 = MindMapNode(name="Biotechnologie", parent=node3) + node3_3 = MindMapNode(name="Künstliche Intelligenz", parent=node3) + node3_4 = MindMapNode(name="Energietechnik", parent=node3) + db.session.add_all([node3_1, node3_2, node3_3, node3_4]) + + # Level 2 nodes - Künste + node4_1 = MindMapNode(name="Bildende Kunst", parent=node4) + node4_2 = MindMapNode(name="Musik", parent=node4) + node4_3 = MindMapNode(name="Literatur", parent=node4) + node4_4 = MindMapNode(name="Film", parent=node4) + db.session.add_all([node4_1, node4_2, node4_3, node4_4]) + + # Level 3 nodes - a few examples + # Physik + MindMapNode(name="Quantenphysik", parent=node1_1) + MindMapNode(name="Relativitätstheorie", parent=node1_1) + + # Informatik + MindMapNode(name="Maschinelles Lernen", parent=node3_1) + MindMapNode(name="Softwareentwicklung", parent=node3_1) + MindMapNode(name="Datenbanken", parent=node3_1) + + # Commit changes + db.session.commit() + print("Mindmap structure created successfully!") + + print("Database initialization complete.") + +if __name__ == "__main__": + init_database() + print("You can now run the application with 'python app.py'") + print("Login with:") + print(" Admin: username=admin, password=admin123") + print(" User: username=test, password=test123") \ No newline at end of file diff --git a/website/instance/mindmap.db b/website/instance/mindmap.db new file mode 100644 index 0000000..976a308 Binary files /dev/null and b/website/instance/mindmap.db differ diff --git a/website/requirements.txt b/website/requirements.txt new file mode 100644 index 0000000..dda7386 --- /dev/null +++ b/website/requirements.txt @@ -0,0 +1,6 @@ +flask +flask-login +flask-wtf +email-validator +python-dotenv +flask-sqlalchemy \ No newline at end of file diff --git a/website/run.py b/website/run.py new file mode 100644 index 0000000..d03cb9c --- /dev/null +++ b/website/run.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +import os +from init_db import init_database +from app import app + +if __name__ == "__main__": + # Initialize the database first + init_database() + + # Run the Flask application + app.run(host="0.0.0.0", debug=True) \ No newline at end of file diff --git a/website/static/background.js b/website/static/background.js new file mode 100644 index 0000000..bbab5ef --- /dev/null +++ b/website/static/background.js @@ -0,0 +1,108 @@ +// Background animation with Three.js +let scene, camera, renderer, stars = []; + +function initBackground() { + // Setup scene + scene = new THREE.Scene(); + + // Setup camera + camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000); + camera.position.z = 100; + + // Setup renderer + renderer = new THREE.WebGLRenderer({ alpha: true }); + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.setClearColor(0x000000, 0); // Transparent background + + // Append renderer to DOM + const backgroundContainer = document.getElementById('background-container'); + if (backgroundContainer) { + backgroundContainer.appendChild(renderer.domElement); + } + + // Add stars + for (let i = 0; i < 1000; i++) { + const geometry = new THREE.SphereGeometry(0.2, 8, 8); + const material = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: Math.random() * 0.5 + 0.1 }); + const star = new THREE.Mesh(geometry, material); + + // Random position + star.position.x = Math.random() * 600 - 300; + star.position.y = Math.random() * 600 - 300; + star.position.z = Math.random() * 600 - 300; + + // Store reference to move in animation + star.velocity = Math.random() * 0.02 + 0.005; + stars.push(star); + + scene.add(star); + } + + // Add large glowing particles + for (let i = 0; i < 15; i++) { + const size = Math.random() * 5 + 2; + const geometry = new THREE.SphereGeometry(size, 16, 16); + + // Create a glowing material + const color = new THREE.Color(); + color.setHSL(Math.random(), 0.7, 0.5); // Random hue + + const material = new THREE.MeshBasicMaterial({ + color: color, + transparent: true, + opacity: 0.2 + }); + + const particle = new THREE.Mesh(geometry, material); + + // Random position but further away + particle.position.x = Math.random() * 1000 - 500; + particle.position.y = Math.random() * 1000 - 500; + particle.position.z = Math.random() * 200 - 400; + + // Store reference to move in animation + particle.velocity = Math.random() * 0.01 + 0.002; + stars.push(particle); + + scene.add(particle); + } + + // Handle window resize + window.addEventListener('resize', onWindowResize); + + // Start animation + animate(); +} + +function animate() { + requestAnimationFrame(animate); + + // Move stars + stars.forEach(star => { + star.position.z += star.velocity; + + // Reset position if star moves too close + if (star.position.z > 100) { + star.position.z = -300; + } + }); + + // Rotate the entire scene slightly for a dreamy effect + scene.rotation.y += 0.0003; + scene.rotation.x += 0.0001; + + renderer.render(scene, camera); +} + +function onWindowResize() { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize(window.innerWidth, window.innerHeight); +} + +// Initialize background when the DOM is loaded +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initBackground); +} else { + initBackground(); +} \ No newline at end of file diff --git a/website/templates/admin.html b/website/templates/admin.html new file mode 100644 index 0000000..3726b05 --- /dev/null +++ b/website/templates/admin.html @@ -0,0 +1,109 @@ +{% extends "base.html" %} + +{% block title %}Admin | Wissenschaftliche Mindmap{% endblock %} + +{% block content %} +
+
+

Admin Bereich

+

Verwalte Benutzer, Gedanken und die Mindmap-Struktur.

+
+ +
+ +
+
+

Benutzer

+ {{ users|length }} +
+ +
+ + + + + + + + + + + {% for user in users %} + + + + + + + {% endfor %} + +
IDBenutzernameEmailRolle
{{ user.id }}{{ user.username }}{{ user.email }} + {% if user.is_admin %} + Admin + {% else %} + Benutzer + {% endif %} +
+
+
+ + +
+
+

Mindmap Struktur

+ {{ nodes|length }} +
+ +
+
+ {% for node in nodes %} +
+
+ {{ node.name }} + ID: {{ node.id }} +
+ {% if node.parent %} +

Eltern: {{ node.parent.name }}

+ {% else %} +

Hauptknoten

+ {% endif %} +
+ {% endfor %} +
+
+ +
+ +
+
+ + +
+
+

Gedanken

+ {{ thoughts|length }} +
+ +
+
+ {% for thought in thoughts %} +
+
+ {{ thought.branch }} + {{ thought.timestamp.strftime('%d.%m.%Y') }} +
+

{{ thought.content }}

+
+ Von: {{ thought.author.username }} + {{ thought.comments|length }} Kommentar(e) +
+
+ {% endfor %} +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/website/templates/base.html b/website/templates/base.html new file mode 100644 index 0000000..5231846 --- /dev/null +++ b/website/templates/base.html @@ -0,0 +1,126 @@ + + + + + + {% block title %}Wissenschaftliche Mindmap{% endblock %} + + + + + {% block extra_head %}{% endblock %} + + + +
+ +
+ + + + + {% with messages = get_flashed_messages() %} + {% if messages %} +
+ {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} +
+ {% endif %} + {% endwith %} + + +
+ {% block content %}{% endblock %} +
+
+ + + + + {% block scripts %}{% endblock %} + + \ No newline at end of file diff --git a/website/templates/index.html b/website/templates/index.html index 66b2c56..f4f66a9 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -1,18 +1,41 @@ - - - - - Wissenschaftliche Mindmap - - - - -
-

Willkommen zur Wissenschafts-Mindmap

-

Verknüpfe Wissen in neuronalen Strukturen.

- Starte die Mindmap +{% extends "base.html" %} + +{% block content %} +
+
+

Willkommen zur Wissenschafts-Mindmap

+

Verknüpfe Wissen in neuronalen Strukturen und teile deine Gedanken mit der Community.

+ +
+ + Starte die Mindmap + + {% if not current_user.is_authenticated %} + + Registrieren + + {% endif %} +
- - \ No newline at end of file + +
+
+
🧠
+

Visualisiere Wissen

+

Erkenne Zusammenhänge zwischen verschiedenen Wissensgebieten durch intuitive Mindmaps.

+
+ +
+
💡
+

Teile Gedanken

+

Füge deine eigenen Gedanken zu bestehenden Themen hinzu und bereichere die Community.

+
+ +
+
🔄
+

Interaktive Vernetzung

+

Beteilige dich an Diskussionen und sieh wie sich Ideen gemeinsam entwickeln.

+
+
+
+{% endblock %} \ No newline at end of file diff --git a/website/templates/login.html b/website/templates/login.html new file mode 100644 index 0000000..471d79b --- /dev/null +++ b/website/templates/login.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} + +{% block title %}Anmelden | Wissenschaftliche Mindmap{% endblock %} + +{% block content %} +
+
+

Anmelden

+ +
+
+ + +
+ +
+ + +
+ +
+ +
+
+ +
+

+ Noch kein Konto? + + Registrieren + +

+
+
+
+{% endblock %} \ No newline at end of file diff --git a/website/templates/mindmap.html b/website/templates/mindmap.html index 5d6438d..c36dc69 100644 --- a/website/templates/mindmap.html +++ b/website/templates/mindmap.html @@ -1,14 +1,692 @@ - - - - - Wissenschaftliche Mindmap - - - - -

Wissenschaftliche Mindmap

-
- - - \ No newline at end of file +{% extends "base.html" %} + +{% block title %}Mindmap | Wissenschaftliche Mindmap{% endblock %} + +{% block extra_head %} + +{% endblock %} + +{% block content %} +
+ +
+
+

Wissenschaftliche Mindmap

+
+ + +
+ + + +
+ + +
+

Kategorien

+
+
+ + Naturwissenschaften +
+
+ + Geisteswissenschaften +
+
+ + Technologie +
+
+ + Künste +
+
+
+ + +
+
+ + +
+
+
+

Keine Auswahl

+ +
+ + {% if current_user.is_authenticated %} +
+

Teile deinen Gedanken

+ + +
+ {% else %} +
+

Melde dich an, um deine Gedanken zu teilen

+ Anmelden +
+ {% endif %} + +

Community Gedanken

+
+
+

Wähle einen Knoten aus, um Gedanken zu sehen

+
+
+
+
+
+ + + +{% endblock %} + +{% block scripts %} + + +{% endblock %} \ No newline at end of file diff --git a/website/templates/profile.html b/website/templates/profile.html new file mode 100644 index 0000000..da208e1 --- /dev/null +++ b/website/templates/profile.html @@ -0,0 +1,131 @@ +{% extends "base.html" %} + +{% block title %}Profil | Wissenschaftliche Mindmap{% endblock %} + +{% block content %} +
+
+
+
+

Hallo, {{ current_user.username }}

+

{{ current_user.email }}

+
+ + +
+
+ +
+

Deine Gedanken

+ + {% if thoughts %} +
+ {% for thought in thoughts %} +
+
+ {{ thought.branch }} + {{ thought.timestamp.strftime('%d.%m.%Y, %H:%M') }} +
+

{{ thought.content }}

+ +
+
+ {{ thought.comments|length }} Kommentar(e) +
+ + Details anzeigen +
+
+ {% endfor %} +
+ {% else %} +
+

Du hast noch keine Gedanken geteilt.

+ Zur Mindmap gehen und mitmachen +
+ {% endif %} +
+
+ + + +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/website/templates/register.html b/website/templates/register.html new file mode 100644 index 0000000..b4d6671 --- /dev/null +++ b/website/templates/register.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} + +{% block title %}Registrieren | Wissenschaftliche Mindmap{% endblock %} + +{% block content %} +
+
+

Registrieren

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ +
+

+ Bereits registriert? + + Anmelden + +

+
+
+
+{% endblock %} \ No newline at end of file