#!/usr/bin/env node /** * Script zum Erstellen von öffentlichen Sprachkursen für verschiedene Sprachen * * Verwendung: * node backend/scripts/create-language-courses.js * * Erstellt öffentliche Kurse für alle Kombinationen von: * - Zielsprachen: Bisaya, Französisch, Spanisch, Latein, Italienisch, Portugiesisch, Tagalog * - Muttersprachen: Deutsch, Englisch, Spanisch, Französisch, Italienisch, Portugiesisch * * Die Kurse werden automatisch einem System-Benutzer zugeordnet und sind öffentlich zugänglich. */ import { sequelize } from '../utils/sequelize.js'; import VocabCourse from '../models/community/vocab_course.js'; import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; import User from '../models/community/user.js'; import crypto from 'crypto'; import bcrypt from 'bcryptjs'; // Kursstruktur für alle Sprachen (4 Wochen, 40 Lektionen) const LESSON_TEMPLATE = [ // WOCHE 1: Grundlagen & Aussprache { week: 1, day: 1, num: 1, type: 'conversation', title: 'Begrüßungen & Höflichkeit', desc: 'Lerne die wichtigsten Begrüßungen und Höflichkeitsformeln', targetMin: 15, targetScore: 80, review: false, cultural: 'Höflichkeit ist wichtig. Lächeln hilft!' }, { week: 1, day: 1, num: 2, type: 'vocab', title: 'Überlebenssätze - Teil 1', desc: 'Die 10 wichtigsten Sätze für den Alltag', targetMin: 20, targetScore: 85, review: true, cultural: 'Diese Sätze helfen dir sofort im Alltag weiter.' }, { week: 1, day: 2, num: 3, type: 'vocab', title: 'Familienwörter', desc: 'Mama, Papa, Geschwister, Großeltern und mehr', targetMin: 20, targetScore: 85, review: true, cultural: 'Familienwörter sind wichtig für echte Gespräche.' }, { week: 1, day: 2, num: 4, type: 'conversation', title: 'Familien-Gespräche', desc: 'Einfache Gespräche mit Familienmitgliedern', targetMin: 15, targetScore: 80, review: false, cultural: 'Familienkonversationen sind herzlicher als formelle Gespräche.' }, { week: 1, day: 3, num: 5, type: 'conversation', title: 'Gefühle & Zuneigung', desc: 'Wie man Gefühle ausdrückt', targetMin: 15, targetScore: 80, review: false, cultural: 'Gefühle auszudrücken ist wichtig für echte Verbindung.' }, { week: 1, day: 3, num: 6, type: 'vocab', title: 'Überlebenssätze - Teil 2', desc: 'Weitere wichtige Alltagssätze', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 1, day: 4, num: 7, type: 'conversation', title: 'Essen & Fürsorge', desc: 'Gespräche rund ums Essen', targetMin: 15, targetScore: 80, review: false, cultural: 'Essen verbindet Menschen überall auf der Welt.' }, { week: 1, day: 4, num: 8, type: 'vocab', title: 'Essen & Trinken', desc: 'Wichtige Wörter rund ums Essen', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 1, day: 5, num: 9, type: 'review', title: 'Woche 1 - Wiederholung', desc: 'Wiederhole alle Inhalte der ersten Woche', targetMin: 30, targetScore: 80, review: false, cultural: 'Wiederholung ist der Schlüssel zum Erfolg!' }, { week: 1, day: 5, num: 10, type: 'vocab', title: 'Woche 1 - Vokabeltest', desc: 'Teste dein Wissen aus Woche 1', targetMin: 15, targetScore: 80, review: true, cultural: null }, // WOCHE 2: Alltag & Familie { week: 2, day: 1, num: 11, type: 'conversation', title: 'Alltagsgespräche - Teil 1', desc: 'Wie war dein Tag? Was machst du?', targetMin: 15, targetScore: 80, review: false, cultural: 'Alltagsgespräche sind wichtig für echte Kommunikation.' }, { week: 2, day: 1, num: 12, type: 'vocab', title: 'Haus & Familie', desc: 'Wörter für Haus, Zimmer, Familie', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 2, day: 2, num: 13, type: 'conversation', title: 'Alltagsgespräche - Teil 2', desc: 'Wohin gehst du? Was machst du heute?', targetMin: 15, targetScore: 80, review: false, cultural: null }, { week: 2, day: 2, num: 14, type: 'vocab', title: 'Ort & Richtung', desc: 'Wo, hier, dort, gehen zu', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 2, day: 3, num: 15, type: 'grammar', title: 'Zeitformen - Grundlagen', desc: 'Vergangenheit, Gegenwart, Zukunft', targetMin: 25, targetScore: 75, review: true, cultural: 'Zeitformen sind wichtig für präzise Kommunikation.' }, { week: 2, day: 3, num: 16, type: 'vocab', title: 'Zeit & Datum', desc: 'Jetzt, morgen, gestern, heute', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 2, day: 4, num: 17, type: 'conversation', title: 'Einkaufen & Preise', desc: 'Wie viel kostet das? Kann es billiger sein?', targetMin: 15, targetScore: 80, review: false, cultural: 'Einkaufen ist eine wichtige Alltagssituation.' }, { week: 2, day: 4, num: 18, type: 'vocab', title: 'Zahlen & Preise', desc: 'Zahlen 1-100, Preise, Mengen', targetMin: 25, targetScore: 85, review: true, cultural: null }, { week: 2, day: 5, num: 19, type: 'review', title: 'Woche 2 - Wiederholung', desc: 'Wiederhole alle Inhalte der zweiten Woche', targetMin: 30, targetScore: 80, review: false, cultural: null }, { week: 2, day: 5, num: 20, type: 'vocab', title: 'Woche 2 - Vokabeltest', desc: 'Teste dein Wissen aus Woche 2', targetMin: 15, targetScore: 80, review: true, cultural: null }, // WOCHE 3: Vertiefung { week: 3, day: 1, num: 21, type: 'conversation', title: 'Gefühle & Emotionen', desc: 'Wie man verschiedene Gefühle ausdrückt', targetMin: 15, targetScore: 80, review: false, cultural: 'Emotionen auszudrücken ist wichtig für echte Verbindung.' }, { week: 3, day: 1, num: 22, type: 'vocab', title: 'Gefühle & Emotionen', desc: 'Wörter für verschiedene Gefühle', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 3, day: 2, num: 23, type: 'conversation', title: 'Gesundheit & Wohlbefinden', desc: 'Gespräche über Gesundheit', targetMin: 15, targetScore: 80, review: false, cultural: null }, { week: 3, day: 2, num: 24, type: 'vocab', title: 'Körper & Gesundheit', desc: 'Wörter rund um den Körper und Gesundheit', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 3, day: 3, num: 25, type: 'grammar', title: 'Höflichkeitsformen', desc: 'Wie man höflich spricht', targetMin: 20, targetScore: 75, review: true, cultural: 'Höflichkeit ist extrem wichtig in jeder Kultur.' }, { week: 3, day: 3, num: 26, type: 'conversation', title: 'Bitten & Fragen', desc: 'Wie man höflich fragt und bittet', targetMin: 15, targetScore: 80, review: false, cultural: null }, { week: 3, day: 4, num: 27, type: 'conversation', title: 'Kinder & Familie', desc: 'Gespräche mit und über Kinder', targetMin: 15, targetScore: 80, review: false, cultural: 'Kinder sind sehr wichtig in Familien.' }, { week: 3, day: 4, num: 28, type: 'vocab', title: 'Kinder & Spiel', desc: 'Wörter für Kinder und Spielsachen', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 3, day: 5, num: 29, type: 'review', title: 'Woche 3 - Wiederholung', desc: 'Wiederhole alle Inhalte der dritten Woche', targetMin: 30, targetScore: 80, review: false, cultural: null }, { week: 3, day: 5, num: 30, type: 'vocab', title: 'Woche 3 - Vokabeltest', desc: 'Teste dein Wissen aus Woche 3', targetMin: 15, targetScore: 80, review: true, cultural: null }, // WOCHE 4: Freies Sprechen { week: 4, day: 1, num: 31, type: 'conversation', title: 'Freies Gespräch - Thema 1', desc: 'Übe freies Sprechen zu verschiedenen Themen', targetMin: 20, targetScore: 75, review: false, cultural: 'Fehler sind okay! Muttersprachler schätzen das Bemühen.' }, { week: 4, day: 1, num: 32, type: 'vocab', title: 'Wiederholung - Woche 1 & 2', desc: 'Wiederhole wichtige Vokabeln aus den ersten beiden Wochen', targetMin: 25, targetScore: 85, review: true, cultural: null }, { week: 4, day: 2, num: 33, type: 'conversation', title: 'Freies Gespräch - Thema 2', desc: 'Weitere Übung im freien Sprechen', targetMin: 20, targetScore: 75, review: false, cultural: null }, { week: 4, day: 2, num: 34, type: 'vocab', title: 'Wiederholung - Woche 3', desc: 'Wiederhole wichtige Vokabeln aus Woche 3', targetMin: 25, targetScore: 85, review: true, cultural: null }, { week: 4, day: 3, num: 35, type: 'conversation', title: 'Komplexere Gespräche', desc: 'Längere Gespräche zu verschiedenen Themen', targetMin: 25, targetScore: 75, review: false, cultural: 'Je mehr du sprichst, desto besser wirst du!' }, { week: 4, day: 3, num: 36, type: 'review', title: 'Gesamtwiederholung', desc: 'Wiederhole alle wichtigen Inhalte des Kurses', targetMin: 30, targetScore: 80, review: false, cultural: null }, { week: 4, day: 4, num: 37, type: 'conversation', title: 'Praktische Übung', desc: 'Simuliere echte Gesprächssituationen', targetMin: 25, targetScore: 75, review: false, cultural: null }, { week: 4, day: 4, num: 38, type: 'vocab', title: 'Abschlusstest - Vokabeln', desc: 'Finaler Vokabeltest über den gesamten Kurs', targetMin: 20, targetScore: 80, review: true, cultural: null }, { week: 4, day: 5, num: 39, type: 'review', title: 'Abschlussprüfung', desc: 'Finale Prüfung über alle Kursinhalte', targetMin: 30, targetScore: 80, review: false, cultural: 'Gratulation zum Abschluss des Kurses!' }, { week: 4, day: 5, num: 40, type: 'culture', title: 'Kulturelle Tipps & Tricks', desc: 'Wichtige kulturelle Hinweise für den Alltag', targetMin: 15, targetScore: 0, review: false, cultural: 'Kulturelles Verständnis ist genauso wichtig wie die Sprache selbst.' } ]; // Zielsprachen (die zu lernenden Sprachen) const TARGET_LANGUAGES = [ 'Bisaya', 'Französisch', 'Spanisch', 'Latein', 'Italienisch', 'Portugiesisch', 'Tagalog' ]; // Muttersprachen (für die Kurse erstellt werden) const NATIVE_LANGUAGES = [ 'Deutsch', 'Englisch', 'Spanisch', 'Französisch', 'Italienisch', 'Portugiesisch' ]; // Generiere Kurskonfigurationen für alle Kombinationen function generateCourseConfigs() { const configs = []; for (const targetLang of TARGET_LANGUAGES) { for (const nativeLang of NATIVE_LANGUAGES) { // Überspringe, wenn Zielsprache = Muttersprache if (targetLang === nativeLang) continue; const title = `${targetLang} für ${nativeLang}sprachige - Schnellstart in 4 Wochen`; let description = `Lerne ${targetLang} schnell und praktisch für den Alltag. `; if (targetLang === 'Latein') { description = `Lerne ${targetLang} systematisch mit Fokus auf Grammatik und Vokabular. `; } else if (targetLang === 'Bisaya') { description = `Lerne ${targetLang} (Cebuano) schnell und praktisch für den Familienalltag. `; } description += `Fokus auf Sprechen & Hören mit strukturiertem 4-Wochen-Plan.`; configs.push({ targetLanguageName: targetLang, nativeLanguageName: nativeLang, title, description, difficultyLevel: 1 }); } } return configs; } const LANGUAGE_COURSES = generateCourseConfigs(); async function findOrCreateLanguage(languageName, ownerUserId) { // Suche zuerst nach vorhandener Sprache const [existing] = await sequelize.query( `SELECT id FROM community.vocab_language WHERE name = :name LIMIT 1`, { replacements: { name: languageName }, type: sequelize.QueryTypes.SELECT } ); if (existing) { console.log(` ✅ Sprache "${languageName}" bereits vorhanden (ID: ${existing.id})`); return existing.id; } // Erstelle neue Sprache const shareCode = crypto.randomBytes(8).toString('hex'); const [created] = await sequelize.query( `INSERT INTO community.vocab_language (owner_user_id, name, share_code) VALUES (:ownerUserId, :name, :shareCode) RETURNING id`, { replacements: { ownerUserId, name: languageName, shareCode }, type: sequelize.QueryTypes.SELECT } ); console.log(` ✅ Sprache "${languageName}" erstellt (ID: ${created.id})`); return created.id; } async function createCourseForLanguage(targetLanguageId, nativeLanguageId, languageConfig, ownerUserId) { const shareCode = crypto.randomBytes(8).toString('hex'); const course = await VocabCourse.create({ ownerUserId, title: languageConfig.title, description: languageConfig.description, languageId: Number(targetLanguageId), nativeLanguageId: nativeLanguageId ? Number(nativeLanguageId) : null, difficultyLevel: languageConfig.difficultyLevel || 1, isPublic: true, shareCode }); console.log(` ✅ Kurs erstellt: "${course.title}" (ID: ${course.id}, Share-Code: ${shareCode})`); // Erstelle Lektionen for (const lessonData of LESSON_TEMPLATE) { await VocabCourseLesson.create({ courseId: course.id, chapterId: null, lessonNumber: lessonData.num, title: lessonData.title, description: lessonData.desc, weekNumber: lessonData.week, dayNumber: lessonData.day, lessonType: lessonData.type, culturalNotes: lessonData.cultural, targetMinutes: lessonData.targetMin, targetScorePercent: lessonData.targetScore, requiresReview: lessonData.review }); } console.log(` ✅ ${LESSON_TEMPLATE.length} Lektionen erstellt`); return course; } async function findOrCreateSystemUser() { // Versuche zuerst einen System-Benutzer zu finden (z.B. mit username "system" oder "admin") let systemUser = await User.findOne({ where: { username: 'system' } }); if (!systemUser) { // Versuche Admin-Benutzer systemUser = await User.findOne({ where: { username: 'admin' } }); } if (!systemUser) { // Erstelle einen System-Benutzer console.log(' ℹ️ Erstelle System-Benutzer für öffentliche Kurse...'); const saltRounds = 10; const randomPassword = crypto.randomBytes(32).toString('hex'); const hashedPassword = await bcrypt.hash(randomPassword, saltRounds); systemUser = await User.create({ username: 'system', email: 'system@your-part.de', password: hashedPassword, active: true, registrationDate: new Date() }); // hashedId wird automatisch vom Hook gesetzt, aber warte kurz darauf await systemUser.reload(); console.log(` ✅ System-Benutzer erstellt (ID: ${systemUser.id}, hashedId: ${systemUser.hashedId})`); } else { console.log(` ✅ System-Benutzer gefunden (ID: ${systemUser.id}, Username: ${systemUser.username})`); } return systemUser; } async function createAllLanguageCourses() { try { // Finde oder erstelle System-Benutzer const systemUser = await findOrCreateSystemUser(); console.log(`\n🚀 Erstelle öffentliche Sprachkurse (Besitzer: System-Benutzer ID ${systemUser.id})\n`); const createdCourses = []; // Stelle sicher, dass alle benötigten Sprachen existieren console.log(`\n🌍 Stelle sicher, dass alle Sprachen existieren...`); const allLanguages = [...new Set([...TARGET_LANGUAGES, ...NATIVE_LANGUAGES])]; const languageMap = new Map(); for (const langName of allLanguages) { const langId = await findOrCreateLanguage(langName, systemUser.id); languageMap.set(langName, langId); } for (const langConfig of LANGUAGE_COURSES) { console.log(`\n📚 Verarbeite: ${langConfig.targetLanguageName} für ${langConfig.nativeLanguageName}sprachige`); const targetLanguageId = languageMap.get(langConfig.targetLanguageName); const nativeLanguageId = languageMap.get(langConfig.nativeLanguageName); // Prüfe, ob Kurs bereits existiert (unabhängig vom Besitzer, wenn öffentlich) const existingCourse = await VocabCourse.findOne({ where: { languageId: targetLanguageId, nativeLanguageId: nativeLanguageId, isPublic: true } }); if (existingCourse) { console.log(` ⚠️ Kurs "${langConfig.title}" existiert bereits (ID: ${existingCourse.id})`); createdCourses.push({ ...langConfig, courseId: existingCourse.id, targetLanguageId, nativeLanguageId, skipped: true }); continue; } // Erstelle Kurs const course = await createCourseForLanguage(targetLanguageId, nativeLanguageId, langConfig, systemUser.id); createdCourses.push({ ...langConfig, courseId: course.id, targetLanguageId, nativeLanguageId, shareCode: course.shareCode }); } console.log(`\n\n🎉 Zusammenfassung:\n`); console.log(` Gesamt: ${LANGUAGE_COURSES.length} Sprachen`); console.log(` Erstellt: ${createdCourses.filter(c => !c.skipped).length} Kurse`); console.log(` Übersprungen: ${createdCourses.filter(c => c.skipped).length} Kurse`); console.log(`\n📋 Erstellte Kurse:\n`); for (const course of createdCourses) { if (course.skipped) { console.log(` ⚠️ ${course.languageName}: Bereits vorhanden (ID: ${course.courseId})`); } else { console.log(` ✅ ${course.languageName}: ${course.title}`); console.log(` Share-Code: ${course.shareCode}`); } } console.log(`\n💡 Nächste Schritte:`); console.log(` 1. Füge Vokabeln zu den Vokabel-Lektionen hinzu`); console.log(` 2. Erstelle Grammatik-Übungen für die Grammatik-Lektionen`); console.log(` 3. Teile die Kurse mit anderen (Share-Codes oben)`); return createdCourses; } catch (error) { console.error('❌ Fehler beim Erstellen der Kurse:', error); throw error; } } // CLI-Aufruf // Keine Parameter mehr nötig - verwendet automatisch System-Benutzer createAllLanguageCourses() .then(() => { process.exit(0); }) .catch((error) => { console.error(error); process.exit(1); });