2015-05-16 00:39:43 -04:00
|
|
|
/**
|
|
|
|
* Created by nathanleniz on 5/15/15.
|
|
|
|
* Copyright (c) 2015, Free Code Camp
|
|
|
|
All rights reserved.
|
|
|
|
|
2015-05-21 11:07:40 -07:00
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions are met:
|
2015-05-16 00:39:43 -04:00
|
|
|
|
2015-05-21 11:07:40 -07:00
|
|
|
1. Redistributions of source code must retain the above copyright notice,
|
2015-06-15 14:50:54 -04:00
|
|
|
this list of conditions and the following disclaimer.
|
2015-05-16 00:39:43 -04:00
|
|
|
|
2015-05-21 11:07:40 -07:00
|
|
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
2015-06-15 14:50:54 -04:00
|
|
|
this list of conditions and the following disclaimer in the documentation
|
|
|
|
and/or other materials provided with the distribution.
|
2015-05-16 00:39:43 -04:00
|
|
|
|
2015-05-21 11:07:40 -07:00
|
|
|
3. Neither the name of the copyright holder nor the names of its contributors
|
2015-06-15 14:50:54 -04:00
|
|
|
may be used to endorse or promote products derived from this software
|
|
|
|
without specific prior written permission.
|
2015-05-16 00:39:43 -04:00
|
|
|
|
2015-05-21 11:07:40 -07:00
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
|
|
|
|
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
|
|
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
|
|
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
|
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
|
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
2015-05-16 00:39:43 -04:00
|
|
|
*/
|
|
|
|
|
2015-05-21 11:07:40 -07:00
|
|
|
var R = require('ramda'),
|
2015-06-20 19:52:37 -07:00
|
|
|
Rx = require('rx'),
|
|
|
|
assign = require('object.assign'),
|
2015-06-20 13:35:26 -07:00
|
|
|
debug = require('debug')('freecc:challenges'),
|
2015-06-15 14:50:54 -04:00
|
|
|
utils = require('../utils'),
|
2015-06-20 19:52:37 -07:00
|
|
|
|
|
|
|
// this would be so much cleaner with destructering...
|
2015-06-20 11:43:12 -07:00
|
|
|
saveUser = require('../utils/rx').saveUser,
|
2015-06-20 19:52:37 -07:00
|
|
|
observableQueryFromModel = require('../utils/rx').observableQueryFromModel,
|
|
|
|
|
2015-06-20 11:43:12 -07:00
|
|
|
userMigration = require('../utils/middleware').userMigration,
|
2015-06-22 16:43:31 -07:00
|
|
|
ifNoUserRedirectTo = require('../utils/middleware').ifNoUserRedirectTo,
|
|
|
|
ifNoUserSend = require('../utils/middleware').ifNoUserSend;
|
2015-05-16 00:39:43 -04:00
|
|
|
|
2015-06-03 17:14:45 -07:00
|
|
|
var challengeMapWithNames = utils.getChallengeMapWithNames();
|
|
|
|
var challengeMapWithIds = utils.getChallengeMapWithIds();
|
2015-06-15 18:50:18 -04:00
|
|
|
var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames();
|
2015-06-20 13:35:26 -07:00
|
|
|
var challangesRegex = /^(bonfire|waypoint|zipline|basejump)/i;
|
|
|
|
|
2015-06-22 17:24:55 -07:00
|
|
|
var dasherize = utils.dasherize;
|
2015-06-23 16:45:35 -07:00
|
|
|
var unDasherize = utils.unDasherize;
|
2015-06-22 17:24:55 -07:00
|
|
|
|
|
|
|
var getMDNLinks = utils.getMDNLinks;
|
2015-06-20 13:35:26 -07:00
|
|
|
|
2015-06-20 19:52:37 -07:00
|
|
|
function updateUserProgress(user, challengeId, completedChallenge) {
|
|
|
|
var index = user.uncompletedChallenges.indexOf(challengeId);
|
|
|
|
if (index > -1) {
|
|
|
|
user.progressTimestamps.push(Date.now());
|
|
|
|
user.uncompletedChallenges.splice(index, 1);
|
|
|
|
}
|
|
|
|
user.completedChallenges.push(completedChallenge);
|
|
|
|
return user;
|
|
|
|
}
|
|
|
|
|
2015-06-02 19:02:54 -07:00
|
|
|
module.exports = function(app) {
|
2015-06-03 16:31:42 -07:00
|
|
|
var router = app.loopback.Router();
|
2015-06-02 19:02:54 -07:00
|
|
|
var Challenge = app.models.Challenge;
|
|
|
|
var User = app.models.User;
|
2015-06-22 16:43:31 -07:00
|
|
|
var redirectNonUser =
|
|
|
|
ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works');
|
|
|
|
var send200toNonUser = ifNoUserSend(true);
|
|
|
|
|
|
|
|
router.post(
|
|
|
|
'/completed-challenge/',
|
|
|
|
send200toNonUser,
|
|
|
|
completedChallenge
|
|
|
|
);
|
|
|
|
router.post(
|
|
|
|
'/completed-zipline-or-basejump',
|
|
|
|
send200toNonUser,
|
|
|
|
completedZiplineOrBasejump
|
|
|
|
);
|
|
|
|
router.post(
|
|
|
|
'/completed-bonfire',
|
|
|
|
send200toNonUser,
|
|
|
|
completedBonfire
|
|
|
|
);
|
2015-06-02 19:02:54 -07:00
|
|
|
|
2015-06-19 14:49:10 -07:00
|
|
|
// the follow routes are covered by userMigration
|
|
|
|
router.use(userMigration);
|
|
|
|
router.get('/map', challengeMap);
|
2015-06-20 11:43:12 -07:00
|
|
|
router.get(
|
|
|
|
'/challenges/next-challenge',
|
2015-06-22 16:43:31 -07:00
|
|
|
redirectNonUser,
|
2015-06-20 11:43:12 -07:00
|
|
|
returnNextChallenge
|
|
|
|
);
|
|
|
|
|
|
|
|
router.get('/challenges/:challengeName', returnIndividualChallenge);
|
|
|
|
|
|
|
|
router.get(
|
|
|
|
'/challenges/',
|
2015-06-22 16:43:31 -07:00
|
|
|
redirectNonUser,
|
2015-06-20 11:43:12 -07:00
|
|
|
returnCurrentChallenge
|
|
|
|
);
|
2015-06-19 14:49:10 -07:00
|
|
|
|
2015-06-03 16:31:42 -07:00
|
|
|
app.use(router);
|
|
|
|
|
2015-06-02 19:02:54 -07:00
|
|
|
function returnNextChallenge(req, res, next) {
|
2015-07-13 00:25:01 -07:00
|
|
|
var completed = req.user.completedChallenges.map(function(elem) {
|
2015-06-04 13:20:42 -07:00
|
|
|
return elem.id;
|
2015-05-16 00:39:43 -04:00
|
|
|
});
|
2015-05-20 00:38:25 -04:00
|
|
|
|
2015-06-03 17:14:45 -07:00
|
|
|
req.user.uncompletedChallenges = utils.allChallengeIds()
|
2015-06-20 11:43:12 -07:00
|
|
|
.filter(function(elem) {
|
2015-06-02 19:02:54 -07:00
|
|
|
if (completed.indexOf(elem) === -1) {
|
|
|
|
return elem;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// find the user's current challenge and block
|
|
|
|
// look in that block and find the index of their current challenge
|
|
|
|
// if index + 1 < block.challenges.length
|
|
|
|
// serve index + 1 challenge
|
|
|
|
// otherwise increment block key and serve the first challenge in that block
|
|
|
|
// unless the next block is undefined, which means no next block
|
|
|
|
var nextChallengeName;
|
|
|
|
|
|
|
|
var challengeId = String(req.user.currentChallenge.challengeId);
|
|
|
|
var challengeBlock = req.user.currentChallenge.challengeBlock;
|
|
|
|
var indexOfChallenge = challengeMapWithIds[challengeBlock]
|
|
|
|
.indexOf(challengeId);
|
|
|
|
|
|
|
|
if (indexOfChallenge + 1
|
|
|
|
< challengeMapWithIds[challengeBlock].length) {
|
|
|
|
nextChallengeName =
|
2015-06-15 18:50:18 -04:00
|
|
|
challengeMapWithDashedNames[challengeBlock][++indexOfChallenge];
|
2015-06-02 19:02:54 -07:00
|
|
|
} else if (typeof challengeMapWithIds[++challengeBlock] !== 'undefined') {
|
2015-06-15 18:50:18 -04:00
|
|
|
nextChallengeName = R.head(challengeMapWithDashedNames[challengeBlock]);
|
2015-06-02 19:02:54 -07:00
|
|
|
} else {
|
|
|
|
req.flash('errors', {
|
|
|
|
msg: 'It looks like you have finished all of our challenges.' +
|
|
|
|
' Great job! Now on to helping nonprofits!'
|
|
|
|
});
|
2015-06-15 18:50:18 -04:00
|
|
|
nextChallengeName = R.head(challengeMapWithDashedNames[0].challenges);
|
2015-05-21 00:34:51 -04:00
|
|
|
}
|
2015-05-19 22:31:01 -04:00
|
|
|
|
2015-06-20 11:43:12 -07:00
|
|
|
saveUser(req.user)
|
|
|
|
.subscribe(
|
|
|
|
function() {},
|
|
|
|
next,
|
|
|
|
function() {
|
2015-06-20 13:35:26 -07:00
|
|
|
res.redirect('/challenges/' + nextChallengeName);
|
2015-06-20 11:43:12 -07:00
|
|
|
}
|
|
|
|
);
|
2015-05-16 00:39:43 -04:00
|
|
|
}
|
|
|
|
|
2015-06-02 19:02:54 -07:00
|
|
|
function returnCurrentChallenge(req, res, next) {
|
2015-07-13 00:25:01 -07:00
|
|
|
var completed = req.user.completedChallenges.map(function(elem) {
|
2015-06-04 13:20:42 -07:00
|
|
|
return elem.id;
|
2015-06-02 19:02:54 -07:00
|
|
|
});
|
2015-05-16 00:39:43 -04:00
|
|
|
|
2015-06-03 17:14:45 -07:00
|
|
|
req.user.uncompletedChallenges = utils.allChallengeIds()
|
2015-07-13 00:25:01 -07:00
|
|
|
.filter(function(elem) {
|
2015-06-02 19:02:54 -07:00
|
|
|
if (completed.indexOf(elem) === -1) {
|
|
|
|
return elem;
|
|
|
|
}
|
|
|
|
});
|
2015-06-20 19:52:37 -07:00
|
|
|
|
2015-06-02 19:02:54 -07:00
|
|
|
if (!req.user.currentChallenge) {
|
|
|
|
req.user.currentChallenge = {};
|
|
|
|
req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0];
|
|
|
|
req.user.currentChallenge.challengeName = challengeMapWithNames['0'][0];
|
|
|
|
req.user.currentChallenge.challengeBlock = '0';
|
2015-06-15 18:50:18 -04:00
|
|
|
req.user.currentChallenge.dashedName =
|
|
|
|
challengeMapWithDashedNames['0'][0];
|
2015-06-02 19:02:54 -07:00
|
|
|
}
|
2015-06-15 18:50:18 -04:00
|
|
|
|
2015-06-15 18:57:45 -04:00
|
|
|
var nameString = req.user.currentChallenge.dashedName;
|
2015-06-15 18:50:18 -04:00
|
|
|
|
2015-06-20 11:43:12 -07:00
|
|
|
saveUser(req.user)
|
|
|
|
.subscribe(
|
|
|
|
function() {},
|
|
|
|
next,
|
|
|
|
function() {
|
2015-06-20 13:35:26 -07:00
|
|
|
res.redirect('/challenges/' + nameString);
|
2015-06-20 11:43:12 -07:00
|
|
|
}
|
|
|
|
);
|
2015-06-02 19:02:54 -07:00
|
|
|
}
|
2015-05-16 00:39:43 -04:00
|
|
|
|
2015-06-02 19:02:54 -07:00
|
|
|
function returnIndividualChallenge(req, res, next) {
|
2015-06-20 13:35:26 -07:00
|
|
|
var origChallengeName = req.params.challengeName;
|
|
|
|
var unDashedName = unDasherize(origChallengeName);
|
|
|
|
|
|
|
|
var challengeName = challangesRegex.test(unDashedName) ?
|
|
|
|
// remove first word if matches
|
|
|
|
unDashedName.split(' ').slice(1).join(' ') :
|
|
|
|
unDashedName;
|
2015-06-02 19:02:54 -07:00
|
|
|
|
2015-06-20 13:35:26 -07:00
|
|
|
debug('looking for ', challengeName);
|
2015-06-15 14:50:54 -04:00
|
|
|
Challenge.findOne(
|
2015-06-20 13:35:26 -07:00
|
|
|
{ where: { name: { like: challengeName, options: 'i' } } },
|
2015-06-15 14:50:54 -04:00
|
|
|
function(err, challenge) {
|
2015-06-02 19:02:54 -07:00
|
|
|
if (err) { return next(err); }
|
|
|
|
|
|
|
|
// Handle not found
|
2015-06-15 18:50:18 -04:00
|
|
|
if (!challenge) {
|
2015-06-20 13:35:26 -07:00
|
|
|
debug('did not find challenge for ' + origChallengeName);
|
2015-06-02 19:02:54 -07:00
|
|
|
req.flash('errors', {
|
2015-06-20 11:43:12 -07:00
|
|
|
msg:
|
|
|
|
'404: We couldn\'t find a challenge with the name `' +
|
2015-06-20 13:35:26 -07:00
|
|
|
origChallengeName +
|
2015-06-20 11:43:12 -07:00
|
|
|
'` Please double check the name.'
|
2015-05-16 00:39:43 -04:00
|
|
|
});
|
2015-06-02 19:02:54 -07:00
|
|
|
return res.redirect('/challenges');
|
|
|
|
}
|
|
|
|
// Redirect to full name if the user only entered a partial
|
2015-06-20 13:35:26 -07:00
|
|
|
if (dasherize(challenge.name) !== origChallengeName) {
|
|
|
|
debug('redirecting to fullname');
|
|
|
|
return res.redirect('/challenges/' + dasherize(challenge.name));
|
|
|
|
}
|
|
|
|
|
2015-06-15 14:50:54 -04:00
|
|
|
if (req.user) {
|
2015-06-02 19:02:54 -07:00
|
|
|
req.user.currentChallenge = {
|
2015-06-04 13:20:42 -07:00
|
|
|
challengeId: challenge.id,
|
2015-06-15 18:50:18 -04:00
|
|
|
challengeName: challenge.name,
|
|
|
|
dashedName: challenge.dashedName,
|
2015-06-20 19:52:37 -07:00
|
|
|
challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds)
|
2015-07-13 00:25:01 -07:00
|
|
|
.map(function(key) {
|
2015-06-02 19:02:54 -07:00
|
|
|
return challengeMapWithIds[key]
|
2015-07-13 00:25:01 -07:00
|
|
|
.filter(function(elem) {
|
2015-06-20 19:52:37 -07:00
|
|
|
return elem === ('' + challenge.id);
|
2015-06-20 11:43:12 -07:00
|
|
|
})
|
2015-07-13 00:25:01 -07:00
|
|
|
.map(function() {
|
2015-06-02 19:02:54 -07:00
|
|
|
return key;
|
|
|
|
});
|
|
|
|
})
|
|
|
|
))
|
|
|
|
};
|
2015-05-16 00:39:43 -04:00
|
|
|
}
|
2015-06-02 19:02:54 -07:00
|
|
|
|
2015-06-20 11:43:12 -07:00
|
|
|
var commonLocals = {
|
|
|
|
title: challenge.name,
|
2015-06-20 13:35:26 -07:00
|
|
|
dashedName: origChallengeName,
|
2015-06-20 11:43:12 -07:00
|
|
|
name: challenge.name,
|
2015-07-12 22:51:06 -07:00
|
|
|
details: challenge.description,
|
2015-06-20 11:43:12 -07:00
|
|
|
tests: challenge.tests,
|
|
|
|
challengeSeed: challenge.challengeSeed,
|
|
|
|
verb: utils.randomVerb(),
|
|
|
|
phrase: utils.randomPhrase(),
|
|
|
|
compliment: utils.randomCompliment(),
|
|
|
|
challengeId: challenge.id,
|
|
|
|
challengeType: challenge.challengeType,
|
|
|
|
// video challenges
|
|
|
|
video: challenge.challengeSeed[0],
|
|
|
|
// bonfires specific
|
|
|
|
difficulty: Math.floor(+challenge.difficulty),
|
|
|
|
bonfires: challenge,
|
|
|
|
MDNkeys: challenge.MDNlinks,
|
|
|
|
MDNlinks: getMDNLinks(challenge.MDNlinks),
|
|
|
|
// htmls specific
|
|
|
|
environment: utils.whichEnvironment()
|
2015-06-02 19:02:54 -07:00
|
|
|
};
|
2015-06-20 11:43:12 -07:00
|
|
|
|
2015-07-13 00:25:01 -07:00
|
|
|
// TODO Berkeley
|
2015-06-20 11:43:12 -07:00
|
|
|
var challengeView = {
|
|
|
|
0: 'coursewares/showHTML',
|
|
|
|
1: 'coursewares/showJS',
|
|
|
|
2: 'coursewares/showVideo',
|
|
|
|
3: 'coursewares/showZiplineOrBasejump',
|
|
|
|
4: 'coursewares/showZiplineOrBasejump',
|
2015-07-13 00:25:01 -07:00
|
|
|
5: 'coursewares/showBonfire'
|
2015-06-20 11:43:12 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
saveUser(req.user)
|
|
|
|
.subscribe(
|
|
|
|
function() {},
|
|
|
|
next,
|
|
|
|
function() {
|
|
|
|
var view = challengeView[challenge.challengeType];
|
|
|
|
res.render(view, commonLocals);
|
2015-06-02 19:02:54 -07:00
|
|
|
}
|
2015-06-20 11:43:12 -07:00
|
|
|
);
|
2015-06-02 19:02:54 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function completedBonfire(req, res, next) {
|
2015-06-20 19:52:37 -07:00
|
|
|
debug('compltedBonfire');
|
|
|
|
var completedWith = req.body.challengeInfo.completedWith || false;
|
2015-06-02 19:02:54 -07:00
|
|
|
var challengeId = req.body.challengeInfo.challengeId;
|
2015-05-16 00:39:43 -04:00
|
|
|
|
2015-06-20 19:52:37 -07:00
|
|
|
var challengeData = {
|
|
|
|
id: challengeId,
|
|
|
|
name: req.body.challengeInfo.challengeName,
|
|
|
|
completedDate: Math.round(+new Date()),
|
|
|
|
solution: req.body.challengeInfo.solution,
|
|
|
|
challengeType: 5
|
|
|
|
};
|
|
|
|
|
|
|
|
observableQueryFromModel(
|
|
|
|
User,
|
|
|
|
'findOne',
|
|
|
|
{ where: { username: ('' + completedWith).toLowerCase() } }
|
|
|
|
)
|
|
|
|
.doOnNext(function(pairedWith) {
|
|
|
|
debug('paired with ', pairedWith);
|
2015-05-25 17:27:27 -04:00
|
|
|
if (pairedWith) {
|
2015-06-20 19:52:37 -07:00
|
|
|
updateUserProgress(
|
|
|
|
pairedWith,
|
|
|
|
challengeId,
|
|
|
|
assign({ completedWith: req.user.id }, challengeData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.withLatestFrom(
|
|
|
|
Rx.Observable.just(req.user),
|
|
|
|
function(pairedWith, user) {
|
|
|
|
return {
|
|
|
|
user: user,
|
|
|
|
pairedWith: pairedWith
|
|
|
|
};
|
2015-05-25 17:27:27 -04:00
|
|
|
}
|
2015-06-20 19:52:37 -07:00
|
|
|
)
|
|
|
|
// side effects should always be done in do's and taps
|
|
|
|
.doOnNext(function(dats) {
|
|
|
|
updateUserProgress(
|
|
|
|
dats.user,
|
|
|
|
challengeId,
|
|
|
|
dats.pairedWith ?
|
|
|
|
// paired programmer found and adding to data
|
|
|
|
assign({ completedWith: dats.pairedWith.id }, challengeData) :
|
|
|
|
// user said they paired, but pair wasn't found
|
|
|
|
challengeData
|
|
|
|
);
|
|
|
|
})
|
|
|
|
// not iterate users
|
|
|
|
.flatMap(function(dats) {
|
|
|
|
debug('flatmap');
|
|
|
|
return Rx.Observable.from([dats.user, dats.pairedWith]);
|
|
|
|
})
|
|
|
|
// save user
|
|
|
|
.flatMap(function(user) {
|
|
|
|
// save user will do nothing if user is falsey
|
|
|
|
return saveUser(user);
|
|
|
|
})
|
|
|
|
.subscribe(
|
|
|
|
function(user) {
|
|
|
|
debug('onNext');
|
|
|
|
if (user) {
|
|
|
|
debug('user %s saved', user.username);
|
2015-05-25 17:27:27 -04:00
|
|
|
}
|
2015-06-20 19:52:37 -07:00
|
|
|
},
|
|
|
|
next,
|
|
|
|
function() {
|
|
|
|
debug('completed');
|
|
|
|
return res.status(200).send(true);
|
|
|
|
}
|
|
|
|
);
|
2015-06-02 19:02:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function completedChallenge(req, res, next) {
|
|
|
|
|
2015-07-22 23:10:57 -07:00
|
|
|
const completedDate = Math.round(+new Date());
|
|
|
|
const { id, name } = req.body;
|
|
|
|
const { challengeId, challengeName } = req.body.challengeInfo || {};
|
2015-06-02 19:02:54 -07:00
|
|
|
|
2015-07-22 23:10:57 -07:00
|
|
|
debug('saving challenge progress');
|
2015-06-20 19:52:37 -07:00
|
|
|
updateUserProgress(
|
|
|
|
req.user,
|
2015-07-22 23:10:57 -07:00
|
|
|
id || challengeId,
|
2015-06-20 19:52:37 -07:00
|
|
|
{
|
2015-07-22 23:10:57 -07:00
|
|
|
id: id || challengeId,
|
2015-06-20 19:52:37 -07:00
|
|
|
completedDate: completedDate,
|
2015-07-22 23:10:57 -07:00
|
|
|
name: name || challengeName,
|
2015-06-20 19:52:37 -07:00
|
|
|
solution: null,
|
|
|
|
githubLink: null,
|
|
|
|
verified: true
|
2015-05-20 21:50:31 -04:00
|
|
|
}
|
2015-06-20 19:52:37 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
saveUser(req.user)
|
|
|
|
.subscribe(
|
|
|
|
function() { },
|
|
|
|
next,
|
|
|
|
function() {
|
|
|
|
res.sendStatus(200);
|
|
|
|
}
|
|
|
|
);
|
2015-05-20 21:50:31 -04:00
|
|
|
}
|
2015-05-19 22:31:01 -04:00
|
|
|
|
2015-06-02 19:02:54 -07:00
|
|
|
function completedZiplineOrBasejump(req, res, next) {
|
|
|
|
|
2015-06-20 19:52:37 -07:00
|
|
|
var completedWith = req.body.challengeInfo.completedWith || false;
|
|
|
|
var completedDate = Math.round(+new Date());
|
2015-06-02 19:02:54 -07:00
|
|
|
var challengeId = req.body.challengeInfo.challengeId;
|
|
|
|
var solutionLink = req.body.challengeInfo.publicURL;
|
2015-06-20 19:52:37 -07:00
|
|
|
|
|
|
|
var githubLink = req.body.challengeInfo.challengeType === '4' ?
|
|
|
|
req.body.challengeInfo.githubURL :
|
|
|
|
true;
|
|
|
|
|
2015-06-02 19:02:54 -07:00
|
|
|
var challengeType = req.body.challengeInfo.challengeType === '4' ?
|
2015-06-20 19:52:37 -07:00
|
|
|
4 :
|
|
|
|
3;
|
|
|
|
|
2015-06-02 19:02:54 -07:00
|
|
|
if (!solutionLink || !githubLink) {
|
|
|
|
req.flash('errors', {
|
|
|
|
msg: 'You haven\'t supplied the necessary URLs for us to inspect ' +
|
|
|
|
'your work.'
|
|
|
|
});
|
|
|
|
return res.sendStatus(403);
|
2015-05-19 22:31:01 -04:00
|
|
|
}
|
2015-05-21 00:17:44 -07:00
|
|
|
|
2015-06-20 19:52:37 -07:00
|
|
|
var challengeData = {
|
|
|
|
id: challengeId,
|
|
|
|
name: req.body.challengeInfo.challengeName,
|
|
|
|
completedDate: completedDate,
|
|
|
|
solution: solutionLink,
|
|
|
|
githubLink: githubLink,
|
|
|
|
challengeType: challengeType,
|
|
|
|
verified: false
|
|
|
|
};
|
|
|
|
|
|
|
|
observableQueryFromModel(
|
|
|
|
User,
|
|
|
|
'findOne',
|
|
|
|
{ where: { username: completedWith.toLowerCase() } }
|
|
|
|
)
|
|
|
|
.doOnNext(function(pairedWith) {
|
|
|
|
if (pairedWith) {
|
|
|
|
updateUserProgress(
|
|
|
|
pairedWith,
|
|
|
|
challengeId,
|
|
|
|
assign({ completedWith: req.user.id }, challengeData)
|
|
|
|
);
|
2015-05-19 22:31:01 -04:00
|
|
|
}
|
2015-06-20 19:52:37 -07:00
|
|
|
})
|
|
|
|
.withLatestFrom(Rx.Observable.just(req.user), function(user, pairedWith) {
|
|
|
|
return {
|
|
|
|
user: user,
|
|
|
|
pairedWith: pairedWith
|
|
|
|
};
|
|
|
|
})
|
|
|
|
.doOnNext(function(dats) {
|
|
|
|
updateUserProgress(
|
|
|
|
dats.user,
|
|
|
|
challengeId,
|
|
|
|
dats.pairedWith ?
|
|
|
|
assign({ completedWith: dats.pairedWith.id }, challengeData) :
|
|
|
|
challengeData
|
|
|
|
);
|
|
|
|
})
|
|
|
|
.flatMap(function(dats) {
|
|
|
|
return Rx.Observable.from([dats.user, dats.pairedWith]);
|
|
|
|
})
|
|
|
|
// save users
|
|
|
|
.flatMap(function(user) {
|
|
|
|
// save user will do nothing if user is falsey
|
|
|
|
return saveUser(user);
|
|
|
|
})
|
|
|
|
.subscribe(
|
|
|
|
function(user) {
|
|
|
|
if (user) {
|
|
|
|
debug('user %s saved', user.username);
|
2015-05-19 22:31:01 -04:00
|
|
|
}
|
2015-06-20 19:52:37 -07:00
|
|
|
},
|
|
|
|
next,
|
|
|
|
function() {
|
|
|
|
return res.status(200).send(true);
|
2015-06-02 19:02:54 -07:00
|
|
|
}
|
2015-06-20 19:52:37 -07:00
|
|
|
);
|
2015-05-19 22:31:01 -04:00
|
|
|
}
|
2015-06-19 14:49:10 -07:00
|
|
|
|
|
|
|
function challengeMap(req, res, next) {
|
|
|
|
var completedList = [];
|
|
|
|
|
|
|
|
if (req.user) {
|
|
|
|
completedList = req.user.completedChallenges;
|
|
|
|
}
|
|
|
|
|
|
|
|
var noDuplicatedChallenges = R.uniq(completedList);
|
|
|
|
|
|
|
|
var completedChallengeList = noDuplicatedChallenges
|
|
|
|
.map(function(challenge) {
|
|
|
|
// backwards compatibility
|
|
|
|
return (challenge.id || challenge._id);
|
|
|
|
});
|
|
|
|
var challengeList = utils.
|
|
|
|
getChallengeMapForDisplay(completedChallengeList);
|
|
|
|
|
|
|
|
Object.keys(challengeList).forEach(function(key) {
|
|
|
|
challengeList[key].completed = challengeList[key]
|
|
|
|
.challenges.filter(function(elem) {
|
|
|
|
// backwards compatibility hack
|
|
|
|
return completedChallengeList.indexOf(elem.id || elem._id) > -1;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
function numberWithCommas(x) {
|
|
|
|
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
|
|
}
|
|
|
|
|
|
|
|
var date1 = new Date('10/15/2014');
|
|
|
|
var date2 = new Date();
|
|
|
|
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
|
|
|
|
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
|
|
|
|
|
|
|
|
User.count(function(err, camperCount) {
|
|
|
|
if (err) { return next(err); }
|
|
|
|
|
|
|
|
res.render('challengeMap/show', {
|
|
|
|
daysRunning: daysRunning,
|
|
|
|
camperCount: numberWithCommas(camperCount),
|
|
|
|
title: "A map of all Free Code Camp's Challenges",
|
|
|
|
challengeList: challengeList,
|
|
|
|
completedChallengeList: completedChallengeList
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2015-06-02 19:02:54 -07:00
|
|
|
};
|