Update OpenAI API key and enhance app functionality: Replace the OpenAI API key in the .env file for improved access. Refactor app.py to include error handling for missing API keys and implement dark mode functionality with session management. Update README.md to reflect the use of Tailwind CSS via CDN and document the Content Security Policy (CSP) adjustments. Enhance mindmap data loading with a new API endpoint for refreshing data, ensuring better user experience during database connection issues. Update styles and templates for improved UI consistency and responsiveness.

This commit is contained in:
2025-04-27 16:56:16 +02:00
parent 2d8cdc052f
commit 4a3092a4d2
42 changed files with 2458 additions and 878 deletions

View File

@@ -7,14 +7,14 @@ This package contains various utilities for database management,
user management, and server administration.
"""
from .db_fix import fix_database_schema
from .db_rebuild import rebuild_database
from .db_test import test_database_connection, test_models, print_database_stats, run_all_tests
from .user_manager import list_users, create_user, reset_password, delete_user, create_admin_user
from .server import run_development_server
# Import utilities that don't depend on app.py first
from .db_check import check_db_connection, initialize_db_if_needed
# Define the list of all available utilities
__all__ = [
# Database utilities
'check_db_connection',
'initialize_db_if_needed',
'fix_database_schema',
'rebuild_database',
'test_database_connection',
@@ -31,4 +31,11 @@ __all__ = [
# Server management
'run_development_server',
]
]
# Import remaining modules that might depend on app
from .db_fix import fix_database_schema
from .db_rebuild import rebuild_database
from .db_test import test_database_connection, test_models, print_database_stats, run_all_tests
from .user_manager import list_users, create_user, reset_password, delete_user, create_admin_user
from .server import run_development_server

Binary file not shown.

79
utils/db_check.py Normal file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import current_app
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import text
import time
def check_db_connection(db):
"""
Überprüft die Datenbankverbindung und versucht ggf. die Verbindung wiederherzustellen
Args:
db: SQLAlchemy-Instanz
Returns:
bool: True, wenn die Verbindung erfolgreich ist, sonst False
"""
max_retries = 3
retry_count = 0
while retry_count < max_retries:
try:
# Führe eine einfache Abfrage durch, um die Verbindung zu testen
with current_app.app_context():
db.session.execute(text('SELECT 1'))
return True
except SQLAlchemyError as e:
retry_count += 1
print(f"DB-Verbindungsfehler (Versuch {retry_count}/{max_retries}): {str(e)}")
if retry_count < max_retries:
# Warte vor dem nächsten Versuch
time.sleep(1)
# Versuche die Verbindung zurückzusetzen
try:
db.session.rollback()
except:
pass
return False
def initialize_db_if_needed(db, initialize_function=None):
"""
Initialisiert die Datenbank, falls erforderlich
Args:
db: SQLAlchemy-Instanz
initialize_function: Funktion, die aufgerufen wird, um die Datenbank zu initialisieren
Returns:
bool: True, wenn die Datenbank bereit ist, sonst False
"""
# Prüfe die Verbindung
if not check_db_connection(db):
return False
# Prüfe, ob die Tabellen existieren
try:
with current_app.app_context():
# Führe eine Testabfrage auf einer Tabelle durch
db.session.execute(text('SELECT COUNT(*) FROM user'))
except SQLAlchemyError:
# Tabellen existieren nicht, erstelle sie
try:
with current_app.app_context():
db.create_all()
# Rufe die Initialisierungsfunktion auf, falls vorhanden
if initialize_function and callable(initialize_function):
initialize_function()
return True
except Exception as e:
print(f"Fehler bei DB-Initialisierung: {str(e)}")
return False
return True

View File

@@ -11,12 +11,20 @@ import importlib.util
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parent_dir)
from app import app, db_path, create_default_categories
# Import models directly to avoid circular import
from models import db, User, Category
def rebuild_database():
def rebuild_database(app_instance=None):
"""Completely rebuilds the database by dropping and recreating all tables."""
with app.app_context():
if app_instance is None:
# Only import app if it's not provided as a parameter
from app import app as app_instance
from app import db_path
else:
# Get db_path from app_instance config
db_path = app_instance.config['SQLALCHEMY_DATABASE_URI'].replace('sqlite:///', '')
with app_instance.app_context():
print(f"Database path: {db_path}")
# Back up existing database if it exists
@@ -68,7 +76,9 @@ def rebuild_database():
# Create default categories
print("Creating default categories...")
create_default_categories()
# Instead of directly importing create_default_categories, call it through app_instance
create_default_categories_func = getattr(sys.modules['app'], 'create_default_categories')
create_default_categories_func()
print("Database rebuild completed successfully!")
return True

225
utils/download_resources.py Executable file
View File

@@ -0,0 +1,225 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Dieses Skript lädt externe Ressourcen wie Font Awesome, Tailwind CSS und Alpine.js herunter
und installiert sie lokal, um die Abhängigkeit von externen CDNs zu vermeiden und
die Content Security Policy zu erfüllen.
"""
import os
import sys
import requests
import zipfile
import io
import shutil
import subprocess
from pathlib import Path
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# URLs für die Ressourcen
RESOURCES = {
'alpine.js': 'https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js',
'font_awesome': 'https://use.fontawesome.com/releases/v6.4.0/fontawesome-free-6.4.0-web.zip',
'inter_font_300': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2',
'inter_font_400': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2',
'inter_font_500': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2',
'inter_font_600': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2',
'inter_font_700': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2',
'jetbrains_font_400': 'https://fonts.gstatic.com/s/jetbrainsmono/v20/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.woff2',
'jetbrains_font_500': 'https://fonts.gstatic.com/s/jetbrainsmono/v20/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwhsk.woff2',
'jetbrains_font_700': 'https://fonts.gstatic.com/s/jetbrainsmono/v20/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwhsk.woff2',
}
# Zielverzeichnisse
DIRECTORIES = {
'js': os.path.join(BASE_DIR, 'static', 'js'),
'css': os.path.join(BASE_DIR, 'static', 'css'),
'fonts': os.path.join(BASE_DIR, 'static', 'fonts'),
'webfonts': os.path.join(BASE_DIR, 'static', 'webfonts'),
}
def download_file(url, filepath):
"""Lädt eine Datei von einer URL herunter und speichert sie lokal."""
response = requests.get(url, stream=True)
if response.status_code == 200:
with open(filepath, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print(f"✅ Heruntergeladen: {os.path.basename(filepath)}")
return True
else:
print(f"❌ Fehler beim Herunterladen von {url}: {response.status_code}")
return False
def extract_zip(zip_data, extract_to):
"""Extrahiert eine ZIP-Datei in das angegebene Verzeichnis."""
with zipfile.ZipFile(io.BytesIO(zip_data)) as zip_ref:
zip_ref.extractall(extract_to)
def setup_directories():
"""Erstellt die benötigten Verzeichnisse, falls sie nicht existieren."""
for directory in DIRECTORIES.values():
os.makedirs(directory, exist_ok=True)
print(f"📁 Verzeichnis erstellt/überprüft: {directory}")
def download_alpine():
"""Lädt Alpine.js herunter."""
url = RESOURCES['alpine.js']
filepath = os.path.join(DIRECTORIES['js'], 'alpine.min.js')
download_file(url, filepath)
def download_font_awesome():
"""Lädt Font Awesome herunter und extrahiert die Dateien."""
url = RESOURCES['font_awesome']
response = requests.get(url)
if response.status_code == 200:
# Temporäres Verzeichnis für die Extraktion
temp_dir = os.path.join(BASE_DIR, 'temp_fontawesome')
os.makedirs(temp_dir, exist_ok=True)
# ZIP-Datei extrahieren
extract_zip(response.content, temp_dir)
# CSS-Datei kopieren
fa_dir = os.path.join(temp_dir, 'fontawesome-free-6.4.0-web')
css_source = os.path.join(fa_dir, 'css', 'all.min.css')
css_dest = os.path.join(DIRECTORIES['css'], 'all.min.css')
shutil.copyfile(css_source, css_dest)
print(f"✅ Font Awesome CSS kopiert nach {css_dest}")
# Webfonts-Verzeichnis kopieren
webfonts_source = os.path.join(fa_dir, 'webfonts')
shutil.rmtree(DIRECTORIES['webfonts'], ignore_errors=True)
shutil.copytree(webfonts_source, DIRECTORIES['webfonts'])
print(f"✅ Font Awesome Webfonts kopiert nach {DIRECTORIES['webfonts']}")
# Temporäres Verzeichnis löschen
shutil.rmtree(temp_dir)
return True
else:
print(f"❌ Fehler beim Herunterladen von Font Awesome: {response.status_code}")
return False
def download_google_fonts():
"""Lädt die Google Fonts (Inter und JetBrains Mono) herunter."""
font_files = {
'inter-light.woff2': RESOURCES['inter_font_300'],
'inter-regular.woff2': RESOURCES['inter_font_400'],
'inter-medium.woff2': RESOURCES['inter_font_500'],
'inter-semibold.woff2': RESOURCES['inter_font_600'],
'inter-bold.woff2': RESOURCES['inter_font_700'],
'jetbrainsmono-regular.woff2': RESOURCES['jetbrains_font_400'],
'jetbrainsmono-medium.woff2': RESOURCES['jetbrains_font_500'],
'jetbrainsmono-bold.woff2': RESOURCES['jetbrains_font_700'],
}
for filename, url in font_files.items():
filepath = os.path.join(DIRECTORIES['fonts'], filename)
download_file(url, filepath)
def setup_tailwind():
"""Richtet Tailwind CSS ein."""
# Tailwind-Konfiguration erstellen, falls sie nicht existiert
tailwind_config = os.path.join(BASE_DIR, 'tailwind.config.js')
if not os.path.exists(tailwind_config):
with open(tailwind_config, 'w') as f:
f.write("""/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
content: [
"./templates/**/*.html",
"./static/**/*.js",
],
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'ui-sans-serif', 'system-ui', '-apple-system', 'sans-serif'],
mono: ['JetBrains Mono', 'ui-monospace', 'monospace']
},
colors: {
primary: {
50: '#f5f3ff',
100: '#ede9fe',
200: '#ddd6fe',
300: '#c4b5fd',
400: '#a78bfa',
500: '#8b5cf6',
600: '#7c3aed',
700: '#6d28d9',
800: '#5b21b6',
900: '#4c1d95'
},
secondary: {
50: '#ecfdf5',
100: '#d1fae5',
200: '#a7f3d0',
300: '#6ee7b7',
400: '#34d399',
500: '#10b981',
600: '#059669',
700: '#047857',
800: '#065f46',
900: '#064e3b'
},
dark: {
500: '#374151',
600: '#1f2937',
700: '#111827',
800: '#0e1220',
900: '#0a0e19'
}
}
}
},
plugins: [],
}
""")
print(f"✅ Tailwind-Konfiguration erstellt: {tailwind_config}")
# Input-CSS-Datei erstellen
input_css_dir = os.path.join(DIRECTORIES['css'], 'src')
os.makedirs(input_css_dir, exist_ok=True)
input_css = os.path.join(input_css_dir, 'input.css')
if not os.path.exists(input_css):
with open(input_css, 'w') as f:
f.write("""@tailwind base;
@tailwind components;
@tailwind utilities;
""")
print(f"✅ Tailwind Input-CSS erstellt: {input_css}")
# Hinweis zur Kompilierung anzeigen
print("\n📋 Um Tailwind CSS zu kompilieren, führe folgenden Befehl aus:")
print("npm install -D tailwindcss")
print(f"npx tailwindcss -i {input_css} -o {os.path.join(DIRECTORIES['css'], 'tailwind.min.css')} --minify")
def main():
"""Hauptfunktion: Lädt alle benötigten Ressourcen herunter."""
print("🚀 Starte den Download externer Ressourcen...")
setup_directories()
# Alpine.js herunterladen
print("\n📦 Lade Alpine.js herunter...")
download_alpine()
# Font Awesome herunterladen
print("\n📦 Lade Font Awesome herunter...")
download_font_awesome()
# Google Fonts herunterladen
print("\n📦 Lade Google Fonts herunter...")
download_google_fonts()
# Tailwind CSS einrichten
print("\n📦 Richte Tailwind CSS ein...")
setup_tailwind()
print("\n✅ Alle Ressourcen wurden erfolgreich heruntergeladen und eingerichtet!")
print("🔒 Die Webseite sollte nun ohne externe CDNs funktionieren und die Content Security Policy erfüllen.")
if __name__ == "__main__":
main()