HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //opt/kw-schueler.bak.21032025/migrate_materials.py
#!/usr/bin/env python3
"""
Migrationsskript zum Import der Lehrmaterialien aus dem Dateisystem in die Datenbank.
"""

import os
import argparse
import mysql.connector
import sys
from pathlib import Path
import logging

# Logging konfigurieren
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

def get_db_connection(host, user, password, database):
    """Stellt eine Verbindung zur MySQL-Datenbank her."""
    try:
        conn = mysql.connector.connect(
            host=host,
            user=user,
            password=password,
            database=database
        )
        return conn
    except Exception as e:
        logger.error(f"Datenbankverbindungsfehler: {e}")
        return None

def get_teacher_id_by_code(cursor, school_code, teacher_code):
    """Ermittelt die ID eines Lehrers anhand der Schul- und Lehrercodes."""
    try:
        cursor.execute(
            "SELECT id FROM teachers WHERE school_code = %s AND teacher_code = %s",
            (school_code, teacher_code)
        )
        result = cursor.fetchone()
        if result:
            return result[0]
        return None
    except Exception as e:
        logger.error(f"Fehler beim Abrufen der Lehrer-ID: {e}")
        return None

def get_subject_id_by_name(cursor, subject_name):
    """Ermittelt die ID eines Fachs anhand des Namens."""
    try:
        cursor.execute(
            "SELECT id FROM subjects WHERE name = %s",
            (subject_name,)
        )
        result = cursor.fetchone()
        if result:
            return result[0]
        return None
    except Exception as e:
        logger.error(f"Fehler beim Abrufen der Fach-ID: {e}")
        return None

def get_assignment_type_id(cursor, name, teacher_id, subject_id):
    """Ermittelt die ID eines Aufgabentyps anhand des Namens und der Lehrer-ID."""
    try:
        # Zuerst in der Zuordnungstabelle nach dem Aufgabentyp suchen
        cursor.execute("""
            SELECT at.id 
            FROM assignment_types at
            JOIN teacher_subject_assignment_types tsat ON at.id = tsat.assignment_type_id
            WHERE at.name = %s AND tsat.teacher_id = %s AND tsat.subject_id = %s
        """, (name, teacher_id, subject_id))
        
        result = cursor.fetchone()
        if result:
            return result[0]
        
        # Wenn nicht gefunden, überprüfen, ob der Aufgabentyp existiert
        cursor.execute(
            "SELECT id FROM assignment_types WHERE name = %s",
            (name,)
        )
        result = cursor.fetchone()
        if result:
            assignment_type_id = result[0]
            
            # Aufgabentyp mit Lehrer und Fach verknüpfen
            cursor.execute("""
                INSERT INTO teacher_subject_assignment_types 
                (teacher_id, subject_id, assignment_type_id)
                VALUES (%s, %s, %s)
            """, (teacher_id, subject_id, assignment_type_id))
            
            return assignment_type_id
        
        # Wenn der Aufgabentyp nicht existiert, neu anlegen
        cursor.execute(
            "INSERT INTO assignment_types (name) VALUES (%s)",
            (name,)
        )
        assignment_type_id = cursor.lastrowid
        
        # Aufgabentyp mit Lehrer und Fach verknüpfen
        cursor.execute("""
            INSERT INTO teacher_subject_assignment_types 
            (teacher_id, subject_id, assignment_type_id)
            VALUES (%s, %s, %s)
        """, (teacher_id, subject_id, assignment_type_id))
        
        return assignment_type_id
    except Exception as e:
        logger.error(f"Fehler beim Abrufen/Erstellen des Aufgabentyps: {e}")
        return None

def read_file_content(file_path):
    """Liest den Inhalt einer Datei ein."""
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            return file.read()
    except Exception as e:
        logger.error(f"Fehler beim Lesen der Datei {file_path}: {e}")
        return None

def add_material(cursor, teacher_id, subject_id, assignment_type_id, name, year):
    """Fügt ein neues Material in die Datenbank ein."""
    try:
        cursor.execute("""
            INSERT INTO materials 
            (teacher_id, subject_id, assignment_type_id, name, description, year)
            VALUES (%s, %s, %s, %s, %s, %s)
        """, (teacher_id, subject_id, assignment_type_id, name, f"Importiert aus {name}", year))
        
        return cursor.lastrowid
    except Exception as e:
        logger.error(f"Fehler beim Hinzufügen des Materials: {e}")
        return None

