UNPKG

@memberjunction/actions-bizapps-lms

Version:

LMS system integration actions for MemberJunction

222 lines (193 loc) 7.38 kB
import { RegisterClass } from '@memberjunction/global'; import { BaseLMSAction } from '../../base/base-lms.action'; import { UserInfo } from '@memberjunction/core'; import { CompanyIntegrationEntity } from '@memberjunction/core-entities'; import { BaseAction } from '@memberjunction/actions'; /** * Base class for all LearnWorlds LMS actions. * Handles LearnWorlds-specific authentication and API interaction patterns. */ @RegisterClass(BaseAction, 'LearnWorldsBaseAction') export abstract class LearnWorldsBaseAction extends BaseLMSAction { protected lmsProvider = 'LearnWorlds'; protected integrationName = 'LearnWorlds'; /** * LearnWorlds API version */ protected apiVersion = 'v2'; /** * Current action parameters (set by the framework) */ protected params: any; /** * Makes an authenticated request to LearnWorlds API */ protected async makeLearnWorldsRequest<T = any>( endpoint: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET', body?: any, contextUser?: UserInfo ): Promise<T> { if (!contextUser) { throw new Error('Context user is required for LearnWorlds API calls'); } // Get company ID from action params const companyId = this.getParamValue(this.params, 'CompanyID'); if (!companyId) { throw new Error('CompanyID parameter is required'); } // Get the integration credentials const integration = await this.getCompanyIntegration(companyId, contextUser); // Get API credentials (from env vars or database) const credentials = await this.getAPICredentials(integration); if (!credentials.apiKey) { throw new Error('API Key is required for LearnWorlds integration'); } // Get the school domain from ExternalSystemID or environment const schoolDomain = integration.ExternalSystemID || this.getCredentialFromEnv(companyId, 'SCHOOL_DOMAIN'); if (!schoolDomain) { throw new Error('School domain not found. Set in CompanyIntegration.ExternalSystemID or environment variable'); } // Build the full URL const baseUrl = `https://${schoolDomain}/api/${this.apiVersion}`; const fullUrl = `${baseUrl}/${endpoint}`; // Prepare headers const headers: Record<string, string> = { 'Authorization': `Bearer ${credentials.apiKey}`, 'Accept': 'application/json', 'Content-Type': 'application/json', 'Lw-Client': 'MemberJunction' }; try { const response = await fetch(fullUrl, { method, headers, body: body ? JSON.stringify(body) : undefined }); if (!response.ok) { const errorText = await response.text(); let errorMessage = `LearnWorlds API error: ${response.status} ${response.statusText}`; try { const errorJson = JSON.parse(errorText); if (errorJson.error) { errorMessage = `LearnWorlds API error: ${errorJson.error.message || errorJson.error}`; } else if (errorJson.message) { errorMessage = `LearnWorlds API error: ${errorJson.message}`; } } catch { errorMessage += ` - ${errorText}`; } throw new Error(errorMessage); } const result = await response.json(); return result as T; } catch (error) { if (error instanceof Error) { throw error; } throw new Error(`LearnWorlds API request failed: ${error}`); } } /** * Makes a paginated request to LearnWorlds API */ protected async makeLearnWorldsPaginatedRequest<T = any>( endpoint: string, params: Record<string, any> = {}, contextUser?: UserInfo ): Promise<T[]> { const results: T[] = []; let page = 1; let hasMore = true; const limit = params.limit || 50; while (hasMore) { const queryParams = new URLSearchParams({ ...params, page: page.toString(), limit: limit.toString() }); const response = await this.makeLearnWorldsRequest<{ data: T[]; meta?: { page: number; totalPages: number; totalItems: number; }; }>(`${endpoint}?${queryParams}`, 'GET', undefined, contextUser); if (response.data && Array.isArray(response.data)) { results.push(...response.data); } // Check if there are more pages if (response.meta && response.meta.page < response.meta.totalPages) { page++; } else { hasMore = false; } // Respect max results if specified const maxResults = this.getParamValue(this.params, 'MaxResults'); if (maxResults && results.length >= maxResults) { return results.slice(0, maxResults); } } return results; } /** * Convert LearnWorlds date format to Date object */ protected parseLearnWorldsDate(dateString: string | number): Date { // LearnWorlds sometimes returns timestamps as seconds since epoch if (typeof dateString === 'number') { return new Date(dateString * 1000); } return new Date(dateString); } /** * Format date for LearnWorlds API (ISO 8601) */ protected formatLearnWorldsDate(date: Date): string { return date.toISOString(); } /** * Map LearnWorlds user status to standard status */ protected mapUserStatus(status: string): 'active' | 'inactive' | 'suspended' { const statusMap: Record<string, 'active' | 'inactive' | 'suspended'> = { 'active': 'active', 'inactive': 'inactive', 'suspended': 'suspended', 'blocked': 'suspended' }; return statusMap[status.toLowerCase()] || 'inactive'; } /** * Map LearnWorlds enrollment status */ protected mapLearnWorldsEnrollmentStatus(enrollment: any): 'active' | 'completed' | 'expired' | 'suspended' { if (enrollment.completed) { return 'completed'; } if (enrollment.expired) { return 'expired'; } if (enrollment.suspended || !enrollment.active) { return 'suspended'; } return 'active'; } /** * Calculate progress from LearnWorlds data */ protected calculateProgress(progressData: any): { percentage: number; completedUnits: number; totalUnits: number; timeSpent: number; } { return { percentage: progressData.percentage || 0, completedUnits: progressData.completed_units || 0, totalUnits: progressData.total_units || 0, timeSpent: progressData.time_spent || 0 }; } }