feat: Implement lesson access control logic including enrollment, prerequisite, and quiz completion checks.

This commit is contained in:
JakkrapartXD 2026-01-19 17:08:06 +07:00
parent 6d59ec06bf
commit 0308995d8e
4 changed files with 760 additions and 1 deletions

View file

@ -0,0 +1,205 @@
import { Get, Body, Post, Route, Tags, SuccessResponse, Response, Security, Path, Request, Query } from 'tsoa';
import { ValidationError } from '../middleware/errorHandler';
import { CoursesStudentService } from '../services/CoursesStudent.service';
import {
EnrollCourseResponse,
ListEnrolledCoursesResponse,
GetCourseLearningResponse,
GetLessonContentResponse,
CheckLessonAccessResponse,
SaveVideoProgressResponse,
SaveVideoProgressBody,
GetVideoProgressResponse,
CompleteLessonResponse,
} from '../types/CoursesStudent.types';
import { EnrollmentStatus } from '@prisma/client';
@Route('api/students')
@Tags('CoursesStudent')
export class CoursesStudentController {
private service = new CoursesStudentService();
/**
*
* Enroll in a course
* @param courseId - / Course ID
*/
@Post('courses/{courseId}/enroll')
@Security('jwt', ['student'])
@SuccessResponse('200', 'Enrolled successfully')
@Response('401', 'Invalid or expired token')
@Response('404', 'Course not found')
@Response('409', 'Already enrolled in this course')
public async enrollCourse(@Request() request: any, @Path() courseId: number): Promise<EnrollCourseResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
return await this.service.enrollCourse({ token, course_id: courseId });
}
/**
*
* Get list of enrolled courses
* @param page - / Page number
* @param limit - / Items per page
* @param status - / Enrollment status
*/
@Get('courses')
@Security('jwt', ['student'])
@SuccessResponse('200', 'Enrolled courses retrieved successfully')
@Response('401', 'Invalid or expired token')
public async getEnrolledCourses(
@Request() request: any,
@Query() page?: number,
@Query() limit?: number,
@Query() status?: EnrollmentStatus
): Promise<ListEnrolledCoursesResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
return await this.service.GetEnrolledCourses({ token, page, limit, status });
}
/**
* ()
* Get course learning page with lesson lock status
* @param courseId - / Course ID
*/
@Get('courses/{courseId}/learn')
@Security('jwt', ['student'])
@SuccessResponse('200', 'Course learning data retrieved successfully')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not enrolled in this course')
@Response('404', 'Course not found')
public async getCourseLearning(@Request() request: any, @Path() courseId: number): Promise<GetCourseLearningResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
return await this.service.getCourseLearning({ token, course_id: courseId });
}
/**
* ()
* Get lesson content (checks prerequisites)
* @param courseId - / Course ID
* @param lessonId - / Lesson ID
*/
@Get('courses/{courseId}/lessons/{lessonId}')
@Security('jwt', ['student'])
@SuccessResponse('200', 'Lesson content retrieved successfully')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not enrolled in this course or lesson is locked')
@Response('404', 'Lesson not found')
public async getLessonContent(
@Request() request: any,
@Path() courseId: number,
@Path() lessonId: number
): Promise<GetLessonContentResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
return await this.service.getlessonContent({ token, course_id: courseId, lesson_id: lessonId });
}
/**
* ()
* Check lesson access without loading content
* @param courseId - / Course ID
* @param lessonId - / Lesson ID
*/
@Get('courses/{courseId}/lessons/{lessonId}/access-check')
@Security('jwt', ['student'])
@SuccessResponse('200', 'Access check completed')
@Response('401', 'Invalid or expired token')
@Response('404', 'Lesson not found')
public async checkLessonAccess(
@Request() request: any,
@Path() courseId: number,
@Path() lessonId: number
): Promise<CheckLessonAccessResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
return await this.service.checkAccessLesson({ token, course_id: courseId, lesson_id: lessonId });
}
/**
*
* Save video progress
* @param lessonId - / Lesson ID
*/
@Post('lessons/{lessonId}/progress')
@Security('jwt', ['student'])
@SuccessResponse('200', 'Video progress saved successfully')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not enrolled in this course')
@Response('404', 'Lesson not found')
public async saveVideoProgress(
@Request() request: any,
@Path() lessonId: number,
@Body() body: SaveVideoProgressBody
): Promise<SaveVideoProgressResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
return await this.service.saveVideoProgress({
token,
lesson_id: lessonId,
video_progress_seconds: body.video_progress_seconds,
video_duration_seconds: body.video_duration_seconds,
});
}
/**
*
* Get video progress
* @param lessonId - / Lesson ID
*/
@Get('lessons/{lessonId}/progress')
@Security('jwt', ['student'])
@SuccessResponse('200', 'Video progress retrieved successfully')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not enrolled in this course')
@Response('404', 'Lesson not found')
public async getVideoProgress(
@Request() request: any,
@Path() lessonId: number
): Promise<GetVideoProgressResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
return await this.service.getVideoProgress({ token, lesson_id: lessonId });
}
/**
*
* Mark lesson as complete
* @param courseId - / Course ID
* @param lessonId - / Lesson ID
*/
@Post('courses/{courseId}/lessons/{lessonId}/complete')
@Security('jwt', ['student'])
@SuccessResponse('200', 'Lesson marked as complete')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not enrolled in this course')
@Response('404', 'Lesson not found')
public async completeLesson(
@Request() request: any,
@Path() courseId: number,
@Path() lessonId: number
): Promise<CompleteLessonResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
return await this.service.completeLesson({ token, lesson_id: lessonId });
}
}