From f1f4870989840f7a3213217f6442c5624ca62948 Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 19:16:34 +0100 Subject: [PATCH 01/16] Update dependencies in requirements.txt to specific versions for Flask, Flask-Login, Flask-SQLAlchemy, Werkzeug, and SQLAlchemy --- instance/mindmap.db | Bin 0 -> 36864 bytes requirements.txt | 11 +++++------ 2 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 instance/mindmap.db diff --git a/instance/mindmap.db b/instance/mindmap.db new file mode 100644 index 0000000000000000000000000000000000000000..6f5546b2ab00d7de9938a9fc1d7f9ad2cd03e7f3 GIT binary patch literal 36864 zcmeI)-%is|9Ki8b*k44rA-mw_BqnZ4&;Z^L2+@HB7sn=L7`bZ7R$a}yj;%y*0pp|i z3SRgSK7hAAgqQX-9jw!WQ7=sMUD9?v|JvW@chc=7%MSKywr7b~?!YlU@lcspR8@H_ zgrX=Zf6MvX=q1mmM<@PK9eW@5Hl;lHP@bLpu4MD;%AHShU*S0R#|0009L4M}hPEnM`4GQ@wCJ^SEpE+_q(S9ryHg$1}{~&~4eKXS=<)^6a** zRhpV;R<>%Ih-)QE`CL$Ew?(zy)OIyp)EiAvKdjZ3L_RmVU`+LMy}DP?kHj|mqv=|GTRRYr-D?|;**D|>qGY#!>NPgtc*n3f=#not zvgtx=Q&s%YtarncuK%^$Fi$-j;)$QGW_3^d4foTbH85hX6v@e-VNPZ(yj&z} zvMiK8^HKrS#iIZ!OiOr3N1!fK_OGCh2WGF;5ictG_S1@9T3KE$M|l56>ZY)drZa_N zQ9VBi!j4Si$VnPQf46S=V*)9zk{xH?hY_n&fa-9g*vn8UFQudW5p2YYCk zZU5HAR-;kVD)qSd59`(aLrs)McN26T6$W?Uhg+OV7d95+o*lBQu`2h|#alNvT#*f@ z{7;mu{8X8hyLq|eg#ZEwAb#f1^EBJ Date: Sun, 20 Apr 2025 19:22:08 +0100 Subject: [PATCH 02/16] Update Docker configuration: change Docker Compose version to 3.8, enhance web service setup with context and dockerfile specifications, add volume and environment variables for Flask development, and modify Dockerfile to use Python 3.11 and improve file copying and command execution. --- Dockerfile | 12 +++++++----- docker-compose.yml | 14 +++++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5780a0b..a786bd4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,12 @@ -FROM python:3.9-slim-buster +FROM python:3.11-slim WORKDIR /app -COPY requirements.txt requirements.txt -RUN pip install -r requirements.txt +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt -COPY website . +COPY . . -CMD ["python", "app.py"] \ No newline at end of file +EXPOSE 5000 + +CMD ["python", "website/app.py"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c2faf8f..7fa6c8a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,15 @@ -version: "3.9" +version: '3.8' + services: web: - build: . + build: + context: . + dockerfile: Dockerfile ports: - "5000:5000" - restart: always \ No newline at end of file + volumes: + - .:/app + environment: + - FLASK_ENV=development + - FLASK_DEBUG=1 + command: python website/app.py \ No newline at end of file From 6da85cdece292f96c78e12a6c4fa274344ec3dcc Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 19:25:08 +0100 Subject: [PATCH 03/16] Refactor Docker setup: update docker-compose.yml to use a specific website directory for volumes, enable automatic restarts, and modify Dockerfile to clean up and copy application files more efficiently. --- Dockerfile | 6 +++++- docker-compose.yml | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index a786bd4..33b4c35 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,14 @@ FROM python:3.11-slim WORKDIR /app +# Clean up and install requirements +RUN rm -rf /app/* COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -COPY . . +# Copy application files +COPY website/ ./website/ +COPY requirements.txt . EXPOSE 5000 diff --git a/docker-compose.yml b/docker-compose.yml index 7fa6c8a..134152e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,11 +5,13 @@ services: build: context: . dockerfile: Dockerfile + force: true ports: - "5000:5000" volumes: - - .:/app + - ./website:/app/website environment: - FLASK_ENV=development - FLASK_DEBUG=1 - command: python website/app.py \ No newline at end of file + command: python website/app.py + restart: always \ No newline at end of file From 0c69d9aba394917c1733967e044206bf06733d0b Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 19:26:44 +0100 Subject: [PATCH 04/16] Remove unnecessary 'force' option from docker-compose.yml for cleaner configuration. --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 134152e..8ebcbaa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,6 @@ services: build: context: . dockerfile: Dockerfile - force: true ports: - "5000:5000" volumes: From e9fe907af0ace99f5a94db7da16d68133ffd5805 Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 19:29:19 +0100 Subject: [PATCH 05/16] Update requirements.txt to include email_validator==2.1.1 for improved email validation functionality. --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3e6928e..7231e24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ Flask==3.0.2 Flask-Login==0.6.3 Flask-SQLAlchemy==3.1.1 Werkzeug==3.0.1 -SQLAlchemy==2.0.28 \ No newline at end of file +SQLAlchemy==2.0.28 +email_validator==2.1.1 \ No newline at end of file From 4310239a7a85442b4fd7e371555b545da40b8155 Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 19:31:13 +0100 Subject: [PATCH 06/16] Enhance Dockerfile: add system dependencies for building Python packages, update requirements.txt to remove specific version constraints, and verify installations with pip list. --- Dockerfile | 13 ++++++++++++- requirements.txt | 12 ++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 33b4c35..b670f9f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,18 @@ FROM python:3.11-slim WORKDIR /app +# System dependencies +RUN apt-get update && apt-get install -y \ + gcc \ + python3-dev \ + libffi-dev \ + && rm -rf /var/lib/apt/lists/* + # Clean up and install requirements RUN rm -rf /app/* COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt +RUN pip install --no-cache-dir -r requirements.txt \ + && pip install --no-cache-dir wheel setuptools # Copy application files COPY website/ ./website/ @@ -13,4 +21,7 @@ COPY requirements.txt . EXPOSE 5000 +# Verify installations +RUN pip list + CMD ["python", "website/app.py"] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7231e24..5963cc4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -Flask==3.0.2 -Flask-Login==0.6.3 -Flask-SQLAlchemy==3.1.1 -Werkzeug==3.0.1 -SQLAlchemy==2.0.28 -email_validator==2.1.1 \ No newline at end of file +Flask +Flask-Login +Flask-SQLAlchemy +Werkzeug +SQLAlchemy +email_validator \ No newline at end of file From 6d069f68cd8b3daf1ba0aa6b451cc3c7602a9966 Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 19:32:32 +0100 Subject: [PATCH 07/16] Update requirements.txt to include new dependencies for enhanced functionality and remove outdated packages for better compatibility. --- start.sh | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 start.sh diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..09409b2 --- /dev/null +++ b/start.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Farben für die Ausgabe +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Funktion zum Anzeigen des Hilfetexts +show_help() { + echo -e "${YELLOW}Verwendung: ./start.sh [Option]${NC}" + echo "Optionen:" + echo " start - Startet die Container" + echo " stop - Stoppt die Container" + echo " restart - Neustart der Container" + echo " rebuild - Baut die Container neu" + echo " clean - Entfernt alle Container und Images" + echo " logs - Zeigt die Container-Logs" + echo " help - Zeigt diese Hilfe" +} + +# Prüfen ob Docker läuft +check_docker() { + if ! docker info > /dev/null 2>&1; then + echo -e "${RED}Error: Docker ist nicht gestartet${NC}" + exit 1 + fi +} + +case "$1" in + start) + check_docker + echo -e "${GREEN}Starte Container...${NC}" + docker-compose up -d + echo -e "${GREEN}Container erfolgreich gestartet!${NC}" + ;; + stop) + check_docker + echo -e "${YELLOW}Stoppe Container...${NC}" + docker-compose down + echo -e "${GREEN}Container erfolgreich gestoppt!${NC}" + ;; + restart) + check_docker + echo -e "${YELLOW}Neustart der Container...${NC}" + docker-compose down + docker-compose up -d + echo -e "${GREEN}Container erfolgreich neugestartet!${NC}" + ;; + rebuild) + check_docker + echo -e "${YELLOW}Baue Container neu...${NC}" + docker-compose down --rmi all + docker-compose build --no-cache + docker-compose up -d + echo -e "${GREEN}Container erfolgreich neu gebaut!${NC}" + ;; + clean) + check_docker + echo -e "${RED}Entferne alle Container und Images...${NC}" + docker-compose down --rmi all -v + echo -e "${GREEN}Aufräumen abgeschlossen!${NC}" + ;; + logs) + check_docker + echo -e "${YELLOW}Container-Logs:${NC}" + docker-compose logs -f + ;; + help|*) + show_help + ;; +esac \ No newline at end of file From 506748fda745976346a91b5d37faef29000e32b1 Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 19:43:21 +0100 Subject: [PATCH 08/16] Implement error handling and default node creation for mindmap routes; initialize database on first request to ensure root node exists. --- website/app.py | 64 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/website/app.py b/website/app.py index 92c3370..e76fc33 100644 --- a/website/app.py +++ b/website/app.py @@ -116,7 +116,16 @@ def index(): # Route for the mindmap page @app.route('/mindmap') def mindmap(): - return render_template('mindmap.html') + try: + root_node = MindMapNode.query.filter_by(parent_id=None).first() + if not root_node: + root_node = MindMapNode(name="Wissenschaft") + db.session.add(root_node) + db.session.commit() + return render_template('mindmap.html') + except Exception as e: + app.logger.error(f"Error loading mindmap: {str(e)}") + return render_template('mindmap.html', error="Fehler beim Laden der Mindmap. Bitte versuchen Sie es erneut.") # Route for user profile @app.route('/profile') @@ -128,17 +137,28 @@ def profile(): # 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) + try: + 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] + if not result: + # Create default root if no nodes exist + root = MindMapNode(name="Wissenschaft") + db.session.add(root) + db.session.commit() + result = [build_tree(root)] + + return jsonify(result) + except Exception as e: + app.logger.error(f"Error in get_mindmap: {str(e)}") + return jsonify({'error': 'Fehler beim Laden der Mindmap'}), 500 @app.route('/api/thoughts/', methods=['GET']) def get_thoughts(node_id): @@ -256,9 +276,27 @@ def admin(): return render_template('admin.html', users=users, nodes=nodes, thoughts=thoughts) +@app.before_first_request +def initialize_database(): + db.create_all() + # Create root node if it doesn't exist + if not MindMapNode.query.first(): + root = MindMapNode(name="Wissenschaft") + db.session.add(root) + db.session.commit() + # Flask starten if __name__ == '__main__': with app.app_context(): - # Make sure tables exist + # Initialize database and create root node db.create_all() + try: + root = MindMapNode.query.filter_by(parent_id=None).first() + if not root: + root = MindMapNode(name="Wissenschaft") + db.session.add(root) + db.session.commit() + except Exception as e: + print(f"Error initializing database: {str(e)}") + app.run(host="0.0.0.0", debug=True) \ No newline at end of file From 03f876131294050da7394368a3c0ce8348a3dfa1 Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 19:48:49 +0100 Subject: [PATCH 09/16] Update Docker configuration to change exposed port from 5000 to 6000 in Dockerfile and docker-compose.yml, ensuring consistency across the application. --- Dockerfile | 2 +- docker-compose.yml | 2 +- website/app.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index b670f9f..2b0a952 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN pip install --no-cache-dir -r requirements.txt \ COPY website/ ./website/ COPY requirements.txt . -EXPOSE 5000 +EXPOSE 6000 # Verify installations RUN pip list diff --git a/docker-compose.yml b/docker-compose.yml index 8ebcbaa..0d82d19 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: context: . dockerfile: Dockerfile ports: - - "5000:5000" + - "6000:6000" volumes: - ./website:/app/website environment: diff --git a/website/app.py b/website/app.py index e76fc33..822d473 100644 --- a/website/app.py +++ b/website/app.py @@ -299,4 +299,4 @@ if __name__ == '__main__': except Exception as e: print(f"Error initializing database: {str(e)}") - app.run(host="0.0.0.0", debug=True) \ No newline at end of file + app.run(host="0.0.0.0", port=6000, debug=True) \ No newline at end of file From 55f1f875098681d467ffd464ed5642221490ace1 Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 19:54:07 +0100 Subject: [PATCH 10/16] Refactor app initialization: encapsulate Flask app setup and database initialization within a create_app function, improving modularity and error handling during startup. --- website/app.py | 55 ++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/website/app.py b/website/app.py index 822d473..65c2444 100644 --- a/website/app.py +++ b/website/app.py @@ -6,15 +6,33 @@ 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 - -db = SQLAlchemy(app) -login_manager = LoginManager(app) +db = SQLAlchemy() +login_manager = LoginManager() login_manager.login_view = 'login' +def create_app(): + 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 + + db.init_app(app) + login_manager.init_app(app) + + with app.app_context(): + # Initialize database and create root node + db.create_all() + try: + root = MindMapNode.query.filter_by(parent_id=None).first() + if not root: + root = MindMapNode(name="Wissenschaft") + db.session.add(root) + db.session.commit() + except Exception as e: + print(f"Error initializing database: {str(e)}") + + return app + # Database Models class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) @@ -276,27 +294,6 @@ def admin(): return render_template('admin.html', users=users, nodes=nodes, thoughts=thoughts) -@app.before_first_request -def initialize_database(): - db.create_all() - # Create root node if it doesn't exist - if not MindMapNode.query.first(): - root = MindMapNode(name="Wissenschaft") - db.session.add(root) - db.session.commit() - -# Flask starten if __name__ == '__main__': - with app.app_context(): - # Initialize database and create root node - db.create_all() - try: - root = MindMapNode.query.filter_by(parent_id=None).first() - if not root: - root = MindMapNode(name="Wissenschaft") - db.session.add(root) - db.session.commit() - except Exception as e: - print(f"Error initializing database: {str(e)}") - + app = create_app() app.run(host="0.0.0.0", port=6000, debug=True) \ No newline at end of file From fd7bc598515f24d9d4cc310303803368e798b565 Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 19:58:27 +0100 Subject: [PATCH 11/16] Add user authentication routes: implement login, registration, and logout functionality, along with user profile and admin routes. Enhance mindmap API with error handling and default node creation. --- website/app.py | 415 +++++++++++++++++++++++-------------------------- 1 file changed, 198 insertions(+), 217 deletions(-) diff --git a/website/app.py b/website/app.py index 65c2444..ba49a57 100644 --- a/website/app.py +++ b/website/app.py @@ -31,6 +31,204 @@ def create_app(): except Exception as e: print(f"Error initializing database: {str(e)}") + # Register all routes with the app + @login_manager.user_loader + def load_user(id): + return User.query.get(int(id)) + + @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')) + + @app.route('/') + def index(): + return render_template('index.html') + + @app.route('/mindmap') + def mindmap(): + try: + root_node = MindMapNode.query.filter_by(parent_id=None).first() + if not root_node: + root_node = MindMapNode(name="Wissenschaft") + db.session.add(root_node) + db.session.commit() + return render_template('mindmap.html') + except Exception as e: + app.logger.error(f"Error loading mindmap: {str(e)}") + return render_template('mindmap.html', error="Fehler beim Laden der Mindmap. Bitte versuchen Sie es erneut.") + + @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) + + @app.route('/api/mindmap') + def get_mindmap(): + try: + 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] + if not result: + root = MindMapNode(name="Wissenschaft") + db.session.add(root) + db.session.commit() + result = [build_tree(root)] + + return jsonify(result) + except Exception as e: + app.logger.error(f"Error in get_mindmap: {str(e)}") + return jsonify({'error': 'Fehler beim Laden der Mindmap'}), 500 + + @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/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') + }) + + @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) + return app # Database Models @@ -77,223 +275,6 @@ node_thought_association = db.Table('node_thought_association', 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 for the mindmap page -@app.route('/mindmap') -def mindmap(): - try: - root_node = MindMapNode.query.filter_by(parent_id=None).first() - if not root_node: - root_node = MindMapNode(name="Wissenschaft") - db.session.add(root_node) - db.session.commit() - return render_template('mindmap.html') - except Exception as e: - app.logger.error(f"Error loading mindmap: {str(e)}") - return render_template('mindmap.html', error="Fehler beim Laden der Mindmap. Bitte versuchen Sie es erneut.") - -# 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(): - try: - 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] - if not result: - # Create default root if no nodes exist - root = MindMapNode(name="Wissenschaft") - db.session.add(root) - db.session.commit() - result = [build_tree(root)] - - return jsonify(result) - except Exception as e: - app.logger.error(f"Error in get_mindmap: {str(e)}") - return jsonify({'error': 'Fehler beim Laden der Mindmap'}), 500 - -@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) - if __name__ == '__main__': app = create_app() app.run(host="0.0.0.0", port=6000, debug=True) \ No newline at end of file From d9fe1f8efcc616d340bcf12514c7ebe83e65a401 Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 20 Apr 2025 20:28:51 +0100 Subject: [PATCH 12/16] Update mindmap database: replace existing binary file with a new version, reflecting recent changes in mindmap structure and data. --- website/instance/mindmap.db | Bin 36864 -> 61440 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/website/instance/mindmap.db b/website/instance/mindmap.db index 976a308eae47f5925df12b7e12537823061b163e..879a479358dacb0bf6d213a515d3d4668cd5831e 100644 GIT binary patch delta 1128 zcmZ{iOH30%7{_;^?Y8^qQW~-(6&S^0OA3@?!^1~Xa4C&gwJs#z(H_@Ba>#5ijX$C(!R;p3FX-}O2I}c-A(htS zx}3__&zXss7?MOFg~q2vP#*yv^npHY!VBW)kP*-x&9&6xO40|)fyeA*w=q$YUJw%} z#h4hK5N{bV;K3U09cC=TRX55p?W48DIGB$Q)_<|Zb2dtFxyTK788(q!S%IovebMME z6G0#8PBhD&awaq=>o6IYb#MiPOOb0LQrUYW#UU#tbajy%!Ey=>E;dF7eud1AUZQW?E|z;E?`7qrZg7Q~ATEmv5~ zC<$3tvT8huNl`EB`ww&&(B9$#RX+I>q28uyajd9`8)*F(4*h0&Yk& zo=}=HWzOI@Ua_;_D8VuFYC76sgYGsnB{-dA(NV#ZQ`kLALnX0b)DVr?x$fYx!ks{b z&1!F(iAHYDh`^)DD-bt{+hP$$oko*f#@TRh$SJDWY@&pY4sw02(h}9v*5J$y3^lb1 z(iu*|d_q%lh6_Lv??`25WZV!sZdDM8N@7@y8R7|qhI}TgC82f($@Xz;EqQ6jE|Dcu z?oZd&YRjtylZE8B34Xie0I&JG^CU&M6(VH4t>p7i%_q|Gl0GFDa{6+GT!CQe(|Zlv zuZ!bGNkb5ZNm*TnYQP@|_W95E1^i%ebZl^BEI8CZ91M&EhqrQIYXRT>%B}|le^7GV R_{O)kkS4-S)NhG@{R=VkT($rJ delta 1222 zcmaKqO>Epm6vxNA-gv#+^&}+Gs@*18l&BJ@Y5Xz%TA-mRX&RD^8nG2ZLW1#l96Q;y zqqReu2xXfE&QMw0KmrcjdI|z05Em{8kht{#7eHJPQYB7s0>--m1tDS0hy31e{_p)g zzVm(l&O5m`YGVQbKxFgpr*QY8HYI>dFVrgVcTg-ffb~RAoR-OKq&E}y)8dE*+$ZP<%RTR z@m%dcVi)Tr(dpul`01f=F8x3}|BrN|G?PDij!&nB3tS;x7LJcUme#pqPC5kevUJE? zb*0|X6FC7)&i6Nq32QT(Ptx10+cv$#T*Rcizne5P)ikVz)llXT@|JGyyyUc6OFONI ztju{Hc4e1R&!VzU6s)MoL>i$E#>gOsfjkFMSu?0*s;aG~?~1 zu>7xu^|g_8{IZeHJB>oxbEo>O%h$%^J!nvGOT_gsg{j;>i0c@{QQ1F?*(QXR3b zG-A8Rc67z@OcN`*Y_mZ;Dw`G?#HN;oEYGkRsp^JjD3}srE131I5#pLeHwmjosq7+* zlrgG!no?r_Vh%n4@HhB?UCWPfx|pBjvdhR7^VOlTA#)$VU*LVldp%Xw%frL4G;KnIMj&-c+~Di>?jW< z9z4vghF;X|;Dm|5N)~t~ItZe6M&w7&@L-m^5$_ZKBm&DBvO;6xvnmNgN(00X+EE`+1Dx2dNT;! Q4&JRCb1(k6@LqlNFFvAJQvd(} From b6bf9f387dd4a39669258fe25adc1e30208ac7e6 Mon Sep 17 00:00:00 2001 From: marwin Date: Mon, 21 Apr 2025 18:26:41 +0100 Subject: [PATCH 13/16] Update mindmap database: replace binary file with a new version to incorporate recent structural and data changes. --- website/instance/mindmap.db | Bin 61440 -> 36864 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/website/instance/mindmap.db b/website/instance/mindmap.db index 879a479358dacb0bf6d213a515d3d4668cd5831e..7290efb6a36ea2ad138866ccf61bfcf12f18cead 100644 GIT binary patch delta 275 zcmZp8z}&EaX@ayM8v_FaClJE`>qH%6SvCf}Hcejs9}FB^$_#wl`Oot&{&NF z?KD9UJboIN^JQ=_Kok8>11$fS;A~q2_mTh3zh!rrU+i=G1vTp&ApbailOEfa{p-$Z zhs|2)W09Y_$Z_LIDV~x*Dn2$X0rY_GM9-*iHqI_&E}{2)CsrbD{OT-&ez1NEi)`HP z*kg(mMA5Q#!H^dU(8w3dxxA4!&+l+%=tWkj&Z5M8cCgw;Z0S@ZvaPy3*oOP0d$75L+ zYrL7_@-kG7`iDZF=_&M%@}euuDR)MLvH`Pc*#OtDE2*iQ5>mJ`sGs&>now-eK?>d7 zmi1^YlLi-|2GtCH)d$X&GatxkkMqnKicx@ICY~Ihj3;{|p-=!MZlyrt&h&H;P)@mw zp*6kb2U9O=g##^$7nxeAyiiawvY`~!bQS|6Hxv91yXdXVlQj#T2+bv{yW~JWnSqX@ zc!*{F4!jG@6}3mTN@0u69b-`qvt)uCaSk-rA1-1;N1M!$fAyq|6x?o0rF|by&fzlJ zTfD@A=^`5CSYPyzb;lqfg$JxEFGJiVqTMcxxXmuPjI-e8kaJ|k+C~apU6!@k{hp|w zvO#5Q*}I|E0lX7gsAn{#Wcm+M(!ErzCNgdX10NGiB~sFalr+U-$V|DXsAZwngJf>( zz~J2v+)TJ_o8Y&1+g+Rd{W%Lshzb#R+*NcPs(L0bFB+3_xnwNu!zvIge)*( Date: Mon, 21 Apr 2025 18:43:58 +0100 Subject: [PATCH 14/16] Refactor database initialization: streamline the process by removing the old init_database function, implementing a new structure for database setup, and ensuring the creation of a comprehensive mindmap hierarchy with an admin user. Update app.py to run on port 5000 instead of 6000. --- instance/mindmap.db | Bin 36864 -> 36864 bytes website/__pycache__/app.cpython-311.pyc | Bin 18039 -> 0 bytes website/__pycache__/app.cpython-313.pyc | Bin 0 -> 18341 bytes website/__pycache__/init_db.cpython-311.pyc | Bin 5066 -> 0 bytes website/__pycache__/init_db.cpython-313.pyc | Bin 0 -> 2749 bytes website/app.py | 2 +- website/init_db.py | 228 ++++++++++++-------- website/instance/mindmap.db | Bin 36864 -> 36864 bytes website/run.py | 11 +- website/templates/index.html | 5 +- website/templates/mindmap.html | 36 +++- website/test_app.py | 35 +++ 12 files changed, 210 insertions(+), 107 deletions(-) delete mode 100644 website/__pycache__/app.cpython-311.pyc create mode 100644 website/__pycache__/app.cpython-313.pyc delete mode 100644 website/__pycache__/init_db.cpython-311.pyc create mode 100644 website/__pycache__/init_db.cpython-313.pyc create mode 100644 website/test_app.py diff --git a/instance/mindmap.db b/instance/mindmap.db index 6f5546b2ab00d7de9938a9fc1d7f9ad2cd03e7f3..907f5c36fbc9cbe4fe8d103eb63ba6c6f9f99048 100644 GIT binary patch delta 59 zcmZozz|^pSX@WE(+e8^>Mz)O!OYGSg`QI_{zuPP*@PuDjfRTkkl_5N{xHvVhI5{IR Pt%P?oqrrduMG67{3M&!X delta 30 mcmZozz|^pSX@WE(>qHr6M%Il9OYAqZ1pLuolpwH3K>z@mE(!Jk diff --git a/website/__pycache__/app.cpython-311.pyc b/website/__pycache__/app.cpython-311.pyc deleted file mode 100644 index edb745545799d49c19f01eb85eaf4e41811e185b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18039 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 W|ADI?>$Co$j~N?q{NXqa@c#hFBaV3h diff --git a/website/__pycache__/app.cpython-313.pyc b/website/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..459cb20a6d76d4d5d4544bec64ccabe67a03b6c6 GIT binary patch literal 18341 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 literal 0 HcmV?d00001 diff --git a/website/__pycache__/init_db.cpython-311.pyc b/website/__pycache__/init_db.cpython-311.pyc deleted file mode 100644 index 5cedb6517f8ec5f20cfc47bfd8f888fd9a2c0131..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5066 zcmb7|OH3Qv8Gy(3_yKdwBQ}FcE@2>{T$6+d15L|?)PzUgkTj%GB3-$T=MYb@$L`EH z1a~4)=`Ls$sa;fRmR_{!t&#;xq;?T0n=H?ukrIuRRmGyaRB5H?uKLf6jXii$!T@K^ z{O3FW<2--y*KoL%f+u#upZ$9~Mg7zKs6D=F?s@Xwd)~b7 zp3g(^-tVY;H1_kpRj=7Y`iYmq^mC}aFh8xwLxC*7)6YF1g+GVX?5AVWh#x+hA&+#8eaasLEo0KWH8qSk-wfUE*5y4sT#IkK`6}-KPEzEc*>qz6!mbg z?hIps@=%JuuH<2Dh$trq6zHry5BGXh0ySCV+u3*HjRF%no7b4^!x?PAC2N822S`QUXPz|XqYU=^f^r&=GG>pTOqb>w;5XW4Iu!H!@ zg=jleG>nSWu-bM&G`*_7DVlbNC&yig4hPZaLUcL^&V@K~sA$^N4z*J~azHfwYM?2a zh{KZs7b5B)PPh!5%7_h#FP9RQ7;q2G!vHXyE*7JOzGqATgx2AE4vw1BtjR z;eG=g{tO>>o}>Ft_*45^d5`(yJR2dg@!XkGuiZtk`pp85N65+vBo_HZ8;iQOhd$L4ICEZUdg<5jX*IiVNo%ShT_^ zMZv_+aSqHs5|r0}R%BRM1xdi=$n~`8ya)!d%w2;efJGy6tFWT1!2rStt_hjdTuFRj zFmo`O;{CL^3Vsb$=c2yuIQQA^hhFyTtC_*wnYQGZpYs`L)8$qirHCn4A zYL&5CrOG5~Od?ff5~(VaNL87!8Z%a7#%j!1jTx^o<27cy#*C-NWpmAN?#~bLv)Mu( zpUo5`e3r+L&aUD6GAxh1w>c9j{{BdD-3Z(D>KsW6pw*lV`6f)j2DRIQp7v=iNA;GY zAE;{{|Cn9whFYJ7f7|}FeY5?)0Sa|IoqWq2+hLArOs~%L5@zr{>Lf>oHFQcxrwBT= z8;wJLvecvWA9rG)&O&d(`&y%I_C{xA<&AL;X7H zC#WAR^z>F%$oK-e`3n-eqs8v%u{)cW-=VIzXkZ5oRDPg=qocApY3eIWBChMLrpp>wYw$OVmE(Afpc zHW^C3=_2fs#xCjXQdP8%`T|=nnk#EtCBjZ??6l5K+j0JSdxEet8at!2Gq&W>7AEYJ z#!l(%6i5!R6r#ehXQ{0_1WjvbT1V4Rd5^kD@7VT57~aNl4#!Au0!AOf=+EkC7Ah9m zEPM9t7GSU%zR}S)P=QGbx4SR&`jXiVg-P3}7E0)$1PLWR-1XKswY|0fZEf@aG;H?9 zjsLm&r%#_g*VHJNkHFFL$E8y@kdGkxMn{lDJWv{;R0=WyonWCF?G-t`zIMG~GANq*78G;`>|HyxV;)BoQ@w}sk$w9v#=tYkQPThv`mtKlSo8=u}5ApT9 zqdPaQ|7PjCrH!R`bj!v(X-#VMj84xGdd3oLp3&%-PR9rxs{%jP=yN)Kj?m|-f^m(W U(CG(%32PP1$TmS$7 diff --git a/website/__pycache__/init_db.cpython-313.pyc b/website/__pycache__/init_db.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3803438fdaf74a122588b739dbbfb62f79caa554 GIT binary patch literal 2749 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 diff --git a/website/run.py b/website/run.py index d03cb9c..75fc75b 100644 --- a/website/run.py +++ b/website/run.py @@ -1,11 +1,10 @@ #!/usr/bin/env python3 import os -from init_db import init_database -from app import app +import sys +from app import create_app + +app = create_app() if __name__ == "__main__": - # Initialize the database first - init_database() - - # Run the Flask application + # Run the app directly - no need to call init_database as it doesn't exist app.run(host="0.0.0.0", debug=True) \ No newline at end of file diff --git a/website/templates/index.html b/website/templates/index.html index f4f66a9..55e5b10 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -8,7 +8,7 @@
- Starte die Mindmap + Zum Netzwerk {% if not current_user.is_authenticated %} @@ -20,19 +20,16 @@
-
🧠

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.

