diff --git a/backend/controllers/vocabController.js b/backend/controllers/vocabController.js index 5af80b0..3cb6dbc 100644 --- a/backend/controllers/vocabController.js +++ b/backend/controllers/vocabController.js @@ -32,6 +32,7 @@ class VocabController { this.deleteCourse = this._wrapWithUser((userId, req) => this.service.deleteCourse(userId, req.params.courseId)); // Lessons + this.getLesson = this._wrapWithUser((userId, req) => this.service.getLesson(userId, req.params.lessonId)); this.addLessonToCourse = this._wrapWithUser((userId, req) => this.service.addLessonToCourse(userId, req.params.courseId, req.body), { successStatus: 201 }); this.updateLesson = this._wrapWithUser((userId, req) => this.service.updateLesson(userId, req.params.lessonId, req.body)); this.deleteLesson = this._wrapWithUser((userId, req) => this.service.deleteLesson(userId, req.params.lessonId)); diff --git a/backend/routers/vocabRouter.js b/backend/routers/vocabRouter.js index bbc6d5f..5585ebe 100644 --- a/backend/routers/vocabRouter.js +++ b/backend/routers/vocabRouter.js @@ -43,6 +43,7 @@ router.delete('/courses/:courseId/enroll', vocabController.unenrollFromCourse); // Progress router.get('/courses/:courseId/progress', vocabController.getCourseProgress); +router.get('/lessons/:lessonId', vocabController.getLesson); router.put('/lessons/:lessonId/progress', vocabController.updateLessonProgress); // Grammar Exercises diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index 8e65f8b..10e7fa5 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -846,6 +846,33 @@ export default class VocabService { return { success: true }; } + async getLesson(hashedUserId, lessonId) { + const user = await this._getUserByHashedId(hashedUserId); + const lesson = await VocabCourseLesson.findByPk(lessonId, { + include: [ + { + model: VocabCourse, + as: 'course' + } + ] + }); + + if (!lesson) { + const err = new Error('Lesson not found'); + err.status = 404; + throw err; + } + + // Prüfe Zugriff + if (lesson.course.ownerUserId !== user.id && !lesson.course.isPublic) { + const err = new Error('Access denied'); + err.status = 403; + throw err; + } + + return lesson.get({ plain: true }); + } + async addLessonToCourse(hashedUserId, courseId, { chapterId, lessonNumber, title, description, weekNumber, dayNumber, lessonType, audioUrl, culturalNotes, targetMinutes, targetScorePercent, requiresReview }) { const user = await this._getUserByHashedId(hashedUserId); const course = await VocabCourse.findByPk(courseId); diff --git a/frontend/src/router/socialRoutes.js b/frontend/src/router/socialRoutes.js index 7555dc1..f8a6f9e 100644 --- a/frontend/src/router/socialRoutes.js +++ b/frontend/src/router/socialRoutes.js @@ -99,6 +99,13 @@ const socialRoutes = [ props: true, meta: { requiresAuth: true } }, + { + path: '/socialnetwork/vocab/courses/:courseId/lessons/:lessonId', + name: 'VocabLesson', + component: () => import('../views/social/VocabLessonView.vue'), + props: true, + meta: { requiresAuth: true } + }, ]; export default socialRoutes; diff --git a/frontend/src/views/social/VocabCourseListView.vue b/frontend/src/views/social/VocabCourseListView.vue index fefe828..e4a8667 100644 --- a/frontend/src/views/social/VocabCourseListView.vue +++ b/frontend/src/views/social/VocabCourseListView.vue @@ -197,6 +197,10 @@ export default { const nativeLang = this.languages.find(lang => lang.name === nativeLanguageName); if (nativeLang) { this.myNativeLanguageId = nativeLang.id; + // Setze die eigene Muttersprache als Standardauswahl + if (!this.selectedNativeLanguageId) { + this.selectedNativeLanguageId = 'my'; + } console.log(`[loadMyNativeLanguageId] Gefunden: ${nativeLanguageName} (ID: ${nativeLang.id})`); } else { console.warn(`[loadMyNativeLanguageId] Sprache "${nativeLanguageName}" nicht in languages-Liste gefunden. Verfügbare Sprachen:`, this.languages.map(l => l.name).join(', ')); @@ -297,6 +301,16 @@ export default { this.loading = false; } }, + async checkIfHasCourses() { + // Prüfe, ob der Benutzer bereits Kurse hat + try { + const res = await apiClient.get('/api/vocab/courses/my'); + const courses = res.data || []; + return courses.length > 0; + } catch (e) { + return false; + } + }, async createCourse() { try { await apiClient.post('/api/vocab/courses', this.newCourse); @@ -318,7 +332,8 @@ export default { async enroll(courseId) { try { await apiClient.post(`/api/vocab/courses/${courseId}/enroll`); - await this.loadAllCourses(); + // Nach dem Einschreiben sofort zum Kurs navigieren + this.openCourse(courseId); } catch (e) { console.error('Fehler beim Einschreiben:', e); alert(e.response?.data?.error || 'Fehler beim Einschreiben'); @@ -333,7 +348,13 @@ export default { }, async mounted() { await this.loadLanguages(); - await this.loadAllCourses(); + // Wenn der Benutzer bereits Kurse hat, zeige "Meine Kurse" als Standard + const hasCourses = await this.checkIfHasCourses(); + if (hasCourses) { + await this.loadMyCourses(); + } else { + await this.loadAllCourses(); + } }, }; diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue new file mode 100644 index 0000000..37e0597 --- /dev/null +++ b/frontend/src/views/social/VocabLessonView.vue @@ -0,0 +1,179 @@ + + + {{ $t('general.loading') }} + + + {{ $t('general.back') }} + {{ lesson.title }} + + + {{ lesson.description }} + + + {{ $t('socialnetwork.vocab.courses.grammarExercises') }} + + {{ exercise.title }} + {{ exercise.description }} + + + + {{ $t('socialnetwork.vocab.courses.checkAnswer') }} + + {{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }} + {{ exercise.explanation }} + + + + + + + {{ $t('socialnetwork.vocab.courses.noExercises') }} + + + + + + + +
{{ lesson.description }}
{{ exercise.description }}
{{ exercise.explanation }}
{{ $t('socialnetwork.vocab.courses.noExercises') }}