2013-05-15 23:25:41 +04:00
|
|
|
const Api = require('./api');
|
2013-05-16 17:19:30 +04:00
|
|
|
const errors = require('./lib/errors');
|
2013-05-13 22:27:20 +04:00
|
|
|
const _ = require('underscore');
|
2013-05-29 04:56:49 +04:00
|
|
|
const jwt = require('jwt-simple');
|
2013-05-13 22:27:20 +04:00
|
|
|
|
2013-05-14 06:59:46 +04:00
|
|
|
const ENDPOINT = process.env['CSOL_OPENBADGER_URL'];
|
2013-05-29 04:56:49 +04:00
|
|
|
const JWT_SECRET = process.env['CSOL_OPENBADGER_SECRET'];
|
|
|
|
const TOKEN_LIFETIME = process.env['CSOL_OPENBADGER_TOKEN_LIFETIME'] || 10000;
|
|
|
|
|
2013-05-16 23:46:44 +04:00
|
|
|
if (!ENDPOINT)
|
|
|
|
throw new Error('Must specify CSOL_OPENBADGER_URL in the environment');
|
2013-05-29 04:56:49 +04:00
|
|
|
if (!JWT_SECRET)
|
|
|
|
throw new Error('Must specify CSOL_OPENBADGER_SECRET in the environment');
|
2013-05-13 22:27:20 +04:00
|
|
|
|
|
|
|
function normalizeBadge (badge, id) {
|
2013-06-05 20:16:24 +04:00
|
|
|
if (badge.shortname)
|
|
|
|
badge.id = badge.shortname;
|
|
|
|
|
2013-05-13 22:27:20 +04:00
|
|
|
if (!id)
|
|
|
|
id = badge.shortname;
|
|
|
|
|
|
|
|
if (!badge.id)
|
|
|
|
badge.id = id;
|
|
|
|
|
|
|
|
if (!badge.url)
|
2013-05-15 00:18:25 +04:00
|
|
|
badge.url = '/earn/' + badge.id;
|
2013-05-13 22:27:20 +04:00
|
|
|
|
|
|
|
return badge;
|
|
|
|
}
|
|
|
|
|
2013-05-31 20:21:00 +04:00
|
|
|
function normalizeBadgeInstance (badge, id) {
|
|
|
|
/* This is dumb, but let's us reuse current templates to
|
|
|
|
build out a single-level object. */
|
2013-06-02 02:44:36 +04:00
|
|
|
_.extend(badge, badge.badgeClass);
|
2013-05-31 20:21:00 +04:00
|
|
|
|
2013-06-03 19:07:59 +04:00
|
|
|
if (!badge.id)
|
|
|
|
badge.id = id;
|
|
|
|
|
2013-05-31 20:21:00 +04:00
|
|
|
if (!badge.url)
|
|
|
|
badge.url = '/mybadges/' + id;
|
|
|
|
|
2013-06-03 06:28:56 +04:00
|
|
|
badge.id = id;
|
|
|
|
|
2013-06-02 02:44:36 +04:00
|
|
|
return badge;
|
2013-05-31 20:21:00 +04:00
|
|
|
}
|
|
|
|
|
2013-05-15 00:18:25 +04:00
|
|
|
function normalizeProgram(program, id) {
|
|
|
|
if (!id)
|
|
|
|
id = program.shortname;
|
|
|
|
|
|
|
|
if (!program.id)
|
|
|
|
program.id = id;
|
|
|
|
|
|
|
|
if (!program.url)
|
2013-06-06 19:33:54 +04:00
|
|
|
program.url = '/explore/' + program.shortname;
|
2013-05-15 00:18:25 +04:00
|
|
|
|
|
|
|
return program;
|
|
|
|
}
|
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
var categories = [
|
|
|
|
{label: 'Science', value: 'science'},
|
|
|
|
{label: 'Technology', value: 'technology'},
|
|
|
|
{label: 'Engineering', value: 'engineering'},
|
|
|
|
{label: 'Art', value: 'art'},
|
|
|
|
{label: 'Math', value: 'math'}
|
|
|
|
];
|
|
|
|
var ageRanges = [
|
|
|
|
{label: 'Under 13', value: '0-13'},
|
|
|
|
{label: '13-18', value: '13-18'},
|
|
|
|
{label: '19-24', value: '19-24'}
|
|
|
|
];
|
|
|
|
var activityTypes = [
|
|
|
|
{label: 'Online', value: 'online'},
|
|
|
|
{label: 'Offline', value: 'offline'}
|
|
|
|
];
|
|
|
|
var badgeTypes = [
|
|
|
|
{label: 'Participation', value: 'participation'},
|
|
|
|
{label: 'Skill', value: 'skill'},
|
|
|
|
{label: 'Activity', value: 'activity'}
|
|
|
|
];
|
2013-06-03 00:00:15 +04:00
|
|
|
var orgs = [];
|
2013-06-02 21:42:33 +04:00
|
|
|
|
2013-06-03 00:00:15 +04:00
|
|
|
function updateOrgs (callback) {
|
2013-06-02 21:42:33 +04:00
|
|
|
if (typeof callback !== 'function')
|
|
|
|
callback = function () {};
|
|
|
|
|
|
|
|
openbadger.getOrgs(function (err, data) {
|
|
|
|
if (err)
|
|
|
|
return callback(err);
|
|
|
|
|
2013-06-03 00:00:15 +04:00
|
|
|
orgs = [];
|
2013-06-02 21:42:33 +04:00
|
|
|
|
2013-06-03 00:00:15 +04:00
|
|
|
(data.orgs || data.issuers).forEach(function (org) {
|
|
|
|
orgs.push({
|
|
|
|
label: org.name,
|
|
|
|
value: org.shortname
|
2013-06-02 21:42:33 +04:00
|
|
|
});
|
|
|
|
});
|
2013-05-31 14:47:03 +04:00
|
|
|
|
2013-06-03 00:00:15 +04:00
|
|
|
orgs.sort(function(a, b) {
|
|
|
|
var aVal = (a && a.label || '').toLowerCase().replace(/^\s*the\s+/, ''),
|
|
|
|
bVal = (b && b.label || '').toLowerCase().replace(/^\s*the\s+/, '');
|
|
|
|
|
|
|
|
return aVal.localeCompare(bVal);
|
|
|
|
});
|
|
|
|
|
|
|
|
callback(null, orgs);
|
2013-06-02 21:42:33 +04:00
|
|
|
});
|
|
|
|
}
|
2013-05-31 14:47:03 +04:00
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
function confirmFilterValue (value, list) {
|
|
|
|
if (!value && value !== 0)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
for (var i = 0, l = list.length; i < l; ++i)
|
|
|
|
if (list[i].value === value)
|
|
|
|
return value;
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
2013-05-31 14:47:03 +04:00
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
function applyFilter (data, query) {
|
|
|
|
return _.filter(data, function(item) {
|
2013-06-03 00:34:49 +04:00
|
|
|
return _.reduce(query, function(memo, value, field) {
|
2013-06-02 21:42:33 +04:00
|
|
|
if (!memo) // We've already failed a test - no point in continuing
|
|
|
|
return memo;
|
2013-05-31 14:47:03 +04:00
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
if (!value && value !== 0)
|
|
|
|
return memo;
|
2013-05-31 14:47:03 +04:00
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
var data = item;
|
2013-05-31 14:47:03 +04:00
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
if (field.indexOf('.') > -1) {
|
|
|
|
var fieldParts = field.split('.').reverse();
|
2013-05-31 14:47:03 +04:00
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
while (data && fieldParts.length > 1) {
|
|
|
|
data = data[fieldParts.pop()];
|
|
|
|
}
|
2013-06-02 00:16:08 +04:00
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
field = fieldParts.reverse().join('.');
|
|
|
|
}
|
2013-05-31 14:47:03 +04:00
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
var itemValue = data ? data[field] : null;
|
2013-05-31 14:47:03 +04:00
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
if (_.isArray(itemValue))
|
|
|
|
return memo && _.contains(itemValue, value);
|
2013-05-31 14:47:03 +04:00
|
|
|
|
2013-06-02 21:42:33 +04:00
|
|
|
return memo && (itemValue === value);
|
|
|
|
}, true);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function filterBadges (data, query) {
|
|
|
|
var category = confirmFilterValue(query.category, categories),
|
|
|
|
ageGroup = confirmFilterValue(query.age, ageRanges),
|
|
|
|
badgeType = confirmFilterValue(query.type, badgeTypes),
|
|
|
|
activityType = confirmFilterValue(query.activity, activityTypes);
|
|
|
|
|
|
|
|
if (!category && !ageGroup && !badgeType && !activityType)
|
|
|
|
return data;
|
|
|
|
|
|
|
|
return applyFilter(data, {
|
|
|
|
'categories': category,
|
|
|
|
'ageRange': ageGroup,
|
|
|
|
'badgeType': badgeType,
|
|
|
|
'activityType': activityType
|
2013-05-31 14:47:03 +04:00
|
|
|
});
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2013-05-30 00:39:24 +04:00
|
|
|
function getJWTToken(email) {
|
|
|
|
var claims = {
|
|
|
|
prn: email,
|
|
|
|
exp: Date.now() + TOKEN_LIFETIME
|
|
|
|
};
|
|
|
|
return jwt.encode(claims, JWT_SECRET);
|
|
|
|
}
|
|
|
|
|
2013-05-15 23:25:41 +04:00
|
|
|
var openbadger = new Api(ENDPOINT, {
|
2013-05-16 01:42:24 +04:00
|
|
|
|
2013-05-15 23:25:41 +04:00
|
|
|
getBadges: {
|
|
|
|
func: function getBadges (query, callback) {
|
2013-06-03 19:46:05 +04:00
|
|
|
this.getAllBadges(query, callback);
|
2013-05-15 23:25:41 +04:00
|
|
|
},
|
2013-05-31 14:47:03 +04:00
|
|
|
filters: filterBadges,
|
2013-05-15 23:25:41 +04:00
|
|
|
paginate: true,
|
|
|
|
key: 'badges'
|
|
|
|
},
|
2013-05-15 00:18:25 +04:00
|
|
|
|
2013-06-03 19:46:05 +04:00
|
|
|
getAllBadges: function getAllBadges (query, callback) {
|
|
|
|
this.get('/badges', function(err, data) {
|
|
|
|
if (err)
|
|
|
|
return callback(err, data);
|
|
|
|
|
|
|
|
return callback(null, {
|
|
|
|
badges: _.map(data.badges, normalizeBadge)
|
|
|
|
});
|
|
|
|
})
|
2013-06-05 01:02:26 +04:00
|
|
|
},
|
|
|
|
|
2013-05-15 23:25:41 +04:00
|
|
|
getBadge: function getBadge (query, callback) {
|
|
|
|
var id = query.id;
|
2013-05-15 00:18:25 +04:00
|
|
|
|
2013-05-15 23:25:41 +04:00
|
|
|
if (!id)
|
2013-05-16 17:19:30 +04:00
|
|
|
return callback(new errors.BadRequest('Invalid badge key'));
|
2013-05-15 00:18:25 +04:00
|
|
|
|
2013-05-17 18:52:43 +04:00
|
|
|
this.get('/badge/' + id, function(err, data) {
|
2013-05-15 23:25:41 +04:00
|
|
|
if (err)
|
|
|
|
return callback(err, data);
|
2013-05-15 00:18:25 +04:00
|
|
|
|
2013-05-16 01:42:24 +04:00
|
|
|
return callback(null, {
|
|
|
|
badge: normalizeBadge(data.badge, id)
|
2013-05-15 23:25:41 +04:00
|
|
|
});
|
2013-05-15 00:18:25 +04:00
|
|
|
});
|
2013-05-15 23:25:41 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
getPrograms: {
|
|
|
|
func: function getPrograms (query, callback) {
|
2013-06-07 01:33:38 +04:00
|
|
|
var qs = {
|
|
|
|
category: query.category,
|
|
|
|
org: query.org,
|
|
|
|
age: query.age,
|
|
|
|
activity: query.activity,
|
|
|
|
};
|
|
|
|
this.get('/programs', {qs: qs}, function(err, data) {
|
2013-05-15 23:25:41 +04:00
|
|
|
if (err)
|
|
|
|
return callback(err, data);
|
|
|
|
|
|
|
|
return callback(null, {
|
2013-05-16 01:42:24 +04:00
|
|
|
programs: _.map(data.programs, normalizeProgram)
|
2013-05-15 23:25:41 +04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
paginate: true,
|
|
|
|
key: 'programs'
|
|
|
|
},
|
|
|
|
|
|
|
|
getProgram: function getProgram (query, callback) {
|
|
|
|
var id = query.id;
|
2013-05-15 00:18:25 +04:00
|
|
|
|
2013-05-15 23:25:41 +04:00
|
|
|
if (!id)
|
2013-05-16 17:19:30 +04:00
|
|
|
return callback(new errors.BadRequest('Invalid program key'));
|
2013-05-15 23:25:41 +04:00
|
|
|
|
2013-05-17 18:52:43 +04:00
|
|
|
this.get('/program/' + id, function(err, data) {
|
2013-05-15 23:25:41 +04:00
|
|
|
if (err)
|
|
|
|
return callback(err, data);
|
|
|
|
|
2013-05-16 01:42:24 +04:00
|
|
|
return callback(null, {
|
|
|
|
program: normalizeProgram(data.program, id)
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
2013-05-15 23:25:41 +04:00
|
|
|
|
2013-05-16 01:42:24 +04:00
|
|
|
getOrgs: function getOrgs (query, callback) {
|
2013-05-17 19:14:53 +04:00
|
|
|
this.get('/issuers/', function(err, data) {
|
2013-05-16 01:42:24 +04:00
|
|
|
if (err)
|
|
|
|
return callback(err, data);
|
2013-05-15 23:25:41 +04:00
|
|
|
|
2013-05-16 01:42:24 +04:00
|
|
|
return callback(null, {
|
|
|
|
orgs: _.values(data.issuers)
|
2013-05-15 23:25:41 +04:00
|
|
|
});
|
|
|
|
});
|
2013-05-29 04:56:49 +04:00
|
|
|
},
|
|
|
|
|
2013-05-30 22:00:19 +04:00
|
|
|
getUserBadges: {
|
|
|
|
func: function getUserBadges (query, callback) {
|
2013-06-03 19:46:05 +04:00
|
|
|
var email = query.email || query.session.user.email;
|
2013-05-30 22:00:19 +04:00
|
|
|
var params = {
|
|
|
|
auth: getJWTToken(email),
|
|
|
|
email: email
|
|
|
|
};
|
|
|
|
this.get('/user', { qs: params }, function(err, data) {
|
|
|
|
if (err)
|
|
|
|
return callback(err, data);
|
|
|
|
|
2013-05-31 20:21:00 +04:00
|
|
|
badges = _.map(data.badges, normalizeBadgeInstance)
|
|
|
|
|
2013-05-30 22:00:19 +04:00
|
|
|
return callback(null, {
|
2013-05-31 20:21:00 +04:00
|
|
|
badges: badges.sort(function(a, b) {
|
|
|
|
return b.issuedOn - a.issuedOn;
|
|
|
|
})
|
2013-05-30 22:00:19 +04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
paginate: true,
|
|
|
|
key: 'badges'
|
|
|
|
},
|
|
|
|
|
2013-05-31 20:21:00 +04:00
|
|
|
getUserBadge: function getUserBadge (query, callback) {
|
|
|
|
var id = query.id;
|
|
|
|
|
2013-06-07 18:25:20 +04:00
|
|
|
var email = query.email || query.session.user.email;
|
2013-05-31 20:21:00 +04:00
|
|
|
var params = {
|
|
|
|
auth: getJWTToken(email),
|
|
|
|
email: email
|
|
|
|
};
|
|
|
|
|
|
|
|
this.get('/user/badge/' + id, { qs: params }, function(err, data) {
|
|
|
|
if (err)
|
|
|
|
return callback(err, data);
|
|
|
|
|
|
|
|
return callback(null, {
|
|
|
|
badge: normalizeBadgeInstance(data.badge, id)
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-06-04 06:04:12 +04:00
|
|
|
awardBadge: function awardBadge (query, callback) {
|
|
|
|
var email = query.email || query.session.user.email;
|
|
|
|
var shortname = query.badge;
|
|
|
|
|
|
|
|
var params = {
|
|
|
|
auth: getJWTToken(email),
|
|
|
|
email: email
|
|
|
|
}
|
|
|
|
|
|
|
|
this.post('/user/badge/' + shortname, { form: params }, function(err, data) {
|
|
|
|
if (err)
|
|
|
|
return callback(err, data);
|
|
|
|
|
|
|
|
return callback(null, {
|
2013-06-06 21:16:23 +04:00
|
|
|
assertionUrl: data.url
|
2013-06-04 06:04:12 +04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-05-30 00:39:24 +04:00
|
|
|
getBadgeFromCode: function getBadgeFromCode (query, callback) {
|
2013-05-29 04:56:49 +04:00
|
|
|
var email = query.email;
|
|
|
|
var code = query.code;
|
2013-05-30 00:39:24 +04:00
|
|
|
var params = {
|
|
|
|
auth: getJWTToken(email),
|
|
|
|
email: email,
|
|
|
|
code: code,
|
2013-05-29 04:56:49 +04:00
|
|
|
};
|
2013-05-30 00:39:24 +04:00
|
|
|
this.get('/unclaimed', { qs: params }, function(err, data) {
|
|
|
|
return callback(err, data);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-06-03 20:43:52 +04:00
|
|
|
|
2013-05-30 00:39:24 +04:00
|
|
|
claim: function claim (query, callback) {
|
|
|
|
var email = query.email;
|
|
|
|
var code = query.code;
|
2013-05-29 04:56:49 +04:00
|
|
|
var params = {
|
2013-05-30 00:39:24 +04:00
|
|
|
auth: getJWTToken(email),
|
2013-05-29 04:56:49 +04:00
|
|
|
email: email,
|
|
|
|
code: code,
|
|
|
|
};
|
|
|
|
this.post('/claim', { json: params }, function(err, data) {
|
|
|
|
return callback(err, data);
|
|
|
|
});
|
2013-05-30 22:00:19 +04:00
|
|
|
},
|
2013-06-03 20:43:52 +04:00
|
|
|
|
|
|
|
getBadgeRecommendations: function getBadgeRecommendations (query, callback) {
|
|
|
|
var id = query.badgeName;
|
2013-06-04 20:45:46 +04:00
|
|
|
var limit = query.limit;
|
|
|
|
var params = {
|
|
|
|
limit: limit
|
|
|
|
};
|
2013-06-03 20:43:52 +04:00
|
|
|
|
|
|
|
if (!id)
|
|
|
|
return callback(new errors.BadRequest('Invalid badge key'));
|
|
|
|
|
2013-06-04 20:45:46 +04:00
|
|
|
this.get('/badge/' + id + '/recommendations', { qs: params }, function(err, data) {
|
2013-06-03 20:44:29 +04:00
|
|
|
if (err)
|
|
|
|
return callback(err, data);
|
2013-06-03 20:43:52 +04:00
|
|
|
|
|
|
|
return callback(null, {
|
|
|
|
badges: _.map(data.badges, normalizeBadge)
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
2013-06-05 06:15:59 +04:00
|
|
|
|
|
|
|
getUserRecommendations: function getUserRecommendations (query, callback) {
|
|
|
|
var user = query.session.user;
|
|
|
|
var email = user.email;
|
|
|
|
var params = {
|
|
|
|
auth: getJWTToken(email),
|
|
|
|
email: email
|
|
|
|
};
|
|
|
|
this.get('/user/recommendations', {qs: params}, function(err, data) {
|
|
|
|
if (err)
|
|
|
|
return callback(err, null);
|
|
|
|
|
|
|
|
return callback(null, {
|
|
|
|
recommendations: _.map(data.badges, normalizeBadge)
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2013-05-15 00:18:25 +04:00
|
|
|
});
|
2013-05-15 23:25:41 +04:00
|
|
|
|
2013-06-03 00:00:15 +04:00
|
|
|
updateOrgs();
|
2013-06-02 21:42:33 +04:00
|
|
|
|
2013-05-15 23:25:41 +04:00
|
|
|
module.exports = openbadger;
|
2013-06-02 21:42:33 +04:00
|
|
|
module.exports.getFilters = function getFilters () {
|
|
|
|
return {
|
2013-06-03 00:00:15 +04:00
|
|
|
categories: {
|
|
|
|
name: 'category',
|
|
|
|
label: 'Category',
|
|
|
|
options: categories
|
|
|
|
},
|
|
|
|
ageRanges: {
|
|
|
|
name: 'age',
|
|
|
|
label: 'Age',
|
|
|
|
options: ageRanges
|
|
|
|
},
|
|
|
|
orgs: {
|
|
|
|
name: 'org',
|
|
|
|
label: 'Organization',
|
|
|
|
options: orgs
|
|
|
|
},
|
|
|
|
activityTypes: {
|
|
|
|
name: 'activity',
|
|
|
|
label: 'Activity',
|
|
|
|
options: activityTypes
|
|
|
|
},
|
|
|
|
badgeTypes: {
|
|
|
|
name: 'type',
|
|
|
|
label: 'Type',
|
|
|
|
options: badgeTypes
|
|
|
|
}
|
2013-06-02 21:42:33 +04:00
|
|
|
};
|
|
|
|
}
|
2013-06-03 23:57:12 +04:00
|
|
|
module.exports.updateOrgs = updateOrgs;
|
2013-06-04 06:18:28 +04:00
|
|
|
module.exports.healthCheck = function(meta, cb) {
|
2013-06-04 00:30:31 +04:00
|
|
|
// Use a privileged API call to ensure we're testing the JWT secret.
|
|
|
|
// A random email should guarantee we bust through any caches.
|
|
|
|
var email = 'healthCheck_test_' +
|
|
|
|
Math.floor(Math.random() * 100000) + '@mozilla.org';
|
|
|
|
|
2013-06-04 06:18:28 +04:00
|
|
|
meta.notes = ENDPOINT;
|
2013-06-04 00:30:31 +04:00
|
|
|
openbadger.getUserBadges({
|
|
|
|
session: {user: {email: email}}
|
|
|
|
}, cb);
|
|
|
|
};
|