Incorporating /v2/program and /v2/programs endpoints, and

refining badge endpoints. The program endpoints currently
do not return enough data from the OpenBadger side to fully
build out their pages. I'm also not sure about the controllers
organizational strategy, and whether I'm breaking it. Changing
site main navigation and major endpoints to "Earn, Learn, Level Up".
This commit is contained in:
Mike Larsson 2013-05-14 16:18:25 -04:00
Родитель 5d549f0d3a
Коммит bf5ce3393e
6 изменённых файлов: 179 добавлений и 73 удалений

Просмотреть файл

@ -98,62 +98,43 @@ module.exports = function (app) {
return filters;
}
app.param('programName', function (req, res, next, badgeName) {
// yep, get stuff from the db.
next();
});
app.param('programName', function (req, res, next, programName) {
badger.getProgram(programName, function(err, data) {
if (err)
return next(data.message);
app.get('/programs', function (req, res, next) {
var programs = [];
for (var i = 0; i < 12; ++i) {
programs.push({
thumbnail: '/media/images/program.png',
description: 'Program blah sed eiusmod...',
url: '/programs/ae784f'
});
}
res.render('programs/list.html', {
filters: getFilters('categories', 'orgs', 'ages'),
items: programs
req.params.program = data.program;
next();
});
});
app.get('/programs/science', function (req, res, next) {
res.send('SCIENCE!!')
app.get('/learn', api(badger.getPrograms), function (req, res, next) {
var data = req.remote;
res.render('programs/list.html', {
filters: getFilters('categories', 'orgs', 'ages'),
items: data.programs,
page: data.page,
pages: data.pages
});
});
app.get('/programs/technology', function (req, res, next) {
res.send('TECHNOLOGY!!!')
app.get('/learn/:programName', function (req, res, next) {
res.render('programs/single.html', {
program: req.params.program
});
});
app.get('/programs/engineering', function (req, res, next) {
res.send('ENGINEERING!!!')
});
app.get('/programs/art', function (req, res, next) {
res.send('ART!!!')
});
app.get('/programs/math', function (req, res, next) {
res.send('MATH!!!')
});
app.get('/programs/:programName', function (req, res, next) {
res.render('programs/single.html');
});
app.get('/programs/:programName/favorite', function (req, res, next) {
app.get('/learn/:programName/favorite', function (req, res, next) {
return res.redirect('/login', 303);
});
app.get('/programs/:programName/unfavorite', function (req, res, next) {
app.get('/learn/:programName/unfavorite', function (req, res, next) {
return res.redirect('/login', 303);
});
app.param('badgeName', function (req, res, next, badgeName) {
api.getBadge(badgeName, function(err, data) {
badger.getBadge(badgeName, function(err, data) {
if (err)
return next(data.message);
@ -162,7 +143,7 @@ module.exports = function (app) {
});
});
app.get('/badges', api(badger.getBadges), function (req, res, next) {
app.get('/earn', api(badger.getBadges), function (req, res, next) {
var data = req.remote;
res.render('badges/list.html', {
@ -171,40 +152,23 @@ module.exports = function (app) {
page: data.page,
pages: data.pages
});
/*
var badges = [];
for (var i = 0; i < 12; ++i) {
badges.push({
thumbnail: '/media/images/badge.png',
description: 'Badge blah in voluptate velit...',
url: '/badges/ae784f'
});
}
res.render('badges/list.html', {
filters: getFilters(),
items: badges
});
*/
});
app.get('/badges/:badgeName', function (req, res, next) {
app.get('/earn/:badgeName', function (req, res, next) {
res.render('badges/single.html', {
badge: req.params.badge
});
});
app.get('/badges/:badgeName/claim', function (req, res, next) {
app.get('/earn/:badgeName/claim', function (req, res, next) {
res.render('badges/claim.html');
});
app.get('/badges/:badgeName/favorite', function (req, res, next) {
app.get('/earn/:badgeName/favorite', function (req, res, next) {
return res.redirect('/login', 303);
});
app.get('/badges/:badgeName/unfavorite', function (req, res, next) {
app.get('/earn/:badgeName/unfavorite', function (req, res, next) {
return res.redirect('/favorites', 303);
});

Просмотреть файл

@ -23,7 +23,7 @@ function normalizeBadge (badge, id) {
badge.id = id;
if (!badge.url)
badge.url = '/badge/' + badge.id;
badge.url = '/earn/' + badge.id;
return badge;
}
@ -60,3 +60,50 @@ exports.getBadge = apiMethod(function getBadge (query, callback) {
});
});
});
function normalizeProgram(program, id) {
if (!id)
id = program.shortname;
if (!program.id)
program.id = id;
if (!program.url)
program.url = '/learn/' + program.id;
return program;
}
exports.getPrograms = apiMethod(paginate('programs', function getPrograms (query, callback) {
remote.get('/v2/programs', function(err, data) {
if (err)
return callback(err, data);
var programs = _.map(data.programs, normalizeProgram);
return callback(null, {
programs: programs
});
});
}));
exports.getProgram = apiMethod(function getProgram (query, callback) {
var id = query.id;
if (!id)
return callback(400, 'Invalid program key');
remote.get('/v2/program/' + id, function(err, data) {
if (err)
return callback(err, data);
var program = data.program;
normalizeProgram(program, id);
callback(null, {
program: program
});
});
});

Просмотреть файл

@ -42,6 +42,30 @@ const DATA = {
image: 'http://openbadger-csol.mofostaging.net/badge/image/link-basic.png',
behaviors: [ { name: 'link', score: 5 } ]
}
},
'programs': {
status: 'ok',
programs: {
"prog-a": {
image: "http://some.org/prog-a/img.png",
name: "Program A"
},
"prog-b": {
image: "http://some.org/prog-b/img.png",
name: "Program B"
},
"prog-c": {
image: "http://some.org/prog-c/img.png",
name: "Program C"
}
}
},
'program': {
status: 'ok',
program: {
image: "http://some.org/prog-a/img.png",
name: "Program A"
}
}
};
@ -75,9 +99,9 @@ test('getBadge', function(t) {
getStub.callsArgWith(1, null, DATA['badge']);
openbadger.getBadge({ id: 'some-id' }, function(err, data) {
t.notOk(err, "no error");
t.ok(getStub.calledWithMatch('/badge/some-id'), 'endpoint');
t.ok(getStub.calledWithMatch('/badge/some-id'), 'remote endpoint');
t.similar(data.badge, { name: "Link Badge, basic"}, 'badge');
t.similar(data.badge, { id: 'some-id', url: '/badge/some-id' }, 'normalized');
t.similar(data.badge, { id: 'some-id', url: '/earn/some-id' }, 'normalized');
t.end();
});
});
@ -91,7 +115,7 @@ test('getBadges', function(t){
openbadger.getBadges(DEFAULT_QUERY, function(err, data) {
t.same(err, 500, 'error');
t.similar(data, { message: 'error of some sort' }, 'data');
t.end();
t.end();
});
});
@ -103,7 +127,7 @@ test('getBadges', function(t){
var badge = data.badges[0];
t.ok(badge.id && badge.url && badge.name && badge.behaviors, 'looks like normalized badge');
t.ok(getStub.calledWithMatch('/v2/badges'), 'endpoint');
t.end();
t.end();
});
});
@ -112,7 +136,78 @@ test('getBadges', function(t){
openbadger.getBadges({ pageSize: 2, page: 1 }, function(err, data) {
t.notOk(err, 'no error');
t.same(data.badges.length, 2, 'paginated');
t.end();
t.end();
});
});
});
test('getProgram', function(t) {
/* reset spy data/stub behavior */
getStub.reset();
getStub.resetBehavior();
t.test('called without id', function(t) {
openbadger.getProgram(function(err, data) {
t.notOk(getStub.called, 'no call');
t.same(err, 400);
t.same(data, { message: "Invalid program key" });
t.end();
});
});
t.test('on error', function(t) {
getStub.callsArgWith(1, 404, 'barf');
openbadger.getProgram({ id: 'whatever' }, function(err, data) {
t.ok(getStub.calledOnce, "called");
t.same(err, 404);
t.same(data.message, "barf", "error message");
t.end();
});
});
t.test('on success', function(t) {
getStub.callsArgWith(1, null, DATA['program']);
openbadger.getProgram({ id: 'some-id' }, function(err, data) {
t.notOk(err, "no error");
t.ok(getStub.calledWithMatch('/program/some-id'), 'endpoint');
t.similar(data.program, { name: "Program A" }, 'program');
t.similar(data.program, { id: 'some-id', url: '/learn/some-id' }, 'normalized');
t.end();
});
});
});
test('getPrograms', function(t) {
t.test('on error', function(t) {
getStub.callsArgWith(1, 500, 'error of some sort');
openbadger.getPrograms(DEFAULT_QUERY, function(err, data) {
t.same(err, 500, 'error');
t.similar(data, { message: 'error of some sort' }, 'data');
t.end();
});
});
t.test('with data', function(t) {
getStub.callsArgWith(1, null, DATA['programs']);
openbadger.getPrograms(DEFAULT_QUERY, function(err, data) {
t.notOk(err, 'no error');
t.same(data.programs.length, 3, 'data length');
var program = data.programs[0];
t.ok(program.id && program.url && program.name, 'looks like normalized program');
t.ok(getStub.calledWithMatch('/v2/programs'), 'endpoint');
t.end();
});
});
t.test('paginates', function(t) {
getStub.callsArgWith(1, null, DATA['programs']);
openbadger.getPrograms({ pageSize: 2, page: 1 }, function(err, data) {
t.notOk(err, 'no error');
t.same(data.programs.length, 2, 'paginated');
t.end();
});
});

Просмотреть файл

@ -50,7 +50,7 @@
<figure class="thumbnail">
<a href="{{ item.url }}"><img src="{{ item.image }}"></a>
<figcaption class="caption">
<p>{{ item.description }}</p>
<p>{{ item.name }}</p>
{% block item_actions_wrapper %}
<p class="text-right">
{% block item_actions %}

Просмотреть файл

@ -21,10 +21,10 @@
<a href="/learn">Learn</a>
</li>
<li class="badges{% if navItem == 'badges' %} active{% endif %}">
<a href="/badges">Badges</a>
<a href="/earn">Earn</a>
</li>
<li class="challenges{% if navItem == 'challenges' %} active{% endif %}">
<a href="/challenges">Challenges</a>
<a href="/challenges">Level Up</a>
</li>
{% if user %}
{% if user.type == 'learner' %}

Просмотреть файл

@ -1,5 +1,5 @@
{% extends 'programs/layout.html' %}
{% set pageTitle = 'Some Program' %}
{% set pageTitle = program.name %}
{% set user = {} %}
{% block content %}