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'; + } } - } -}); + }); +}