def add_material_content(cursor, material_id, content_type, title, content):
    """Fügt einen Materialinhalt in die Datenbank ein."""
    try:
        cursor.execute("""
            INSERT INTO material_contents 
            (material_id, content_type, title, content)
            VALUES (%s, %s, %s, %s)
        """, (material_id, content_type, title, content))
        
        return cursor.lastrowid
    except Exception as e:
        logger.error(f"Fehler beim Hinzufügen des Materialinhalts: {e}")
        return None

def migrate_materials(conn, base_dir):
    """
    Migriert die Materialien aus dem Dateisystem in die Datenbank.
    
    Verzeichnisstruktur: base_dir/school/teacher/year/subject/assignment_type/
    """
    cursor = conn.cursor()
    
    # Überprüfen, ob die neuen Tabellen existieren
    try:
        cursor.execute("SHOW TABLES LIKE 'materials'")
        materials_exists = cursor.fetchone() is not None
        
        cursor.execute("SHOW TABLES LIKE 'material_contents'")
        contents_exists = cursor.fetchone() is not None
        
        if not materials_exists or not contents_exists:
            logger.error("Die erforderlichen Tabellen 'materials' und 'material_contents' existieren nicht.")
            return False
    except Exception as e:
        logger.error(f"Fehler beim Überprüfen der Tabellen: {e}")
        return False
    
    # Durch die Verzeichnisstruktur navigieren
    for school_dir in os.listdir(base_dir):
        school_path = os.path.join(base_dir, school_dir)
        if not os.path.isdir(school_path):
            continue
        
        for teacher_dir in os.listdir(school_path):
            teacher_path = os.path.join(school_path, teacher_dir)
            if not os.path.isdir(teacher_path):
                continue
            
            # Lehrer-ID abrufen
            teacher_id = get_teacher_id_by_code(cursor, school_dir, teacher_dir)
            if not teacher_id:
                logger.warning(f"Lehrer mit Code {school_dir}/{teacher_dir} nicht gefunden, überspringe.")
                continue
            
            for year_dir in os.listdir(teacher_path):
                year_path = os.path.join(teacher_path, year_dir)
                if not os.path.isdir(year_path):
                    continue
                
                for subject_dir in os.listdir(year_path):
                    subject_path = os.path.join(year_path, subject_dir)
                    if not os.path.isdir(subject_path):
                        continue
                    
                    # Fach-ID abrufen
                    subject_name = subject_dir.capitalize()
                    subject_id = get_subject_id_by_name(cursor, subject_name)
                    if not subject_id:
                        logger.warning(f"Fach {subject_name} nicht gefunden, überspringe.")
                        continue
                    
                    for assignment_type_dir in os.listdir(subject_path):
                        assignment_type_path = os.path.join(subject_path, assignment_type_dir)
                        if not os.path.isdir(assignment_type_path):
                            continue
                        
                        # Aufgabentyp-ID abrufen oder neu anlegen
                        assignment_type_name = assignment_type_dir.capitalize()
                        assignment_type_id = get_assignment_type_id(cursor, assignment_type_name, teacher_id, subject_id)
                        if not assignment_type_id:
                            logger.warning(f"Aufgabentyp {assignment_type_name} konnte nicht verarbeitet werden, überspringe.")
                            continue
                        
                        # Erstelle ein Basismaterial für diesen Aufgabentyp
                        logger.info(f"Verarbeite Material für {school_dir}/{teacher_dir}/{year_dir}/{subject_dir}/{assignment_type_dir}")
                        material_name = f"{subject_dir} - {assignment_type_dir}"
                        material_id = add_material(cursor, teacher_id, subject_id, assignment_type_id, material_name, year_dir)
                        if not material_id:
                            logger.error(f"Fehler beim Erstellen des Materials für {material_name}")
                            continue
                        
                        # Verarbeite Aufgabe.html
                        task_path = os.path.join(assignment_type_path, "aufgabe.html")
                        if os.path.exists(task_path):
                            task_content = read_file_content(task_path)
                            if task_content:
                                add_material_content(cursor, material_id, "task", None, task_content)
                                logger.info(f"Aufgabe für {material_name} importiert")
                        
                        # Verarbeite Prompt.html
                        prompt_path = os.path.join(assignment_type_path, "prompt.html")
                        if os.path.exists(prompt_path):
                            prompt_content = read_file_content(prompt_path)
                            if prompt_content:
                                add_material_content(cursor, material_id, "prompt", None, prompt_content)
                                logger.info(f"Prompt für {material_name} importiert")
                        
                        # Verarbeite Kriterien.html
                        criteria_path = os.path.join(assignment_type_path, "kriterien.html")
                        if os.path.exists(criteria_path):
                            criteria_content = read_file_content(criteria_path)
                            if criteria_content:
                                add_material_content(cursor, material_id, "criteria", None, criteria_content)
                                logger.info(f"Kriterien für {material_name} importiert")
                        
                        # Verarbeite Originaltexte
                        originals_dir = os.path.join(assignment_type_path, "originaltexte")
                        if os.path.exists(originals_dir) and os.path.isdir(originals_dir):
                            for original_file in os.listdir(originals_dir):
                                if original_file.endswith(".html"):
                                    original_path = os.path.join(originals_dir, original_file)
                                    original_content = read_file_content(original_path)
                                    if original_content:
                                        # Titel ist der Dateiname ohne .html
                                        title = original_file[:-5]
                                        add_material_content(cursor, material_id, "original_text", title, original_content)
                                        logger.info(f"Originaltext {title} für {material_name} importiert")
    
    # Änderungen speichern
    conn.commit()
    return True

