diff --git a/client/commonFramework/bindings.js b/client/commonFramework/bindings.js
index dc36502f67..0c4d5b6fc4 100644
--- a/client/commonFramework/bindings.js
+++ b/client/commonFramework/bindings.js
@@ -81,9 +81,15 @@ window.common = (function(global) {
data = {
id: common.challengeId,
name: common.challengeName,
- challengeType: common.challengeType
+ challengeType: +common.challengeType
};
- $.post('/completed-challenge/', data)
+ $.ajax({
+ url: '/completed-challenge/',
+ type: 'POST',
+ data: JSON.stringify(data),
+ contentType: 'application/json',
+ dataType: 'json'
+ })
.success(function(res) {
if (!res) {
return;
@@ -92,7 +98,7 @@ window.common = (function(global) {
common.challengeId;
})
.fail(function() {
- window.location.href = '/challenges';
+ window.location.replace(window.location.href);
});
break;
@@ -106,7 +112,13 @@ window.common = (function(global) {
githubLink
};
- $.post('/completed-zipline-or-basejump/', data)
+ $.ajax({
+ url: '/completed-zipline-or-basejump/',
+ type: 'POST',
+ data: JSON.stringify(data),
+ contentType: 'application/json',
+ dataType: 'json'
+ })
.success(function() {
window.location.href = '/challenges/next-challenge?id=' +
common.challengeId;
diff --git a/client/commonFramework/show-completion.js b/client/commonFramework/show-completion.js
index f579f4626d..86d852a660 100644
--- a/client/commonFramework/show-completion.js
+++ b/client/commonFramework/show-completion.js
@@ -57,21 +57,31 @@ window.common = (function(global) {
`;
console.error(err);
}
- const data = {
+ const data = JSON.stringify({
id: common.challengeId,
name: common.challengeName,
completedWith: didCompleteWith,
- challengeType: common.challengeType,
+ challengeType: +common.challengeType,
solution,
timezone
- };
-
- $.post('/completed-challenge/', data, function(res) {
- if (res) {
- window.location =
- '/challenges/next-challenge?id=' + common.challengeId;
- }
});
+
+ $.ajax({
+ url: '/completed-challenge/',
+ type: 'POST',
+ data,
+ contentType: 'application/json',
+ dataType: 'json'
+ })
+ .success(function(res) {
+ if (res) {
+ window.location =
+ '/challenges/next-challenge?id=' + common.challengeId;
+ }
+ })
+ .fail(function() {
+ window.location.replace(window.location.href);
+ });
});
};
diff --git a/client/commonFramework/step-challenge.js b/client/commonFramework/step-challenge.js
index fc0378d883..15ee010f2d 100644
--- a/client/commonFramework/step-challenge.js
+++ b/client/commonFramework/step-challenge.js
@@ -149,38 +149,45 @@ window.common = (function({ $, common = { init: [] }}) {
e.preventDefault();
$('#submit-challenge')
- .attr('disabled', 'true')
- .removeClass('btn-primary')
- .addClass('btn-warning disabled');
+ .attr('disabled', 'true')
+ .removeClass('btn-primary')
+ .addClass('btn-warning disabled');
var $checkmarkContainer = $('#checkmark-container');
$checkmarkContainer.css({ height: $checkmarkContainer.innerHeight() });
$('#challenge-checkmark')
- .addClass('zoomOutUp')
- .delay(1000)
- .queue(function(next) {
- $(this).replaceWith(
- '
' +
- 'submitting...
'
- );
- next();
- });
+ .addClass('zoomOutUp')
+ .delay(1000)
+ .queue(function(next) {
+ $(this).replaceWith(
+ '' +
+ 'submitting...
'
+ );
+ next();
+ });
- $.post(
- '/completed-challenge/', {
+ $.ajax({
+ url: '/completed-challenge/',
+ type: 'POST',
+ data: JSON.stringify({
id: common.challengeId,
name: common.challengeName,
- challengeType: common.challengeType
- },
- function(res) {
+ challengeType: +common.challengeType
+ }),
+ contentType: 'application/json',
+ dataType: 'json'
+ })
+ .success(function(res) {
if (res) {
window.location =
'/challenges/next-challenge?id=' + common.challengeId;
}
- }
- );
+ })
+ .fail(function() {
+ window.location.replace(window.location.href);
+ });
}
common.init.push(function($) {
diff --git a/server/boot/challenge.js b/server/boot/challenge.js
index ca222579ad..33f1418f5b 100644
--- a/server/boot/challenge.js
+++ b/server/boot/challenge.js
@@ -515,8 +515,28 @@ module.exports = function(app) {
}
function completedChallenge(req, res, next) {
+ req.checkBody('id', 'id must be a ObjectId').isMongoId();
+
+ req.checkBody('name', 'name must be at least 3 characters')
+ .isString()
+ .isLength({ min: 3 });
+
+ req.checkBody('challengeType', 'challengeType must be an integer')
+ .isNumber()
+ .isInt();
const type = accepts(req).type('html', 'json', 'text');
+ const errors = req.validationErrors(true);
+
+ if (errors) {
+ if (type === 'json') {
+ return res.status(403).send({ errors });
+ }
+
+ log('errors', errors);
+ return res.sendStatus(403);
+ }
+
const completedDate = Date.now();
const {
id,
@@ -543,6 +563,7 @@ module.exports = function(app) {
const points = alreadyCompleted ?
user.progressTimestamps.length :
user.progressTimestamps.length + 1;
+
return user.update$(updateData)
.doOnNext(({ count }) => log('%s documents updated', count))
.subscribe(
@@ -561,37 +582,34 @@ module.exports = function(app) {
}
function completedZiplineOrBasejump(req, res, next) {
+ const type = accepts(req).type('html', 'json', 'text');
+ req.checkBody('id', 'id must be an ObjectId').isMongoId();
+ req.checkBody('name', 'Name must be at least 3 characters')
+ .isString()
+ .isLength({ min: 3 });
+ req.checkBody('challengeType', 'must be a number')
+ .isNumber()
+ .isInt();
+ req.checkBody('solution', 'solution must be a url').isURL();
+
+ const errors = req.validationErrors(true);
+
+ if (errors) {
+ if (type === 'json') {
+ return res.status(403).send({ errors });
+ }
+ log('errors', errors);
+ return res.sendStatus(403);
+ }
+
const { user, body = {} } = req;
- let completedChallenge;
- // backwards compatibility
- // please remove once in production
- // to allow users to transition to new client code
- if (body.challengeInfo) {
-
- if (!body.challengeInfo.challengeId) {
- req.flash('error', { msg: 'No id returned during save' });
- return res.sendStatus(403);
- }
-
- completedChallenge = {
- id: body.challengeInfo.challengeId,
- name: body.challengeInfo.challengeName || '',
- completedDate: Date.now(),
-
- challengeType: +body.challengeInfo.challengeType === 4 ? 4 : 3,
-
- solution: body.challengeInfo.publicURL,
- githubLink: body.challengeInfo.githubURL
- };
- } else {
- completedChallenge = _.pick(
- body,
- [ 'id', 'name', 'solution', 'githubLink', 'challengeType' ]
- );
- completedChallenge.challengeType = +completedChallenge.challengeType;
- completedChallenge.completedDate = Date.now();
- }
+ const completedChallenge = _.pick(
+ body,
+ [ 'id', 'name', 'solution', 'githubLink', 'challengeType' ]
+ );
+ completedChallenge.challengeType = +completedChallenge.challengeType;
+ completedChallenge.completedDate = Date.now();
if (
!completedChallenge.solution ||
@@ -603,18 +621,30 @@ module.exports = function(app) {
) {
req.flash('errors', {
msg: 'You haven\'t supplied the necessary URLs for us to inspect ' +
- 'your work.'
+ 'your work.'
});
return res.sendStatus(403);
}
const {
+ alreadyCompleted,
updateData
} = buildUserUpdate(req.user, completedChallenge.id, completedChallenge);
- return user.updateTo$(updateData)
- .doOnNext(() => res.status(200).send(true))
+ return user.update$(updateData)
+ .doOnNext(({ count }) => log('%s documents updated', count))
+ .doOnNext(() => {
+ if (type === 'json') {
+ return res.send({
+ alreadyCompleted,
+ points: alreadyCompleted ?
+ user.progressTimestamps.length :
+ user.progressTimestamps.length + 1
+ });
+ }
+ res.status(200).send(true);
+ })
.subscribe(() => {}, next);
}
diff --git a/server/middlewares/validator.js b/server/middlewares/validator.js
index b40765bc89..7405525bef 100644
--- a/server/middlewares/validator.js
+++ b/server/middlewares/validator.js
@@ -1,9 +1,17 @@
import validator from 'express-validator';
-export default validator.bind(validator, {
- customValidators: {
- matchRegex: function matchRegex(param, regex) {
- return regex.test(param);
+export default function() {
+ return validator({
+ customValidators: {
+ matchRegex(param, regex) {
+ return regex.test(param);
+ },
+ isString(value) {
+ return typeof value === 'string';
+ },
+ isNumber(value) {
+ return typeof value === 'number';
+ }
}
- }
-});
+ });
+}