diff --git a/models/badge-instance.js b/models/badge-instance.js index 715d7ba..61f9891 100644 --- a/models/badge-instance.js +++ b/models/badge-instance.js @@ -9,6 +9,17 @@ const regex = { email: /[a-z0-9!#$%&'*+\/=?\^_`{|}~\-]+(?:\.[a-z0-9!#$%&'*+\/=?\^_`{|}~\-]+)*@(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?/i }; +const EvidenceFileSchema = new Schema({ + path: { + type: String, + required: true + }, + mimeType: { + type: String, + required: true + } +}); + const BadgeInstanceSchema = new Schema({ _id: { type: String, @@ -36,6 +47,7 @@ const BadgeInstanceSchema = new Schema({ type: String, trim: true, }, + evidenceFiles: [EvidenceFileSchema], seen: { type: Boolean, required: true, diff --git a/models/badge.js b/models/badge.js index 47064f0..71f4476 100644 --- a/models/badge.js +++ b/models/badge.js @@ -548,10 +548,7 @@ Badge.prototype.redeemClaimCode = function redeemClaimCode(code, email, cb) { if (!claim.multi && claim.claimedBy && claim.claimedBy !== email) return cb(null, false); claim.claimedBy = email; - Badge.temporaryEvidence.destroy(claim, function(err) { - if (err) return cb(err); - cb(null, true); - }); + cb(null, true); }; Badge.prototype.removeClaimCode = function removeClaimCode(code, cb) { @@ -623,10 +620,12 @@ Badge.prototype.award = function award(options, callback) { const categories = this.categories; const weight = this.weight; const evidence = options.evidence; + const evidenceFiles = options.evidenceFiles; const instance = new BadgeInstance({ user: email, badge: this.id, evidence: evidence, + evidenceFiles: evidenceFiles }); // We don't want to fail with an error if the user already has the @@ -649,9 +648,12 @@ Badge.prototype.award = function award(options, callback) { }); }; -Badge.prototype.awardOrFind = function awardOrFind(email, callback) { - const query = { userBadgeKey: [email, this.id].join('.') }; - this.award(email, function (err, instance) { +Badge.prototype.awardOrFind = function awardOrFind(options, callback) { + if (typeof options === 'string') + options = {user: options}; + + const query = { userBadgeKey: [options.user, this.id].join('.') }; + this.award({ user: options.user, evidenceFiles: options.evidenceFiles }, function (err, instance) { if (!instance) { BadgeInstance.findOne(query, function (err, instance) { if (err) return callback(err); diff --git a/routes/api.js b/routes/api.js index 426a74c..0482366 100644 --- a/routes/api.js +++ b/routes/api.js @@ -373,6 +373,7 @@ function tryAwardingBadge(opts, res, successCb) { const badge = opts.badge; const email = opts.email; const evidence = opts.evidence; + const evidenceFiles = opts.evidenceFiles; if (!badge) return res.json(404, {status: 'error', reason: 'badge not found'}); @@ -381,7 +382,8 @@ function tryAwardingBadge(opts, res, successCb) { return badge.award({ email: email, - evidence: evidence + evidence: evidence, + evidenceFiles: evidenceFiles }, function (err, instance, autoAwardedInstances) { if (err) { // TODO: log error properly @@ -484,10 +486,12 @@ exports.awardBadgeFromClaimCode = function(req, res, next) { return res.json(400, {status: 'error', reason: 'missing email address'}); getUnclaimedBadgeFromCode(code, req, res, next, function(badge) { + var claim = badge.getClaimCode(code); tryAwardingBadge({ badge: badge, email: email, - evidence: evidence + evidence: evidence, + evidenceFiles: claim.evidence }, res, function(success) { async.series([ badge.redeemClaimCode.bind(badge, code, email), diff --git a/routes/badge.js b/routes/badge.js index 26a1adf..3cd9b7f 100644 --- a/routes/badge.js +++ b/routes/badge.js @@ -6,6 +6,8 @@ const BadgeInstance = require('../models/badge-instance'); const Work = require('../models/work'); const util = require('../lib/util'); const async = require('async'); +const s3 = require('../lib/s3'); +const mime = require('mime'); function handleTagInput(input) { return ( @@ -146,6 +148,36 @@ exports.assertion = function assertion(req, res) { }); }; +exports.getEvidenceFile = function getEvidenceFile(req, res) { + var assertionId = req.param('hash'); + var evidenceIndex = req.param('index'); + + BadgeInstance.findOne({ _id: assertionId }, function (err, instance) { + if (err) + return res.send(500, err); + if (!instance) + return res.send(404); + + if (evidenceIndex < 0 || evidenceIndex >= instance.evidenceFiles.length) + return res.send(404); + + var evidence = instance.evidenceFiles[evidenceIndex]; + + s3.get(evidence.path).on('response', function(s) { + if (err) return res.json(500, { + status: 'error', + reason: 'cannot retrieve evidence' + }); + res.type(evidence.mimeType); + var ext = mime.extension(evidence.mimeType); + var filename = 'evidence-' + evidenceIndex + (ext ? '.' + ext : ''); + res.set('Content-Disposition', + 'attachment; filename="' + filename + '"'); + s.pipe(res); + }).end(); + }); +}; + exports.meta = function meta(req, res) { req.badge.populate('program', function(err) { if (err) @@ -242,6 +274,7 @@ exports.awardToUser = function awardToUser(req, res, next) { var email = (form.email || '').trim(); var code = (form.code || '').trim(); var badge = req.badge; + var evidenceFiles = req.claim.evidence; badge.redeemClaimCode(code, email, function(err, claimSuccess) { if (err) return res.send(reportError(err)); @@ -251,7 +284,7 @@ exports.awardToUser = function awardToUser(req, res, next) { if (claimSuccess === null) return res.send({ status: 'not-found' }); - badge.awardOrFind(email, function (err, instance) { + badge.awardOrFind({ user: email, evidenceFiles: evidenceFiles }, function (err, instance) { if (err) return res.send(reportError(err)); badge.save(function (err) { if (err) return res.send(reportError(err)); diff --git a/routes/index.js b/routes/index.js index 2b85991..c17d7d5 100644 --- a/routes/index.js +++ b/routes/index.js @@ -154,6 +154,9 @@ exports.define = function defineRoutes(app) { app.get('/badge/criteria/:shortname', [ findBadgeByParamShortname ], render.criteria); + app.get('/badge/evidence/:hash', render.evidence); + app.get('/badge/evidence/:hash/:index', badge.getEvidenceFile); + app.get('/program/meta/:programId', [ issuer.findProgramById ], issuer.meta); diff --git a/routes/render.js b/routes/render.js index cfa9891..55c74a8 100644 --- a/routes/render.js +++ b/routes/render.js @@ -1,6 +1,7 @@ const Issuer = require('../models/issuer'); const Program = require('../models/program'); const Badge = require('../models/badge'); +const BadgeInstance = require('../models/badge-instance'); const phrases = require('../lib/phrases'); const logger = require('../lib/logger'); const async = require('async'); @@ -170,6 +171,27 @@ exports.criteria = function criteria(req, res) { }); } +exports.evidence = function evidence(req, res) { + var assertionId = req.param('hash'); + + BadgeInstance.findOne({ _id: assertionId }, function (err, instance) { + if (err) + return res.send(500, err); + if (!instance) + return res.send(404); + instance.populate('badge', function(err) { + if (err) + return res.send(500, err); + + return res.render('public/evidence.html', { + instance: instance, + user: req.session.user, + csrf: req.session._csrf + }); + }); + }); +} + exports.anonymousHome = function all(req, res) { return res.render('public/anonymous-home.html', { user: req.session.user, diff --git a/views/public/evidence.html b/views/public/evidence.html new file mode 100644 index 0000000..e85a8c0 --- /dev/null +++ b/views/public/evidence.html @@ -0,0 +1,21 @@ +{% extends "public/layout.html" %} +{% block title %}{{ instance.badge.name }}{% endblock %} +{% block body %} +
+ {{ instance.evidence }} +
+ + {% for file in instance.evidenceFiles %} + + {% endfor %} +