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

132 lines
4.1 KiB
JavaScript

const express = require('express');
const path = require('path');
const fs = require('fs');
const {
authenticateMediaAccess,
checkCourseAccess,
addMediaSecurityHeaders,
logMediaAccess
} = require('../middleware/mediaAuth');
const router = express.Router();
/**
* Secure Media Serving Route
* Single endpoint for all media files with proper security
* Replaces the multiple fragmented routes created during CORS attempts
*/
/**
* GET /api/media/*
* Serves all media files (images, videos, documents) with security controls
*/
router.get('/*',
// Extract media path from request
(req, res, next) => {
req.mediaPath = req.path.substring(1); // Remove leading slash
next();
},
// Security middleware chain
authenticateMediaAccess,
checkCourseAccess,
addMediaSecurityHeaders,
logMediaAccess,
// Serve the actual file
async (req, res) => {
try {
const { mediaPath } = req;
// Decode URL-encoded characters (important for Arabic folder names)
const decodedPath = decodeURIComponent(mediaPath);
const fullPath = path.join(__dirname, '../uploads', decodedPath);
// Security check: Ensure path is within uploads directory
const uploadsDir = path.join(__dirname, '../uploads');
const resolvedPath = path.resolve(fullPath);
if (!resolvedPath.startsWith(path.resolve(uploadsDir))) {
return res.status(403).json({ error: 'Access denied: Invalid path' });
}
// Check if file exists
if (!fs.existsSync(fullPath)) {
return res.status(404).json({
error: 'File not found',
path: decodedPath
});
}
// Get file stats for proper headers
const stats = fs.statSync(fullPath);
const fileExtension = path.extname(fullPath).toLowerCase();
// Set appropriate content type
let contentType = 'application/octet-stream'; // default
// Image types
if (fileExtension === '.jpg' || fileExtension === '.jpeg') {
contentType = 'image/jpeg';
} else if (fileExtension === '.png') {
contentType = 'image/png';
} else if (fileExtension === '.gif') {
contentType = 'image/gif';
} else if (fileExtension === '.webp') {
contentType = 'image/webp';
} else if (fileExtension === '.svg') {
contentType = 'image/svg+xml';
}
// Video types
else if (fileExtension === '.mp4') {
contentType = 'video/mp4';
} else if (fileExtension === '.webm') {
contentType = 'video/webm';
} else if (fileExtension === '.avi') {
contentType = 'video/avi';
}
// Document types
else if (fileExtension === '.pdf') {
contentType = 'application/pdf';
} else if (fileExtension === '.doc') {
contentType = 'application/msword';
} else if (fileExtension === '.docx') {
contentType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
}
// Set content headers
res.setHeader('Content-Type', contentType);
res.setHeader('Content-Length', stats.size);
// Handle range requests for video streaming
const range = req.headers.range;
if (range && contentType.startsWith('video/')) {
const parts = range.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1;
const chunksize = (end - start) + 1;
const fileStream = fs.createReadStream(fullPath, { start, end });
res.writeHead(206, {
'Content-Range': `bytes ${start}-${end}/${stats.size}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
});
fileStream.pipe(res);
} else {
// Serve entire file
res.sendFile(fullPath);
}
} catch (error) {
console.error('Media serving error:', error);
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? error.message : 'File serving failed'
});
}
}
);
module.exports = router;