def setup_db_tables(conn):
    """Erstellt die erforderlichen Tabellen in der Datenbank."""
    cursor = conn.cursor()
    
    try:
        # Überprüfen, ob die Tabellen bereits existieren
        cursor.execute("SHOW TABLES LIKE 'materials'")
        materials_exists = cursor.fetchone() is not None
        
        cursor.execute("SHOW TABLES LIKE 'material_contents'")
        contents_exists = cursor.fetchone() is not None
        
        if materials_exists and contents_exists:
            logger.info("Die Tabellen existieren bereits.")
            return True
        
        # Tabellen erstellen
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS materials (
                id INT AUTO_INCREMENT PRIMARY KEY,
                teacher_id INT NOT NULL,
                subject_id INT NOT NULL,
                assignment_type_id INT NOT NULL,
                name VARCHAR(255) NOT NULL,
                description TEXT,
                year VARCHAR(20) NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                FOREIGN KEY (teacher_id) REFERENCES teachers(id) ON DELETE CASCADE,
                FOREIGN KEY (subject_id) REFERENCES subjects(id) ON DELETE CASCADE,
                FOREIGN KEY (assignment_type_id) REFERENCES assignment_types(id) ON DELETE CASCADE
            )
        """)
        
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS material_contents (
                id INT AUTO_INCREMENT PRIMARY KEY,
                material_id INT NOT NULL,
                content_type ENUM('prompt', 'criteria', 'task', 'original_text') NOT NULL,
                title VARCHAR(255),
                content TEXT NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                FOREIGN KEY (material_id) REFERENCES materials(id) ON DELETE CASCADE,
                UNIQUE KEY (material_id, content_type, title)
            )
        """)
        
        conn.commit()
        logger.info("Tabellen erfolgreich erstellt.")
        return True
        
    except Exception as e:
        conn.rollback()
        logger.error(f"Fehler beim Erstellen der Tabellen: {e}")
        return False

def main():
    parser = argparse.ArgumentParser(description='Migriert Lehrmaterialien vom Dateisystem in die Datenbank.')
    parser.add_argument('--host', required=True, help='MySQL-Host')
    parser.add_argument('--user', required=True, help='MySQL-Benutzer')
    parser.add_argument('--password', required=True, help='MySQL-Passwort')
    parser.add_argument('--database', required=True, help='MySQL-Datenbankname')
    parser.add_argument('--materials-dir', default='schule-materialien', help='Pfad zum Materialien-Verzeichnis')
    parser.add_argument('--setup-tables', action='store_true', help='Erstelle die benötigten Tabellen in der Datenbank')
    
    args = parser.parse_args()
    
    logger.info("Verbinde mit der Datenbank...")
    conn = get_db_connection(args.host, args.user, args.password, args.database)
    
    if not conn:
        logger.error("Konnte keine Verbindung zur Datenbank herstellen. Abbruch.")
        sys.exit(1)
    
    # Wenn --setup-tables gesetzt ist, Tabellen erstellen
    if args.setup_tables:
        logger.info("Erstelle Tabellen...")
        if not setup_db_tables(conn):
            logger.error("Fehler beim Erstellen der Tabellen. Abbruch.")
            sys.exit(1)
    
    base_dir = Path(args.materials_dir)
    if not base_dir.exists() or not base_dir.is_dir():
        logger.error(f"Das Verzeichnis {base_dir} existiert nicht. Abbruch.")
        sys.exit(1)
    
    logger.info(f"Starte Migration von Materialien aus {base_dir}...")
    
    if migrate_materials(conn, base_dir):
        logger.info("Migration erfolgreich abgeschlossen.")
    else:
        logger.error("Fehler bei der Migration.")
        sys.exit(1)
    
    if conn.is_connected():
        conn.close()
        logger.info("Datenbankverbindung geschlossen.")

if __name__ == "__main__":
    main()