courseworx/backend/routes/traineeProgress.js
mmabdalla 5477297914 v2.0.2 - Complete Plugin Architecture System and Multi-Currency Implementation
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.
2025-09-14 04:20:37 +03:00

152 lines
4.7 KiB
JavaScript

const express = require('express');
const { param, validationResult } = require('express-validator');
const { Enrollment, Course, User, CourseContent, LessonCompletion, CourseSection } = require('../models');
const { auth, requireTrainer } = require('../middleware/auth');
const router = express.Router();
// @route GET /api/trainee-progress/:courseId/:traineeId
// @desc Get trainee progress for a specific course
// @access Private (Trainer or Super Admin)
router.get('/:courseId/:traineeId', [
auth,
requireTrainer,
param('courseId').isUUID().withMessage('Invalid course ID'),
param('traineeId').isUUID().withMessage('Invalid trainee ID')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { courseId, traineeId } = req.params;
// Check if enrollment exists
const enrollment = await Enrollment.findOne({
where: { courseId, userId: traineeId },
include: [{
model: Course,
as: 'course',
attributes: ['id', 'title', 'trainerId']
}]
});
if (!enrollment) {
return res.status(404).json({ error: 'Enrollment not found.' });
}
// Check permissions
if (req.user.role === 'trainer') {
if (enrollment.course.trainerId !== req.user.id) {
return res.status(403).json({ error: 'You can only view progress for your own courses.' });
}
}
// Get course sections and content
const sections = await CourseSection.findAll({
where: { courseId },
include: [{
model: CourseContent,
as: 'contents',
attributes: ['id', 'title', 'type', 'order']
}],
order: [['order', 'ASC'], [{ model: CourseContent, as: 'contents' }, 'order', 'ASC']]
});
// Get lesson completions
const completions = await LessonCompletion.findAll({
where: {
courseId,
userId: traineeId
},
attributes: ['contentId', 'isCompleted', 'completedAt', 'timeSpent']
});
const completionMap = {};
completions.forEach(completion => {
completionMap[completion.contentId] = completion;
});
// Calculate progress
let totalLessons = 0;
let completedLessons = 0;
let totalTimeSpent = 0;
const sectionsWithProgress = sections.map(section => {
const sectionContents = section.contents || [];
const sectionCompleted = sectionContents.filter(content =>
completionMap[content.id]?.isCompleted
).length;
totalLessons += sectionContents.length;
completedLessons += sectionCompleted;
// Calculate time spent for this section
const sectionTimeSpent = sectionContents.reduce((total, content) => {
const completion = completionMap[content.id];
return total + (completion?.timeSpent || 0);
}, 0);
totalTimeSpent += sectionTimeSpent;
return {
id: section.id,
name: section.title,
order: section.order,
totalLessons: sectionContents.length,
completedLessons: sectionCompleted,
progress: sectionContents.length > 0 ? Math.round((sectionCompleted / sectionContents.length) * 100) : 0,
timeSpent: sectionTimeSpent,
lessons: sectionContents.map(content => {
const completion = completionMap[content.id];
return {
id: content.id,
title: content.title,
type: content.type,
order: content.order,
status: completion?.isCompleted ? 'completed' : 'not-started',
completedAt: completion?.completedAt,
timeSpent: completion?.timeSpent || 0
};
})
};
});
const overallProgress = totalLessons > 0 ? Math.round((completedLessons / totalLessons) * 100) : 0;
// Get recent activity (last 10 completions)
const recentActivity = completions
.filter(completion => completion.isCompleted)
.sort((a, b) => new Date(b.completedAt) - new Date(a.completedAt))
.slice(0, 10)
.map(completion => {
const content = sections
.flatMap(s => s.contents || [])
.find(c => c.id === completion.contentId);
return {
description: content ? `Completed: ${content.title}` : 'Completed lesson',
timestamp: new Date(completion.completedAt).toLocaleString(),
type: 'completion'
};
});
res.json({
overallProgress,
totalLessons,
completedLessons,
totalTimeSpent,
sections: sectionsWithProgress,
recentActivity
});
} catch (error) {
console.error('Get trainee progress error:', error);
res.status(500).json({ error: 'Failed to get trainee progress.' });
}
});
module.exports = router;