diff --git a/app.js b/app.js index 10289b6..91ad752 100644 --- a/app.js +++ b/app.js @@ -13,6 +13,7 @@ var admin = require('./routes/admin'); var issuer = require('./routes/issuer'); var api = require('./routes/api'); var debug = require('./routes/debug'); +var stats = require('./routes/stats'); var app = express(); var logger = app.logger = require('./lib/logger'); @@ -69,6 +70,9 @@ app.configure('production', function () { app.get('/admin/config', admin.configure); app.post('/admin/config', issuer.update); +app.get('/admin/stats', [stats.monthly], admin.stats); + + // Badge listing // ------------- var indexMiddleware = [badge.findAll, behavior.findAll]; diff --git a/models/stats.js b/models/stats.js new file mode 100644 index 0000000..f60ec5f --- /dev/null +++ b/models/stats.js @@ -0,0 +1,21 @@ +const BadgeInstance = require('./badge-instance'); + +const Stats = {}; + +function isoMonthDay(date) { + return date.toISOString().slice(0, 7); +} + +Stats.monthly = function monthlyStats(callback) { + const months = {}; + BadgeInstance.find({}, function (err, instances) { + if (err) return callback(err); + instances.forEach(function (instance) { + const month = isoMonthDay(instance.issuedOn); + months[month] = (months[month] || 0) + 1; + }); + callback(null, months); + }); +}; + +module.exports = Stats; \ No newline at end of file diff --git a/routes/admin.js b/routes/admin.js index 9d2c2c7..c2afb66 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -140,6 +140,17 @@ exports.userList = function userList(req, res, next) { }); }; +exports.stats = function stats(req, res, next) { + return res.render('admin/stats.html', { + page: 'stats', + issuer: req.issuer, + stats: req.stats, + user: req.session.user, + csrf: req.session._csrf, + users: req.users + }); +}; + exports.notFound = function notFound(req, res, next) { res.status(404) return res.render('public/404.html', {}); diff --git a/routes/stats.js b/routes/stats.js new file mode 100644 index 0000000..a878100 --- /dev/null +++ b/routes/stats.js @@ -0,0 +1,12 @@ +const Stats = require('../models/stats'); + +exports.monthly = function monthly(req, res, next) { + Stats.monthly(function (err, stats) { + if (err) return next(err); + const months = Object.keys(stats).map(function (month) { + return { month: month, count: stats[month] }; + }).reverse(); + req.stats = months; + return next(); + }); +}; \ No newline at end of file diff --git a/tests/stats-model.test.js b/tests/stats-model.test.js new file mode 100644 index 0000000..7965304 --- /dev/null +++ b/tests/stats-model.test.js @@ -0,0 +1,60 @@ +const test = require('./'); +const db = require('../models'); +const BadgeInstance = require('../models/badge-instance'); +const Stats = require('../models/stats'); +const util = require('../lib/util'); + +test.applyFixtures({ + 'instance': new BadgeInstance({ + issuedOn: new Date('2012-10-15'), + user: 'brian@example.org', + hash: 'hash', + badge: 'link-advanced', + assertion: '{ "assertion" : "yep" }', + }), + 'instance2': new BadgeInstance({ + issuedOn: new Date('2012-11-15'), + user: 'brian@example.org', + hash: 'hash', + badge: 'link-hyper-advanced', + assertion: '{ "assertion" : "yep" }', + }), + 'instance3': new BadgeInstance({ + issuedOn: new Date('2012-12-15'), + user: 'brian-delete@example.org', + hash: 'hash', + badge: 'link-hyper-advanced', + assertion: '{ "assertion" : "yep" }', + }), + 'instance4': new BadgeInstance({ + issuedOn: new Date('2013-01-15'), + user: 'brian-delete@example.org', + hash: 'otherhash', + badge: 'link-turbo-advanced', + assertion: '{ "assertion" : "yep" }', + }), + 'instance5': new BadgeInstance({ + issuedOn: new Date('2013-01-16'), + user: 'brian-delete@example.org', + hash: 'otherhash', + badge: 'link-mega-advanced', + assertion: '{ "assertion" : "yep" }', + }), +}, function (fixtures) { + test('Stats.monthly', function (t) { + Stats.monthly(function (err, stats) { + t.notOk(err, 'should not have an error'); + t.same(stats['2011-01'], undefined, 'should not have badges for jan 2011'); + t.same(stats['2012-10'], 1, 'should have one badge'); + t.same(stats['2012-11'], 1, 'should have one badge'); + t.same(stats['2012-12'], 1, 'should have one badge'); + t.same(stats['2013-01'], 2, 'should have two badges'); + t.end(); + }); + }); + + // necessary to stop the test runner + test('shutting down #', function (t) { + db.close(); t.end(); + }); +}) \ No newline at end of file diff --git a/views/admin/stats.html b/views/admin/stats.html new file mode 100644 index 0000000..5660326 --- /dev/null +++ b/views/admin/stats.html @@ -0,0 +1,17 @@ +{% extends "admin/base.html" %} +{% block main %} + +

Statistics

+ + + +{% endblock %}