Add validation to challenge completion
Change ajax requests to send and accept JSON to preserve data types. Fix typos
This commit is contained in:
@ -81,9 +81,15 @@ window.common = (function(global) {
|
|||||||
data = {
|
data = {
|
||||||
id: common.challengeId,
|
id: common.challengeId,
|
||||||
name: common.challengeName,
|
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) {
|
.success(function(res) {
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return;
|
return;
|
||||||
@ -92,7 +98,7 @@ window.common = (function(global) {
|
|||||||
common.challengeId;
|
common.challengeId;
|
||||||
})
|
})
|
||||||
.fail(function() {
|
.fail(function() {
|
||||||
window.location.href = '/challenges';
|
window.location.replace(window.location.href);
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -106,7 +112,13 @@ window.common = (function(global) {
|
|||||||
githubLink
|
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() {
|
.success(function() {
|
||||||
window.location.href = '/challenges/next-challenge?id=' +
|
window.location.href = '/challenges/next-challenge?id=' +
|
||||||
common.challengeId;
|
common.challengeId;
|
||||||
|
@ -57,21 +57,31 @@ window.common = (function(global) {
|
|||||||
`;
|
`;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
const data = {
|
const data = JSON.stringify({
|
||||||
id: common.challengeId,
|
id: common.challengeId,
|
||||||
name: common.challengeName,
|
name: common.challengeName,
|
||||||
completedWith: didCompleteWith,
|
completedWith: didCompleteWith,
|
||||||
challengeType: common.challengeType,
|
challengeType: +common.challengeType,
|
||||||
solution,
|
solution,
|
||||||
timezone
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -149,38 +149,45 @@ window.common = (function({ $, common = { init: [] }}) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
$('#submit-challenge')
|
$('#submit-challenge')
|
||||||
.attr('disabled', 'true')
|
.attr('disabled', 'true')
|
||||||
.removeClass('btn-primary')
|
.removeClass('btn-primary')
|
||||||
.addClass('btn-warning disabled');
|
.addClass('btn-warning disabled');
|
||||||
|
|
||||||
var $checkmarkContainer = $('#checkmark-container');
|
var $checkmarkContainer = $('#checkmark-container');
|
||||||
$checkmarkContainer.css({ height: $checkmarkContainer.innerHeight() });
|
$checkmarkContainer.css({ height: $checkmarkContainer.innerHeight() });
|
||||||
|
|
||||||
$('#challenge-checkmark')
|
$('#challenge-checkmark')
|
||||||
.addClass('zoomOutUp')
|
.addClass('zoomOutUp')
|
||||||
.delay(1000)
|
.delay(1000)
|
||||||
.queue(function(next) {
|
.queue(function(next) {
|
||||||
$(this).replaceWith(
|
$(this).replaceWith(
|
||||||
'<div id="challenge-spinner" ' +
|
'<div id="challenge-spinner" ' +
|
||||||
'class="animated zoomInUp inner-circles-loader">' +
|
'class="animated zoomInUp inner-circles-loader">' +
|
||||||
'submitting...</div>'
|
'submitting...</div>'
|
||||||
);
|
);
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
$.post(
|
$.ajax({
|
||||||
'/completed-challenge/', {
|
url: '/completed-challenge/',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
id: common.challengeId,
|
id: common.challengeId,
|
||||||
name: common.challengeName,
|
name: common.challengeName,
|
||||||
challengeType: common.challengeType
|
challengeType: +common.challengeType
|
||||||
},
|
}),
|
||||||
function(res) {
|
contentType: 'application/json',
|
||||||
|
dataType: 'json'
|
||||||
|
})
|
||||||
|
.success(function(res) {
|
||||||
if (res) {
|
if (res) {
|
||||||
window.location =
|
window.location =
|
||||||
'/challenges/next-challenge?id=' + common.challengeId;
|
'/challenges/next-challenge?id=' + common.challengeId;
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
);
|
.fail(function() {
|
||||||
|
window.location.replace(window.location.href);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
common.init.push(function($) {
|
common.init.push(function($) {
|
||||||
|
@ -515,8 +515,28 @@ module.exports = function(app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function completedChallenge(req, res, next) {
|
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 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 completedDate = Date.now();
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@ -543,6 +563,7 @@ module.exports = function(app) {
|
|||||||
const points = alreadyCompleted ?
|
const points = alreadyCompleted ?
|
||||||
user.progressTimestamps.length :
|
user.progressTimestamps.length :
|
||||||
user.progressTimestamps.length + 1;
|
user.progressTimestamps.length + 1;
|
||||||
|
|
||||||
return user.update$(updateData)
|
return user.update$(updateData)
|
||||||
.doOnNext(({ count }) => log('%s documents updated', count))
|
.doOnNext(({ count }) => log('%s documents updated', count))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
@ -561,37 +582,34 @@ module.exports = function(app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function completedZiplineOrBasejump(req, res, next) {
|
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;
|
const { user, body = {} } = req;
|
||||||
|
|
||||||
let completedChallenge;
|
const completedChallenge = _.pick(
|
||||||
// backwards compatibility
|
body,
|
||||||
// please remove once in production
|
[ 'id', 'name', 'solution', 'githubLink', 'challengeType' ]
|
||||||
// to allow users to transition to new client code
|
);
|
||||||
if (body.challengeInfo) {
|
completedChallenge.challengeType = +completedChallenge.challengeType;
|
||||||
|
completedChallenge.completedDate = Date.now();
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!completedChallenge.solution ||
|
!completedChallenge.solution ||
|
||||||
@ -603,18 +621,30 @@ module.exports = function(app) {
|
|||||||
) {
|
) {
|
||||||
req.flash('errors', {
|
req.flash('errors', {
|
||||||
msg: 'You haven\'t supplied the necessary URLs for us to inspect ' +
|
msg: 'You haven\'t supplied the necessary URLs for us to inspect ' +
|
||||||
'your work.'
|
'your work.'
|
||||||
});
|
});
|
||||||
return res.sendStatus(403);
|
return res.sendStatus(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
alreadyCompleted,
|
||||||
updateData
|
updateData
|
||||||
} = buildUserUpdate(req.user, completedChallenge.id, completedChallenge);
|
} = buildUserUpdate(req.user, completedChallenge.id, completedChallenge);
|
||||||
|
|
||||||
return user.updateTo$(updateData)
|
return user.update$(updateData)
|
||||||
.doOnNext(() => res.status(200).send(true))
|
.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);
|
.subscribe(() => {}, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
import validator from 'express-validator';
|
import validator from 'express-validator';
|
||||||
|
|
||||||
export default validator.bind(validator, {
|
export default function() {
|
||||||
customValidators: {
|
return validator({
|
||||||
matchRegex: function matchRegex(param, regex) {
|
customValidators: {
|
||||||
return regex.test(param);
|
matchRegex(param, regex) {
|
||||||
|
return regex.test(param);
|
||||||
|
},
|
||||||
|
isString(value) {
|
||||||
|
return typeof value === 'string';
|
||||||
|
},
|
||||||
|
isNumber(value) {
|
||||||
|
return typeof value === 'number';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
Reference in New Issue
Block a user