courseworx/backend/core/plugin-loader.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

334 lines
9.5 KiB
JavaScript

/**
* CourseWorx Plugin Loader
*
* This module is responsible for discovering, validating, and loading
* plugins from the plugins directory. It handles plugin lifecycle
* management and integration with the Express application.
*/
const fs = require('fs').promises;
const path = require('path');
const pluginRegistry = require('./plugin-registry');
const pluginValidator = require('./plugin-validator');
class PluginLoader {
constructor() {
this.pluginsDir = path.join(__dirname, '..', 'plugins');
this.loadedPlugins = new Map();
this.loadErrors = new Map();
}
/**
* Initialize the plugin system
* @param {Object} app - Express application instance
*/
async initialize(app) {
console.log('🔌 Initializing plugin system...');
try {
// Ensure plugins directory exists
await this.ensurePluginsDirectory();
// Load all plugins
await this.loadAllPlugins(app);
// Register API routes for enabled plugins
this.registerApiRoutes(app);
console.log(`✅ Plugin system initialized. Loaded ${this.loadedPlugins.size} plugins.`);
// Log any load errors
if (this.loadErrors.size > 0) {
console.warn('⚠️ Some plugins failed to load:');
for (const [pluginName, error] of this.loadErrors) {
console.warn(` - ${pluginName}: ${error.message}`);
}
}
} catch (error) {
console.error('❌ Failed to initialize plugin system:', error);
throw error;
}
}
/**
* Ensure the plugins directory exists
*/
async ensurePluginsDirectory() {
try {
await fs.access(this.pluginsDir);
} catch (error) {
// Directory doesn't exist, create it
await fs.mkdir(this.pluginsDir, { recursive: true });
console.log(`📁 Created plugins directory: ${this.pluginsDir}`);
}
}
/**
* Load all plugins from the plugins directory
* @param {Object} app - Express application instance
*/
async loadAllPlugins(app) {
try {
const pluginFolders = await fs.readdir(this.pluginsDir);
for (const folder of pluginFolders) {
const pluginPath = path.join(this.pluginsDir, folder);
const pluginStat = await fs.stat(pluginPath);
if (pluginStat.isDirectory()) {
await this.loadPlugin(folder, pluginPath, app);
}
}
} catch (error) {
console.error('❌ Error reading plugins directory:', error);
throw error;
}
}
/**
* Load a single plugin
* @param {string} pluginName - Plugin folder name
* @param {string} pluginPath - Full path to plugin directory
* @param {Object} app - Express application instance
*/
async loadPlugin(pluginName, pluginPath, app) {
try {
console.log(`📦 Loading plugin: ${pluginName}`);
// Check if plugin has required files
const packageJsonPath = path.join(pluginPath, 'package.json');
const indexJsPath = path.join(pluginPath, 'index.js');
// Check if package.json exists
try {
await fs.access(packageJsonPath);
} catch (error) {
throw new Error('Missing package.json file');
}
// Check if index.js exists
try {
await fs.access(indexJsPath);
} catch (error) {
throw new Error('Missing index.js file');
}
// Load and validate package.json
const packageJson = require(packageJsonPath);
const validationResult = pluginValidator.validatePackageJson(packageJson);
if (!validationResult.isValid) {
throw new Error(`Package.json validation failed: ${validationResult.errors.join(', ')}`);
}
// Load the plugin module
const pluginModule = require(pluginPath);
if (typeof pluginModule.register !== 'function') {
throw new Error('Plugin must export a register function');
}
// Register the plugin in the registry
const pluginInfo = pluginRegistry.registerPlugin(pluginName, packageJson);
// Call the plugin's register function
const registerContext = {
app,
registry: pluginRegistry,
pluginName,
pluginPath,
packageJson
};
await pluginModule.register(registerContext);
// Enable the plugin by default (unless specified otherwise)
if (packageJson.courseworx?.autoEnable !== false) {
pluginRegistry.enablePlugin(pluginName);
}
// Store the loaded plugin info
this.loadedPlugins.set(pluginName, {
path: pluginPath,
module: pluginModule,
info: pluginInfo,
loadedAt: new Date()
});
console.log(`✅ Plugin loaded successfully: ${pluginName} v${packageJson.version}`);
} catch (error) {
console.error(`❌ Failed to load plugin ${pluginName}:`, error.message);
this.loadErrors.set(pluginName, error);
// Don't throw here, continue loading other plugins
}
}
/**
* Register API routes for enabled plugins
* @param {Object} app - Express application instance
*/
registerApiRoutes(app) {
const apiRoutes = pluginRegistry.getApiRoutes();
console.log(`🔗 Found ${apiRoutes.length} API routes to register:`, apiRoutes.map(r => r.path));
for (const routeInfo of apiRoutes) {
try {
console.log(`🔗 Registering API routes: ${routeInfo.path} for plugin: ${routeInfo.plugin}`);
app.use(routeInfo.path, routeInfo.router);
console.log(`✅ Successfully registered API routes: ${routeInfo.path}`);
} catch (error) {
console.error(`❌ Failed to register API routes for ${routeInfo.plugin}:`, error);
}
}
}
/**
* Reload a specific plugin
* @param {string} pluginName - Plugin name
* @param {Object} app - Express application instance
*/
async reloadPlugin(pluginName, app) {
try {
console.log(`🔄 Reloading plugin: ${pluginName}`);
// Unregister the plugin from registry
if (pluginRegistry.getPlugin(pluginName)) {
pluginRegistry.unregisterPlugin(pluginName);
}
// Remove from loaded plugins
this.loadedPlugins.delete(pluginName);
this.loadErrors.delete(pluginName);
// Reload the plugin
const pluginPath = path.join(this.pluginsDir, pluginName);
await this.loadPlugin(pluginName, pluginPath, app);
console.log(`✅ Plugin reloaded: ${pluginName}`);
} catch (error) {
console.error(`❌ Failed to reload plugin ${pluginName}:`, error);
throw error;
}
}
/**
* Reload all plugins
* @param {Object} app - Express application instance
*/
async reloadAllPlugins(app) {
console.log('🔄 Reloading all plugins...');
// Clear the registry
pluginRegistry.clear();
// Clear loaded plugins
this.loadedPlugins.clear();
this.loadErrors.clear();
// Reload all plugins
await this.loadAllPlugins(app);
// Re-register API routes
this.registerApiRoutes(app);
console.log(`✅ All plugins reloaded. Loaded ${this.loadedPlugins.size} plugins.`);
}
/**
* Get information about loaded plugins
*/
getLoadedPlugins() {
return Array.from(this.loadedPlugins.values()).map(plugin => ({
name: plugin.info.name,
version: plugin.info.version,
description: plugin.info.description,
author: plugin.info.author,
enabled: plugin.info.enabled,
loadedAt: plugin.loadedAt,
path: plugin.path
}));
}
/**
* Get plugin load errors
*/
getLoadErrors() {
return Array.from(this.loadErrors.entries()).map(([name, error]) => ({
name,
error: error.message,
stack: error.stack
}));
}
/**
* Check if a plugin is loaded
* @param {string} pluginName - Plugin name
*/
isPluginLoaded(pluginName) {
return this.loadedPlugins.has(pluginName);
}
/**
* Get plugin statistics
*/
getStats() {
const registryStats = pluginRegistry.getStats();
return {
...registryStats,
loadedPlugins: this.loadedPlugins.size,
loadErrors: this.loadErrors.size,
pluginsDirectory: this.pluginsDir
};
}
/**
* Validate a plugin without loading it
* @param {string} pluginPath - Path to plugin directory
*/
async validatePlugin(pluginPath) {
try {
const packageJsonPath = path.join(pluginPath, 'package.json');
const indexJsPath = path.join(pluginPath, 'index.js');
// Check required files
await fs.access(packageJsonPath);
await fs.access(indexJsPath);
// Load and validate package.json
const packageJson = require(packageJsonPath);
const validationResult = pluginValidator.validatePackageJson(packageJson);
// Check if register function exists
const pluginModule = require(pluginPath);
const hasRegisterFunction = typeof pluginModule.register === 'function';
return {
isValid: validationResult.isValid && hasRegisterFunction,
errors: [
...validationResult.errors,
...(hasRegisterFunction ? [] : ['Missing register function'])
],
warnings: validationResult.warnings,
metadata: packageJson
};
} catch (error) {
return {
isValid: false,
errors: [error.message],
warnings: [],
metadata: null
};
}
}
}
// Create and export a singleton instance
const pluginLoader = new PluginLoader();
module.exports = pluginLoader;