From 0572a0eb505db7fe4cfa0eb3a2b6da9b2dbb3f51 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 19 Jan 2026 15:15:24 +0100 Subject: [PATCH] Add grammar exercise creation in course generation - Integrated functionality to create example grammar exercises for grammar lessons during course creation. - Added a new helper function to generate gap fill and multiple choice exercises based on lesson data. - Enhanced logging to confirm the number of grammar exercises created, improving feedback during course setup. --- ...d-grammar-exercises-to-existing-courses.js | 141 ++++++++ .../scripts/create-bisaya-course-content.js | 327 ++++++++++++++++++ backend/scripts/create-language-courses.js | 70 +++- 3 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 backend/scripts/add-grammar-exercises-to-existing-courses.js create mode 100644 backend/scripts/create-bisaya-course-content.js diff --git a/backend/scripts/add-grammar-exercises-to-existing-courses.js b/backend/scripts/add-grammar-exercises-to-existing-courses.js new file mode 100644 index 0000000..6f65143 --- /dev/null +++ b/backend/scripts/add-grammar-exercises-to-existing-courses.js @@ -0,0 +1,141 @@ +#!/usr/bin/env node +/** + * Script zum Hinzufügen von Grammatik-Übungen zu bestehenden Kursen + * + * Verwendung: + * node backend/scripts/add-grammar-exercises-to-existing-courses.js + * + * Fügt Beispiel-Grammatik-Übungen zu allen Grammar-Lektionen hinzu, die noch keine Übungen haben. + */ + +import { sequelize } from '../utils/sequelize.js'; +import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'; +import User from '../models/community/user.js'; + +async function findOrCreateSystemUser() { + let systemUser = await User.findOne({ + where: { + username: 'system' + } + }); + + if (!systemUser) { + systemUser = await User.findOne({ + where: { + username: 'admin' + } + }); + } + + if (!systemUser) { + console.error('❌ System-Benutzer nicht gefunden. Bitte erstelle einen System-Benutzer.'); + throw new Error('System user not found'); + } + + return systemUser; +} + +// Erstelle Beispiel-Grammatik-Übungen für eine Grammar-Lektion +function createExampleGrammarExercises(lessonId, lessonTitle, ownerUserId) { + const exercises = []; + + // Beispiel-Übung 1: Gap Fill (Lückentext) + exercises.push({ + lessonId: lessonId, + exerciseTypeId: 1, // gap_fill + exerciseNumber: 1, + title: `${lessonTitle} - Übung 1`, + instruction: 'Fülle die Lücken mit den richtigen Wörtern.', + questionData: JSON.stringify({ + type: 'gap_fill', + text: 'Hallo! Wie geht es {gap}? Mir geht es {gap}, danke!', + gaps: 2 + }), + answerData: JSON.stringify({ + type: 'gap_fill', + answers: ['dir', 'gut'] + }), + explanation: 'Die richtigen Antworten sind "dir" und "gut".', + createdByUserId: ownerUserId + }); + + // Beispiel-Übung 2: Multiple Choice + exercises.push({ + lessonId: lessonId, + exerciseTypeId: 2, // multiple_choice + exerciseNumber: 2, + title: `${lessonTitle} - Übung 2`, + instruction: 'Wähle die richtige Antwort aus.', + questionData: JSON.stringify({ + type: 'multiple_choice', + question: 'Wie sagt man "Guten Tag"?', + options: ['Guten Tag', 'Gute Nacht', 'Auf Wiedersehen', 'Tschüss'] + }), + answerData: JSON.stringify({ + type: 'multiple_choice', + correctAnswer: 0 + }), + explanation: 'Die richtige Antwort ist "Guten Tag".', + createdByUserId: ownerUserId + }); + + return exercises; +} + +async function addGrammarExercisesToExistingCourses() { + await sequelize.authenticate(); + console.log('Datenbankverbindung erfolgreich hergestellt.\n'); + + const systemUser = await findOrCreateSystemUser(); + console.log(`Verwende System-Benutzer: ${systemUser.username} (ID: ${systemUser.id})\n`); + + // Finde alle Grammar-Lektionen ohne Übungen + const grammarLessons = await sequelize.query( + `SELECT l.id, l.title, l.course_id, c.owner_user_id + FROM community.vocab_course_lesson l + JOIN community.vocab_course c ON c.id = l.course_id + WHERE l.lesson_type = 'grammar' + AND NOT EXISTS ( + SELECT 1 FROM community.vocab_grammar_exercise e + WHERE e.lesson_id = l.id + ) + ORDER BY l.course_id, l.lesson_number`, + { + type: sequelize.QueryTypes.SELECT + } + ); + + console.log(`Gefunden: ${grammarLessons.length} Grammar-Lektionen ohne Übungen\n`); + + if (grammarLessons.length === 0) { + console.log('✅ Alle Grammar-Lektionen haben bereits Übungen.'); + return; + } + + let addedCount = 0; + for (const lesson of grammarLessons) { + const exercises = createExampleGrammarExercises(lesson.id, lesson.title, lesson.owner_user_id); + + for (const exercise of exercises) { + await VocabGrammarExercise.create(exercise); + addedCount++; + } + + console.log(`✅ ${exercises.length} Übungen zu "${lesson.title}" hinzugefügt`); + } + + console.log(`\n🎉 Zusammenfassung:`); + console.log(` ${addedCount} Grammatik-Übungen zu ${grammarLessons.length} Lektionen hinzugefügt`); +} + +addGrammarExercisesToExistingCourses() + .then(() => { + sequelize.close(); + process.exit(0); + }) + .catch((error) => { + console.error('❌ Fehler:', error); + sequelize.close(); + process.exit(1); + }); diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js new file mode 100644 index 0000000..3267f34 --- /dev/null +++ b/backend/scripts/create-bisaya-course-content.js @@ -0,0 +1,327 @@ +#!/usr/bin/env node +/** + * Script zum Erstellen von sprachspezifischem Content für Bisaya-Kurse + * + * Verwendung: + * node backend/scripts/create-bisaya-course-content.js + * + * Erstellt Grammatik-Übungen für alle Lektionen in Bisaya-Kursen basierend auf dem Thema der Lektion. + */ + +import { sequelize } from '../utils/sequelize.js'; +import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'; +import VocabCourse from '../models/community/vocab_course.js'; +import User from '../models/community/user.js'; + +// Bisaya-spezifische Übungen basierend auf Lektionsthemen +const BISAYA_EXERCISES = { + // Lektion 1: Begrüßungen & Höflichkeit + 'Begrüßungen & Höflichkeit': [ + { + exerciseTypeId: 2, // multiple_choice + title: 'Wie sagt man "Wie geht es dir?" auf Bisaya?', + instruction: 'Wähle die richtige Begrüßung aus.', + questionData: { + type: 'multiple_choice', + question: 'Wie sagt man "Wie geht es dir?" auf Bisaya?', + options: ['Kumusta ka?', 'Maayo', 'Salamat', 'Palihug'] + }, + answerData: { + type: 'multiple_choice', + correctAnswer: 0 + }, + explanation: '"Kumusta ka?" ist die Standard-Begrüßung auf Bisaya, ähnlich wie "Wie geht es dir?"' + }, + { + exerciseTypeId: 1, // gap_fill + title: 'Begrüßungen vervollständigen', + instruction: 'Fülle die Lücken mit den richtigen Bisaya-Wörtern.', + questionData: { + type: 'gap_fill', + text: 'Kumusta ka? Maayo {gap}. Salamat {gap} palihug.', + gaps: 2 + }, + answerData: { + type: 'gap_fill', + answers: ['ko', 'ug'] + }, + explanation: '"Maayo ko" bedeutet "Mir geht es gut" und "ug" verbindet die Wörter.' + }, + { + exerciseTypeId: 2, // multiple_choice + title: 'Was bedeutet "Salamat"?', + instruction: 'Wähle die richtige Übersetzung.', + questionData: { + type: 'multiple_choice', + question: 'Was bedeutet "Salamat"?', + options: ['Danke', 'Bitte', 'Entschuldigung', 'Auf Wiedersehen'] + }, + answerData: { + type: 'multiple_choice', + correctAnswer: 0 + }, + explanation: '"Salamat" bedeutet "Danke" auf Bisaya.' + } + ], + + // Lektion 15: Zeitformen - Grundlagen + 'Zeitformen - Grundlagen': [ + { + exerciseTypeId: 4, // transformation + title: 'Vergangenheit verstehen', + instruction: 'Was bedeutet "Ni-kaon ko"?', + questionData: { + type: 'transformation', + text: 'Ni-kaon ko', + sourceLanguage: 'Bisaya', + targetLanguage: 'Deutsch' + }, + answerData: { + type: 'transformation', + correct: 'Ich habe gegessen', + alternatives: ['I ate', 'I have eaten'] + }, + explanation: 'Das Präfix "Ni-" zeigt die Vergangenheit an. "Ni-kaon ko" bedeutet "Ich habe gegessen".' + }, + { + exerciseTypeId: 4, // transformation + title: 'Zukunft verstehen', + instruction: 'Was bedeutet "Mo-kaon ko"?', + questionData: { + type: 'transformation', + text: 'Mo-kaon ko', + sourceLanguage: 'Bisaya', + targetLanguage: 'Deutsch' + }, + answerData: { + type: 'transformation', + correct: 'Ich werde essen', + alternatives: ['I will eat', 'I am going to eat'] + }, + explanation: 'Das Präfix "Mo-" zeigt die Zukunft an. "Mo-kaon ko" bedeutet "Ich werde essen".' + }, + { + exerciseTypeId: 1, // gap_fill + title: 'Zeitformen erkennen', + instruction: 'Setze die richtigen Präfixe ein.', + questionData: { + type: 'gap_fill', + text: '{gap}-kaon ko (Vergangenheit) | {gap}-kaon ko (Zukunft)', + gaps: 2 + }, + answerData: { + type: 'gap_fill', + answers: ['Ni', 'Mo'] + }, + explanation: 'Ni- für Vergangenheit, Mo- für Zukunft.' + } + ], + + // Lektion 25: Höflichkeitsformen + 'Höflichkeitsformen': [ + { + exerciseTypeId: 2, // multiple_choice + title: 'Wie sagt man "Bitte langsam"?', + instruction: 'Wähle die höfliche Form aus.', + questionData: { + type: 'multiple_choice', + question: 'Wie sagt man "Bitte langsam" auf Bisaya?', + options: ['Hinay-hinay lang', 'Palihug', 'Salamat', 'Maayo'] + }, + answerData: { + type: 'multiple_choice', + correctAnswer: 0 + }, + explanation: '"Hinay-hinay lang" bedeutet "Bitte langsam" und ist sehr höflich.' + }, + { + exerciseTypeId: 1, // gap_fill + title: 'Höfliche Sätze vervollständigen', + instruction: 'Fülle die Lücken mit höflichen Wörtern.', + questionData: { + type: 'gap_fill', + text: '{gap} lang, wala ko kasabot. {gap} ka mubalik?', + gaps: 2 + }, + answerData: { + type: 'gap_fill', + answers: ['Hinay-hinay', 'Palihug'] + }, + explanation: '"Hinay-hinay lang" = "Bitte langsam", "Palihug" = "Bitte".' + }, + { + exerciseTypeId: 2, // multiple_choice + title: 'Was bedeutet "Palihug"?', + instruction: 'Wähle die richtige Übersetzung.', + questionData: { + type: 'multiple_choice', + question: 'Was bedeutet "Palihug"?', + options: ['Bitte', 'Danke', 'Entschuldigung', 'Gern geschehen'] + }, + answerData: { + type: 'multiple_choice', + correctAnswer: 0 + }, + explanation: '"Palihug" bedeutet "Bitte" auf Bisaya und wird für höfliche Bitten verwendet.' + } + ] +}; + +async function findOrCreateSystemUser() { + let systemUser = await User.findOne({ + where: { + username: 'system' + } + }); + + if (!systemUser) { + systemUser = await User.findOne({ + where: { + username: 'admin' + } + }); + } + + if (!systemUser) { + console.error('❌ System-Benutzer nicht gefunden.'); + throw new Error('System user not found'); + } + + return systemUser; +} + +function getExercisesForLesson(lessonTitle) { + // Suche nach exaktem Titel + if (BISAYA_EXERCISES[lessonTitle]) { + return BISAYA_EXERCISES[lessonTitle]; + } + + // Fallback: Suche nach Teilstring + for (const [key, exercises] of Object.entries(BISAYA_EXERCISES)) { + if (lessonTitle.includes(key) || key.includes(lessonTitle)) { + return exercises; + } + } + + // Standard-Übungen für unbekannte Lektionen + return [ + { + exerciseTypeId: 2, // multiple_choice + title: `${lessonTitle} - Übung 1`, + instruction: 'Wähle die richtige Antwort aus.', + questionData: { + type: 'multiple_choice', + question: 'Was ist die richtige Übersetzung?', + options: ['Option A', 'Option B', 'Option C', 'Option D'] + }, + answerData: { + type: 'multiple_choice', + correctAnswer: 0 + }, + explanation: 'Dies ist eine Beispiel-Übung. Bitte füge sprachspezifische Inhalte hinzu.' + } + ]; +} + +async function createBisayaCourseContent() { + await sequelize.authenticate(); + console.log('Datenbankverbindung erfolgreich hergestellt.\n'); + + const systemUser = await findOrCreateSystemUser(); + console.log(`Verwende System-Benutzer: ${systemUser.username} (ID: ${systemUser.id})\n`); + + // Finde alle Bisaya-Kurse + const [bisayaLanguage] = await sequelize.query( + `SELECT id FROM community.vocab_language WHERE name = 'Bisaya' LIMIT 1`, + { + type: sequelize.QueryTypes.SELECT + } + ); + + if (!bisayaLanguage) { + console.error('❌ Bisaya-Sprache nicht gefunden.'); + return; + } + + const courses = await sequelize.query( + `SELECT id, title FROM community.vocab_course WHERE language_id = :languageId`, + { + replacements: { languageId: bisayaLanguage.id }, + type: sequelize.QueryTypes.SELECT + } + ); + + console.log(`Gefunden: ${courses.length} Bisaya-Kurse\n`); + + let totalExercisesAdded = 0; + let totalLessonsProcessed = 0; + + for (const course of courses) { + console.log(`📚 Kurs: ${course.title} (ID: ${course.id})`); + + const lessons = await VocabCourseLesson.findAll({ + where: { courseId: course.id }, + order: [['lessonNumber', 'ASC']] + }); + + console.log(` ${lessons.length} Lektionen gefunden\n`); + + for (const lesson of lessons) { + // Prüfe, ob bereits Übungen existieren + const existingCount = await VocabGrammarExercise.count({ + where: { lessonId: lesson.id } + }); + + if (existingCount > 0) { + console.log(` ⏭️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - bereits ${existingCount} Übung(en) vorhanden`); + continue; + } + + // Hole Übungen für diese Lektion + const exercises = getExercisesForLesson(lesson.title); + + if (exercises.length === 0) { + console.log(` ⚠️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - keine Übungen definiert`); + continue; + } + + // Erstelle Übungen + let exerciseNumber = 1; + for (const exerciseData of exercises) { + await VocabGrammarExercise.create({ + lessonId: lesson.id, + exerciseTypeId: exerciseData.exerciseTypeId, + exerciseNumber: exerciseNumber++, + title: exerciseData.title, + instruction: exerciseData.instruction, + questionData: JSON.stringify(exerciseData.questionData), + answerData: JSON.stringify(exerciseData.answerData), + explanation: exerciseData.explanation, + createdByUserId: course.ownerUserId || systemUser.id + }); + totalExercisesAdded++; + } + + console.log(` ✅ Lektion ${lesson.lessonNumber}: "${lesson.title}" - ${exercises.length} Übung(en) erstellt`); + totalLessonsProcessed++; + } + + console.log(''); + } + + console.log(`\n🎉 Zusammenfassung:`); + console.log(` ${totalLessonsProcessed} Lektionen bearbeitet`); + console.log(` ${totalExercisesAdded} Grammatik-Übungen erstellt`); +} + +createBisayaCourseContent() + .then(() => { + sequelize.close(); + process.exit(0); + }) + .catch((error) => { + console.error('❌ Fehler:', error); + sequelize.close(); + process.exit(1); + }); diff --git a/backend/scripts/create-language-courses.js b/backend/scripts/create-language-courses.js index d248d90..54e472c 100755 --- a/backend/scripts/create-language-courses.js +++ b/backend/scripts/create-language-courses.js @@ -15,6 +15,7 @@ import { sequelize } from '../utils/sequelize.js'; import VocabCourse from '../models/community/vocab_course.js'; import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'; import User from '../models/community/user.js'; import crypto from 'crypto'; import bcrypt from 'bcryptjs'; @@ -330,8 +331,9 @@ async function createCourseForLanguage(targetLanguageId, nativeLanguageId, langu console.log(` ✅ Kurs erstellt: "${course.title}" (ID: ${course.id}, Share-Code: ${shareCode})`); // Erstelle Lektionen + const createdLessons = []; for (const lessonData of LESSON_TEMPLATE) { - await VocabCourseLesson.create({ + const lesson = await VocabCourseLesson.create({ courseId: course.id, chapterId: null, lessonNumber: lessonData.num, @@ -345,12 +347,78 @@ async function createCourseForLanguage(targetLanguageId, nativeLanguageId, langu targetScorePercent: lessonData.targetScore, requiresReview: lessonData.review }); + createdLessons.push({ lesson, lessonData }); } console.log(` ✅ ${LESSON_TEMPLATE.length} Lektionen erstellt`); + + // Erstelle Beispiel-Grammatik-Übungen für Grammar-Lektionen + let grammarExerciseCount = 0; + for (const { lesson, lessonData } of createdLessons) { + if (lessonData.type === 'grammar') { + // Erstelle 2-3 Beispiel-Übungen für jede Grammar-Lektion + const exercises = createExampleGrammarExercises(lesson.id, lessonData, ownerUserId); + for (const exercise of exercises) { + await VocabGrammarExercise.create(exercise); + grammarExerciseCount++; + } + } + } + + if (grammarExerciseCount > 0) { + console.log(` ✅ ${grammarExerciseCount} Grammatik-Übungen erstellt`); + } + return course; } +// Erstelle Beispiel-Grammatik-Übungen für eine Grammar-Lektion +function createExampleGrammarExercises(lessonId, lessonData, ownerUserId) { + const exercises = []; + + // Beispiel-Übung 1: Gap Fill (Lückentext) + exercises.push({ + lessonId: lessonId, + exerciseTypeId: 1, // gap_fill + exerciseNumber: 1, + title: `${lessonData.title} - Übung 1`, + instruction: 'Fülle die Lücken mit den richtigen Wörtern.', + questionData: JSON.stringify({ + type: 'gap_fill', + text: 'Hallo! Wie geht es {gap}? Mir geht es {gap}, danke!', + gaps: 2 + }), + answerData: JSON.stringify({ + type: 'gap_fill', + answers: ['dir', 'gut'] + }), + explanation: 'Die richtigen Antworten sind "dir" und "gut".', + createdByUserId: ownerUserId + }); + + // Beispiel-Übung 2: Multiple Choice + exercises.push({ + lessonId: lessonId, + exerciseTypeId: 2, // multiple_choice + exerciseNumber: 2, + title: `${lessonData.title} - Übung 2`, + instruction: 'Wähle die richtige Antwort aus.', + questionData: JSON.stringify({ + type: 'multiple_choice', + question: 'Wie sagt man "Guten Tag" auf ' + lessonData.title.split(' - ')[0] + '?', + options: ['Option A', 'Option B', 'Option C', 'Option D'] + }), + answerData: JSON.stringify({ + type: 'multiple_choice', + correctAnswer: 0 + }), + explanation: 'Die richtige Antwort ist Option A.', + createdByUserId: ownerUserId + }); + + return exercises; +} + async function findOrCreateSystemUser() { // Versuche zuerst einen System-Benutzer zu finden (z.B. mit username "system" oder "admin") let systemUser = await User.findOne({