feat: fix quiz scoring to use Choice model correctness, add YouTube video support to lesson content, and include show_answers_after_completion in quiz data

Update quiz attempt detail scoring to determine correctness from Choice.is_correct instead of StudentAnswer.is_correct and calculate earned score based on correctness. Add YouTube video support to getLessonContent endpoint by checking LessonAttachment for YouTube videos (mime_type 'video/youtube') and building YouTube URLs from video IDs. Ad
This commit is contained in:
JakkrapartXD 2026-02-04 14:19:17 +07:00
parent 827b016114
commit 7e8c8e2532
3 changed files with 36 additions and 11 deletions

View file

@ -905,14 +905,19 @@ export class CoursesInstructorService {
const selectedChoiceId = studentAnswer?.choice_id || null; const selectedChoiceId = studentAnswer?.choice_id || null;
const selectedChoice = selectedChoiceId ? question.choices.find(c => c.id === selectedChoiceId) : null; const selectedChoice = selectedChoiceId ? question.choices.find(c => c.id === selectedChoiceId) : null;
// Check if selected choice is correct from Choice model
const isCorrect = selectedChoice?.is_correct || false;
// Score is question.score if correct, otherwise 0
const earnedScore = isCorrect ? question.score : 0;
return { return {
question_id: question.id, question_id: question.id,
sort_order: question.sort_order, sort_order: question.sort_order,
question_text: question.question as { th: string; en: string }, question_text: question.question as { th: string; en: string },
selected_choice_id: selectedChoiceId, selected_choice_id: selectedChoiceId,
selected_choice_text: selectedChoice ? selectedChoice.text as { th: string; en: string } : null, selected_choice_text: selectedChoice ? selectedChoice.text as { th: string; en: string } : null,
is_correct: studentAnswer?.is_correct || false, is_correct: isCorrect,
score: studentAnswer?.score || 0, score: earnedScore,
question_score: question.score, question_score: question.score,
}; };
}); });

View file

@ -486,17 +486,35 @@ export class CoursesStudentService {
// Import additional MinIO functions // Import additional MinIO functions
// Using MinIO functions imported above // Using MinIO functions imported above
// Get video URL from video folder (first file) // Get video URL - check for YouTube or MinIO
let video_url: string | null = null; let video_url: string | null = null;
try { const videoAttachment = await prisma.lessonAttachment.findFirst({
const videoPrefix = getVideoFolder(chapter_course_id, lesson_id); where: { lesson_id, sort_order: 0 }
const videoFiles = await listObjects(videoPrefix); });
if (videoFiles.length > 0) {
// Get presigned URL for the first video file if (videoAttachment) {
video_url = await getPresignedUrl(videoFiles[0].name, 3600); if (videoAttachment.mime_type === 'video/youtube') {
// YouTube video - build URL from video ID stored in file_path
video_url = `https://www.youtube.com/watch?v=${videoAttachment.file_path}`;
} else {
// MinIO video - get presigned URL
try {
video_url = await getPresignedUrl(videoAttachment.file_path, 3600);
} catch (err) {
logger.error(`Failed to get video from MinIO: ${err}`);
}
}
} else {
// Fallback: try to get video from MinIO folder (legacy support)
try {
const videoPrefix = getVideoFolder(chapter_course_id, lesson_id);
const videoFiles = await listObjects(videoPrefix);
if (videoFiles.length > 0) {
video_url = await getPresignedUrl(videoFiles[0].name, 3600);
}
} catch (err) {
logger.error(`Failed to get video from MinIO: ${err}`);
} }
} catch (err) {
logger.error(`Failed to get video from MinIO: ${err}`);
} }
// Get attachments from MinIO folder // Get attachments from MinIO folder
@ -578,6 +596,7 @@ export class CoursesStudentService {
shuffle_questions: lesson.quiz.shuffle_questions, shuffle_questions: lesson.quiz.shuffle_questions,
shuffle_choices: lesson.quiz.shuffle_choices, shuffle_choices: lesson.quiz.shuffle_choices,
is_skippable: lesson.quiz.is_skippable, is_skippable: lesson.quiz.is_skippable,
show_answers_after_completion: lesson.quiz.show_answers_after_completion,
questions: lesson.quiz.questions.map(q => ({ questions: lesson.quiz.questions.map(q => ({
id: q.id, id: q.id,
question: q.question as { th: string; en: string }, question: q.question as { th: string; en: string },

View file

@ -158,6 +158,7 @@ export interface LessonContentData {
shuffle_questions: boolean; shuffle_questions: boolean;
shuffle_choices: boolean; shuffle_choices: boolean;
is_skippable: boolean; is_skippable: boolean;
show_answers_after_completion: boolean;
questions: { questions: {
id: number; id: number;
question: MultiLangText; question: MultiLangText;