feat: add API endpoint and service logic for reordering quiz questions.
This commit is contained in:
parent
44c61c8fb2
commit
84e4d478c7
3 changed files with 125 additions and 0 deletions
|
|
@ -22,6 +22,8 @@ import {
|
|||
ReorderLessonsBody,
|
||||
AddQuestionBody,
|
||||
UpdateQuestionBody,
|
||||
ReorderQuestionResponse,
|
||||
ReorderQuestionBody,
|
||||
} from '../types/ChaptersLesson.typs';
|
||||
|
||||
const chaptersLessonService = new ChaptersLessonService();
|
||||
|
|
@ -305,6 +307,28 @@ export class ChaptersLessonInstructorController {
|
|||
});
|
||||
}
|
||||
|
||||
@Put('chapters/{chapterId}/lessons/{lessonId}/questions/{questionId}/reorder')
|
||||
@Security('jwt', ['instructor'])
|
||||
@SuccessResponse('200', 'Question reordered successfully')
|
||||
public async reorderQuestion(
|
||||
@Request() request: any,
|
||||
@Path() courseId: number,
|
||||
@Path() chapterId: number,
|
||||
@Path() lessonId: number,
|
||||
@Path() questionId: number,
|
||||
@Body() body: ReorderQuestionBody
|
||||
): Promise<ReorderQuestionResponse> {
|
||||
const token = request.headers.authorization?.replace('Bearer ', '');
|
||||
if (!token) throw new ValidationError('No token provided');
|
||||
return await chaptersLessonService.reorderQuestion({
|
||||
token,
|
||||
course_id: courseId,
|
||||
lesson_id: lessonId,
|
||||
question_id: questionId,
|
||||
sort_order: body.sort_order,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ลบคำถาม
|
||||
* Delete a question
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -477,6 +477,20 @@ export interface DeleteQuestionInput {
|
|||
question_id: number;
|
||||
}
|
||||
|
||||
export interface ReorderQuestionInput {
|
||||
token: string;
|
||||
course_id: number;
|
||||
lesson_id: number;
|
||||
question_id: number;
|
||||
sort_order: number;
|
||||
}
|
||||
|
||||
export interface ReorderQuestionResponse {
|
||||
code: number;
|
||||
message: string;
|
||||
data: QuizQuestionData[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Response for deleting a question
|
||||
*/
|
||||
|
|
@ -526,6 +540,10 @@ export interface ReorderLessonsBody {
|
|||
sort_order: number;
|
||||
}
|
||||
|
||||
export interface ReorderQuestionBody {
|
||||
sort_order: number;
|
||||
}
|
||||
|
||||
export interface AddQuestionBody {
|
||||
question: MultiLanguageText;
|
||||
explanation?: MultiLanguageText;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue