@memberjunction/actions-bizapps-lms
Version:
LMS system integration actions for MemberJunction
440 lines • 17.2 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { RegisterClass } from '@memberjunction/global';
import { LearnWorldsBaseAction } from '../learnworlds-base.action.js';
import { BaseAction } from '@memberjunction/actions';
/**
* Action to retrieve courses from LearnWorlds LMS
*/
let GetLearnWorldsCoursesAction = class GetLearnWorldsCoursesAction extends LearnWorldsBaseAction {
/**
* Description of the action
*/
get Description() {
return 'Retrieves the course catalog from LearnWorlds with filtering, search, and sorting options';
}
/**
* Typed public method for direct (non-framework) callers.
* Sets company context, fetches courses, and returns a strongly-typed result.
* Throws on error.
*/
async GetCourses(params, contextUser) {
this.SetCompanyContext(params.CompanyID);
const queryParams = this.buildQueryParams(params);
const courses = await this.makeLearnWorldsPaginatedRequest('courses', queryParams, contextUser);
const mappedCourses = courses.map((course) => this.mapLearnWorldsCourse(course));
const summary = this.calculateCourseCatalogSummary(mappedCourses);
return {
Courses: mappedCourses,
TotalCount: mappedCourses.length,
Summary: summary,
};
}
/**
* Framework entry-point – thin wrapper around the typed public method.
*/
async InternalRunAction(params) {
const { Params, ContextUser } = params;
this.params = Params;
try {
const typedParams = this.extractGetCoursesParams(Params);
const result = await this.GetCourses(typedParams, ContextUser);
this.setOutputParam(Params, 'Courses', result.Courses);
this.setOutputParam(Params, 'TotalCount', result.TotalCount);
this.setOutputParam(Params, 'Summary', result.Summary);
return this.buildSuccessResult(`Successfully retrieved ${result.TotalCount} courses from LearnWorlds`, Params);
}
catch (error) {
const msg = error instanceof Error ? error.message : 'Unknown error occurred';
return this.buildErrorResult('ERROR', msg, Params);
}
}
/**
* Extract typed params from framework ActionParam[]
*/
extractGetCoursesParams(params) {
return {
CompanyID: this.getRequiredStringParam(params, 'CompanyID'),
SearchText: this.getOptionalStringParam(params, 'SearchText'),
Status: this.getOptionalStringParam(params, 'Status'),
CategoryID: this.getOptionalStringParam(params, 'CategoryID'),
Level: this.getOptionalStringParam(params, 'Level'),
Language: this.getOptionalStringParam(params, 'Language'),
OnlyFree: this.getOptionalBooleanParam(params, 'OnlyFree', false),
MinPrice: this.getOptionalNumberParam(params, 'MinPrice', undefined),
MaxPrice: this.getOptionalNumberParam(params, 'MaxPrice', undefined),
Tags: this.getOptionalStringParam(params, 'Tags'),
InstructorID: this.getOptionalStringParam(params, 'InstructorID'),
CreatedAfter: this.getOptionalStringParam(params, 'CreatedAfter'),
CreatedBefore: this.getOptionalStringParam(params, 'CreatedBefore'),
SortBy: this.getOptionalStringParam(params, 'SortBy'),
SortOrder: (this.getOptionalStringParam(params, 'SortOrder') || 'desc'),
IncludeEnrollmentStats: this.getOptionalBooleanParam(params, 'IncludeEnrollmentStats', true),
MaxResults: this.getOptionalNumberParam(params, 'MaxResults', LearnWorldsBaseAction.LW_MAX_PAGE_SIZE),
};
}
/**
* Build query parameters for the LearnWorlds API request
*/
buildQueryParams(params) {
const queryParams = {};
if (params.SearchText) {
queryParams.search = params.SearchText;
}
if (params.Status) {
queryParams.status = params.Status;
}
if (params.CategoryID) {
queryParams.category_id = params.CategoryID;
}
if (params.Level) {
queryParams.level = params.Level;
}
if (params.Language) {
queryParams.language = params.Language;
}
if (params.OnlyFree) {
queryParams.is_free = true;
}
if (params.MinPrice != null && params.MinPrice > 0) {
queryParams.min_price = params.MinPrice;
}
if (params.MaxPrice != null && params.MaxPrice > 0) {
queryParams.max_price = params.MaxPrice;
}
if (params.Tags) {
queryParams.tags = params.Tags;
}
if (params.InstructorID) {
queryParams.instructor_id = params.InstructorID;
}
if (params.CreatedAfter) {
queryParams.created_after = this.formatLearnWorldsDate(new Date(params.CreatedAfter));
}
if (params.CreatedBefore) {
queryParams.created_before = this.formatLearnWorldsDate(new Date(params.CreatedBefore));
}
const sortBy = params.SortBy || 'created';
const sortOrder = params.SortOrder || 'desc';
queryParams.sort = `${sortOrder === 'asc' ? '' : '-'}${sortBy}`;
const includeEnrollmentStats = params.IncludeEnrollmentStats ?? true;
if (includeEnrollmentStats) {
queryParams.include = 'enrollment_stats';
}
const maxResults = params.MaxResults || 100;
queryParams.limit = Math.min(maxResults, 100);
return queryParams;
}
/**
* Map LearnWorlds course data to our interface
*/
mapLearnWorldsCourse(lwCourse) {
return {
...this.mapCourseIdentity(lwCourse),
...this.mapCoursePricing(lwCourse),
...this.mapCourseInstructor(lwCourse),
...this.mapCourseContentStats(lwCourse),
...this.mapCourseMeta(lwCourse),
};
}
mapCourseIdentity(lw) {
return {
id: lw.id || lw._id || '',
title: lw.title,
subtitle: lw.subtitle,
description: lw.description,
shortDescription: lw.short_description || lw.excerpt,
status: this.mapCourseStatus(lw.status || ''),
visibility: this.mapCourseVisibility(lw.visibility || lw.access_type || ''),
isActive: lw.is_active !== false,
isFree: lw.is_free || lw.price === 0 || false,
categoryId: lw.category_id || lw.category?.id,
categoryName: lw.category_name || lw.category?.name,
tags: lw.tags || [],
level: this.mapCourseLevel(lw.level || lw.difficulty || ''),
language: lw.language || 'en',
duration: lw.duration || lw.estimated_duration,
thumbnailUrl: lw.thumbnail_url || lw.image,
coverImageUrl: lw.cover_image_url || lw.cover_image,
promoVideoUrl: lw.promo_video_url || lw.video_url,
};
}
mapCoursePricing(lw) {
return {
price: lw.price,
currency: lw.currency || 'USD',
originalPrice: lw.original_price,
discountPercentage: this.calculateDiscountPercentage(lw.original_price, lw.price),
};
}
mapCourseInstructor(lw) {
return {
instructorId: lw.instructor_id || lw.instructor?.id || lw.author_id,
instructorName: lw.instructor_name || lw.instructor?.name || lw.author_name,
instructorBio: lw.instructor_bio || lw.instructor?.bio,
instructorAvatarUrl: lw.instructor_avatar || lw.instructor?.avatar_url,
};
}
mapCourseContentStats(lw) {
return {
totalUnits: lw.total_units || lw.sections_count || 0,
totalLessons: lw.total_lessons || lw.lessons_count || 0,
totalQuizzes: lw.total_quizzes || lw.quizzes_count || 0,
totalAssignments: lw.total_assignments || lw.assignments_count || 0,
estimatedDuration: lw.estimated_duration || lw.total_duration,
totalEnrollments: lw.total_enrollments || lw.students_count || 0,
activeEnrollments: lw.active_enrollments || lw.active_students || 0,
completionRate: lw.completion_rate || 0,
averageRating: lw.average_rating || lw.rating,
totalReviews: lw.total_reviews || lw.reviews_count,
};
}
mapCourseMeta(lw) {
return {
createdAt: this.parseLearnWorldsDate(lw.created || lw.created_at || ''),
updatedAt: this.parseLearnWorldsDate(lw.updated || lw.updated_at || ''),
publishedAt: lw.published_at ? this.parseLearnWorldsDate(lw.published_at) : undefined,
enrollmentStartDate: lw.enrollment_start ? this.parseLearnWorldsDate(lw.enrollment_start) : undefined,
enrollmentEndDate: lw.enrollment_end ? this.parseLearnWorldsDate(lw.enrollment_end) : undefined,
requiresApproval: lw.requires_approval || false,
hasPrerequisites: lw.has_prerequisites || false,
prerequisites: lw.prerequisites || [],
certificateAvailable: lw.certificate_available || lw.has_certificate || false,
objectives: lw.objectives || lw.learning_objectives || [],
targetAudience: lw.target_audience || [],
requirements: lw.requirements || lw.prerequisites_text || [],
};
}
/**
* Map course status
*/
mapCourseStatus(status) {
const statusMap = {
published: 'published',
active: 'published',
draft: 'draft',
unpublished: 'draft',
coming_soon: 'coming_soon',
upcoming: 'coming_soon',
};
return statusMap[status?.toLowerCase()] || 'draft';
}
/**
* Map course visibility
*/
mapCourseVisibility(visibility) {
const visibilityMap = {
public: 'public',
open: 'public',
private: 'private',
closed: 'private',
hidden: 'hidden',
unlisted: 'hidden',
};
return visibilityMap[visibility?.toLowerCase()] || 'public';
}
/**
* Map course level
*/
mapCourseLevel(level) {
const levelMap = {
beginner: 'beginner',
basic: 'beginner',
introductory: 'beginner',
intermediate: 'intermediate',
medium: 'intermediate',
advanced: 'advanced',
expert: 'advanced',
all: 'all',
any: 'all',
mixed: 'all',
};
return levelMap[level?.toLowerCase()] || 'all';
}
/**
* Calculate discount percentage
*/
calculateDiscountPercentage(originalPrice, currentPrice) {
if (!originalPrice || !currentPrice || originalPrice <= currentPrice) {
return undefined;
}
return Math.round(((originalPrice - currentPrice) / originalPrice) * 100);
}
/**
* Calculate course catalog summary
*/
calculateCourseCatalogSummary(courses) {
const summary = {
totalCourses: courses.length,
publishedCourses: courses.filter((c) => c.status === 'published').length,
draftCourses: courses.filter((c) => c.status === 'draft').length,
freeCourses: courses.filter((c) => c.isFree).length,
paidCourses: courses.filter((c) => !c.isFree).length,
categoryCounts: {},
levelCounts: {},
languageCounts: {},
enrollmentStats: {
totalEnrollments: 0,
averageEnrollmentsPerCourse: 0,
mostPopularCourses: [],
},
priceStats: {
averagePrice: 0,
minPrice: 0,
maxPrice: 0,
currency: 'USD',
},
};
this.populateCategoryCounts(courses, summary);
this.populateEnrollmentStats(courses, summary);
this.populatePriceStats(courses, summary);
return summary;
}
/**
* Populate category, level, and language counts in the summary
*/
populateCategoryCounts(courses, summary) {
courses.forEach((course) => {
if (course.categoryName) {
summary.categoryCounts[course.categoryName] = (summary.categoryCounts[course.categoryName] || 0) + 1;
}
if (course.level) {
summary.levelCounts[course.level] = (summary.levelCounts[course.level] || 0) + 1;
}
if (course.language) {
summary.languageCounts[course.language] = (summary.languageCounts[course.language] || 0) + 1;
}
summary.enrollmentStats.totalEnrollments += course.totalEnrollments;
});
}
/**
* Populate enrollment statistics in the summary
*/
populateEnrollmentStats(courses, summary) {
if (courses.length > 0) {
summary.enrollmentStats.averageEnrollmentsPerCourse = Math.round(summary.enrollmentStats.totalEnrollments / courses.length);
}
summary.enrollmentStats.mostPopularCourses = courses
.filter((c) => c.totalEnrollments > 0)
.sort((a, b) => b.totalEnrollments - a.totalEnrollments)
.slice(0, 5)
.map((c) => ({
id: c.id,
title: c.title,
enrollments: c.totalEnrollments,
}));
}
/**
* Populate price statistics in the summary
*/
populatePriceStats(courses, summary) {
const paidCourses = courses.filter((c) => !c.isFree && c.price !== undefined);
if (paidCourses.length > 0) {
const prices = paidCourses.map((c) => c.price);
summary.priceStats = {
averagePrice: Math.round(prices.reduce((sum, p) => sum + p, 0) / prices.length),
minPrice: Math.min(...prices),
maxPrice: Math.max(...prices),
currency: paidCourses[0].currency || 'USD',
};
}
}
/**
* Define the parameters for this action
*/
get Params() {
const baseParams = this.getCommonLMSParams();
const specificParams = [
{
Name: 'SearchText',
Type: 'Input',
Value: null,
},
{
Name: 'Status',
Type: 'Input',
Value: null,
},
{
Name: 'CategoryID',
Type: 'Input',
Value: null,
},
{
Name: 'Level',
Type: 'Input',
Value: null,
},
{
Name: 'Language',
Type: 'Input',
Value: null,
},
{
Name: 'OnlyFree',
Type: 'Input',
Value: false,
},
{
Name: 'MinPrice',
Type: 'Input',
Value: null,
},
{
Name: 'MaxPrice',
Type: 'Input',
Value: null,
},
{
Name: 'Tags',
Type: 'Input',
Value: null,
},
{
Name: 'InstructorID',
Type: 'Input',
Value: null,
},
{
Name: 'CreatedAfter',
Type: 'Input',
Value: null,
},
{
Name: 'CreatedBefore',
Type: 'Input',
Value: null,
},
{
Name: 'SortBy',
Type: 'Input',
Value: 'created',
},
{
Name: 'SortOrder',
Type: 'Input',
Value: 'desc',
},
{
Name: 'IncludeEnrollmentStats',
Type: 'Input',
Value: true,
},
{
Name: 'MaxResults',
Type: 'Input',
Value: 100,
},
];
return [...baseParams, ...specificParams];
}
};
GetLearnWorldsCoursesAction = __decorate([
RegisterClass(BaseAction, 'GetLearnWorldsCoursesAction')
], GetLearnWorldsCoursesAction);
export { GetLearnWorldsCoursesAction };
//# sourceMappingURL=get-courses.action.js.map