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

372 lines
9.7 KiB
JavaScript

/**
* CourseWorx Plugin Registry
*
* This module manages the global plugin registry that stores all plugin
* metadata, routes, menu items, and event listeners. It serves as the
* central hub for plugin management and discovery.
*/
class PluginRegistry {
constructor() {
// Initialize the registry with empty collections
this.plugins = new Map(); // Plugin metadata by name
this.apiRoutes = new Map(); // API routes by plugin name
this.adminMenuItems = []; // Menu items for admin dashboard
this.eventListeners = new Map(); // Event listeners by event type
this.hooks = new Map(); // Hook functions by hook point
this.permissions = new Set(); // Custom permissions
this.settings = new Map(); // Plugin settings by plugin name
this.enabledPlugins = new Set(); // Set of enabled plugin names
}
/**
* Register a new plugin
* @param {string} name - Plugin name
* @param {Object} metadata - Plugin metadata from package.json
* @param {Object} config - Plugin configuration
*/
registerPlugin(name, metadata, config = {}) {
if (this.plugins.has(name)) {
throw new Error(`Plugin '${name}' is already registered`);
}
const pluginInfo = {
name,
metadata,
config,
registeredAt: new Date(),
enabled: false,
version: metadata.version || '1.0.0',
description: metadata.description || '',
author: metadata.author || 'Unknown',
license: metadata.license || 'MIT'
};
this.plugins.set(name, pluginInfo);
console.log(`✓ Plugin registered: ${name} v${pluginInfo.version}`);
return pluginInfo;
}
/**
* Enable a plugin
* @param {string} name - Plugin name
*/
enablePlugin(name) {
if (!this.plugins.has(name)) {
throw new Error(`Plugin '${name}' is not registered`);
}
const plugin = this.plugins.get(name);
plugin.enabled = true;
this.enabledPlugins.add(name);
console.log(`✓ Plugin enabled: ${name}`);
return plugin;
}
/**
* Disable a plugin
* @param {string} name - Plugin name
*/
disablePlugin(name) {
if (!this.plugins.has(name)) {
throw new Error(`Plugin '${name}' is not registered`);
}
const plugin = this.plugins.get(name);
plugin.enabled = false;
this.enabledPlugins.delete(name);
console.log(`✓ Plugin disabled: ${name}`);
return plugin;
}
/**
* Unregister a plugin
* @param {string} name - Plugin name
*/
unregisterPlugin(name) {
if (!this.plugins.has(name)) {
throw new Error(`Plugin '${name}' is not registered`);
}
// Remove all plugin-related data
this.plugins.delete(name);
this.apiRoutes.delete(name);
this.enabledPlugins.delete(name);
this.settings.delete(name);
// Remove menu items for this plugin
this.adminMenuItems = this.adminMenuItems.filter(item => item.plugin !== name);
// Remove event listeners for this plugin
for (const [eventType, listeners] of this.eventListeners) {
this.eventListeners.set(eventType, listeners.filter(listener => listener.plugin !== name));
}
// Remove hooks for this plugin
for (const [hookPoint, hooks] of this.hooks) {
this.hooks.set(hookPoint, hooks.filter(hook => hook.plugin !== name));
}
console.log(`✓ Plugin unregistered: ${name}`);
}
/**
* Register API routes for a plugin
* @param {string} pluginName - Plugin name
* @param {string} routePath - Route path (e.g., '/api/financial')
* @param {Object} router - Express router
*/
registerApiRoutes(pluginName, routePath, router) {
if (!this.plugins.has(pluginName)) {
throw new Error(`Plugin '${pluginName}' is not registered`);
}
this.apiRoutes.set(pluginName, {
path: routePath,
router,
plugin: pluginName,
registeredAt: new Date()
});
console.log(`✓ API routes registered for ${pluginName}: ${routePath}`);
}
/**
* Register admin menu items for a plugin
* @param {string} pluginName - Plugin name
* @param {Array} menuItems - Array of menu item objects
*/
registerMenuItems(pluginName, menuItems) {
if (!this.plugins.has(pluginName)) {
throw new Error(`Plugin '${pluginName}' is not registered`);
}
// Add plugin reference to each menu item
const itemsWithPlugin = menuItems.map(item => ({
...item,
plugin: pluginName,
registeredAt: new Date()
}));
this.adminMenuItems.push(...itemsWithPlugin);
console.log(`✓ Menu items registered for ${pluginName}: ${menuItems.length} items`);
}
/**
* Register event listeners for a plugin
* @param {string} pluginName - Plugin name
* @param {string} eventType - Event type (e.g., 'user:created')
* @param {Function} listener - Event listener function
*/
registerEventListener(pluginName, eventType, listener) {
if (!this.plugins.has(pluginName)) {
throw new Error(`Plugin '${pluginName}' is not registered`);
}
if (!this.eventListeners.has(eventType)) {
this.eventListeners.set(eventType, []);
}
const listeners = this.eventListeners.get(eventType);
listeners.push({
plugin: pluginName,
listener,
registeredAt: new Date()
});
console.log(`✓ Event listener registered for ${pluginName}: ${eventType}`);
}
/**
* Register hooks for a plugin
* @param {string} pluginName - Plugin name
* @param {string} hookPoint - Hook point (e.g., 'before:user:create')
* @param {Function} hook - Hook function
*/
registerHook(pluginName, hookPoint, hook) {
if (!this.plugins.has(pluginName)) {
throw new Error(`Plugin '${pluginName}' is not registered`);
}
if (!this.hooks.has(hookPoint)) {
this.hooks.set(hookPoint, []);
}
const hooks = this.hooks.get(hookPoint);
hooks.push({
plugin: pluginName,
hook,
registeredAt: new Date()
});
console.log(`✓ Hook registered for ${pluginName}: ${hookPoint}`);
}
/**
* Register custom permissions for a plugin
* @param {string} pluginName - Plugin name
* @param {Array} permissions - Array of permission strings
*/
registerPermissions(pluginName, permissions) {
if (!this.plugins.has(pluginName)) {
throw new Error(`Plugin '${pluginName}' is not registered`);
}
permissions.forEach(permission => {
this.permissions.add(permission);
});
console.log(`✓ Permissions registered for ${pluginName}: ${permissions.length} permissions`);
}
/**
* Set plugin settings
* @param {string} pluginName - Plugin name
* @param {Object} settings - Plugin settings
*/
setPluginSettings(pluginName, settings) {
if (!this.plugins.has(pluginName)) {
throw new Error(`Plugin '${pluginName}' is not registered`);
}
this.settings.set(pluginName, {
...settings,
updatedAt: new Date()
});
}
/**
* Get plugin settings
* @param {string} pluginName - Plugin name
*/
getPluginSettings(pluginName) {
return this.settings.get(pluginName) || {};
}
/**
* Get all registered plugins
*/
getAllPlugins() {
return Array.from(this.plugins.values());
}
/**
* Get enabled plugins only
*/
getEnabledPlugins() {
return Array.from(this.plugins.values()).filter(plugin => plugin.enabled);
}
/**
* Get plugin by name
* @param {string} name - Plugin name
*/
getPlugin(name) {
return this.plugins.get(name);
}
/**
* Get API routes for enabled plugins
*/
getApiRoutes() {
const routes = [];
for (const [pluginName, routeInfo] of this.apiRoutes) {
if (this.enabledPlugins.has(pluginName)) {
routes.push(routeInfo);
}
}
return routes;
}
/**
* Get admin menu items for a specific user role
* @param {string} userRole - User role (sa, trainer, trainee)
*/
getMenuItems(userRole) {
return this.adminMenuItems.filter(item => {
// Only return items for enabled plugins
if (!this.enabledPlugins.has(item.plugin)) {
return false;
}
// Filter by user role
if (item.section && item.section !== userRole) {
return false;
}
return true;
});
}
/**
* Get event listeners for a specific event type
* @param {string} eventType - Event type
*/
getEventListeners(eventType) {
const listeners = this.eventListeners.get(eventType) || [];
// Only return listeners for enabled plugins
return listeners.filter(listener => this.enabledPlugins.has(listener.plugin));
}
/**
* Get hooks for a specific hook point
* @param {string} hookPoint - Hook point
*/
getHooks(hookPoint) {
const hooks = this.hooks.get(hookPoint) || [];
// Only return hooks for enabled plugins
return hooks.filter(hook => this.enabledPlugins.has(hook.plugin));
}
/**
* Get all custom permissions
*/
getPermissions() {
return Array.from(this.permissions);
}
/**
* Check if a plugin is enabled
* @param {string} name - Plugin name
*/
isPluginEnabled(name) {
return this.enabledPlugins.has(name);
}
/**
* Get registry statistics
*/
getStats() {
return {
totalPlugins: this.plugins.size,
enabledPlugins: this.enabledPlugins.size,
apiRoutes: this.apiRoutes.size,
menuItems: this.adminMenuItems.length,
eventTypes: this.eventListeners.size,
hookPoints: this.hooks.size,
permissions: this.permissions.size
};
}
/**
* Clear the entire registry (for testing)
*/
clear() {
this.plugins.clear();
this.apiRoutes.clear();
this.adminMenuItems.length = 0;
this.eventListeners.clear();
this.hooks.clear();
this.permissions.clear();
this.settings.clear();
this.enabledPlugins.clear();
}
}
// Create and export a singleton instance
const pluginRegistry = new PluginRegistry();
module.exports = pluginRegistry;