Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Basic badge claiming by code.

  • Loading branch information...
commit 09a6ed7c45fe715180ba23ac5e8aeb6e91195c89 1 parent f547bda
@brianloveswords brianloveswords authored
View
14 app.js
@@ -40,7 +40,8 @@ app.configure(function () {
'/login',
'/logout',
'/badge/*', // public badge resources
- '/v1/*' // api endpoints
+ '/v1/*', // api endpoints
+ '/claim*'
],
redirectTo: '/login'
}));
@@ -96,6 +97,7 @@ app.post('/admin/badge/:shortname/behavior', badge.addBehavior);
app.delete('/admin/badge/:shortname/behavior', badge.removeBehavior);
app.post('/admin/badge/:shortname/claims', badge.addClaimCodes);
app.delete('/admin/badge/:shortname/claims/:code', badge.removeClaimCode);
+app.patch('/admin/badge/:shortname/claims/:code', badge.releaseClaimCode);
// Creating new behaviors
// ----------------------
@@ -118,6 +120,16 @@ app.get('/badge/criteria/:shortname', [
findBadgeByParamShortname
], admin.criteria);
+app.get('/claim', admin.claim);
+
+app.post('/claim',[
+ badge.findByClaimCode()
+], admin.confirmClaim);
+
+app.post('/claim/confirm',[
+ badge.findByClaimCode()
+], badge.awardToUser);
+
// User login/logout
// -------------------
app.get('/login', admin.login);
View
15 models/badge.js
@@ -341,7 +341,7 @@ Badge.prototype.redeemClaimCode = function redeemClaimCode(code, email) {
const claim = this.getClaimCode(code);
if (!claim)
return null;
- if (claim.claimedBy)
+ if (claim.claimedBy && claim.claimedBy != email)
return false;
claim.claimedBy = email;
return true;
@@ -360,6 +360,19 @@ Badge.prototype.removeClaimCode = function removeClaimCode(code) {
};
/**
+ * Release a claim code back into the wild (remove `claimedBy`)
+ *
+ * @param {String} code
+ */
+
+Badge.prototype.releaseClaimCode = function releaseClaimCode(code) {
+ const claim = this.getClaimCode(code);
+ claim.claimedBy = null;
+ return true;
+};
+
+
+/**
* Check if the credits are enough to earn the badge
*
* @param {User} user An object resembling a User object.
View
BIN  public/img/bg-stone.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
33 public/js/claim-award.js
@@ -0,0 +1,33 @@
+(function claimAward($) {
+ var $form = $('.js-confirm-form');
+ var $emailInput = $form.find('.js-email-input');
+ var where = $form.prop('action');
+ var method = $form.prop('method');
+ var code = $form.find('input[name="code"]').val();
+ var csrfToken = $form.find('input[name="csrf"]').val();
+
+ jQuery.ajaxSetup({
+ headers: {'x-csrf-token': csrfToken}
+ });
+
+ function getAssertion(email, callback) {
+ var data = { email: email, code: code};
+ jQuery.post('/claim/confirm', data)
+ .success(function (data) { callback(null, data) })
+ .error(function (err) { callback(err.statusText) });
+ }
+
+ $form.on('submit', function (e) {
+ var $this = $(this);
+ var email = $emailInput.val().trim();
+ var url;
+ getAssertion(email, function (err, data) {
+ if (err)
+ return window.alert('there was an error trying to claim the badge :(');
+ url = data.assertionUrl;
+ OpenBadges.issue([url]);
+ });
+
+ return (e.preventDefault(), false);
+ });
+})(jQuery);
View
58 public/stylesheets/criteria.css
@@ -57,4 +57,62 @@ h2 {
line-height: 38px;
}
+h3 {
+ margin: 0;
+ padding: 0;
+}
+
+.claim-form {
+ text-align: center;
+ padding: 30px 0;
+}
+
+.claim-form .input,
+.confirm-form .input {
+ background: none repeat scroll 0 0 #FAFAFA;
+ border-color: #DBDBDB;
+ border-radius: 6px 6px 6px 6px;
+ border-style: solid;
+ border-width: 1px;
+ box-shadow: 0 2px 1px rgba(0, 0, 0, 0.1) inset;
+ font-size: inherit;
+ height: 1.714em;
+ line-height: 1.714em;
+ max-width: 450px;
+ padding: 4px 8px;
+ transition: box-shadow 0.1s linear 0s;
+ width: 100%;
+}
+
+.claim-form .button,
+.confirm-form .button {
+ background-color: #276195;
+ background-image: linear-gradient(#3C88CC, #276195);
+ background-repeat: repeat-x;
+ border: 0 none;
+ border-radius: 0.25em 0.25em 0.25em 0.25em;
+ box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.1), 0 -2px 0 0 rgba(0, 0, 0, 0.2) inset;
+ color: #FFFFFF;
+ display: inline-block;
+ font-family: arial,sans-serif;
+ font-size: 1em;
+ height: 2.5em;
+ line-height: 2.5em;
+ margin: 0;
+ padding: 0 1.5em;
+ text-align: center;
+ text-decoration: none;
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ transition: box-shadow 0.25s linear 0s;
+ white-space: nowrap;
+}
+
+.confirm-form .input {
+ width: 400px;
+}
+
+.claim-form strong {
+ color: #e30;
+}
+
/* END TEMPORARY CRITERIA STYLES */
View
2  public/stylesheets/webmaker.css
@@ -735,7 +735,7 @@ body {
border-top: none;
}
.bg-stone {
- background: #f9f9f9 url(/media/img/bg-stone.png) 0 70px repeat-x;
+ background: #f9f9f9 url(/img/bg-stone.png) 0 70px repeat-x;
}
header {
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
View
16 routes/admin.js
@@ -76,6 +76,22 @@ exports.criteria = function criteria(req, res) {
});
}
+exports.claim = function claim(req, res) {
+ return res.render('public/claim.html', {
+ csrf: req.session._csrf,
+ code: req.query.code,
+ missing: req.query.missing,
+ });
+};
+
+exports.confirmClaim = function confirmClaim(req, res) {
+ return res.render('public/confirm-claim.html', {
+ csrf: req.session._csrf,
+ code: req.body.code,
+ badge: req.badge,
+ });
+};
+
exports.manageClaimCodes = function (req, res) {
return res.render('admin/manage-claim-codes.html', {
page: 'edit-badge',
View
46 routes/badge.js
@@ -131,11 +131,53 @@ exports.removeClaimCode = function removeClaimCode(req, res, next) {
var badge = req.badge;
badge.removeClaimCode(code);
badge.save(function (err) {
- if (err) next(err);
- res.redirect('back');
+ if (err) return next(err);
+ return res.redirect('back');
+ });
+};
+
+exports.releaseClaimCode = function releaseClaimCode(req, res, next) {
+ var code = req.param('code');
+ var badge = req.badge;
+ badge.releaseClaimCode(code);
+ badge.save(function (err) {
+ if (err) return next(err);
+ return res.redirect('back');
+ })
+};
+
+
+exports.awardToUser = function awardToUser(req, res, next) {
+ var email = req.body.email;
+ var code = req.body.code;
+ var badge = req.badge;
+ if (!badge.redeemClaimCode(code, email))
+ return res.send({ status: 'already-claimed' })
+ badge.awardOrFind(email, function (err, instance) {
+ if (err) return res.send({ status: 'error', error: err });
+ badge.save(function (err) {
+ if (err) return res.send({ status: 'error', error: err });
+ return res.send({
+ status: 'ok',
+ assertionUrl: instance.absoluteUrl('assertion')
+ });
+ })
});
};
+exports.findByClaimCode = function findByClaimCode(options) {
+ return function (req, res, next) {
+ var code = req.body.code;
+ Badge.findByClaimCode(code, function (err, badge) {
+ if (err) return next(err);
+ if (!badge)
+ return res.redirect('/claim?code=' + code + '&missing=true');
+ req.badge = badge;
+ return next();
+ });
+ }
+};
+
exports.findByShortName = function (options) {
var required = !!options.required;
View
29 views/public/claim.html
@@ -0,0 +1,29 @@
+{% extends "public/layout.html" %}
+{% block head %}
+ <link rel="stylesheet" href="/stylesheets/criteria.css">
+{% endblock %}
+{% block body %}
+ <div class="row">
+ <div class="span7">
+ <h2>Mozilla Webmaker Badges</h2>
+ </div>
+ </div>
+ <div class="media card">
+ <div class="bd">
+ <hgroup>
+ <h3>What's your claim code?</h3>
+ </hgroup>
+
+ <form method="post" class="claim-form" action="">
+ {% if missing %}
+ <p><strong class="error">Sorry, could not find a badge with that code.</strong></p>
+ {% endif %}
+
+ <input id="name" type="hidden" name="csrf" value="{{ csrf }}">
+ <input tabindex="1" type="text" name="code" placeholder="Enter your claim code" class="input" value="{{ code | default('') }}">
+ <input tabindex="2" type="submit" value="Get your badge!" class="button">
+ </form>
+
+ </div>
+ </div>
+{% endblock %}
View
42 views/public/confirm-claim.html
@@ -0,0 +1,42 @@
+{% extends "public/layout.html" %}
+{% block head %}
+ <link rel="stylesheet" href="/stylesheets/criteria.css">
+{% endblock %}
+{% block body %}
+ <div class="row">
+ <div class="span7">
+ <h2>Mozilla Webmaker Badges</h2>
+ </div>
+ </div>
+ <div class="media card">
+ <img class="img badge" src="{{ badge.relativeUrl('image')}}">
+ <div class="bd">
+ <hgroup>
+ <span class="wordmark">Mozilla</span>
+ <h1>{{ badge.name }}</h1>
+ </hgroup>
+ <dl class="dl-horizontal narrow-dt">
+ <dt>Description:</dt>
+ <dd>
+ {% if badge.criteria.content %}
+ {{ badge.criteria.content | markdown }}
+ {% else %}
+ {{ badge.description | stupidSafe }}
+ {% endif %}
+ </dd>
+ </dl>
+ <hr>
+
+ <form method="post" action="/claim/confirm" class="confirm-form js-confirm-form">
+ <input type="hidden" name="csrf" value="{{ csrf }}">
+ <input type="hidden" name="code" value="{{ code }}">
+ <input tabindex="1" type="email" name="email" placeholder="you@example.org" class="input js-email-input" required>
+ <input tabindex="2" type="submit" value="Claim your badge" class="button js-submit-button">
+ </form>
+ </div>
+ </div>
+
+ <script src="https://beta.openbadges.org/issuer.js"></script>
+ <script src="/js/claim-award.js"></script>
+{% endblock %}
+
View
4 views/public/layout.html
@@ -7,8 +7,9 @@
<link rel="stylesheet" href="/stylesheets/webmaker.css">
<link href="//www.mozilla.org/tabzilla/media/css/tabzilla.css" rel="stylesheet">
<!--[if lt IE 9]>
- <script src="/js/html5shiv.js"></script>
+ <script src="/js/html5shiv.js"></script>
<![endif]-->
+ <script src="/js/jquery.min.js"></script>
{% block head %}{% endblock %}
</head>
<body>
@@ -77,7 +78,6 @@
</div>
</footer>
</div>
- <script src="//www.mozilla.org/media/js/libs/jquery-1.7.1.min.js"></script>
<script src="//www.mozilla.org/tabzilla/media/js/tabzilla.js"></script>
{% block scripts %}{% endblock %}
</body>
Please sign in to comment.
Something went wrong with that request. Please try again.