Major Features Added: - Complete Plugin Architecture System with financial plugin - Multi-currency support with exchange rates - Course type system (online, classroom, hybrid) - Attendance tracking and QR code scanning - Classroom sessions management - Course sections and content management - Professional video player with authentication - Secure media serving system - Shopping cart and checkout system - Financial dashboard and earnings tracking - Trainee progress tracking - User notes and assignments system Backend Infrastructure: - Plugin loader and registry system - Multi-currency database models - Secure media middleware - Course access middleware - Financial plugin with payment processing - Database migrations for new features - API endpoints for all new functionality Frontend Components: - Course management interface - Content creation and editing - Section management with drag-and-drop - Professional video player - QR scanner for attendance - Shopping cart and checkout flow - Financial dashboard - Plugin management interface - Trainee details and progress views This represents a major evolution of CourseWorx from a basic LMS to a comprehensive educational platform with plugin architecture.
221 lines
6.9 KiB
JavaScript
221 lines
6.9 KiB
JavaScript
const express = require('express');
|
|
const { body, validationResult } = require('express-validator');
|
|
const { LessonCompletion, CourseContent, Enrollment } = require('../models');
|
|
const { auth } = require('../middleware/auth');
|
|
const { requirePaidEnrollment, requireEnrollment, requireCourseAccess } = require('../middleware/courseAccess');
|
|
|
|
const router = express.Router();
|
|
|
|
// @route GET /api/lesson-completion/:courseId/progress
|
|
// @desc Get user's progress for a specific course
|
|
// @access Private (Enrolled students who have paid)
|
|
router.get('/:courseId/progress', auth, requireCourseAccess, async (req, res) => {
|
|
try {
|
|
const { courseId } = req.params;
|
|
const userId = req.user.id;
|
|
const isTrainer = req.user.role === 'trainer' || req.user.role === 'super_admin';
|
|
|
|
// Check if user is enrolled in the course (only for non-trainers)
|
|
let enrollment = null;
|
|
if (!isTrainer) {
|
|
enrollment = await Enrollment.findOne({
|
|
where: { userId, courseId, status: ['active', 'completed'] }
|
|
});
|
|
}
|
|
|
|
// Get all course content
|
|
// For trainers, show all content. For trainees, only show published content
|
|
const contentWhere = isTrainer ? { courseId } : { courseId, isPublished: true };
|
|
const courseContents = await CourseContent.findAll({
|
|
where: contentWhere,
|
|
order: [['order', 'ASC']]
|
|
});
|
|
|
|
// Get user's lesson completions
|
|
const lessonCompletions = await LessonCompletion.findAll({
|
|
where: { userId, courseId }
|
|
});
|
|
|
|
// Calculate progress
|
|
const totalLessons = courseContents.length;
|
|
const completedLessons = lessonCompletions.filter(lc => lc.isCompleted).length;
|
|
const progress = totalLessons > 0 ? Math.round((completedLessons / totalLessons) * 100) : 0;
|
|
|
|
// Update enrollment progress if enrollment exists
|
|
if (enrollment) {
|
|
await enrollment.update({ progress });
|
|
}
|
|
|
|
res.json({
|
|
progress,
|
|
totalLessons,
|
|
completedLessons,
|
|
lessonCompletions: lessonCompletions.map(lc => ({
|
|
contentId: lc.contentId,
|
|
isCompleted: lc.isCompleted,
|
|
progress: lc.progress,
|
|
timeSpent: lc.timeSpent,
|
|
lastAccessedAt: lc.lastAccessedAt
|
|
}))
|
|
});
|
|
} catch (error) {
|
|
console.error('Get lesson progress error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route POST /api/lesson-completion/:courseId/:contentId
|
|
// @desc Mark a lesson as completed or update progress
|
|
// @access Private (Enrolled students who have paid)
|
|
router.post('/:courseId/:contentId', [
|
|
auth,
|
|
requireEnrollment,
|
|
body('isCompleted').optional().isBoolean(),
|
|
body('progress').optional().isInt({ min: 0, max: 100 }),
|
|
body('timeSpent').optional().isInt({ min: 0 })
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { courseId, contentId } = req.params;
|
|
const userId = req.user.id;
|
|
const { isCompleted, progress, timeSpent } = req.body;
|
|
|
|
// Check if user is enrolled in the course
|
|
const enrollment = await Enrollment.findOne({
|
|
where: { userId, courseId, status: ['active', 'completed'] }
|
|
});
|
|
|
|
if (!enrollment) {
|
|
return res.status(403).json({ error: 'Not enrolled in this course.' });
|
|
}
|
|
|
|
// Check if content exists and belongs to the course
|
|
const courseContent = await CourseContent.findOne({
|
|
where: { id: contentId, courseId, isPublished: true }
|
|
});
|
|
|
|
if (!courseContent) {
|
|
return res.status(404).json({ error: 'Course content not found.' });
|
|
}
|
|
|
|
// Find or create lesson completion record
|
|
let lessonCompletion = await LessonCompletion.findOne({
|
|
where: { userId, courseId, contentId }
|
|
});
|
|
|
|
if (!lessonCompletion) {
|
|
lessonCompletion = await LessonCompletion.create({
|
|
userId,
|
|
courseId,
|
|
contentId,
|
|
isCompleted: false,
|
|
progress: 0,
|
|
timeSpent: 0
|
|
});
|
|
}
|
|
|
|
// Update the record
|
|
const updateData = {
|
|
lastAccessedAt: new Date()
|
|
};
|
|
|
|
if (isCompleted !== undefined) {
|
|
updateData.isCompleted = isCompleted;
|
|
if (isCompleted) {
|
|
updateData.completedAt = new Date();
|
|
}
|
|
}
|
|
|
|
if (progress !== undefined) {
|
|
updateData.progress = progress;
|
|
}
|
|
|
|
if (timeSpent !== undefined) {
|
|
updateData.timeSpent = lessonCompletion.timeSpent + timeSpent;
|
|
}
|
|
|
|
await lessonCompletion.update(updateData);
|
|
|
|
// Recalculate overall course progress
|
|
const allCourseContents = await CourseContent.findAll({
|
|
where: { courseId, isPublished: true }
|
|
});
|
|
|
|
const allLessonCompletions = await LessonCompletion.findAll({
|
|
where: { userId, courseId }
|
|
});
|
|
|
|
const totalLessons = allCourseContents.length;
|
|
const completedLessons = allLessonCompletions.filter(lc => lc.isCompleted).length;
|
|
const overallProgress = totalLessons > 0 ? Math.round((completedLessons / totalLessons) * 100) : 0;
|
|
|
|
// Update enrollment progress
|
|
await enrollment.update({ progress: overallProgress });
|
|
|
|
res.json({
|
|
message: 'Lesson completion updated successfully.',
|
|
lessonCompletion: {
|
|
contentId: lessonCompletion.contentId,
|
|
isCompleted: lessonCompletion.isCompleted,
|
|
progress: lessonCompletion.progress,
|
|
timeSpent: lessonCompletion.timeSpent,
|
|
lastAccessedAt: lessonCompletion.lastAccessedAt
|
|
},
|
|
overallProgress
|
|
});
|
|
} catch (error) {
|
|
console.error('Update lesson completion error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
// @route GET /api/lesson-completion/:courseId/:contentId
|
|
// @desc Get user's progress for a specific lesson
|
|
// @access Private (Enrolled students who have paid)
|
|
router.get('/:courseId/:contentId', auth, requirePaidEnrollment, async (req, res) => {
|
|
try {
|
|
const { courseId, contentId } = req.params;
|
|
const userId = req.user.id;
|
|
|
|
// Check if user is enrolled in the course
|
|
const enrollment = await Enrollment.findOne({
|
|
where: { userId, courseId, status: ['active', 'completed'] }
|
|
});
|
|
|
|
if (!enrollment) {
|
|
return res.status(403).json({ error: 'Not enrolled in this course.' });
|
|
}
|
|
|
|
// Get lesson completion record
|
|
const lessonCompletion = await LessonCompletion.findOne({
|
|
where: { userId, courseId, contentId }
|
|
});
|
|
|
|
if (!lessonCompletion) {
|
|
return res.json({
|
|
contentId,
|
|
isCompleted: false,
|
|
progress: 0,
|
|
timeSpent: 0,
|
|
lastAccessedAt: null
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
contentId: lessonCompletion.contentId,
|
|
isCompleted: lessonCompletion.isCompleted,
|
|
progress: lessonCompletion.progress,
|
|
timeSpent: lessonCompletion.timeSpent,
|
|
lastAccessedAt: lessonCompletion.lastAccessedAt
|
|
});
|
|
} catch (error) {
|
|
console.error('Get lesson completion error:', error);
|
|
res.status(500).json({ error: 'Server error.' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|