зеркало из https://github.com/mozilla/CSOL-site.git
Merge pull request #173 from andrewhayward/api
[WIP] Pull data from remote data source
This commit is contained in:
Коммит
743a7d3169
|
@ -0,0 +1,195 @@
|
|||
var request = require('request');
|
||||
var _ = require('underscore');
|
||||
|
||||
|
||||
var DEFAULT_ERROR = 'There was a problem accessing this data.';
|
||||
var DEFAULT_QUERY = {
|
||||
page: 1,
|
||||
pageSize: 12
|
||||
};
|
||||
|
||||
|
||||
// Core API function
|
||||
// Loads data into `request.remote`, and intercepts XHR requests
|
||||
function api (method, default_query) {
|
||||
return function (req, res, next) {
|
||||
if (!_.isFunction(method))
|
||||
method = api[method];
|
||||
|
||||
if (!_.isFunction(method)) {
|
||||
console.error('Method supplied to API not a function');
|
||||
return next('Supplied method not valid');
|
||||
}
|
||||
|
||||
// Build query from various inputs
|
||||
var query = _.extend(
|
||||
DEFAULT_QUERY,
|
||||
default_query || {},
|
||||
req.query || {},
|
||||
req.body || {},
|
||||
req.params || {}
|
||||
);
|
||||
|
||||
method(query, function(err, data) {
|
||||
if (!_.isObject(data))
|
||||
data = {};
|
||||
|
||||
if (err)
|
||||
data.error = err;
|
||||
|
||||
if (req.xhr)
|
||||
return res.json(data);
|
||||
|
||||
req.remote = {
|
||||
err: err,
|
||||
data: data
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper for API methods
|
||||
// Normalises input and output
|
||||
function apiMethod (method) {
|
||||
return function (query, callback) {
|
||||
if (_.isFunction(query) && !callback) {
|
||||
callback = query;
|
||||
query = {};
|
||||
}
|
||||
|
||||
// Assume any non-object query is being passed in as an ID
|
||||
if (!_.isObject(query))
|
||||
query = {id: query};
|
||||
|
||||
query = _.defaults(query, DEFAULT_QUERY);
|
||||
|
||||
method(query, function(err, data) {
|
||||
if (!err)
|
||||
return callback(null, data);
|
||||
|
||||
if (!data || _.isString(data))
|
||||
data = {message: data || DEFAULT_ERROR};
|
||||
|
||||
callback(err, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Load data from remote endpoint
|
||||
var remote = (function() {
|
||||
function remote (method, path, callback) {
|
||||
// TODO - put this in settings somewhere
|
||||
var origin = 'http://openbadger-csol.mofostaging.net';
|
||||
|
||||
if (!request[method])
|
||||
return callback(500, 'Unknown method');
|
||||
|
||||
// TODO - need to add ability to pass data through
|
||||
// TODO - might want to cache this at some point
|
||||
request[method](origin + path, function(err, response, body) {
|
||||
if (err)
|
||||
return callback(500, err);
|
||||
|
||||
if (response.statusCode !== 200)
|
||||
return callback(500, 'Upstream error');
|
||||
|
||||
try {
|
||||
var data = JSON.parse(body);
|
||||
} catch (e) {
|
||||
return callback(500, e.message);
|
||||
}
|
||||
|
||||
if (data.status !== 'ok')
|
||||
return callback(500, data.reason);
|
||||
|
||||
callback(null, data);
|
||||
});
|
||||
}
|
||||
|
||||
_.each(['get', 'post', 'put', 'patch', 'head', 'del'], function(method) {
|
||||
Object.defineProperty(remote, method, {
|
||||
enumerable: true,
|
||||
value: function(path, callback) {
|
||||
remote(method, path, callback);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return remote;
|
||||
})();
|
||||
|
||||
// Make sure badges returned from remote API
|
||||
// contain all the information we need
|
||||
function normalizeBadge (badge, id) {
|
||||
if (!id)
|
||||
id = badge.shortname;
|
||||
|
||||
if (!badge.id)
|
||||
badge.id = id;
|
||||
|
||||
if (!badge.url)
|
||||
badge.url = '/badges/' + badge.id;
|
||||
|
||||
return badge;
|
||||
}
|
||||
|
||||
api.getBadges = apiMethod(function getBadges (query, callback) {
|
||||
var pageSize = parseInt(query.pageSize, 10),
|
||||
page = parseInt(query.page, 10);
|
||||
|
||||
if (isNaN(pageSize) || pageSize < 1)
|
||||
return callback(400, 'Invalid pageSize number');
|
||||
|
||||
if (isNaN(page) || page < 1)
|
||||
return callback(400, 'Invalid page number');
|
||||
|
||||
var start = (page - 1) * pageSize,
|
||||
end = start + pageSize;
|
||||
|
||||
remote.get('/v1/badges', function(err, data) {
|
||||
if (err)
|
||||
return callback(err, data);
|
||||
|
||||
var badges = _.map(data.badges, normalizeBadge);
|
||||
var pages = Math.ceil(badges.length / pageSize);
|
||||
|
||||
if (page > pages)
|
||||
return callback(404, {
|
||||
message: 'Page not found',
|
||||
page: page,
|
||||
pages: pages
|
||||
});
|
||||
|
||||
callback(null, {
|
||||
page: page,
|
||||
pages: pages,
|
||||
items: badges.slice(start, end)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
api.getBadge = apiMethod(function getBadge (query, callback) {
|
||||
var id = query.id;
|
||||
|
||||
if (!id)
|
||||
return callback(400, 'Invalid badge key');
|
||||
|
||||
remote('get', '/v1/badges', function(err, data) {
|
||||
if (err)
|
||||
return callback(err, data);
|
||||
|
||||
var badge = data.badges[id];
|
||||
|
||||
if (!badge)
|
||||
return callback(404, 'Badge not found');
|
||||
|
||||
normalizeBadge(badge, id);
|
||||
|
||||
callback(null, {
|
||||
badge: badge
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = api;
|
|
@ -1,3 +1,5 @@
|
|||
var api = require('../api');
|
||||
|
||||
module.exports = function (app) {
|
||||
|
||||
function getFilters() {
|
||||
|
@ -150,11 +152,30 @@ module.exports = function (app) {
|
|||
});
|
||||
|
||||
app.param('badgeName', function (req, res, next, badgeName) {
|
||||
// yep, get stuff from the db.
|
||||
next();
|
||||
api.getBadge(badgeName, function(err, data) {
|
||||
if (err)
|
||||
return next(data.message);
|
||||
|
||||
req.params.badge = data.badge;
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/badges', function (req, res, next) {
|
||||
app.get('/badges', api('getBadges'), function (req, res, next) {
|
||||
var err = req.remote.error;
|
||||
var data = req.remote.data;
|
||||
|
||||
if (err)
|
||||
return next({status: err, message: data.message});
|
||||
|
||||
res.render('badges/list.html', {
|
||||
filters: getFilters(),
|
||||
items: data.items,
|
||||
page: data.page,
|
||||
pages: data.pages
|
||||
});
|
||||
|
||||
/*
|
||||
var badges = [];
|
||||
|
||||
for (var i = 0; i < 12; ++i) {
|
||||
|
@ -169,10 +190,13 @@ module.exports = function (app) {
|
|||
filters: getFilters(),
|
||||
items: badges
|
||||
});
|
||||
*/
|
||||
});
|
||||
|
||||
app.get('/badges/:badgeName', function (req, res, next) {
|
||||
res.render('badges/single.html');
|
||||
res.render('badges/single.html', {
|
||||
badge: req.params.badge
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/badges/:badgeName/claim', function (req, res, next) {
|
||||
|
|
11
package.json
11
package.json
|
@ -4,13 +4,14 @@
|
|||
"description": "The website for the Chicago Summer of Learning, developed by Ocupop and Mozilla.",
|
||||
"main": "app.js",
|
||||
"dependencies": {
|
||||
"express": "~3.1.0",
|
||||
"nunjucks": "~0.1.8a",
|
||||
"mysql": "~2.0.0-alpha7",
|
||||
"sequelize": "~1.6.0",
|
||||
"tap": "~0.4.1",
|
||||
"async": "~0.2.6",
|
||||
"bcrypt": "~0.7.5",
|
||||
"express": "~3.1.0",
|
||||
"mysql": "~2.0.0-alpha7",
|
||||
"nunjucks": "~0.1.8a",
|
||||
"request": "~2.21.0",
|
||||
"sequelize": "~1.6.0",
|
||||
"tap": "~0.4.1",
|
||||
"underscore": "~1.4.4"
|
||||
},
|
||||
"devDependencies": {},
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
{% extends 'badges/layout.html' %}
|
||||
{% set pageTitle = 'Some Badge' %}
|
||||
{% set user = {} %}
|
||||
{% set pageTitle = badge.name %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="span4">
|
||||
<img src="/media/images/badge-large.png">
|
||||
<img src="{{ badge.image }}">
|
||||
</div>
|
||||
<div class="span8">
|
||||
<p><strong>Part of <a href="/programs/ae784f">Some Program</a>, from <a href="/orgs/some-organisation">Some Organisation</a>.</strong></p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>{{ badge.description }}</p>
|
||||
|
||||
<p class="text-right">
|
||||
<a href="/badges/ae784f/claim" class="btn">Claim this badge</a>
|
||||
<a href="/badges/ae784f/favorite" class="btn"><i class="icon-heart"></i> Add to your favorites</a>
|
||||
<a href="{{ badge.url }}/claim" class="btn">Claim this badge</a>
|
||||
<a href="{{ badge.url }}/favorite" class="btn"><i class="icon-heart"></i> Add to your favorites</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{#
|
||||
<h3><a href="/badges">Related Badges</a></h3>
|
||||
<ul class="thumbnails">
|
||||
<li class="span3">
|
||||
|
@ -68,4 +67,5 @@
|
|||
</figure>
|
||||
</li>
|
||||
</ul>
|
||||
#}
|
||||
{% endblock %}
|
|
@ -48,7 +48,7 @@
|
|||
{% include item.template %}
|
||||
{% else %}
|
||||
<figure class="thumbnail">
|
||||
<a href="{{ item.url }}"><img src="{{ item.thumbnail }}"></a>
|
||||
<a href="{{ item.url }}"><img src="{{ item.image }}"></a>
|
||||
<figcaption class="caption">
|
||||
<p>{{ item.description }}</p>
|
||||
{% block item_actions_wrapper %}
|
||||
|
@ -68,15 +68,28 @@
|
|||
</ul>
|
||||
{% endblock %}
|
||||
{% block pagination %}
|
||||
<nav class="pagination pagination-centered">
|
||||
<ul>
|
||||
<li class="disabled"><span>«</span></li>
|
||||
<li class="disabled"><span>1</span></li>
|
||||
<li><a href="#">2</a></li>
|
||||
<li><a href="#">3</a></li>
|
||||
<li><a href="#">4</a></li>
|
||||
<li><a href="#">»</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% if pages > 1 %}
|
||||
<nav class="pagination pagination-centered">
|
||||
<ul>
|
||||
{% if page == 1 %}
|
||||
<li class="disabled"><span>«</span></li>
|
||||
{% else %}
|
||||
<li><a href="?page={{ page - 1 }}">«</a></li>
|
||||
{% endif %}
|
||||
{% for pageNum in range(1, (pages + 1)) %}
|
||||
{% if page == pageNum %}
|
||||
<li class="disabled"><span>{{ pageNum }}</span></li>
|
||||
{% else %}
|
||||
<li><a href="?page={{ pageNum }}">{{ pageNum }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if page == pages %}
|
||||
<li class="disabled"><span>»</span></li>
|
||||
{% else %}
|
||||
<li><a href="?page={{ page + 1 }}">»</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
Загрузка…
Ссылка в новой задаче