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

270 lines
8.1 KiB
JavaScript

const express = require('express');
const { body, validationResult } = require('express-validator');
const { CourseSection, CourseContent, Course } = require('../models');
const { auth, requireTrainer } = require('../middleware/auth');
const { requirePaidEnrollment, requireEnrollment, requireCourseAccess } = require('../middleware/courseAccess');
const router = express.Router();
// @route GET /api/course-sections/:courseId
// @desc Get all sections for a course
// @access Private (Course owner or enrolled students)
router.get('/:courseId', auth, requireCourseAccess, async (req, res) => {
try {
const { courseId } = req.params;
const isTrainer = req.user.role === 'trainer' || req.user.role === 'super_admin';
// For trainers/admins, show all content. For students, only show published content
const sectionWhere = isTrainer ? { courseId } : { courseId, isPublished: true };
const contentWhere = isTrainer ? {} : { isPublished: true };
const sections = await CourseSection.findAll({
where: sectionWhere,
include: [
{
model: CourseContent,
as: 'contents',
where: contentWhere,
required: false,
order: [['order', 'ASC']]
}
],
order: [['order', 'ASC']]
});
res.json({ sections });
} catch (error) {
console.error('Get course sections error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
// @route POST /api/course-sections/:courseId
// @desc Create a new section for a course
// @access Private (Course trainer or Super Admin)
router.post('/:courseId', [
auth,
requireTrainer,
body('title').isLength({ min: 1, max: 200 }).withMessage('Title must be between 1 and 200 characters'),
body('description').optional().isLength({ max: 1000 }).withMessage('Description must be less than 1000 characters'),
body('order').optional().isInt({ min: 0 }).withMessage('Order must be a positive integer')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { courseId } = req.params;
const { title, description, order } = req.body;
// Check if course exists and user is the trainer
const course = await Course.findByPk(courseId);
if (!course) {
return res.status(404).json({ error: 'Course not found.' });
}
if (course.trainerId !== req.user.id && req.user.role !== 'super_admin') {
return res.status(403).json({ error: 'Not authorized to create sections for this course.' });
}
// Get the next order if not provided
let sectionOrder = order;
if (sectionOrder === undefined) {
const lastSection = await CourseSection.findOne({
where: { courseId },
order: [['order', 'DESC']]
});
sectionOrder = lastSection ? lastSection.order + 1 : 0;
}
const section = await CourseSection.create({
courseId,
title,
description,
order: sectionOrder
});
res.status(201).json({
message: 'Section created successfully.',
section
});
} catch (error) {
console.error('Create course section error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
// @route PUT /api/course-sections/:sectionId
// @desc Update a course section
// @access Private (Course trainer or Super Admin)
router.put('/:sectionId', [
auth,
requireTrainer,
body('title').optional().isLength({ min: 1, max: 200 }).withMessage('Title must be between 1 and 200 characters'),
body('description').optional().isLength({ max: 1000 }).withMessage('Description must be less than 1000 characters'),
body('order').optional().isInt({ min: 0 }).withMessage('Order must be a positive integer'),
body('isPublished').optional().isBoolean().withMessage('isPublished must be a boolean'),
body('isCollapsible').optional().isBoolean().withMessage('isCollapsible must be a boolean'),
body('isExpanded').optional().isBoolean().withMessage('isExpanded must be a boolean')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { sectionId } = req.params;
const updateData = req.body;
const section = await CourseSection.findByPk(sectionId, {
include: [
{
model: Course,
as: 'course'
}
]
});
if (!section) {
return res.status(404).json({ error: 'Section not found.' });
}
if (section.course.trainerId !== req.user.id && req.user.role !== 'super_admin') {
return res.status(403).json({ error: 'Not authorized to update this section.' });
}
await section.update(updateData);
res.json({
message: 'Section updated successfully.',
section
});
} catch (error) {
console.error('Update course section error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
// @route DELETE /api/course-sections/:sectionId
// @desc Delete a course section
// @access Private (Course trainer or Super Admin)
router.delete('/:sectionId', [
auth,
requireTrainer
], async (req, res) => {
try {
const { sectionId } = req.params;
const section = await CourseSection.findByPk(sectionId, {
include: [
{
model: Course,
as: 'course'
}
]
});
if (!section) {
return res.status(404).json({ error: 'Section not found.' });
}
if (section.course.trainerId !== req.user.id && req.user.role !== 'super_admin') {
return res.status(403).json({ error: 'Not authorized to delete this section.' });
}
// Check if section has content
const contentCount = await CourseContent.count({
where: { sectionId }
});
if (contentCount > 0) {
return res.status(400).json({
error: 'Cannot delete section with content. Move or delete all content first.'
});
}
await section.destroy();
res.json({
message: 'Section deleted successfully.'
});
} catch (error) {
console.error('Delete course section error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
// @route PUT /api/course-sections/:sectionId/reorder
// @desc Reorder sections
// @access Private (Course trainer or Super Admin)
router.put('/:sectionId/reorder', [
auth,
requireTrainer,
body('newOrder').isInt({ min: 0 }).withMessage('New order must be a positive integer')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { sectionId } = req.params;
const { newOrder } = req.body;
const section = await CourseSection.findByPk(sectionId, {
include: [
{
model: Course,
as: 'course'
}
]
});
if (!section) {
return res.status(404).json({ error: 'Section not found.' });
}
if (section.course.trainerId !== req.user.id && req.user.role !== 'super_admin') {
return res.status(403).json({ error: 'Not authorized to reorder this section.' });
}
const oldOrder = section.order;
if (newOrder > oldOrder) {
// Moving down - decrease order of sections in between
await CourseSection.update(
{ order: sequelize.literal('order - 1') },
{
where: {
courseId: section.courseId,
order: { [require('sequelize').Op.between]: [oldOrder + 1, newOrder] }
}
}
);
} else if (newOrder < oldOrder) {
// Moving up - increase order of sections in between
await CourseSection.update(
{ order: sequelize.literal('order + 1') },
{
where: {
courseId: section.courseId,
order: { [require('sequelize').Op.between]: [newOrder, oldOrder - 1] }
}
}
);
}
await section.update({ order: newOrder });
res.json({
message: 'Section reordered successfully.',
section
});
} catch (error) {
console.error('Reorder course section error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
module.exports = router;