diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js index 97dfe1a..b96e5a4 100644 --- a/backend/scripts/create-bisaya-course-content.js +++ b/backend/scripts/create-bisaya-course-content.js @@ -738,7 +738,7 @@ async function createBisayaCourseContent() { } const courses = await sequelize.query( - `SELECT id, title FROM community.vocab_course WHERE language_id = :languageId`, + `SELECT id, title, owner_user_id AS "ownerUserId" FROM community.vocab_course WHERE language_id = :languageId`, { replacements: { languageId: bisayaLanguage.id }, type: sequelize.QueryTypes.SELECT @@ -761,24 +761,33 @@ async function createBisayaCourseContent() { console.log(` ${lessons.length} Lektionen gefunden\n`); for (const lesson of lessons) { - // Prüfe, ob bereits Übungen existieren + const exercises = getExercisesForLesson(lesson.title); + if (exercises.length === 0) { + const existingCount = await VocabGrammarExercise.count({ where: { lessonId: lesson.id } }); + if (existingCount > 0) { + console.log(` ⏭️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - bereits ${existingCount} Übung(en) vorhanden`); + } else { + console.log(` ⚠️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - keine Übungen definiert`); + } + continue; + } + + // Bei Woche-1-Wiederholung/Vokabeltest: Alte Platzhalter entfernen und ersetzen + const replacePlaceholders = ['Woche 1 - Wiederholung', 'Woche 1 - Vokabeltest'].includes(lesson.title); const existingCount = await VocabGrammarExercise.count({ where: { lessonId: lesson.id } }); - if (existingCount > 0) { + if (existingCount > 0 && !replacePlaceholders) { 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; + if (replacePlaceholders && existingCount > 0) { + const deleted = await VocabGrammarExercise.destroy({ where: { lessonId: lesson.id } }); + console.log(` 🗑️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - ${deleted} Platzhalter entfernt`); } - + // Erstelle Übungen let exerciseNumber = 1; for (const exerciseData of exercises) { diff --git a/backend/scripts/update-week1-bisaya-exercises.js b/backend/scripts/update-week1-bisaya-exercises.js new file mode 100644 index 0000000..a5116e5 --- /dev/null +++ b/backend/scripts/update-week1-bisaya-exercises.js @@ -0,0 +1,129 @@ +#!/usr/bin/env node +/** + * Script zum Aktualisieren der Woche-1-Lektionen in Bisaya-Kursen + * + * Verwendung: + * node backend/scripts/update-week1-bisaya-exercises.js + * + * - Entfernt alte Platzhalter-Übungen + * - Ersetzt durch korrekte Inhalte für "Woche 1 - Wiederholung" und "Woche 1 - Vokabeltest" + */ + +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'; + +const LESSON_TITLES = ['Woche 1 - Wiederholung', 'Woche 1 - Vokabeltest']; + +const BISAYA_EXERCISES = { + 'Woche 1 - Wiederholung': [ + { exerciseTypeId: 2, title: 'Wiederholung: Wie sagt man "Wie geht es dir?"?', 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.' }, + { exerciseTypeId: 2, title: 'Wiederholung: Wie sagt man "Mutter" auf Bisaya?', instruction: 'Wähle die richtige Übersetzung.', questionData: { type: 'multiple_choice', question: 'Wie sagt man "Mutter" auf Bisaya?', options: ['Nanay', 'Tatay', 'Kuya', 'Ate'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Nanay" bedeutet "Mutter" auf Bisaya.' }, + { exerciseTypeId: 2, title: 'Wiederholung: Was bedeutet "Palangga taka"?', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Palangga taka"?', options: ['Ich hab dich lieb', 'Danke', 'Guten Tag', 'Auf Wiedersehen'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Palangga taka" bedeutet "Ich hab dich lieb" - wärmer als "I love you" im Familienkontext.' }, + { exerciseTypeId: 2, title: 'Wiederholung: Was fragt man mit "Nikaon ka?"?', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Nikaon ka?"?', options: ['Hast du schon gegessen?', 'Wie geht es dir?', 'Danke', 'Bitte'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Nikaon ka?" bedeutet "Hast du schon gegessen?" - typisch fürsorglich auf den Philippinen.' }, + { exerciseTypeId: 2, title: 'Wiederholung: Wie sagt man "Ich verstehe nicht"?', instruction: 'Wähle die richtige Übersetzung.', questionData: { type: 'multiple_choice', question: 'Wie sagt man "Ich verstehe nicht" auf Bisaya?', options: ['Wala ko kasabot', 'Salamat', 'Maayo', 'Palihug'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Wala ko kasabot" bedeutet "Ich verstehe nicht".' } + ], + 'Woche 1 - Vokabeltest': [ + { exerciseTypeId: 2, title: 'Vokabeltest: Kumusta', instruction: 'Was bedeutet "Kumusta"?', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Kumusta"?', options: ['Wie geht es dir?', 'Danke', 'Bitte', 'Auf Wiedersehen'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Kumusta" kommt von spanisch "¿Cómo está?" - "Wie geht es dir?"' }, + { exerciseTypeId: 2, title: 'Vokabeltest: Lola', instruction: 'Wähle die richtige Übersetzung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Lola"?', options: ['Großmutter', 'Großvater', 'Mutter', 'Vater'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Lola" = Großmutter, "Lolo" = Großvater.' }, + { exerciseTypeId: 2, title: 'Vokabeltest: Salamat', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Salamat"?', options: ['Danke', 'Bitte', 'Entschuldigung', 'Gern geschehen'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Salamat" bedeutet "Danke".' }, + { exerciseTypeId: 2, title: 'Vokabeltest: Lami', instruction: 'Was bedeutet "Lami"?', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Lami"?', options: ['Lecker', 'Viel', 'Gut', 'Schnell'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Lami" bedeutet "lecker" oder "schmackhaft" - wichtig beim Essen!' }, + { exerciseTypeId: 2, title: 'Vokabeltest: Mingaw ko nimo', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Mingaw ko nimo"?', options: ['Ich vermisse dich', 'Ich freue mich', 'Ich mag dich', 'Ich liebe dich'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Mingaw ko nimo" bedeutet "Ich vermisse dich".' } + ] +}; + +async function updateWeek1BisayaExercises() { + await sequelize.authenticate(); + console.log('Datenbankverbindung erfolgreich hergestellt.\n'); + + let systemUser; + try { + systemUser = await User.findOne({ where: { username: 'system' } }); + if (!systemUser) systemUser = await User.findOne({ where: { username: 'admin' } }); + if (!systemUser) throw new Error('System user not found'); + } catch (e) { + console.error('❌ System-Benutzer nicht gefunden.'); + throw e; + } + + 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 c.id, c.title, c.owner_user_id + FROM community.vocab_course c + WHERE c.language_id = :languageId`, + { + replacements: { languageId: bisayaLanguage.id }, + type: sequelize.QueryTypes.SELECT + } + ); + + console.log(`Gefunden: ${courses.length} Bisaya-Kurs(e)\n`); + + let totalDeleted = 0; + let totalAdded = 0; + + for (const course of courses) { + console.log(`📚 Kurs: ${course.title} (ID: ${course.id})`); + + for (const lessonTitle of LESSON_TITLES) { + const exercises = BISAYA_EXERCISES[lessonTitle]; + if (!exercises || exercises.length === 0) continue; + + const lessons = await VocabCourseLesson.findAll({ + where: { courseId: course.id, title: lessonTitle }, + order: [['lessonNumber', 'ASC']] + }); + + for (const lesson of lessons) { + const deletedCount = await VocabGrammarExercise.destroy({ + where: { lessonId: lesson.id } + }); + totalDeleted += deletedCount; + console.log(` 🗑️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - ${deletedCount} alte Übung(en) entfernt`); + + let exerciseNumber = 1; + for (const ex of exercises) { + await VocabGrammarExercise.create({ + lessonId: lesson.id, + exerciseTypeId: ex.exerciseTypeId, + exerciseNumber: exerciseNumber++, + title: ex.title, + instruction: ex.instruction, + questionData: JSON.stringify(ex.questionData), + answerData: JSON.stringify(ex.answerData), + explanation: ex.explanation, + createdByUserId: course.owner_user_id || systemUser.id + }); + totalAdded++; + } + console.log(` ✅ Lektion ${lesson.lessonNumber}: "${lesson.title}" - ${exercises.length} neue Übung(en)`); + } + } + console.log(''); + } + + console.log(`\n🎉 Zusammenfassung:`); + console.log(` ${totalDeleted} Platzhalter-Übungen entfernt`); + console.log(` ${totalAdded} neue Übungen erstellt`); +} + +updateWeek1BisayaExercises() + .then(() => { + sequelize.close(); + process.exit(0); + }) + .catch((error) => { + console.error('❌ Fehler:', error); + sequelize.close(); + process.exit(1); + }); diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 8b0b46b..318f7fa 100644 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -24,7 +24,7 @@ class="tab-button" > {{ $t('socialnetwork.vocab.courses.exercises') }} - ({{ lesson?.grammarExercises?.length || 0 }}) + ({{ effectiveExercises?.length || 0 }}) @@ -160,11 +160,11 @@ - +
-
+

{{ $t('socialnetwork.vocab.courses.grammarExercises') }}

-
+

{{ exercise.title }}

{{ exercise.instruction }}

@@ -351,7 +351,7 @@
-
+

{{ $t('socialnetwork.vocab.courses.noExercises') }}

@@ -465,22 +465,33 @@ export default { computed: { ...mapGetters(['user']), hasExercises() { - if (!this.lesson) return false; - if (!this.lesson.grammarExercises) return false; - if (!Array.isArray(this.lesson.grammarExercises)) return false; - return this.lesson.grammarExercises.length > 0; + const exercises = this.effectiveExercises; + return exercises && Array.isArray(exercises) && exercises.length > 0; + }, + /** Für Wiederholungslektionen: Übungen aus vorherigen Lektionen (Kapitelprüfung). Sonst: eigene Grammatik-Übungen. */ + effectiveExercises() { + if (!this.lesson) return []; + const isReview = this.lesson.lessonType === 'review' || this.lesson.lessonType === 'vocab_review'; + if (isReview && this.lesson.reviewVocabExercises && Array.isArray(this.lesson.reviewVocabExercises) && this.lesson.reviewVocabExercises.length > 0) { + return this.lesson.reviewVocabExercises; + } + if (this.lesson.grammarExercises && Array.isArray(this.lesson.grammarExercises)) { + return this.lesson.grammarExercises; + } + return []; }, grammarExplanations() { // Extrahiere Grammatik-Erklärungen aus den Übungen try { - if (!this.lesson || !this.lesson.grammarExercises || !Array.isArray(this.lesson.grammarExercises)) { + const exercises = this.effectiveExercises; + if (!exercises || !Array.isArray(exercises) || exercises.length === 0) { return []; } const explanations = []; const seen = new Set(); - this.lesson.grammarExercises.forEach(exercise => { + exercises.forEach(exercise => { if (exercise.explanation && !seen.has(exercise.explanation)) { seen.add(exercise.explanation); explanations.push({ @@ -499,24 +510,14 @@ export default { importantVocab() { // Extrahiere wichtige Begriffe aus den Übungen try { - // Bei Wiederholungslektionen: Verwende Vokabeln aus vorherigen Lektionen - if (this.lesson && (this.lesson.lessonType === 'review' || this.lesson.lessonType === 'vocab_review')) { - if (this.lesson.reviewVocabExercises && Array.isArray(this.lesson.reviewVocabExercises)) { - console.log('[importantVocab] Wiederholungslektion - verwende reviewVocabExercises:', this.lesson.reviewVocabExercises.length); - return this._extractVocabFromExercises(this.lesson.reviewVocabExercises); - } else { - console.log('[importantVocab] Wiederholungslektion aber keine reviewVocabExercises vorhanden'); - return []; - } - } - - // Normale Lektion: Verwende Übungen aus der aktuellen Lektion - if (!this.lesson || !this.lesson.grammarExercises || !Array.isArray(this.lesson.grammarExercises)) { + // Bei Wiederholungslektionen: Verwende Vokabeln aus vorherigen Lektionen (effectiveExercises = reviewVocabExercises) + // Normale Lektion: Verwende effectiveExercises (grammarExercises) + const exercises = this.effectiveExercises; + if (!exercises || !Array.isArray(exercises) || exercises.length === 0) { console.log('[importantVocab] Keine Übungen vorhanden'); return []; } - - return this._extractVocabFromExercises(this.lesson.grammarExercises); + return this._extractVocabFromExercises(exercises); } catch (e) { console.error('Fehler in importantVocab computed property:', e); return []; @@ -706,19 +707,17 @@ export default { const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}`); this.lesson = res.data; console.log('[VocabLessonView] Geladene Lektion:', this.lesson?.id, this.lesson?.title); - console.log('[VocabLessonView] Übungen:', this.lesson?.grammarExercises?.length || 0); - // Initialisiere Übungen aus der Lektion oder lade sie separat - if (this.lesson && this.lesson.id) { - if (this.lesson.grammarExercises && this.lesson.grammarExercises.length > 0) { - // Übungen sind bereits in der Lektion enthalten - console.log('[VocabLessonView] Übungen bereits in Lektion enthalten:', this.lesson.grammarExercises.length); - this.initializeExercises(this.lesson.grammarExercises); + // Initialisiere mit effectiveExercises (für Review: reviewVocabExercises, sonst: grammarExercises) + this.$nextTick(async () => { + const exercises = this.effectiveExercises; + if (exercises && exercises.length > 0) { + console.log('[VocabLessonView] Übungen für Kapitel-Prüfung:', exercises.length); + this.initializeExercises(exercises); } else { - // Lade Übungen separat (Fallback) console.log('[VocabLessonView] Lade Übungen separat...'); await this.loadGrammarExercises(); } - } + }); console.log('[VocabLessonView] loadLesson abgeschlossen'); } catch (e) { console.error('[VocabLessonView] Fehler beim Laden der Lektion:', e); @@ -826,7 +825,7 @@ export default { }, async checkAnswer(exerciseId) { try { - const exercise = this.lesson.grammarExercises.find(e => e.id === exerciseId); + const exercise = this.effectiveExercises.find(e => e.id === exerciseId); if (!exercise) return; const exerciseType = this.getExerciseType(exercise); @@ -869,13 +868,12 @@ export default { return; } - // Prüfe ob alle Übungen korrekt beantwortet wurden - if (!this.lesson || !this.lesson.grammarExercises) { + // Prüfe ob alle Übungen korrekt beantwortet wurden (effectiveExercises = Kapitel-Prüfung) + const allExercises = this.effectiveExercises; + if (!this.lesson || !allExercises || allExercises.length === 0) { console.log('[VocabLessonView] checkLessonCompletion übersprungen - keine Lektion/Übungen'); return; } - - const allExercises = this.lesson.grammarExercises; if (allExercises.length === 0) { console.log('[VocabLessonView] checkLessonCompletion übersprungen - keine Übungen'); return; @@ -1261,7 +1259,7 @@ export default { return !!this.activeRecognition[exerciseId]; }, startReadingAloud(exerciseId) { - const exercise = this.lesson.grammarExercises.find(e => e.id === exerciseId); + const exercise = this.effectiveExercises.find(e => e.id === exerciseId); if (!exercise) return; const qData = this.getQuestionData(exercise); @@ -1273,7 +1271,7 @@ export default { this.stopRecognition(exerciseId); }, startSpeakingFromMemory(exerciseId) { - const exercise = this.lesson.grammarExercises.find(e => e.id === exerciseId); + const exercise = this.effectiveExercises.find(e => e.id === exerciseId); if (!exercise) return; const qData = this.getQuestionData(exercise);