Skip to content
This repository was archived by the owner on Dec 14, 2023. It is now read-only.

Commit f098c52

Browse files
authored
Invite users when email already used in LMS (#230)
* Invite users when email already used in LMS
1 parent 7c0bfe7 commit f098c52

File tree

2 files changed

+155
-30
lines changed

2 files changed

+155
-30
lines changed

lib/users/lms/award-badge.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
var async = require('async');
33
var _ = require('lodash');
4+
var request = require('request');
45

56
/**
67
* Webhook handler to award badges based on courses
@@ -13,6 +14,9 @@ function awardLMSBadge (args, cb) {
1314
var plugin = args.role;
1415
var certif = args;
1516
var user = certif.user;
17+
var LMSUsername = process.env.LMSUsername;
18+
var LMSPassword = process.env.LMSPassword;
19+
var APIUrl = process.env.LMS_API_URL;
1620

1721
function checkTestStatus (waterfallCb) {
1822
if (certif.header.webHookType !== 'course_completion') {
@@ -38,11 +42,35 @@ function awardLMSBadge (args, cb) {
3842
seneca.act({role: 'cd-users', cmd: 'list', query: {lmsId: user.userId}},
3943
function (err, sysUser) {
4044
if (err) return cb(err);
41-
if (_.isEmpty(sysUser)) return cb(null, {ok: false, why: 'LMSUser not found'});
42-
return waterfallCb(null, sysUser[0], badge);
45+
// This may happen if a user can see courses without being synced, they need to reclick the login link from Zen
46+
if (_.isEmpty(sysUser)) {
47+
return getUserByEmail(badge, waterfallCb);
48+
} else {
49+
return waterfallCb(null, sysUser[0], badge);
50+
}
4351
});
4452
}
4553

54+
function checkIfUserExists (sysUser, badge, waterfallCb) {
55+
if (_.isEmpty(sysUser)) {
56+
return cb(null, {ok: false, why: 'LMSUser not found'});
57+
} else {
58+
return waterfallCb(null, sysUser, badge);
59+
}
60+
}
61+
62+
function getUserByEmail (badge, waterfallCb) {
63+
// We have this as a backup for invited users where the lmsId is not necessarly synced
64+
// No need to update the user, really, with the userId. It'll be done on the next login from Zen
65+
seneca.act({role: 'cd-users', cmd: 'list', query: {email: user.email}},
66+
function (err, sysUser) {
67+
if (err) return cb(err);
68+
if (sysUser.length > 1) return cb(null, {ok: false, why: 'Duplicate user for email' + user.email + ' when awarding a LMS badge'});
69+
return waterfallCb(null, sysUser[0], badge);
70+
}
71+
);
72+
}
73+
4674
function awardBadge (sysUser, badge, waterfallCb) {
4775
var applicationData = {
4876
user: sysUser,
@@ -64,6 +92,7 @@ function awardLMSBadge (args, cb) {
6492
checkTestStatus,
6593
getBadge,
6694
getUser,
95+
checkIfUserExists,
6796
awardBadge
6897
], cb);
6998

lib/users/lms/get-lms-link.js

Lines changed: 124 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function getLMSLink (args, cb) {
2020
var plugin = args.role;
2121
var APIKey = process.env.LMS_KEY;
2222
var user = args.user;
23-
var APIUrl = 'https://coderdojo.learnupon.com/api/v1/';
23+
var APIUrl = process.env.LMS_API_URL;
2424
var hash = crypto.createHash('md5');
2525
var response = {
2626
'url': ''
@@ -29,6 +29,7 @@ function getLMSLink (args, cb) {
2929
var LMSPassword = process.env.LMSPassword;
3030
/**
3131
* GetLMSUser or Create it if it doesn't exists
32+
* or invite it if already exists in LMS but not in CoderDojo Portal
3233
* @return {Number} lmsUserId
3334
* @return {Array} UserTypes as GroupMembership
3435
*/
@@ -43,40 +44,135 @@ function getLMSLink (args, cb) {
4344
function (err, userDojos) {
4445
userTypes = _.flatten(userTypes.concat(_.map(userDojos, 'userTypes')));
4546
userTypes = _.intersection(userTypes, allowedUserTypes);
46-
if (!_.isEmpty(userTypes)) {
47+
async.waterfall([
48+
isAllowed,
49+
isNotRegistered,
50+
approvedSharing,
51+
isExisting
52+
]);
53+
// Conditions determinating the workflow to get the user
54+
function isAllowed (wfCb) {
55+
if (!_.isEmpty(userTypes)) {
56+
return wfCb();
57+
} else {
58+
return cb(null, {ok: false, why: 'UserType not allowed', http$: {status: 403}});
59+
}
60+
}
61+
62+
function isNotRegistered (wfCb) {
4763
if (_.isEmpty(user.lmsId)) {
48-
if (!_.isEmpty(args.approval)) {
49-
// The user doesn't exists yet on the LMS, we create it to save the corresponding Id
50-
request.post(APIUrl + 'users', {
64+
return wfCb();
65+
} else {
66+
// The user already exists in the LMS, no need to recreate it
67+
// After that, we simply recheck the group membership
68+
return waterfallCb(null, user.lmsId, userTypes);
69+
}
70+
}
71+
72+
function approvedSharing (wfCb) {
73+
if (!_.isEmpty(args.approval)) {
74+
return wfCb();
75+
} else {
76+
return cb(null, {approvalRequired: true});
77+
}
78+
}
79+
80+
function isExisting (wfCb) {
81+
request.get(APIUrl + 'users/search?email=' + encodeURIComponent(user.email), {
82+
auth: {
83+
user: LMSUsername,
84+
pass: LMSPassword
85+
},
86+
json: true,
87+
}, function (err, res, LMSUser) {
88+
if (LMSUser && !_.isEmpty(LMSUser.user)) {
89+
// We need to sync up the LMS id into our system
90+
return updateUser(LMSUser.user[0].id);
91+
} else {
92+
// We need to create the user & update the LMSUserId into our system
93+
return createUser();
94+
}
95+
});
96+
}
97+
98+
// Processes to apply according to the user status
99+
100+
/**
101+
* Create a LMS user based on Zen user details
102+
* @return {[type]} [description]
103+
*/
104+
function createUser () {
105+
// The user doesn't exists yet on the LMS, we create it to save the corresponding Id
106+
request.post(APIUrl + 'users', {
107+
auth: {
108+
user: LMSUsername,
109+
pass: LMSPassword
110+
},
111+
json: {
112+
User: {
113+
email: user.email,
114+
password: Uuid() }
115+
}
116+
}, function(err, res, response) {
117+
if (response && response.response_type === 'ERROR' && response.response_code === 400) {
118+
return inviteUser();
119+
} else {
120+
return updateUser(response.id);
121+
}
122+
});
123+
}
124+
125+
/**
126+
* Update a Zen user to save the LmsId
127+
* @param {String} lmsUserId
128+
* @return {[type]} [description]
129+
*/
130+
function updateUser (lmsUserId) {
131+
seneca.act({role: 'cd-users', cmd: 'update', user: {
132+
id: user.id,
133+
lmsId: lmsUserId
134+
}}, function (err, profile) {
135+
return waterfallCb(null, lmsUserId, userTypes);
136+
});
137+
}
138+
139+
/**
140+
* Invite an existing LMS user into our Portal
141+
* @return exit act
142+
*/
143+
function inviteUser () {
144+
// We assume that inviting multiple time the same user won't break shit
145+
async.eachSeries(userTypes, function (userType, serieCb) {
146+
request.get(APIUrl + 'groups?title=' + userType, {
147+
auth: {
148+
user: LMSUsername,
149+
pass: LMSPassword
150+
},
151+
json: true
152+
}, function (err, res, group) {
153+
group = group.groups[0];
154+
request.post(APIUrl + 'group_invites', {
51155
auth: {
52156
user: LMSUsername,
53157
pass: LMSPassword
54158
},
55159
json: {
56-
User: {
57-
email: user.email,
58-
password: Uuid() }
160+
GroupInvite: {
161+
email_addresses: user.email,
162+
group_membership_type_id: 1, // Type: Learner
163+
group_id: group.id
59164
}
60-
}, function(err, res, lmsUser) {
61-
seneca.act({role: 'cd-users', cmd: 'update', user: {
62-
id: user.id,
63-
lmsId: lmsUser.id
64-
}}, function (err, profile) {
65-
waterfallCb(null, lmsUser.id, userTypes);
66-
});
67-
});
68-
} else {
69-
cb(null, {approvalRequired: true});
165+
}
166+
}, function (err, res, body) {
167+
return serieCb();
168+
});
70169
}
71-
} else {
72-
// The user already exists in the LMS, no need to recreate it
73-
waterfallCb(null, user.lmsId, userTypes);
74-
}
75-
} else {
76-
cb(null, {ok: false, why: 'UserType not allowed', http$: { status: 403}});
77-
}
170+
);
171+
}, function (err, res, body) {
172+
return cb(null, {ok: false, why: 'An invitation has been sent by email', http$: {status: 400}});
173+
});
174+
}
78175
});
79-
80176
});
81177
}
82178

@@ -109,8 +205,8 @@ function getLMSLink (args, cb) {
109205
*/
110206
function updateGroup (lmsUserId, userTypesToSync, waterfallCb) {
111207
// Get GroupId
112-
async.eachSeries(userTypesToSync, function(userType, serieCb){
113-
request.get(APIUrl+ 'groups?title=' + userType, {
208+
async.eachSeries(userTypesToSync, function (userType, serieCb) {
209+
request.get(APIUrl + 'groups?title=' + userType, {
114210
auth: {
115211
user: LMSUsername,
116212
pass: LMSPassword

0 commit comments

Comments
 (0)