@memberjunction/actions-bizapps-lms
Version:
LMS system integration actions for MemberJunction
236 lines • 11.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';
import { CreateUserAction } from './create-user.action.js';
import { EnrollUserAction } from './enroll-user.action.js';
import { SSOLoginAction } from './sso-login.action.js';
/**
* Action that orchestrates full learner onboarding:
* create user, enroll in courses/bundles, and generate SSO URL.
*/
let OnboardLearnerAction = class OnboardLearnerAction extends LearnWorldsBaseAction {
/**
* Orchestrates the full onboarding flow for a new or existing learner:
* 1. Find or create user
* 2. Enroll in courses and bundles
* 3. Generate SSO login URL
*/
async OnboardLearner(params, contextUser) {
this.SetCompanyContext(params.CompanyID);
this.validateEmail(params.Email);
if (params.Role) {
this.validateRole(params.Role);
}
if (params.CourseIDs) {
params.CourseIDs.forEach((id) => this.validatePathSegment(id, 'CourseID'));
}
if (params.BundleIDs) {
params.BundleIDs.forEach((id) => this.validatePathSegment(id, 'BundleID'));
}
if (params.RedirectTo) {
this.validateRedirectTo(params.RedirectTo);
}
// Step 1: Find or create the user
const { userId, isNewUser, loginUrlFallback } = await this.resolveUser(params, contextUser);
// Step 2: Enroll in courses and bundles
const { enrollments, errors } = await this.enrollInProducts(params, userId, contextUser);
// Step 3: Generate SSO login URL
const loginURL = await this.generateLoginUrl(params, userId, loginUrlFallback, contextUser);
const hasEnrollmentErrors = errors.length > 0;
const allEnrollmentsFailed = enrollments.length > 0 && enrollments.every((e) => !e.success);
return {
Success: !allEnrollmentsFailed,
LoginURL: loginURL,
LearnWorldsUserId: userId,
IsNewUser: isNewUser,
Enrollments: enrollments,
Errors: hasEnrollmentErrors ? errors : [],
};
}
/**
* Finds an existing user by email or creates a new one.
* Returns the user ID, whether they are new, and a fallback login URL from creation.
*/
async resolveUser(params, contextUser) {
const existingUser = await this.FindUserByEmail(params.Email, contextUser);
if (existingUser) {
return { userId: existingUser.id, isNewUser: false };
}
return this.createNewUser(params, contextUser);
}
/**
* Creates a new user via the CreateUserAction typed public method.
* Returns the user's ID and login URL.
*/
async createNewUser(params, contextUser) {
const createAction = new CreateUserAction();
const createResult = await createAction.CreateUser({
CompanyID: params.CompanyID,
Email: params.Email,
FirstName: params.FirstName,
LastName: params.LastName,
Role: params.Role || 'student',
Tags: params.Tags,
CustomFields: params.CustomFields,
SendWelcomeEmail: params.SendWelcomeEmail ?? false,
}, contextUser);
if (!createResult.UserDetails.id) {
throw new Error('User creation succeeded but no user ID was returned');
}
return {
userId: createResult.UserDetails.id,
isNewUser: true,
loginUrlFallback: createResult.UserDetails.loginUrl,
};
}
/**
* Enrolls the user in all specified courses and bundles in parallel,
* collecting results and errors.
*/
async enrollInProducts(params, userId, contextUser) {
const courseIDs = params.CourseIDs || [];
const bundleIDs = params.BundleIDs || [];
// Fire enrollment requests with controlled concurrency
const allProducts = [
...courseIDs.map((id) => ({ id, type: 'course' })),
...bundleIDs.map((id) => ({ id, type: 'bundle' })),
];
const enrollments = await this.processInBatches(allProducts, (product) => this.enrollInSingleProduct(params.CompanyID, userId, product.id, product.type, contextUser));
const errors = enrollments.filter((e) => e.error).map((e) => e.error);
return { enrollments, errors };
}
/**
* Enrolls a user in a single course or bundle, catching errors for partial success.
*/
async enrollInSingleProduct(companyId, userId, productId, productType, contextUser) {
try {
const enrollAction = new EnrollUserAction();
const enrollResult = await enrollAction.EnrollUser({
CompanyID: companyId,
UserID: userId,
CourseID: productId,
ProductType: productType,
Price: 0,
Justification: 'Onboarding enrollment',
NotifyUser: false,
}, contextUser);
return {
productId,
productType,
success: true,
enrollmentId: enrollResult.EnrollmentDetails.id,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
productId,
productType,
success: false,
error: `Enrollment failed for ${productType} ${productId}: ${errorMessage}`,
};
}
}
/**
* Generates an SSO login URL, falling back to the user creation login URL on failure.
*/
async generateLoginUrl(params, userId, loginUrlFallback, contextUser) {
try {
const ssoAction = new SSOLoginAction();
ssoAction.SetCompanyContext(params.CompanyID);
const ssoResult = await ssoAction.GenerateSSOUrl({
CompanyID: params.CompanyID,
Email: params.Email,
RedirectTo: params.RedirectTo,
}, contextUser);
return ssoResult.LoginURL;
}
catch (error) {
console.warn(`SSO URL generation failed for userId ${userId}, using fallback:`, error instanceof Error ? error.message : error);
return loginUrlFallback;
}
}
/**
* Framework entry point that delegates to the typed public method
*/
async InternalRunAction(params) {
const { Params, ContextUser } = params;
this.params = Params;
try {
const onboardParams = {
CompanyID: this.getRequiredStringParam(Params, 'CompanyID'),
Email: this.getRequiredStringParam(Params, 'Email'),
FirstName: this.getOptionalStringParam(Params, 'FirstName'),
LastName: this.getOptionalStringParam(Params, 'LastName'),
Role: this.getOptionalStringParam(Params, 'Role'),
Tags: this.getOptionalStringArrayParam(Params, 'Tags'),
CustomFields: this.getParamValue(Params, 'CustomFields'),
CourseIDs: this.getOptionalStringArrayParam(Params, 'CourseIDs'),
BundleIDs: this.getOptionalStringArrayParam(Params, 'BundleIDs'),
RedirectTo: this.getOptionalStringParam(Params, 'RedirectTo'),
SendWelcomeEmail: this.getOptionalBooleanParam(Params, 'SendWelcomeEmail', false),
};
if (!onboardParams.Email) {
return this.buildErrorResult('VALIDATION_ERROR', 'Email is required', Params);
}
const result = await this.OnboardLearner(onboardParams, ContextUser);
this.setOutputParam(Params, 'LoginURL', result.LoginURL);
this.setOutputParam(Params, 'LearnWorldsUserId', result.LearnWorldsUserId);
this.setOutputParam(Params, 'IsNewUser', result.IsNewUser);
this.setOutputParam(Params, 'Enrollments', result.Enrollments);
this.setOutputParam(Params, 'Errors', result.Errors);
const successCount = result.Enrollments.filter((e) => e.success).length;
const totalCount = result.Enrollments.length;
const userStatus = result.IsNewUser ? 'new user created' : 'existing user found';
if (result.Success) {
return this.buildSuccessResult(`Onboarding complete (${userStatus}): ${successCount}/${totalCount} enrollment(s) succeeded`, Params);
}
return this.buildErrorResult('PARTIAL_FAILURE', `Onboarding partially failed (${userStatus}): ${successCount}/${totalCount} enrollment(s) succeeded. Errors: ${result.Errors.join('; ')}`, Params);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return this.buildErrorResult('ERROR', `Error during onboarding: ${errorMessage}`, Params);
}
}
/**
* Define the parameters this action expects
*/
get Params() {
const baseParams = this.getCommonLMSParams();
const specificParams = [
{ Name: 'Email', Type: 'Input', Value: null },
{ Name: 'FirstName', Type: 'Input', Value: null },
{ Name: 'LastName', Type: 'Input', Value: null },
{ Name: 'Role', Type: 'Input', Value: 'student' },
{ Name: 'Tags', Type: 'Input', Value: null },
{ Name: 'CustomFields', Type: 'Input', Value: null },
{ Name: 'CourseIDs', Type: 'Input', Value: null },
{ Name: 'BundleIDs', Type: 'Input', Value: null },
{ Name: 'RedirectTo', Type: 'Input', Value: null },
{ Name: 'SendWelcomeEmail', Type: 'Input', Value: false },
{ Name: 'LoginURL', Type: 'Output', Value: null },
{ Name: 'LearnWorldsUserId', Type: 'Output', Value: null },
{ Name: 'IsNewUser', Type: 'Output', Value: null },
{ Name: 'Enrollments', Type: 'Output', Value: null },
{ Name: 'Errors', Type: 'Output', Value: null },
];
return [...baseParams, ...specificParams];
}
/**
* Metadata about this action
*/
get Description() {
return 'Orchestrates full learner onboarding: create user, enroll in courses/bundles, and generate SSO URL';
}
};
OnboardLearnerAction = __decorate([
RegisterClass(BaseAction, 'OnboardLearnerAction')
], OnboardLearnerAction);
export { OnboardLearnerAction };
//# sourceMappingURL=onboard-learner.action.js.map