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:
parent
827b016114
commit
7e8c8e2532
3 changed files with 36 additions and 11 deletions
|
|
@ -905,14 +905,19 @@ export class CoursesInstructorService {
|
|||
const selectedChoiceId = studentAnswer?.choice_id || 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 {
|
||||
question_id: question.id,
|
||||
sort_order: question.sort_order,
|
||||
question_text: question.question as { th: string; en: string },
|
||||
selected_choice_id: selectedChoiceId,
|
||||
selected_choice_text: selectedChoice ? selectedChoice.text as { th: string; en: string } : null,
|
||||
is_correct: studentAnswer?.is_correct || false,
|
||||
score: studentAnswer?.score || 0,
|
||||
is_correct: isCorrect,
|
||||
score: earnedScore,
|
||||
question_score: question.score,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -486,17 +486,35 @@ export class CoursesStudentService {
|
|||
// Import additional MinIO functions
|
||||
// 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;
|
||||
try {
|
||||
const videoPrefix = getVideoFolder(chapter_course_id, lesson_id);
|
||||
const videoFiles = await listObjects(videoPrefix);
|
||||
if (videoFiles.length > 0) {
|
||||
// Get presigned URL for the first video file
|
||||
video_url = await getPresignedUrl(videoFiles[0].name, 3600);
|
||||
const videoAttachment = await prisma.lessonAttachment.findFirst({
|
||||
where: { lesson_id, sort_order: 0 }
|
||||
});
|
||||
|
||||
if (videoAttachment) {
|
||||
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
|
||||
|
|
@ -578,6 +596,7 @@ export class CoursesStudentService {
|
|||
shuffle_questions: lesson.quiz.shuffle_questions,
|
||||
shuffle_choices: lesson.quiz.shuffle_choices,
|
||||
is_skippable: lesson.quiz.is_skippable,
|
||||
show_answers_after_completion: lesson.quiz.show_answers_after_completion,
|
||||
questions: lesson.quiz.questions.map(q => ({
|
||||
id: q.id,
|
||||
question: q.question as { th: string; en: string },
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ export interface LessonContentData {
|
|||
shuffle_questions: boolean;
|
||||
shuffle_choices: boolean;
|
||||
is_skippable: boolean;
|
||||
show_answers_after_completion: boolean;
|
||||
questions: {
|
||||
id: number;
|
||||
question: MultiLangText;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue