feat: add instructor endpoints for student progress tracking and quiz score management

Add four new instructor endpoints: getEnrolledStudents to view all enrolled students with progress, getQuizScores to view quiz scores for all students in a lesson, getQuizAttemptDetail to view detailed quiz attempt for a specific student, and searchStudents to search enrolled students by name/email/username. Add getQuizAttempts endpoint for students to retrieve their own quiz attempt history. All endpoints include
This commit is contained in:
JakkrapartXD 2026-02-02 18:02:19 +07:00
parent a648c41b72
commit 80d7372dfa
6 changed files with 832 additions and 1 deletions

View file

@ -15,7 +15,11 @@ import {
submitCourseResponse,
listinstructorCourseResponse,
GetCourseApprovalsResponse,
SearchInstructorResponse
SearchInstructorResponse,
GetEnrolledStudentsResponse,
GetQuizScoresResponse,
GetQuizAttemptDetailResponse,
SearchStudentsResponse,
} from '../types/CoursesInstructor.types';
import { CreateCourseValidator } from "../validators/CoursesInstructor.validator";
@ -262,4 +266,121 @@ export class CoursesInstructorController {
if (!token) throw new ValidationError('No token provided')
return await CoursesInstructorService.setPrimaryInstructor({ token, course_id: courseId, user_id: userId });
}
/**
* progress
* Get all enrolled students with their progress
* @param courseId - / Course ID
* @param page - / Page number
* @param limit - / Items per page
*/
@Get('{courseId}/students')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Enrolled students retrieved successfully')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not an instructor of this course')
public async getEnrolledStudents(
@Request() request: any,
@Path() courseId: number,
@Query() page?: number,
@Query() limit?: number
): Promise<GetEnrolledStudentsResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
return await CoursesInstructorService.getEnrolledStudents({
token,
course_id: courseId,
page,
limit,
});
}
/**
* lesson quiz
* Get quiz scores of all students for a specific lesson
* @param courseId - / Course ID
* @param lessonId - / Lesson ID
* @param page - / Page number
* @param limit - / Items per page
*/
@Get('{courseId}/lessons/{lessonId}/quiz/scores')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Quiz scores retrieved successfully')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not an instructor of this course')
@Response('404', 'Lesson or quiz not found')
public async getQuizScores(
@Request() request: any,
@Path() courseId: number,
@Path() lessonId: number,
@Query() page?: number,
@Query() limit?: number
): Promise<GetQuizScoresResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
return await CoursesInstructorService.getQuizScores({
token,
course_id: courseId,
lesson_id: lessonId,
page,
limit,
});
}
/**
*
* Get latest quiz attempt detail for a specific student
* @param courseId - / Course ID
* @param lessonId - / Lesson ID
* @param studentId - / Student ID
*/
@Get('{courseId}/lessons/{lessonId}/quiz/students/{studentId}')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Quiz attempt detail retrieved successfully')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not an instructor of this course')
@Response('404', 'Student or quiz attempt not found')
public async getQuizAttemptDetail(
@Request() request: any,
@Path() courseId: number,
@Path() lessonId: number,
@Path() studentId: number
): Promise<GetQuizAttemptDetailResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
return await CoursesInstructorService.getQuizAttemptDetail({
token,
course_id: courseId,
lesson_id: lessonId,
student_id: studentId,
});
}
/**
*
* Search students in course by firstname, lastname, email, or username
* @param courseId - / Course ID
* @param query - / Search query
* @param limit - / Max results
*/
@Get('{courseId}/students/search')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Students found successfully')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not an instructor of this course')
public async searchStudents(
@Request() request: any,
@Path() courseId: number,
@Query() query: string,
@Query() limit?: number
): Promise<SearchStudentsResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
return await CoursesInstructorService.searchStudentsInCourse({
token,
course_id: courseId,
query,
limit,
});
}
}

View file

@ -13,6 +13,7 @@ import {
CompleteLessonResponse,
SubmitQuizResponse,
SubmitQuizBody,
GetQuizAttemptsResponse,
} from '../types/CoursesStudent.types';
import { EnrollmentStatus } from '@prisma/client';
@ -234,4 +235,32 @@ export class CoursesStudentController {
answers: body.answers,
});
}
/**
* Quiz
* Get quiz attempts and scores for a lesson
* @param courseId - / Course ID
* @param lessonId - / Lesson ID
*/
@Get('courses/{courseId}/lessons/{lessonId}/quiz/attempts')
@Security('jwt', ['student'])
@SuccessResponse('200', 'Quiz attempts retrieved successfully')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not enrolled in this course')
@Response('404', 'Lesson or quiz not found')
public async getQuizAttempts(
@Request() request: any,
@Path() courseId: number,
@Path() lessonId: number
): Promise<GetQuizAttemptsResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
return await this.service.getQuizAttempts({
token,
course_id: courseId,
lesson_id: lessonId,
});
}
}