feat(vocab): implement user vocab lesson progress reset functionality
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
- Added `resetUserVocabLessonProgress` method in `AdminController` to allow admins to reset a user's progress for a specific vocab lesson. - Introduced corresponding route in `adminRouter` for the new reset functionality. - Enhanced `VocabService` with methods to purge lesson progress for users, ensuring that only the specified lesson's progress is affected. - Updated UI components in `UsersView` to facilitate the selection of courses and lessons for resetting progress, including confirmation dialogs and loading states. - Added localization support for the new reset functionality across multiple languages. - Implemented reset functionality in `VocabLessonView` for users to reset their own lesson progress.
This commit is contained in:
@@ -2485,6 +2485,66 @@ export default class VocabService {
|
||||
return this._serializeLessonProgress(progress, lessonData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht nur den Fortschritt zu einer Lektion (Zeile vocab_course_progress + zugehörige grammar-exercise-progress).
|
||||
* Gesamtkurs / andere Lektionen bleiben unberührt.
|
||||
*/
|
||||
async _purgeLessonProgressForUser(userId, lessonId) {
|
||||
const numericLessonId = Number(lessonId);
|
||||
const exercises = await VocabGrammarExercise.findAll({
|
||||
where: { lessonId: numericLessonId },
|
||||
attributes: ['id']
|
||||
});
|
||||
const exerciseIds = exercises.map((e) => e.id);
|
||||
let deletedExerciseProgressRows = 0;
|
||||
if (exerciseIds.length > 0) {
|
||||
deletedExerciseProgressRows = await VocabGrammarExerciseProgress.destroy({
|
||||
where: { userId, exerciseId: { [Op.in]: exerciseIds } }
|
||||
});
|
||||
}
|
||||
const deletedLessonProgressRows = await VocabCourseProgress.destroy({
|
||||
where: { userId, lessonId: numericLessonId }
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
lessonId: numericLessonId,
|
||||
deletedLessonProgressRows,
|
||||
deletedExerciseProgressRows
|
||||
};
|
||||
}
|
||||
|
||||
/** Eingeloggter Nutzer setzt eigene Lektion zurück (nur bei Kurs-Einschreibung). */
|
||||
async resetMyLessonProgress(hashedUserId, lessonId) {
|
||||
const user = await this._getUserByHashedId(hashedUserId);
|
||||
const lesson = await VocabCourseLesson.findByPk(Number(lessonId));
|
||||
if (!lesson) {
|
||||
const err = new Error('Lesson not found');
|
||||
err.status = 404;
|
||||
throw err;
|
||||
}
|
||||
const enrollment = await VocabCourseEnrollment.findOne({
|
||||
where: { userId: user.id, courseId: lesson.courseId }
|
||||
});
|
||||
if (!enrollment) {
|
||||
const err = new Error('Not enrolled in this course');
|
||||
err.status = 403;
|
||||
throw err;
|
||||
}
|
||||
return this._purgeLessonProgressForUser(user.id, lesson.id);
|
||||
}
|
||||
|
||||
/** Admin: Zielnutzer per Hash, ohne Einschreibungszwang (idempotentes Löschen). */
|
||||
async adminResetLessonProgressForUser(targetHashedUserId, lessonId) {
|
||||
const user = await this._getUserByHashedId(targetHashedUserId);
|
||||
const lesson = await VocabCourseLesson.findByPk(Number(lessonId));
|
||||
if (!lesson) {
|
||||
const err = new Error('Lesson not found');
|
||||
err.status = 404;
|
||||
throw err;
|
||||
}
|
||||
return this._purgeLessonProgressForUser(user.id, lesson.id);
|
||||
}
|
||||
|
||||
// ========== GRAMMAR EXERCISE METHODS ==========
|
||||
|
||||
async getExerciseTypes() {
|
||||
|
||||
Reference in New Issue
Block a user