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.
372 lines
9.7 KiB
JavaScript
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;
|