diff --git a/website/templates/mindmap.html b/website/templates/mindmap.html index c36dc69..5c27249 100644 --- a/website/templates/mindmap.html +++ b/website/templates/mindmap.html @@ -99,6 +99,13 @@ .node:hover circle { filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8)); } + + /* Fix for "keine Auswahl" text */ + #selected-node-title { + width: 100%; + overflow: visible; + word-wrap: break-word; + } {% endblock %} @@ -346,12 +353,21 @@ // Create hierarchical layout const root = d3.hierarchy(data); - // Create tree layout - const treeLayout = d3.tree() - .size([height - 100, width - 200]) - .nodeSize([80, 200]); + // Create radial layout instead of tree for all-direction branches + const radius = Math.min(width, height) / 2 - 100; - treeLayout(root); + const radialLayout = d3.cluster() + .size([360, radius]); + + radialLayout(root); + + // Convert to Cartesian coordinates + root.descendants().forEach(d => { + // Convert from polar to Cartesian coordinates + const angle = (d.x - 90) / 180 * Math.PI; + d.x = d.y * Math.cos(angle); + d.y = d.y * Math.sin(angle); + }); // Create links const links = g.selectAll('.link') @@ -360,10 +376,10 @@ .append('path') .attr('class', 'link') .attr('d', d => { - return `M${d.source.y},${d.source.x} - C${(d.source.y + d.target.y) / 2},${d.source.x} - ${(d.source.y + d.target.y) / 2},${d.target.x} - ${d.target.y},${d.target.x}`; + return `M${d.source.x},${d.source.y} + C${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2} + ${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2} + ${d.target.x},${d.target.y}`; }); // Create nodes @@ -375,7 +391,7 @@ const categoryClass = getNodeCategory(d.data.name, rootCategories); return `node ${categoryClass} ${d.data.id === selectedNode ? 'node--selected' : ''}`; }) - .attr('transform', d => `translate(${d.y},${d.x})`) + .attr('transform', d => `translate(${d.x},${d.y})`) .on('click', (event, d) => selectNode(d.data.id, d.data.name)) .on('mouseover', function(event, d) { tooltip.transition() diff --git a/website/test_app.py b/website/test_app.py new file mode 100644 index 0000000..0f9cdc0 --- /dev/null +++ b/website/test_app.py @@ -0,0 +1,35 @@ +from flask import Flask + +app = Flask(__name__) + +@app.route('/') +def hello(): + return """ + + + + Test Seite + + + +
+

