UNPKG

@memberjunction/actions-bizapps-lms

Version:

LMS system integration actions for MemberJunction

382 lines (344 loc) 14.2 kB
import { RegisterClass } from '@memberjunction/global'; import { LearnWorldsBaseAction } from '../learnworlds-base.action'; import { ActionParam, ActionResultSimple, RunActionParams } from '@memberjunction/actions-base'; import { BaseAction } from '@memberjunction/actions'; /** * Interface for LearnWorlds user detailed information */ export interface LearnWorldsUserDetails { id: string; email: string; username: string; firstName?: string; lastName?: string; fullName?: string; status: 'active' | 'inactive' | 'suspended'; role: string; createdAt: Date; lastLoginAt?: Date; avatarUrl?: string; bio?: string; location?: string; timezone?: string; language?: string; phone?: string; tags?: string[]; customFields?: Record<string, any>; // Learning statistics totalCourses: number; completedCourses: number; inProgressCourses: number; notStartedCourses: number; totalTimeSpent: number; averageCompletionRate: number; lastActivityDate?: Date; // Enrollment details enrollments?: UserEnrollment[]; // Achievement data totalCertificates: number; totalBadges: number; points?: number; level?: string; // Account settings emailNotifications?: boolean; twoFactorEnabled?: boolean; agreedToTerms?: boolean; marketingConsent?: boolean; } /** * Interface for user enrollment in a course */ export interface UserEnrollment { courseId: string; courseTitle: string; enrolledAt: Date; status: 'active' | 'completed' | 'expired' | 'suspended'; progress: number; completedAt?: Date; expiresAt?: Date; lastAccessedAt?: Date; timeSpent: number; certificateUrl?: string; grade?: number; } /** * Action to retrieve detailed information about a specific LearnWorlds user */ @RegisterClass(BaseAction, 'GetLearnWorldsUserDetailsAction') export class GetLearnWorldsUserDetailsAction extends LearnWorldsBaseAction { /** * Description of the action */ public get Description(): string { return 'Retrieves comprehensive details about a specific user in LearnWorlds including profile, enrollments, progress, and achievements'; } /** * Main execution method */ protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> { try { const contextUser = params.ContextUser; if (!contextUser) { return { Success: false, ResultCode: 'ERROR', Message: 'Context user is required for LearnWorlds API calls' }; } // Store params for base class methods this.params = params.Params; // Get user ID const userId = this.getParamValue(params.Params, 'UserID'); if (!userId) { return { Success: false, ResultCode: 'ERROR', Message: 'UserID parameter is required' }; } // Include enrollments const includeEnrollments = this.getParamValue(params.Params, 'IncludeEnrollments') ?? true; const includeStats = this.getParamValue(params.Params, 'IncludeStats') ?? true; // Make the API request for user details const userResponse = await this.makeLearnWorldsRequest<any>( `users/${userId}`, 'GET', undefined, contextUser ); // Map basic user details const userDetails = this.mapUserDetails(userResponse); // Get enrollments if requested if (includeEnrollments) { const enrollmentsResponse = await this.makeLearnWorldsRequest<{ data: any[] }>( `users/${userId}/enrollments`, 'GET', undefined, contextUser ); if (enrollmentsResponse.data) { userDetails.enrollments = enrollmentsResponse.data.map(e => this.mapEnrollment(e)); // Calculate enrollment statistics this.calculateEnrollmentStats(userDetails); } } // Get additional stats if requested if (includeStats) { try { const statsResponse = await this.makeLearnWorldsRequest<any>( `users/${userId}/stats`, 'GET', undefined, contextUser ); if (statsResponse) { this.mergeStatistics(userDetails, statsResponse); } } catch (error) { // Stats endpoint might not be available for all plans console.log('Stats endpoint not available:', error); } } // Create output parameters if (!params.Params.find(p => p.Name === 'UserDetails')) { params.Params.push({ Name: 'UserDetails', Type: 'Output', Value: userDetails }); } else { params.Params.find(p => p.Name === 'UserDetails')!.Value = userDetails; } if (!params.Params.find(p => p.Name === 'Summary')) { params.Params.push({ Name: 'Summary', Type: 'Output', Value: this.createUserSummary(userDetails) }); } else { params.Params.find(p => p.Name === 'Summary')!.Value = this.createUserSummary(userDetails); } return { Success: true, ResultCode: 'SUCCESS', Message: `Successfully retrieved details for user ${userDetails.email}` }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { Success: false, ResultCode: 'ERROR', Message: errorMessage }; } } /** * Map LearnWorlds user data to detailed interface */ private mapUserDetails(lwUser: any): LearnWorldsUserDetails { return { id: lwUser.id || lwUser._id, email: lwUser.email, username: lwUser.username || lwUser.email, firstName: lwUser.first_name, lastName: lwUser.last_name, fullName: lwUser.full_name || `${lwUser.first_name || ''} ${lwUser.last_name || ''}`.trim(), status: this.mapUserStatus(lwUser.status || 'active'), role: lwUser.role || 'student', createdAt: this.parseLearnWorldsDate(lwUser.created || lwUser.created_at), lastLoginAt: lwUser.last_login ? this.parseLearnWorldsDate(lwUser.last_login) : undefined, avatarUrl: lwUser.avatar_url || lwUser.profile_image, bio: lwUser.bio || lwUser.description, location: lwUser.location || lwUser.country, timezone: lwUser.timezone, language: lwUser.language || 'en', phone: lwUser.phone, tags: lwUser.tags || [], customFields: lwUser.custom_fields || {}, // Initialize statistics totalCourses: 0, completedCourses: 0, inProgressCourses: 0, notStartedCourses: 0, totalTimeSpent: 0, averageCompletionRate: 0, lastActivityDate: lwUser.last_activity ? this.parseLearnWorldsDate(lwUser.last_activity) : undefined, // Achievement data totalCertificates: lwUser.certificates_count || 0, totalBadges: lwUser.badges_count || 0, points: lwUser.points || 0, level: lwUser.level, // Account settings emailNotifications: lwUser.email_notifications, twoFactorEnabled: lwUser.two_factor_enabled, agreedToTerms: lwUser.agreed_to_terms, marketingConsent: lwUser.marketing_consent }; } /** * Map enrollment data */ private mapEnrollment(enrollment: any): UserEnrollment { const progress = this.calculateProgress(enrollment.progress || {}); return { courseId: enrollment.course_id || enrollment.course?.id, courseTitle: enrollment.course_title || enrollment.course?.title || 'Unknown Course', enrolledAt: this.parseLearnWorldsDate(enrollment.enrolled_at || enrollment.created), status: this.mapLearnWorldsEnrollmentStatus(enrollment), progress: progress.percentage, completedAt: enrollment.completed_at ? this.parseLearnWorldsDate(enrollment.completed_at) : undefined, expiresAt: enrollment.expires_at ? this.parseLearnWorldsDate(enrollment.expires_at) : undefined, lastAccessedAt: enrollment.last_accessed_at ? this.parseLearnWorldsDate(enrollment.last_accessed_at) : undefined, timeSpent: progress.timeSpent || enrollment.time_spent || 0, certificateUrl: enrollment.certificate_url, grade: enrollment.grade || enrollment.final_grade }; } /** * Calculate enrollment statistics */ private calculateEnrollmentStats(userDetails: LearnWorldsUserDetails): void { if (!userDetails.enrollments) return; userDetails.totalCourses = userDetails.enrollments.length; userDetails.completedCourses = userDetails.enrollments.filter(e => e.status === 'completed').length; userDetails.inProgressCourses = userDetails.enrollments.filter(e => e.status === 'active' && e.progress > 0 && e.progress < 100 ).length; userDetails.notStartedCourses = userDetails.enrollments.filter(e => e.status === 'active' && e.progress === 0 ).length; // Calculate total time spent userDetails.totalTimeSpent = userDetails.enrollments.reduce((sum, e) => sum + e.timeSpent, 0); // Calculate average completion rate const activeEnrollments = userDetails.enrollments.filter(e => e.status === 'active' || e.status === 'completed'); if (activeEnrollments.length > 0) { const totalProgress = activeEnrollments.reduce((sum, e) => sum + e.progress, 0); userDetails.averageCompletionRate = Math.round(totalProgress / activeEnrollments.length); } } /** * Merge additional statistics */ private mergeStatistics(userDetails: LearnWorldsUserDetails, stats: any): void { if (stats.total_time_spent) { userDetails.totalTimeSpent = stats.total_time_spent; } if (stats.certificates_earned !== undefined) { userDetails.totalCertificates = stats.certificates_earned; } if (stats.badges_earned !== undefined) { userDetails.totalBadges = stats.badges_earned; } if (stats.points !== undefined) { userDetails.points = stats.points; } } /** * Create a summary of user information */ private createUserSummary(userDetails: LearnWorldsUserDetails): any { return { userId: userDetails.id, displayName: userDetails.fullName || userDetails.email, status: userDetails.status, role: userDetails.role, learningProgress: { totalCourses: userDetails.totalCourses, completedCourses: userDetails.completedCourses, inProgressCourses: userDetails.inProgressCourses, notStartedCourses: userDetails.notStartedCourses, averageCompletionRate: userDetails.averageCompletionRate, totalTimeSpent: this.formatDuration(userDetails.totalTimeSpent) }, achievements: { certificates: userDetails.totalCertificates, badges: userDetails.totalBadges, points: userDetails.points, level: userDetails.level }, engagement: { accountAge: Math.floor((new Date().getTime() - userDetails.createdAt.getTime()) / (1000 * 60 * 60 * 24)), lastLoginDaysAgo: userDetails.lastLoginAt ? Math.floor((new Date().getTime() - userDetails.lastLoginAt.getTime()) / (1000 * 60 * 60 * 24)) : null, lastActivityDaysAgo: userDetails.lastActivityDate ? Math.floor((new Date().getTime() - userDetails.lastActivityDate.getTime()) / (1000 * 60 * 60 * 24)) : null }, recentActivity: userDetails.enrollments ? userDetails.enrollments .filter(e => e.lastAccessedAt) .sort((a, b) => (b.lastAccessedAt?.getTime() || 0) - (a.lastAccessedAt?.getTime() || 0)) .slice(0, 5) .map(e => ({ courseTitle: e.courseTitle, progress: e.progress, lastAccessed: e.lastAccessedAt })) : [] }; } /** * Define the parameters for this action */ public get Params(): ActionParam[] { const baseParams = this.getCommonLMSParams(); const specificParams: ActionParam[] = [ { Name: 'UserID', Type: 'Input', Value: null }, { Name: 'IncludeEnrollments', Type: 'Input', Value: true }, { Name: 'IncludeStats', Type: 'Input', Value: true } ]; return [...baseParams, ...specificParams]; } }