- 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.
558 lines
20 KiB
JavaScript
Executable File
558 lines
20 KiB
JavaScript
Executable File
#!/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 VocabGrammarExercise from '../models/community/vocab_grammar_exercise.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
|
||
const createdLessons = [];
|
||
for (const lessonData of LESSON_TEMPLATE) {
|
||
const lesson = 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
|
||
});
|
||
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({
|
||
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);
|
||
});
|