feat: add API endpoint and service logic for reordering quiz questions.

This commit is contained in:
JakkrapartXD 2026-01-26 12:00:59 +07:00
parent 44c61c8fb2
commit 84e4d478c7
3 changed files with 125 additions and 0 deletions

View file

@ -37,6 +37,8 @@ import {
DeleteQuestionInput,
DeleteQuestionResponse,
QuizQuestionData,
ReorderQuestionInput,
ReorderQuestionResponse,
} from "../types/ChaptersLesson.typs";
import { CoursesInstructorService } from './CoursesInstructor.service';
@ -832,6 +834,87 @@ export class ChaptersLessonService {
}
}
async reorderQuestion(request: ReorderQuestionInput): Promise<ReorderQuestionResponse> {
try {
const { token, course_id, lesson_id, question_id, sort_order } = request;
const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
await CoursesInstructorService.validateCourseStatus(course_id);
const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
if (!user) throw new UnauthorizedError('Invalid token');
const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');
// Verify lesson exists and is QUIZ type
const lesson = await prisma.lesson.findUnique({
where: { id: lesson_id },
include: { quiz: true }
});
if (!lesson) throw new NotFoundError('Lesson not found');
if (lesson.type !== 'QUIZ') throw new ValidationError('Cannot reorder question in non-QUIZ type lesson');
if (!lesson.quiz) throw new NotFoundError('Quiz not found for this lesson');
// Verify question exists and belongs to this quiz
const existingQuestion = await prisma.question.findUnique({ where: { id: question_id } });
if (!existingQuestion || existingQuestion.quiz_id !== lesson.quiz.id) {
throw new NotFoundError('Question not found in this quiz');
}
const oldSortOrder = existingQuestion.sort_order;
const quizId = lesson.quiz.id;
// If same position, no need to reorder
if (oldSortOrder === sort_order) {
const questions = await prisma.question.findMany({
where: { quiz_id: quizId },
orderBy: { sort_order: 'asc' },
include: { choices: { orderBy: { sort_order: 'asc' } } }
});
return { code: 200, message: 'Question reordered successfully', data: questions as QuizQuestionData[] };
}
// Shift other questions to make room for the insert
if (oldSortOrder > sort_order) {
// Moving up: shift questions between newSortOrder and oldSortOrder-1 down by 1
await prisma.question.updateMany({
where: {
quiz_id: quizId,
sort_order: { gte: sort_order, lt: oldSortOrder }
},
data: { sort_order: { increment: 1 } }
});
} else {
// Moving down: shift questions between oldSortOrder+1 and newSortOrder up by 1
await prisma.question.updateMany({
where: {
quiz_id: quizId,
sort_order: { gt: oldSortOrder, lte: sort_order }
},
data: { sort_order: { decrement: 1 } }
});
}
// Update the question's sort order
await prisma.question.update({
where: { id: question_id },
data: { sort_order }
});
// Fetch all questions with updated order
const questions = await prisma.question.findMany({
where: { quiz_id: quizId },
orderBy: { sort_order: 'asc' },
include: { choices: { orderBy: { sort_order: 'asc' } } }
});
return { code: 200, message: 'Question reordered successfully', data: questions as QuizQuestionData[] };
} catch (error) {
logger.error(`Error reordering question: ${error}`);
throw error;
}
}
/**
*
* Delete a question and all its choices