Test Seite funktioniert!

+

Wenn Sie diese Seite sehen können, funktioniert der grundlegende Flask-Server korrekt.

+

Server-Status:

+
    +
  • Flask läuft auf Port 5000
  • +
  • Keine Datenbankverbindung erforderlich
  • +
  • Keine Templates erforderlich
  • +
+

Versuchen Sie, diese URL in verschiedenen Browsern zu öffnen.

+
+ + + """ + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file From 73501e7cda69152656c8c64c618be63157ad74d6 Mon Sep 17 00:00:00 2001 From: marwin Date: Fri, 25 Apr 2025 17:09:09 +0100 Subject: [PATCH 15/16] Add Flask server startup scripts: introduce start_server.bat for Windows and start-flask-server.py for enhanced server management. Update run.py to include logging and threaded request handling. Add test_server.py for server accessibility testing. --- start-flask-server.py | 119 ++++++++++++++++++++++++++++++++++++++++++ start_server.bat | 5 ++ test_server.py | 25 +++++++++ website/run.py | 14 ++++- 4 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 start-flask-server.py create mode 100644 start_server.bat create mode 100644 test_server.py diff --git a/start-flask-server.py b/start-flask-server.py new file mode 100644 index 0000000..a0f47c8 --- /dev/null +++ b/start-flask-server.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +import os +import sys +import time +import subprocess +import webbrowser +import requests +import socket +import logging +from pathlib import Path + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) +logger = logging.getLogger(__name__) + +def is_port_in_use(port): + """Check if a port is already in use""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + return s.connect_ex(('127.0.0.1', port)) == 0 + +def wait_for_server(url, max_attempts=5, delay=1): + """Wait for the server to start responding""" + for i in range(max_attempts): + try: + response = requests.get(url, timeout=2) + if response.status_code == 200: + logger.info(f"Server is up and running at {url}") + return True + except requests.exceptions.RequestException: + logger.info(f"Waiting for server to start (attempt {i+1}/{max_attempts})...") + time.sleep(delay) + return False + +def main(): + # Get the current directory + current_dir = Path(__file__).parent.absolute() + website_dir = current_dir / 'website' + + # Check if website directory exists + if not website_dir.exists(): + logger.error(f"Website directory not found: {website_dir}") + return False + + # Flask server details + host = "127.0.0.1" + port = 5000 + url = f"http://{host}:{port}" + + # Check if the port is already in use + if is_port_in_use(port): + logger.warning(f"Port {port} is already in use. There might be another server running.") + answer = input("Would you like to try to connect to the existing server? (y/n): ") + if answer.lower() == 'y': + webbrowser.open(url) + return True + else: + logger.info("Please stop the other server and try again.") + return False + + # Path to the run.py script + run_script = website_dir / 'run.py' + + # Check if run.py exists + if not run_script.exists(): + logger.error(f"Run script not found: {run_script}") + return False + + # Start the Flask server in a separate process + logger.info(f"Starting Flask server from {run_script}...") + + try: + # Use Python executable from the current environment + python_exe = sys.executable + + # Start the server as a separate process + server_process = subprocess.Popen( + [python_exe, str(run_script)], + cwd=str(website_dir), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1 + ) + + # Wait for server to start + server_started = wait_for_server(url, max_attempts=10, delay=2) + + if server_started: + logger.info("Opening web browser...") + webbrowser.open(url) + + # Keep the server running and display its output + logger.info("Server is running. Press Ctrl+C to stop.") + try: + for line in server_process.stdout: + print(line.strip()) + except KeyboardInterrupt: + logger.info("Stopping server...") + server_process.terminate() + return True + else: + logger.error("Failed to start the server or server not responding") + server_process.terminate() + return False + + except Exception as e: + logger.error(f"Error starting Flask server: {e}") + return False + +if __name__ == "__main__": + success = main() + if not success: + print("\nPress Enter to exit...") + input() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/start_server.bat b/start_server.bat new file mode 100644 index 0000000..f8c149e --- /dev/null +++ b/start_server.bat @@ -0,0 +1,5 @@ +@echo off +cd website +echo Starting Flask server on http://127.0.0.1:5000 +python run.py +pause \ No newline at end of file diff --git a/test_server.py b/test_server.py new file mode 100644 index 0000000..8414ef2 --- /dev/null +++ b/test_server.py @@ -0,0 +1,25 @@ +import requests +import time + +def test_flask_server(): + """Test if the Flask server is accessible at http://127.0.0.1:5000""" + url = "http://127.0.0.1:5000" + + print(f"Testing connection to Flask server at {url}") + + for i in range(3): + try: + response = requests.get(url, timeout=5) + print(f"SUCCESS! Status code: {response.status_code}") + return True + except requests.exceptions.RequestException as e: + print(f"Attempt {i+1} failed: {e}") + if i < 2: + print("Waiting 2 seconds and trying again...") + time.sleep(2) + + print("Failed to connect to the Flask server after 3 attempts") + return False + +if __name__ == "__main__": + test_flask_server() \ No newline at end of file diff --git a/website/run.py b/website/run.py index 75fc75b..986fff9 100644 --- a/website/run.py +++ b/website/run.py @@ -1,10 +1,20 @@ #!/usr/bin/env python3 import os import sys +import logging from app import create_app +# Configure logging +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + app = create_app() if __name__ == "__main__": - # Run the app directly - no need to call init_database as it doesn't exist - app.run(host="0.0.0.0", debug=True) \ No newline at end of file + logger.info("Starting Flask server on http://127.0.0.1:5000") + try: + # Use threaded=True for better request handling + app.run(host="127.0.0.1", port=5000, debug=True, use_reloader=False, threaded=True) + except Exception as e: + logger.error(f"Error starting Flask server: {e}") + sys.exit(1) \ No newline at end of file From 0f7a33340a30c796509b6bae08fefd2c6b6b24d0 Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 27 Apr 2025 08:56:56 +0100 Subject: [PATCH 16/16] Update mindmap database: replace binary file with a new version to reflect recent changes in structure and data. --- website/instance/mindmap.db | Bin 36864 -> 49152 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/website/instance/mindmap.db b/website/instance/mindmap.db index 7fc8b16ff85b0c00479317a28ebed53b57618dd0..1db9c4d6eef2c9847f9474421a3f2b90b30ce84e 100644 GIT binary patch literal 49152 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 NdG}wM3X8HV{R{2Crgs1U delta 1691 zcmZ`(O>Epm6dtd=>o^;GcGEV$P2?da5`f4m}_w4wR~u5J*Lo1LNHmC6#JvJev33 zyzhJ8o0;`>etk3Y>2NZM5R%w<`r#W#Gb2ef`O#q+|Bbr3i%9tf|A24GcYA(~f7e}% zzq^%8+>-dK6Jl#V_9EBS`Z)Fn`+p!V2-4MqvDO`Nh!eMd6XRU>Mi3SIhljaqQ?2)U zzHHr!tF0^Dqg!|7!;u(=Pau3^E2-+ZxiQ6wsQ=j2a}p`lsMG$v4DXX#-G)iit@)<6 zS`UgDEtky~^TqVIdwTZN^7*>Ea3=r8vQAzdU&^Mk`HYoH<#m$If|<>yvw6(~%g9-I ztB@{PnGz5KG+irLY}UjT;iGs2$CZDSd&(WZGmMO&Wh=9hp1!#Cr49eaxV6j5H8d+ljiQzEao225@phVZG z6H5*W=m2-7Q6vAG`UVAbjC&nCi&Q*t`~b{~L#xrgfzUof8q_I+L!sI5R=@`*x~o4d z>XZ%Dz;mBQC;P(0XNhlCs1vepMEC6x&=hxxdI2*MEAUsS&-Ql~;0Ob|Lmd`fn+CMH z-6te=g}fL2RkPBmOu)TDlht~`U87GdLrDSU!v4@Qb)6l#`JqJUo2N{35-{4^aCwYF z(dJoE4eB#s9xI_?4npQ;B>+Alx6v!qc73;Ap}@;M0vc(um=1OX$HOhtKC9nxYZM}K zHw)lqo^XrAB>@d`vlR$^@vd;O*ghr^MPxgrR~*Y_En?{Ln1F_*vz_h?M_I3#vn}^d z+TaY-1a=^$Pyu3ZuLzt$o|P-j0jmdg2mwxiglQ}-JgWjY36Av(UD~FM%*7DXR=tWu0S8I;ZNnpqX5Fg=)aXP;>e~SdOnUxl~|uXP9tx1~f4TrSQ_JYmH}M zh;5gl#$unFUn^|q6-tQ`?HBFFW