courseworx/backend/routes/classroomSessions.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

353 lines
10 KiB
JavaScript

const express = require('express');
const { body, param, query, validationResult } = require('express-validator');
const { auth, requireTrainer, requireSuperAdmin } = require('../middleware/auth');
const ClassroomSession = require('../models/ClassroomSession');
const Course = require('../models/Course');
const User = require('../models/User');
const AttendanceRecord = require('../models/AttendanceRecord');
const { v4: uuidv4 } = require('uuid');
const QRCode = require('qrcode');
const { getFrontendURL } = require('../utils/getServerIP');
const router = express.Router();
// Get all classroom sessions for a course
router.get('/course/:courseId', [
auth,
requireTrainer,
param('courseId').isUUID().withMessage('Invalid course ID')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { courseId } = req.params;
const { page = 1, limit = 10, status } = req.query;
// Check if user has access to this course
const course = await Course.findByPk(courseId);
if (!course) {
return res.status(404).json({ error: 'Course not found' });
}
if (req.user.role !== 'super_admin' && course.trainerId !== req.user.id) {
return res.status(403).json({ error: 'Access denied' });
}
const whereClause = { courseId };
if (status) {
whereClause.status = status;
}
const sessions = await ClassroomSession.findAndCountAll({
where: whereClause,
include: [
{
model: Course,
as: 'Course',
attributes: ['id', 'title', 'courseType']
}
],
order: [['sessionDate', 'DESC'], ['startTime', 'DESC']],
limit: parseInt(limit),
offset: (parseInt(page) - 1) * parseInt(limit)
});
res.json({
sessions: sessions.rows,
totalCount: sessions.count,
totalPages: Math.ceil(sessions.count / parseInt(limit)),
currentPage: parseInt(page)
});
} catch (error) {
console.error('Error fetching classroom sessions:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Create a new classroom session
router.post('/', [
auth,
requireTrainer,
body('courseId').isUUID().withMessage('Invalid course ID'),
body('sessionDate').isISO8601().withMessage('Invalid session date'),
body('startTime').matches(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/).withMessage('Invalid start time format'),
body('endTime').matches(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/).withMessage('Invalid end time format'),
body('location').optional().isString().isLength({ max: 500 }),
body('roomNumber').optional().isString().isLength({ max: 50 }),
body('maxCapacity').optional().isInt({ min: 1 }),
body('notes').optional().isString()
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { courseId, sessionDate, startTime, endTime, location, roomNumber, maxCapacity, notes } = req.body;
// Check if course exists and user has access
const course = await Course.findByPk(courseId);
if (!course) {
return res.status(404).json({ error: 'Course not found' });
}
if (req.user.role !== 'super_admin' && course.trainerId !== req.user.id) {
return res.status(403).json({ error: 'Access denied' });
}
// Check if course is classroom type
if (course.courseType !== 'classroom' && course.courseType !== 'hybrid') {
return res.status(400).json({ error: 'Course must be classroom or hybrid type' });
}
// Generate unique QR code
const sessionId = uuidv4();
// Create QR code URL using server IP for network access
const baseUrl = process.env.FRONTEND_URL || getFrontendURL(3000);
const qrCodeUrl = `${baseUrl}/attendance/join/${sessionId}`;
const qrCode = await QRCode.toDataURL(qrCodeUrl);
// Set QR code expiry to end of session day + 1 day
const qrCodeExpiry = new Date(sessionDate);
qrCodeExpiry.setDate(qrCodeExpiry.getDate() + 1);
qrCodeExpiry.setHours(23, 59, 59, 999);
const session = await ClassroomSession.create({
id: sessionId,
courseId,
sessionDate,
startTime,
endTime,
location: location || course.location,
roomNumber,
qrCode: qrCodeUrl,
qrCodeExpiry,
maxCapacity,
notes
});
res.status(201).json({
session,
qrCodeImage: qrCode
});
} catch (error) {
console.error('Error creating classroom session:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get QR code for a session
router.get('/:sessionId/qr-code', [
auth,
requireTrainer,
param('sessionId').isUUID().withMessage('Invalid session ID')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { sessionId } = req.params;
const session = await ClassroomSession.findByPk(sessionId, {
include: [
{
model: Course,
as: 'Course',
attributes: ['id', 'title', 'trainerId']
}
]
});
if (!session) {
return res.status(404).json({ error: 'Session not found' });
}
// Check access
if (req.user.role !== 'super_admin' && session.Course.trainerId !== req.user.id) {
return res.status(403).json({ error: 'Access denied' });
}
// Generate QR code image from URL
const qrCodeImage = await QRCode.toDataURL(session.qrCode);
res.json({
session: {
id: session.id,
courseId: session.courseId,
sessionDate: session.sessionDate,
startTime: session.startTime,
endTime: session.endTime,
location: session.location,
roomNumber: session.roomNumber,
status: session.status,
qrCodeExpiry: session.qrCodeExpiry
},
qrCodeImage
});
} catch (error) {
console.error('Error generating QR code:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Update session status
router.patch('/:sessionId/status', [
auth,
requireTrainer,
param('sessionId').isUUID().withMessage('Invalid session ID'),
body('status').isIn(['scheduled', 'in_progress', 'completed', 'cancelled']).withMessage('Invalid status')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { sessionId } = req.params;
const { status } = req.body;
const session = await ClassroomSession.findByPk(sessionId, {
include: [
{
model: Course,
as: 'Course',
attributes: ['id', 'trainerId']
}
]
});
if (!session) {
return res.status(404).json({ error: 'Session not found' });
}
// Check access
if (req.user.role !== 'super_admin' && session.Course.trainerId !== req.user.id) {
return res.status(403).json({ error: 'Access denied' });
}
await session.update({ status });
res.json({ message: 'Session status updated successfully', session });
} catch (error) {
console.error('Error updating session status:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get session attendance
router.get('/:sessionId/attendance', [
auth,
requireTrainer,
param('sessionId').isUUID().withMessage('Invalid session ID')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { sessionId } = req.params;
const session = await ClassroomSession.findByPk(sessionId, {
include: [
{
model: Course,
as: 'Course',
attributes: ['id', 'title', 'trainerId']
}
]
});
if (!session) {
return res.status(404).json({ error: 'Session not found' });
}
// Check access
if (req.user.role !== 'super_admin' && session.Course.trainerId !== req.user.id) {
return res.status(403).json({ error: 'Access denied' });
}
const attendance = await AttendanceRecord.findAll({
where: { sessionId },
include: [
{
model: User,
as: 'User',
attributes: ['id', 'firstName', 'lastName', 'email', 'phone']
}
],
order: [['checkInTime', 'ASC']]
});
res.json({
session: {
id: session.id,
courseId: session.courseId,
sessionDate: session.sessionDate,
startTime: session.startTime,
endTime: session.endTime,
location: session.location,
roomNumber: session.roomNumber,
status: session.status
},
attendance
});
} catch (error) {
console.error('Error fetching attendance:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Delete a session
router.delete('/:sessionId', [
auth,
requireTrainer,
param('sessionId').isUUID().withMessage('Invalid session ID')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { sessionId } = req.params;
const session = await ClassroomSession.findByPk(sessionId, {
include: [
{
model: Course,
as: 'Course',
attributes: ['id', 'trainerId']
}
]
});
if (!session) {
return res.status(404).json({ error: 'Session not found' });
}
// Check access
if (req.user.role !== 'super_admin' && session.Course.trainerId !== req.user.id) {
return res.status(403).json({ error: 'Access denied' });
}
// Delete attendance records first
await AttendanceRecord.destroy({ where: { sessionId } });
// Delete session
await session.destroy();
res.json({ message: 'Session deleted successfully' });
} catch (error) {
console.error('Error deleting session:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;