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.
173 lines
4.6 KiB
JavaScript
173 lines
4.6 KiB
JavaScript
const express = require('express');
|
|
const { body, param, validationResult } = require('express-validator');
|
|
const { auth, requireTrainer, requireSuperAdmin } = require('../middleware/auth');
|
|
const AttendanceRecord = require('../models/AttendanceRecord');
|
|
const ClassroomSession = require('../models/ClassroomSession');
|
|
const Course = require('../models/Course');
|
|
const User = require('../models/User');
|
|
const router = express.Router();
|
|
|
|
// Device-based check in
|
|
router.post('/device/checkin', [
|
|
body('sessionId').isUUID().withMessage('Valid session ID is required'),
|
|
body('deviceId').isString().withMessage('Device ID is required')
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { sessionId, deviceId } = req.body;
|
|
|
|
// Find the session
|
|
const session = await ClassroomSession.findByPk(sessionId, {
|
|
include: [{ model: Course, as: 'Course' }]
|
|
});
|
|
|
|
if (!session) {
|
|
return res.status(404).json({ error: 'Session not found' });
|
|
}
|
|
|
|
// Check if already checked in
|
|
const existingCheckin = await AttendanceRecord.findOne({
|
|
where: {
|
|
sessionId,
|
|
deviceId,
|
|
status: 'checked_in'
|
|
}
|
|
});
|
|
|
|
if (existingCheckin) {
|
|
return res.status(400).json({ error: 'Already checked in for this session' });
|
|
}
|
|
|
|
// Create check-in record
|
|
const attendanceRecord = await AttendanceRecord.create({
|
|
sessionId,
|
|
deviceId,
|
|
status: 'checked_in',
|
|
checkInTime: new Date()
|
|
});
|
|
|
|
res.status(201).json({
|
|
message: 'Successfully checked in',
|
|
attendanceRecord: {
|
|
id: attendanceRecord.id,
|
|
status: attendanceRecord.status,
|
|
checkInTime: attendanceRecord.checkInTime
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Device check-in error:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Device-based check out
|
|
router.post('/device/checkout', [
|
|
body('sessionId').isUUID().withMessage('Valid session ID is required'),
|
|
body('deviceId').isString().withMessage('Device ID is required')
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { sessionId, deviceId } = req.body;
|
|
|
|
// Find existing check-in record
|
|
const attendanceRecord = await AttendanceRecord.findOne({
|
|
where: {
|
|
sessionId,
|
|
deviceId,
|
|
status: 'checked_in'
|
|
}
|
|
});
|
|
|
|
if (!attendanceRecord) {
|
|
return res.status(400).json({ error: 'No active check-in found for this device' });
|
|
}
|
|
|
|
// Update to checked out
|
|
await attendanceRecord.update({
|
|
status: 'checked_out',
|
|
checkOutTime: new Date()
|
|
});
|
|
|
|
res.json({
|
|
message: 'Successfully checked out',
|
|
attendanceRecord: {
|
|
id: attendanceRecord.id,
|
|
status: attendanceRecord.status,
|
|
checkInTime: attendanceRecord.checkInTime,
|
|
checkOutTime: attendanceRecord.checkOutTime
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Device check-out error:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Link device to user
|
|
router.post('/device/link', [
|
|
auth,
|
|
body('deviceId').isString().withMessage('Device ID is required')
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { deviceId } = req.body;
|
|
const userId = req.user.id;
|
|
|
|
// Update attendance records to link device to user
|
|
await AttendanceRecord.update(
|
|
{ traineeId: userId },
|
|
{ where: { deviceId, traineeId: null } }
|
|
);
|
|
|
|
res.json({
|
|
message: 'Device successfully linked to user account',
|
|
deviceId,
|
|
userId
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Device link error:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Get device attendance history
|
|
router.get('/device/:deviceId/history', async (req, res) => {
|
|
try {
|
|
const { deviceId } = req.params;
|
|
|
|
const attendanceHistory = await AttendanceRecord.findAll({
|
|
where: { deviceId },
|
|
include: [
|
|
{ model: ClassroomSession, as: 'ClassroomSession' },
|
|
{ model: User, as: 'User' }
|
|
],
|
|
order: [['checkInTime', 'DESC']]
|
|
});
|
|
|
|
res.json({
|
|
deviceId,
|
|
attendanceHistory
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Device attendance history error:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|