Merge pull request #256 from terakilobyte/ux-improvements

User will now get a point for news/stories interactions
This commit is contained in:
Quincy Larson
2015-03-28 06:12:18 -07:00
8 changed files with 1131 additions and 1025 deletions

3
app.js
View File

@ -398,6 +398,8 @@ app.post(
app.all('/account', passportConf.isAuthenticated); app.all('/account', passportConf.isAuthenticated);
app.get('/account/api', userController.getAccountAngular); app.get('/account/api', userController.getAccountAngular);
app.get('/user/streak', userController.getStreak);
/** /**
* API routes * API routes
*/ */
@ -449,7 +451,6 @@ app.post('/account/password', userController.postUpdatePassword);
app.post('/account/delete', userController.postDeleteAccount); app.post('/account/delete', userController.postDeleteAccount);
app.get('/account/unlink/:provider', userController.getOauthUnlink); app.get('/account/unlink/:provider', userController.getOauthUnlink);
app.get('/sitemap.xml', resourcesController.sitemap); app.get('/sitemap.xml', resourcesController.sitemap);
/** /**
* OAuth sign-in routes. * OAuth sign-in routes.
*/ */

View File

@ -236,6 +236,7 @@ exports.completedBonfire = function (req, res) {
} else { } else {
var index = req.user.uncompletedBonfires.indexOf(bonfireHash); var index = req.user.uncompletedBonfires.indexOf(bonfireHash);
if (index > -1) { if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0); req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedBonfires.splice(index, 1) req.user.uncompletedBonfires.splice(index, 1)
} }
@ -243,6 +244,7 @@ exports.completedBonfire = function (req, res) {
index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); index = pairedWith.uncompletedBonfires.indexOf(bonfireHash);
if (index > -1) { if (index > -1) {
pairedWith.progressTimestamps.push(Date.now() || 0); pairedWith.progressTimestamps.push(Date.now() || 0);
pairedWith.uncompletedBonfires.splice(index, 1); pairedWith.uncompletedBonfires.splice(index, 1);
@ -285,6 +287,7 @@ exports.completedBonfire = function (req, res) {
var index = req.user.uncompletedBonfires.indexOf(bonfireHash); var index = req.user.uncompletedBonfires.indexOf(bonfireHash);
if (index > -1) { if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0); req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedBonfires.splice(index, 1) req.user.uncompletedBonfires.splice(index, 1)
} }

View File

@ -1,271 +1,263 @@
var _ = require('lodash'), var _ = require('lodash'),
debug = require('debug')('freecc:cntr:courseware'), debug = require('debug')('freecc:cntr:courseware'),
Courseware = require('./../models/Courseware'), Courseware = require('./../models/Courseware'),
User = require('./../models/User'), User = require('./../models/User'),
resources = require('./resources'), resources = require('./resources'),
R = require('ramda'); R = require('ramda'),
moment = require('moment');
/** /**
* Courseware controller * Courseware controller
*/ */
exports.showAllCoursewares = function(req, res) { exports.showAllCoursewares = function(req, res) {
var completedCoursewares = req.user.completedCoursewares.map(function(elem) { var completedCoursewares = req.user.completedCoursewares.map(function(elem) {
return elem._id; return elem._id;
}); });
var noDuplicatedCoursewares = R.uniq(completedCoursewares); var noDuplicatedCoursewares = R.uniq(completedCoursewares);
var data = {}; var data = {};
data.coursewareList = resources.allCoursewareNames(); data.coursewareList = resources.allCoursewareNames();
data.completedList = noDuplicatedCoursewares; data.completedList = noDuplicatedCoursewares;
res.send(data); res.send(data);
}; };
exports.returnNextCourseware = function(req, res) { exports.returnNextCourseware = function(req, res) {
if (!req.user) { if (!req.user) {
return res.redirect('../challenges/learn-how-free-code-camp-works'); return res.redirect('../challenges/learn-how-free-code-camp-works');
}
var completed = req.user.completedCoursewares.map(function (elem) {
return elem._id;
});
req.user.uncompletedCoursewares = resources.allCoursewareIds().filter(function (elem) {
if (completed.indexOf(elem) === -1) {
return elem;
} }
var completed = req.user.completedCoursewares.map(function (elem) { });
return elem._id; req.user.save();
});
req.user.uncompletedCoursewares = resources.allCoursewareIds().filter(function (elem) { var uncompletedCoursewares = req.user.uncompletedCoursewares.shift();
if (completed.indexOf(elem) === -1) {
return elem;
}
});
req.user.save();
var uncompletedCoursewares = req.user.uncompletedCoursewares.shift();
var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares}); var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares});
displayedCoursewares.exec(function(err, courseware) { displayedCoursewares.exec(function(err, courseware) {
if (err) { if (err) {
next(err); next(err);
} }
courseware = courseware.pop(); courseware = courseware.pop();
if (courseware === undefined) { if (courseware === undefined) {
req.flash('errors', { req.flash('errors', {
msg: "It looks like you've completed all the courses we have available. Good job!" msg: "It looks like you've completed all the courses we have available. Good job!"
}); });
return res.redirect('../challenges/learn-how-free-code-camp-works'); return res.redirect('../challenges/learn-how-free-code-camp-works');
} }
nameString = courseware.name.toLowerCase().replace(/\s/g, '-'); nameString = courseware.name.toLowerCase().replace(/\s/g, '-');
return res.redirect('../challenges/' + nameString); return res.redirect('../challenges/' + nameString);
}); });
}; };
exports.returnIndividualCourseware = function(req, res, next) { exports.returnIndividualCourseware = function(req, res, next) {
var dashedName = req.params.coursewareName; var dashedName = req.params.coursewareName;
coursewareName = dashedName.replace(/\-/g, ' '); coursewareName = dashedName.replace(/\-/g, ' ');
Courseware.find({"name" : new RegExp(coursewareName, 'i')}, function(err, courseware) { Courseware.find({"name" : new RegExp(coursewareName, 'i')}, function(err, courseware) {
if (err) { if (err) {
next(err); next(err);
} }
// Handle not found // Handle not found
if (courseware.length < 1) { if (courseware.length < 1) {
req.flash('errors', { req.flash('errors', {
msg: "404: We couldn't find a challenge with that name. Please double check the name." msg: "404: We couldn't find a challenge with that name. Please double check the name."
});
return res.redirect('/challenges');
}
courseware = courseware.pop();
// Redirect to full name if the user only entered a partial
var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-');
if (dashedNameFull != dashedName) {
return res.redirect('../challenges/' + dashedNameFull);
}
var challengeType = {
0 : function() {
res.render('coursewares/showHTML', {
title: courseware.name,
dashedName: dashedName,
name: courseware.name,
brief: courseware.description[0],
details: courseware.description.slice(1),
tests: courseware.tests,
challengeSeed: courseware.challengeSeed,
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
coursewareHash: courseware._id,
environment: resources.whichEnvironment()
}); });
return res.redirect('/challenges'); },
}
courseware = courseware.pop();
// Redirect to full name if the user only entered a partial 1 : function() {
var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-'); res.render('coursewares/showJS', {
if (dashedNameFull != dashedName) { title: courseware.name,
return res.redirect('../challenges/' + dashedNameFull); dashedName: dashedName,
} name: courseware.name,
brief: courseware.description[0],
details: courseware.description.slice(1),
tests: courseware.tests,
challengeSeed: courseware.challengeSeed,
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
coursewareHash: courseware._id,
var challengeType = { });
0 : function() { },
res.render('coursewares/showHTML', {
title: courseware.name,
dashedName: dashedName,
name: courseware.name,
brief: courseware.description[0],
details: courseware.description.slice(1),
tests: courseware.tests,
challengeSeed: courseware.challengeSeed,
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
coursewareHash: courseware._id,
environment: resources.whichEnvironment()
});
},
1 : function() { 2: function() {
res.render('coursewares/showJS', { res.render('coursewares/showVideo', {
title: courseware.name, title: courseware.name,
dashedName: dashedName, dashedName: dashedName,
name: courseware.name, name: courseware.name,
brief: courseware.description[0], details: courseware.description,
details: courseware.description.slice(1), tests: courseware.tests,
tests: courseware.tests, video: courseware.challengeSeed[0],
challengeSeed: courseware.challengeSeed, verb: resources.randomVerb(),
verb: resources.randomVerb(), phrase: resources.randomPhrase(),
phrase: resources.randomPhrase(), compliment: resources.randomCompliment(),
compliment: resources.randomCompliment(), coursewareHash: courseware._id,
coursewareHash: courseware._id, challengeType: 'video'
});
},
}); 3: function() {
}, res.render('coursewares/showVideo', {
title: courseware.name,
dashedName: dashedName,
name: courseware.name,
details: courseware.description,
tests: courseware.tests,
video: courseware.challengeSeed[0],
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
coursewareHash: courseware._id,
challengeType: 'zipline'
});
},
2: function() { 4: function() {
res.render('coursewares/showVideo', { res.render('coursewares/showVideo', {
title: courseware.name, title: courseware.name,
dashedName: dashedName, dashedName: dashedName,
name: courseware.name, name: courseware.name,
details: courseware.description, details: courseware.description,
tests: courseware.tests, tests: courseware.tests,
video: courseware.challengeSeed[0], video: courseware.challengeSeed[0],
verb: resources.randomVerb(), verb: resources.randomVerb(),
phrase: resources.randomPhrase(), phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(), compliment: resources.randomCompliment(),
coursewareHash: courseware._id, coursewareHash: courseware._id,
challengeType: 'video' challengeType: 'basejump'
}); });
}, }
};
3: function() { return challengeType[courseware.challengeType]();
res.render('coursewares/showVideo', {
title: courseware.name,
dashedName: dashedName,
name: courseware.name,
details: courseware.description,
tests: courseware.tests,
video: courseware.challengeSeed[0],
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
coursewareHash: courseware._id,
challengeType: 'zipline'
});
},
4: function() { });
res.render('coursewares/showVideo', {
title: courseware.name,
dashedName: dashedName,
name: courseware.name,
details: courseware.description,
tests: courseware.tests,
video: courseware.challengeSeed[0],
verb: resources.randomVerb(),
phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(),
coursewareHash: courseware._id,
challengeType: 'basejump'
});
}
};
return challengeType[courseware.challengeType]();
});
}; };
exports.testCourseware = function(req, res) { exports.testCourseware = function(req, res) {
var coursewareName = req.body.name, var coursewareName = req.body.name,
coursewareTests = req.body.tests, coursewareTests = req.body.tests,
coursewareDifficulty = req.body.difficulty, coursewareDifficulty = req.body.difficulty,
coursewareDescription = req.body.description, coursewareDescription = req.body.description,
coursewareEntryPoint = req.body.challengeEntryPoint, coursewareEntryPoint = req.body.challengeEntryPoint,
coursewareChallengeSeed = req.body.challengeSeed; coursewareChallengeSeed = req.body.challengeSeed;
coursewareTests = coursewareTests.split('\r\n'); coursewareTests = coursewareTests.split('\r\n');
coursewareDescription = coursewareDescription.split('\r\n'); coursewareDescription = coursewareDescription.split('\r\n');
coursewareTests.filter(getRidOfEmpties); coursewareTests.filter(getRidOfEmpties);
coursewareDescription.filter(getRidOfEmpties); coursewareDescription.filter(getRidOfEmpties);
coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', '');
res.render('courseware/show', { res.render('courseware/show', {
completedWith: null, completedWith: null,
title: coursewareName, title: coursewareName,
name: coursewareName, name: coursewareName,
difficulty: +coursewareDifficulty, difficulty: +coursewareDifficulty,
brief: coursewareDescription[0], brief: coursewareDescription[0],
details: coursewareDescription.slice(1), details: coursewareDescription.slice(1),
tests: coursewareTests, tests: coursewareTests,
challengeSeed: coursewareChallengeSeed, challengeSeed: coursewareChallengeSeed,
challengeEntryPoint: coursewareEntryPoint, challengeEntryPoint: coursewareEntryPoint,
cc: req.user ? req.user.coursewaresHash : undefined, cc: req.user ? req.user.coursewaresHash : undefined,
progressTimestamps: req.user ? req.user.progressTimestamps : undefined, progressTimestamps: req.user ? req.user.progressTimestamps : undefined,
verb: resources.randomVerb(), verb: resources.randomVerb(),
phrase: resources.randomPhrase(), phrase: resources.randomPhrase(),
compliment: resources.randomCompliment(), compliment: resources.randomCompliment(),
coursewares: [], coursewares: [],
coursewareHash: "test" coursewareHash: "test"
}); });
}; };
function getRidOfEmpties(elem) { function getRidOfEmpties(elem) {
if (elem.length > 0) { if (elem.length > 0) {
return elem; return elem;
} }
}; };
exports.publicGenerator = function(req, res) { exports.publicGenerator = function(req, res) {
res.render('courseware/public-generator'); res.render('courseware/public-generator');
}; };
exports.generateChallenge = function(req, res) { exports.generateChallenge = function(req, res) {
var coursewareName = req.body.name, var coursewareName = req.body.name,
coursewareTests = req.body.tests, coursewareTests = req.body.tests,
coursewareDifficulty = req.body.difficulty, coursewareDifficulty = req.body.difficulty,
coursewareDescription = req.body.description, coursewareDescription = req.body.description,
coursewareEntryPoint = req.body.challengeEntryPoint, coursewareEntryPoint = req.body.challengeEntryPoint,
coursewareChallengeSeed = req.body.challengeSeed; coursewareChallengeSeed = req.body.challengeSeed;
coursewareTests = coursewareTests.split('\r\n'); coursewareTests = coursewareTests.split('\r\n');
coursewareDescription = coursewareDescription.split('\r\n'); coursewareDescription = coursewareDescription.split('\r\n');
coursewareTests.filter(getRidOfEmpties); coursewareTests.filter(getRidOfEmpties);
coursewareDescription.filter(getRidOfEmpties); coursewareDescription.filter(getRidOfEmpties);
coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', '');
var response = { var response = {
_id: randomString(), _id: randomString(),
name: coursewareName, name: coursewareName,
difficulty: coursewareDifficulty, difficulty: coursewareDifficulty,
description: coursewareDescription, description: coursewareDescription,
challengeEntryPoint: coursewareEntryPoint, challengeEntryPoint: coursewareEntryPoint,
challengeSeed: coursewareChallengeSeed, challengeSeed: coursewareChallengeSeed,
tests: coursewareTests tests: coursewareTests
}; };
res.send(response); res.send(response);
}; };
exports.completedCourseware = function (req, res, next) { exports.completedCourseware = function (req, res, next) {
var isCompletedDate = Math.round(+new Date()); var isCompletedDate = Math.round(+new Date());
var coursewareHash = req.body.coursewareInfo.coursewareHash; var coursewareHash = req.body.coursewareInfo.coursewareHash;
debug('this is the coursewarehash we got', coursewareHash); debug('this is the coursewarehash we got', coursewareHash);
req.user.completedCoursewares.push({ req.user.completedCoursewares.push({
_id: coursewareHash, _id: coursewareHash,
completedDate: isCompletedDate, completedDate: isCompletedDate,
name: req.body.coursewareInfo.coursewareName name: req.body.coursewareInfo.coursewareName
}); });
var index = req.user.completedCoursewares.indexOf(coursewareHash); var index = req.user.completedCoursewares.indexOf(coursewareHash);
if (index === -1) { if (index === -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedCoursewares.splice(index, 1);
}
req.user.save(function (err, user) { req.user.progressTimestamps.push(Date.now() || 0);
if (err) { req.user.uncompletedCoursewares.splice(index, 1);
return next(err); }
}
if (user) {
res.send(true);
}
});
};
exports.completedBasejump = function (req, res, next) { exports.completedBasejump = function (req, res, next) {
var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; var isCompletedWith = req.body.bonfireInfo.completedWith || undefined;
@ -275,76 +267,94 @@ exports.completedBasejump = function (req, res, next) {
if(!solutionLink) { if(!solutionLink) {
// flash error and redirect // flash error and redirect
} }
if (user) {
res.send(true);
}
};
};
if (isCompletedWith) { exports.completedZiplineOrBasejump = function (req, res, next) {
var paired = User.find({"profile.username": isCompletedWith.toLowerCase()}).limit(1); var isCompletedWith = req.body.bonfireInfo.completedWith || false;
paired.exec(function (err, pairedWith) { var isCompletedDate = Math.round(+new Date());
if (err) { var coursewareHash = req.body.coursewareInfo.coursewareHash;
return err; var solutionLink = req.body.coursewareInfo.solutionLink;
} else { if (!solutionLink) {
var index = req.user.uncompletedBonfires.indexOf(bonfireHash); // flash error and redirect
if (index > -1) { return next(new Error('No solution provided'));
req.user.progressTimestamps.push(Date.now() || 0); }
req.user.uncompletedBonfires.splice(index, 1)
}
pairedWith = pairedWith.pop();
index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); if (isCompletedWith) {
if (index > -1) { var paired = User.find({'profile.username': isCompletedWith.toLowerCase()}).limit(1);
pairedWith.progressTimestamps.push(Date.now() || 0); paired.exec(function (err, pairedWith) {
pairedWith.uncompletedBonfires.splice(index, 1); if (err) {
return next(err);
} } else {
var index = req.user.uncompletedCoursewares.indexOf(coursewareHash);
pairedWith.completedBonfires.push({
_id: bonfireHash,
completedWith: req.user._id,
completedDate: isCompletedDate,
solution: isSolution
});
req.user.completedBonfires.push({
_id: bonfireHash,
completedWith: pairedWith._id,
completedDate: isCompletedDate,
solution: isSolution
});
req.user.save(function (err, user) {
pairedWith.save(function (err, paired) {
if (err) {
throw err;
}
if (user && paired) {
res.send(true);
}
})
});
}
})
} else {
req.user.completedBonfires.push({
_id: bonfireHash,
completedWith: null,
completedDate: isCompletedDate,
solution: isSolution
});
var index = req.user.uncompletedCourse.indexOf(bonfireHash);
if (index > -1) { if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0); req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedBonfires.splice(index, 1) req.user.uncompletedCoursewares.splice(index, 1);
}
pairedWith = pairedWith.pop();
index = pairedWith.uncompletedCoursewares.indexOf(coursewareHash);
if (index > -1) {
pairedWith.progressTimestamps.push(Date.now() || 0);
pairedWith.uncompletedCoursewares.splice(index, 1);
} }
req.user.save(function (err, user) { pairedWith.completedCoursewares.push({
if (err) { _id: coursewareHash,
throw err; completedWith: req.user._id,
} completedDate: isCompletedDate,
if (user) { solution: solutionLink
debug('Saving user');
res.send(true)
}
}); });
req.user.completedCoursewares.push({
_id: coursewareHash,
completedWith: pairedWith._id,
completedDate: isCompletedDate,
solution: solutionLink
});
req.user.save(function (err, user) {
if (err) {
return next(err);
}
pairedWith.save(function (err, paired) {
if (err) {
return next(err);
}
if (user && paired) {
return res.send(true);
}
});
});
}
});
} else {
req.user.completedCoursewares.push({
_id: coursewareHash,
completedWith: null,
completedDate: isCompletedDate,
solution: solutionLink
});
var index = req.user.uncompletedCourse.indexOf(coursewareHash);
if (index > -1) {
req.user.progressTimestamps.push(Date.now() || 0);
req.user.uncompletedCoursewares.splice(index, 1);
} }
};
req.user.save(function (err, user) {
if (err) {
return next(err);
}
if (user) {
debug('Saving user');
return res.send(true);
}
});
}
};

View File

@ -1,20 +1,20 @@
var async = require('async'), var async = require('async'),
User = require('../models/User'), User = require('../models/User'),
Challenge = require('./../models/Challenge'), Challenge = require('./../models/Challenge'),
Bonfire = require('./../models/Bonfire'), Bonfire = require('./../models/Bonfire'),
Story = require('./../models/Story'), Story = require('./../models/Story'),
Comment = require('./../models/Comment'), Comment = require('./../models/Comment'),
resources = require('./resources.json'), resources = require('./resources.json'),
steps = resources.steps, steps = resources.steps,
secrets = require('./../config/secrets'), secrets = require('./../config/secrets'),
bonfires = require('../seed_data/bonfires.json'), bonfires = require('../seed_data/bonfires.json'),
coursewares = require('../seed_data/coursewares.json'), coursewares = require('../seed_data/coursewares.json'),
moment = require('moment'), moment = require('moment'),
https = require('https'), https = require('https'),
debug = require('debug')('freecc:cntr:resources'), debug = require('debug')('freecc:cntr:resources'),
cheerio = require('cheerio'), cheerio = require('cheerio'),
request = require('request'), request = require('request'),
R = require('ramda'); R = require('ramda');
/** /**
* GET / * GET /
@ -26,370 +26,381 @@ Array.zip = function(left, right, combinerFunction) {
results = []; results = [];
for (counter = 0; counter < Math.min(left.length, right.length); counter++) { for (counter = 0; counter < Math.min(left.length, right.length); counter++) {
results.push(combinerFunction(left[counter],right[counter])); results.push(combinerFunction(left[counter], right[counter]));
} }
return results; return results;
}; };
module.exports = { module.exports = {
privacy: function privacy(req, res) { privacy: function privacy(req, res) {
res.render('resources/privacy', { res.render('resources/privacy', {
title: 'Privacy' title: 'Privacy'
}); });
}, },
sitemap: function sitemap(req, res, next) { sitemap: function sitemap(req, res, next) {
var appUrl = 'http://www.freecodecamp.com'; var appUrl = 'http://www.freecodecamp.com';
var now = moment(new Date()).format('YYYY-MM-DD'); var now = moment(new Date()).format('YYYY-MM-DD');
User.find({'profile.username': {'$ne': '' }}, function(err, users) { User.find({'profile.username': {'$ne': '' }}, function(err, users) {
if (err) {
debug('User err: ', err);
return next(err);
}
Challenge.find({}, function (err, challenges) {
if (err) {
debug('User err: ', err);
return next(err);
}
Bonfire.find({}, function (err, bonfires) {
if (err) {
debug('User err: ', err);
return next(err);
}
Story.find({}, function (err, stories) {
if (err) { if (err) {
debug('User err: ', err); debug('User err: ', err);
return next(err); return next(err);
} }
Challenge.find({}, function (err, challenges) { res.header('Content-Type', 'application/xml');
if (err) { res.render('resources/sitemap', {
debug('User err: ', err); appUrl: appUrl,
return next(err); now: now,
} users: users,
Bonfire.find({}, function (err, bonfires) { challenges: challenges,
if (err) { bonfires: bonfires,
debug('User err: ', err); stories: stories
return next(err);
}
Story.find({}, function (err, stories) {
if (err) {
debug('User err: ', err);
return next(err);
}
res.header('Content-Type', 'application/xml');
res.render('resources/sitemap', {
appUrl: appUrl,
now: now,
users: users,
challenges: challenges,
bonfires: bonfires,
stories: stories
});
});
});
}); });
});
}); });
}, });
});
},
deployAWebsite: function deployAWebsite(req, res) { deployAWebsite: function deployAWebsite(req, res) {
res.render('resources/deploy-a-website', { res.render('resources/deploy-a-website', {
title: 'Deploy a Dynamic Website in 7 Minutes' title: 'Deploy a Dynamic Website in 7 Minutes'
}); });
}, },
chat: function chat(req, res) { chat: function chat(req, res) {
res.render('resources/chat', { res.render('resources/chat', {
title: "Enter Free Code Camp's Chat Rooms" title: "Enter Free Code Camp's Chat Rooms"
}); });
}, },
nonprofitProjectInstructions: function nonprofitProjectInstructions(req, res) { nonprofitProjectInstructions: function nonprofitProjectInstructions(req, res) {
res.render('resources/nonprofit-project-instructions', { res.render('resources/nonprofit-project-instructions', {
title: 'Nonprofit Project Instructions' title: 'Nonprofit Project Instructions'
}); });
}, },
gmailShortcuts: function gmailShortcuts(req, res) { gmailShortcuts: function gmailShortcuts(req, res) {
res.render('resources/gmail-shortcuts', { res.render('resources/gmail-shortcuts', {
title: 'These Gmail Shortcuts will save you Hours' title: 'These Gmail Shortcuts will save you Hours'
}); });
}, },
guideToOurNonprofitProjects: function guideToOurNonprofitProjects(req, res) { guideToOurNonprofitProjects: function guideToOurNonprofitProjects(req, res) {
res.render('resources/guide-to-our-nonprofit-projects', { res.render('resources/guide-to-our-nonprofit-projects', {
title: 'A guide to our Nonprofit Projects' title: 'A guide to our Nonprofit Projects'
}); });
}, },
controlShortcuts: function controlShortcuts(req, res) { controlShortcuts: function controlShortcuts(req, res) {
res.render('resources/control-shortcuts', { res.render('resources/control-shortcuts', {
title: 'These Control Shortcuts will save you Hours' title: 'These Control Shortcuts will save you Hours'
}); });
}, },
chromebook: function chromebook(req, res) { chromebook: function chromebook(req, res) {
res.render('resources/chromebook', { res.render('resources/chromebook', {
title: 'Win a Chromebook' title: 'Win a Chromebook'
}); });
}, },
jqueryExercises: function jqueryExercises(req, res) { jqueryExercises: function jqueryExercises(req, res) {
res.render('resources/jquery-exercises', { res.render('resources/jquery-exercises', {
title: 'jQuery Exercises' title: 'jQuery Exercises'
}); });
}, },
livePairProgramming: function(req, res) { livePairProgramming: function(req, res) {
res.render('resources/live-pair-programming', { res.render('resources/live-pair-programming', {
title: 'Live Pair Programming' title: 'Live Pair Programming'
}); });
}, },
installScreenHero: function(req, res) { installScreenHero: function(req, res) {
res.render('resources/install-screenhero', { res.render('resources/install-screenhero', {
title: 'Install ScreenHero' title: 'Install ScreenHero'
}); });
}, },
javaScriptInYourInbox: function(req, res) { javaScriptInYourInbox: function(req, res) {
res.render('resources/javascript-in-your-inbox', { res.render('resources/javascript-in-your-inbox', {
title: 'JavaScript in your Inbox' title: 'JavaScript in your Inbox'
}); });
}, },
nodeSchoolChallenges: function(req, res) { nodeSchoolChallenges: function(req, res) {
res.render('resources/nodeschool-challenges', { res.render('resources/nodeschool-challenges', {
title: 'NodeSchool Challenges' title: 'NodeSchool Challenges'
}); });
}, },
githubCalls: function(req, res) { githubCalls: function(req, res) {
var githubHeaders = {headers: {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36'}, port:80 }; var githubHeaders = {headers: {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36'}, port:80 };
request('https://api.github.com/repos/freecodecamp/freecodecamp/pulls?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function(err, status1, pulls) { request('https://api.github.com/repos/freecodecamp/freecodecamp/pulls?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function(err, status1, pulls) {
pulls = pulls ? Object.keys(JSON.parse(pulls)).length : "Can't connect to github"; pulls = pulls ? Object.keys(JSON.parse(pulls)).length : "Can't connect to github";
request('https://api.github.com/repos/freecodecamp/freecodecamp/issues?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function (err, status2, issues) { request('https://api.github.com/repos/freecodecamp/freecodecamp/issues?client_id=' + secrets.github.clientID + '&client_secret=' + secrets.github.clientSecret, githubHeaders, function (err, status2, issues) {
issues = ((pulls === parseInt(pulls)) && issues) ? Object.keys(JSON.parse(issues)).length - pulls : "Can't connect to GitHub"; issues = ((pulls === parseInt(pulls)) && issues) ? Object.keys(JSON.parse(issues)).length - pulls : "Can't connect to GitHub";
res.send({"issues": issues, "pulls" : pulls}); res.send({"issues": issues, "pulls" : pulls});
}); });
}); });
}, },
about: function(req, res, next) { about: function(req, res, next) {
if (req.user) { if (req.user) {
if (!req.user.profile.picture || req.user.profile.picture === "https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png") { if (!req.user.profile.picture || req.user.profile.picture === "https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png") {
req.user.profile.picture = "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png"; req.user.profile.picture = "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png";
req.user.save(); req.user.save();
} }
}
var date1 = new Date('10/15/2014');
var date2 = new Date();
var progressTimestamps = req.user.progressTimestamps;
var now = Date.now() || 0;
if (req.user.pointsNeedMigration) {
var challengesHash = req.user.challengesHash;
for (var key in challengesHash) {
if (challengesHash[key] > 0) {
req.user.progressTimestamps.push(challengesHash[key]);
}
}
var timeStamps = [];
R.keys(req.user.challengesHash).forEach(function(key) {
"use strict";
var timeStamp = parseInt(challengesHash[key], 10);
timeStamps.push({timeStamp: timeStamp.length !== 13 ? (+timeStamp) : (+timeStamp * 1000)});
});
req.user.completedCoursewares = Array.zip(timeStamps, coursewares,
function(left, right) {
"use strict";
return ({
completedDate: left.timeStamp,
_id: right._id,
name: right.name
});
}).filter(function(elem) {
"use strict";
return elem.completedDate !== 0;
});
req.user.pointsNeedMigration = false;
req.user.save();
}
if (progressTimestamps[progressTimestamps.length - 1] <= (now - 43200)) {
req.user.progressTimestamps.push(now);
}
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
var announcements = resources.announcements;
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
User.count({}, function (err, c3) {
if (err) {
debug('User err: ', err);
return next(err);
}
User.count({'points': {'$gt': 53}}, function (err, all) {
if (err) {
debug('User err: ', err);
return next(err);
}
res.render('resources/learn-to-code', {
title: 'About Free Code Camp and Our Team of Volunteers',
daysRunning: daysRunning,
c3: numberWithCommas(c3),
all: all,
announcements: announcements
});
});
});
},
randomPhrase: function() {
var phrases = resources.phrases;
return phrases[Math.floor(Math.random() * phrases.length)];
},
randomVerb: function() {
var verbs = resources.verbs;
return verbs[Math.floor(Math.random() * verbs.length)];
},
randomCompliment: function() {
var compliments = resources.compliments;
return compliments[Math.floor(Math.random() * compliments.length)];
},
allBonfireIds: function() {
return bonfires.map(function(elem) {
return {
_id: elem._id,
difficulty: elem.difficulty
}
})
.sort(function(a, b) {
return a.difficulty - b.difficulty;
})
.map(function(elem) {
return elem._id;
});
},
allBonfireNames: function() {
return bonfires.map(function(elem) {
return {
name: elem.name,
difficulty: elem.difficulty,
_id: elem._id
}
})
.sort(function(a, b) {
return a.difficulty - b.difficulty;
})
.map (function(elem) {
return {
name : elem.name,
_id: elem._id
}
});
},
getAllCourses: function() {
"use strict";
return coursewares;
},
allCoursewareIds: function() {
return coursewares.map(function(elem) {
return {
_id: elem._id,
difficulty: elem.difficulty
};
})
.sort(function(a, b) {
return a.difficulty - b.difficulty;
})
.map(function(elem) {
return elem._id;
});
},
allCoursewareNames: function() {
return coursewares.map(function(elem) {
return {
name: elem.name,
difficulty: elem.difficulty,
_id: elem._id
};
})
.sort(function(a, b) {
return a.difficulty - b.difficulty;
})
.map (function(elem) {
return {
name: elem.name,
_id: elem._id
};
});
},
whichEnvironment: function() {
return process.env.NODE_ENV;
},
getURLTitle: function(url, callback) {
(function () {
var result = {title: '', image: '', url: '', description: ''};
request(url, function (error, response, body) {
if (!error && response.statusCode === 200) {
var $ = cheerio.load(body);
var metaDescription = $("meta[name='description']");
var metaImage = $("meta[property='og:image']");
var urlImage = metaImage.attr('content') ? metaImage.attr('content') : '';
var description = metaDescription.attr('content') ? metaDescription.attr('content') : '';
result.title = $('title').text().length < 141 ? $('title').text() : $('title').text().slice(0, 137) + " ...";
result.image = urlImage;
result.description = description;
callback(null, result);
} else {
callback('failed');
}
});
})();
},
updateUserStoryPictures: function(userId, picture, username, cb) {
var counter = 0,
foundStories,
foundComments;
Story.find({'author.userId': userId}, function(err, stories) {
if (err) {
return cb(err);
}
foundStories = stories;
counter++;
saveStoriesAndComments();
});
Comment.find({'author.userId': userId}, function(err, comments) {
if (err) {
return cb(err);
}
foundComments = comments;
counter++;
saveStoriesAndComments();
});
function saveStoriesAndComments() {
if (counter !== 2) {
return;
}
var tasks = [];
R.forEach(function(comment) {
comment.author.picture = picture;
comment.author.username = username;
comment.markModified('author');
tasks.push(function(cb) {
comment.save(cb);
});
}, foundComments);
R.forEach(function(story) {
story.author.picture = picture;
story.author.username = username;
story.markModified('author');
tasks.push(function(cb) {
story.save(cb);
});
}, foundStories);
async.parallel(tasks, function(err) {
if (err) { return cb(err); }
cb();
});
}
} }
var date1 = new Date('10/15/2014');
var date2 = new Date();
var progressTimestamps = req.user.progressTimestamps;
var now = Date.now() || 0;
if (req.user.pointsNeedMigration) {
var challengesHash = req.user.challengesHash;
for (var key in challengesHash) {
if (challengesHash[key] > 0) {
req.user.progressTimestamps.push(challengesHash[key]);
}
}
var oldChallengeKeys = R.keys(req.user.challengesHash);
var updatedTimesFromOldChallenges = oldChallengeKeys.map(function(timeStamp) {
if (timeStamp.toString().length !== 13) {
timeStamp *= 1000;
}
return timeStamp;
});
var newTimeStamps = R.map(function(timeStamp) {
if (timeStamp.toString().length !== 13) {
timeStamp *= 1000;
}
return timeStamp;
}, req.user.progressTimestamps);
req.user.progressTimestamps = newTimeStamps;
req.user.completedCoursewares = Array.zip(updatedTimesFromOldChallenges, coursewares,
function(left, right) {
return ({
completedDate: left.timeStamp,
_id: right._id,
name: right.name
});
}).filter(function(elem) {
return elem.completedDate !== 0;
});
req.user.pointsNeedMigration = false;
req.user.save();
}
if (progressTimestamps[progressTimestamps.length - 1] <= (now - 43200)) {
req.user.progressTimestamps.push(now);
}
var timeDiff = Math.abs(date2.getTime() - date1.getTime());
var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
var announcements = resources.announcements;
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
User.count({}, function (err, c3) {
if (err) {
debug('User err: ', err);
return next(err);
}
User.count({'points': {'$gt': 53}}, function (err, all) {
if (err) {
debug('User err: ', err);
return next(err);
}
res.render('resources/learn-to-code', {
title: 'About Free Code Camp and Our Team of Volunteers',
daysRunning: daysRunning,
c3: numberWithCommas(c3),
all: all,
announcements: announcements
});
});
});
},
randomPhrase: function() {
var phrases = resources.phrases;
return phrases[Math.floor(Math.random() * phrases.length)];
},
randomVerb: function() {
var verbs = resources.verbs;
return verbs[Math.floor(Math.random() * verbs.length)];
},
randomCompliment: function() {
var compliments = resources.compliments;
return compliments[Math.floor(Math.random() * compliments.length)];
},
allBonfireIds: function() {
return bonfires.map(function(elem) {
return {
_id: elem._id,
difficulty: elem.difficulty
}
})
.sort(function(a, b) {
return a.difficulty - b.difficulty;
})
.map(function(elem) {
return elem._id;
});
},
allBonfireNames: function() {
return bonfires.map(function(elem) {
return {
name: elem.name,
difficulty: elem.difficulty,
_id: elem._id
}
})
.sort(function(a, b) {
return a.difficulty - b.difficulty;
})
.map (function(elem) {
return {
name : elem.name,
_id: elem._id
}
});
},
getAllCourses: function() {
"use strict";
return coursewares;
},
allCoursewareIds: function() {
return coursewares.map(function(elem) {
return {
_id: elem._id,
difficulty: elem.difficulty
};
})
.sort(function(a, b) {
return a.difficulty - b.difficulty;
})
.map(function(elem) {
return elem._id;
});
},
allCoursewareNames: function() {
return coursewares.map(function(elem) {
return {
name: elem.name,
difficulty: elem.difficulty,
_id: elem._id
};
})
.sort(function(a, b) {
return a.difficulty - b.difficulty;
})
.map (function(elem) {
return {
name: elem.name,
_id: elem._id
};
});
},
whichEnvironment: function() {
return process.env.NODE_ENV;
},
getURLTitle: function(url, callback) {
(function () {
var result = {title: '', image: '', url: '', description: ''};
request(url, function (error, response, body) {
if (!error && response.statusCode === 200) {
var $ = cheerio.load(body);
var metaDescription = $("meta[name='description']");
var metaImage = $("meta[property='og:image']");
var urlImage = metaImage.attr('content') ? metaImage.attr('content') : '';
var description = metaDescription.attr('content') ? metaDescription.attr('content') : '';
result.title = $('title').text().length < 141 ? $('title').text() : $('title').text().slice(0, 137) + " ...";
result.image = urlImage;
result.description = description;
callback(null, result);
} else {
callback('failed');
}
});
})();
},
updateUserStoryPictures: function(userId, picture, username, cb) {
var counter = 0,
foundStories,
foundComments;
Story.find({'author.userId': userId}, function(err, stories) {
if (err) {
return cb(err);
}
foundStories = stories;
counter++;
saveStoriesAndComments();
});
Comment.find({'author.userId': userId}, function(err, comments) {
if (err) {
return cb(err);
}
foundComments = comments;
counter++;
saveStoriesAndComments();
});
function saveStoriesAndComments() {
if (counter !== 2) {
return;
}
var tasks = [];
R.forEach(function(comment) {
comment.author.picture = picture;
comment.author.username = username;
comment.markModified('author');
tasks.push(function(cb) {
comment.save(cb);
});
}, foundComments);
R.forEach(function(story) {
story.author.picture = picture;
story.author.username = username;
story.markModified('author');
tasks.push(function(cb) {
story.save(cb);
});
}, foundStories);
async.parallel(tasks, function(err) {
if (err) { return cb(err); }
cb();
});
}
}
}; };

View File

@ -1,431 +1,447 @@
/* eslint-disable no-catch-shadow, no-unused-vars */
var R = require('ramda'), var R = require('ramda'),
debug = require('debug')('freecc:cntr:story'), debug = require('debug')('freecc:cntr:story'),
Story = require('./../models/Story'), Story = require('./../models/Story'),
Comment = require('./../models/Comment'), Comment = require('./../models/Comment'),
User = require('./../models/User'), User = require('./../models/User'),
moment = require('../public/js/lib/moment/moment.js'), moment = require('../public/js/lib/moment/moment.js'),
resources = require('./resources'), resources = require('./resources'),
mongodb = require('mongodb'), mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient, MongoClient = mongodb.MongoClient,
secrets = require('../config/secrets'), secrets = require('../config/secrets'),
sanitizeHtml = require('sanitize-html'); sanitizeHtml = require('sanitize-html');
function hotRank(timeValue, rank) { function hotRank(timeValue, rank) {
/* /*
* Hotness ranking algorithm: http://amix.dk/blog/post/19588 * Hotness ranking algorithm: http://amix.dk/blog/post/19588
* tMS = postedOnDate - foundationTime; * tMS = postedOnDate - foundationTime;
* Ranking... * Ranking...
* f(ts, 1, rank) = log(10)z + (ts)/45000; * f(ts, 1, rank) = log(10)z + (ts)/45000;
*/ */
var hotness; var hotness;
var z = Math.log(rank) / Math.log(10); var z = Math.log(rank) / Math.log(10);
hotness = z + (timeValue / 115200000); hotness = z + (timeValue / 115200000);
return hotness; return hotness;
} }
exports.hotJSON = function(req, res) { exports.hotJSON = function(req, res, next) {
var story = Story.find({}).sort({'timePosted': -1}).limit(1000); var story = Story.find({}).sort({'timePosted': -1}).limit(1000);
story.exec(function(err, stories) { story.exec(function(err, stories) {
if (err) { if (err) {
res.send(500); return next(err);
return next(err); }
}
var foundationDate = 1413298800000; var foundationDate = 1413298800000;
var sliceVal = stories.length >= 100 ? 100 : stories.length; var sliceVal = stories.length >= 100 ? 100 : stories.length;
return res.json(stories.map(function(elem) { return res.json(stories.map(function(elem) {
return elem; return elem;
}).sort(function(a, b) { }).sort(function(a, b) {
return hotRank(b.timePosted - foundationDate, b.rank, b.headline) - hotRank(a.timePosted - foundationDate, a.rank, a.headline); return hotRank(b.timePosted - foundationDate, b.rank, b.headline)
}).slice(0, sliceVal)); - hotRank(a.timePosted - foundationDate, a.rank, a.headline);
}).slice(0, sliceVal));
}); });
}; };
exports.recentJSON = function(req, res, next) { exports.recentJSON = function(req, res, next) {
var story = Story.find({}).sort({'timePosted': -1}).limit(100); var story = Story.find({}).sort({'timePosted': -1}).limit(100);
story.exec(function(err, stories) { story.exec(function(err, stories) {
if (err) { if (err) {
res.status(500); return next(err);
return next(err); }
} return res.json(stories);
res.json(stories); });
});
}; };
exports.hot = function(req, res) { exports.hot = function(req, res) {
res.render('stories/index', { return res.render('stories/index', {
title: 'Hot stories currently trending on Camper News', title: 'Hot stories currently trending on Camper News',
page: 'hot' page: 'hot'
}); });
}; };
exports.submitNew = function(req, res) { exports.submitNew = function(req, res) {
res.render('stories/index', { return res.render('stories/index', {
title: 'Submit a new story to Camper News', title: 'Submit a new story to Camper News',
page: 'submit' page: 'submit'
}); });
}; };
exports.search = function(req, res) { exports.search = function(req, res) {
res.render('stories/index', { return res.render('stories/index', {
title: 'Search the archives of Camper News', title: 'Search the archives of Camper News',
page: 'search' page: 'search'
}); });
}; };
exports.recent = function(req, res) { exports.recent = function(req, res) {
res.render('stories/index', { return res.render('stories/index', {
title: 'Recently submitted stories on Camper News', title: 'Recently submitted stories on Camper News',
page: 'recent' page: 'recent'
}); });
}; };
exports.preSubmit = function(req, res) { exports.preSubmit = function(req, res) {
var data = req.query; var data = req.query;
var cleanData = sanitizeHtml(data.url, { var cleanData = sanitizeHtml(data.url, {
allowedTags: [], allowedTags: [],
allowedAttributes: [] allowedAttributes: []
}).replace(/&quot;;/g, '"'); }).replace(/&quot;;/g, '"');
if (data.url.replace(/&/g, '&amp;') !== cleanData) { if (data.url.replace(/&/g, '&amp;') !== cleanData) {
req.flash('errors', { req.flash('errors', {
msg: 'The data for this post is malformed' msg: 'The data for this post is malformed'
});
return res.render('stories/index', {
page: 'stories/submit'
});
}
var title = data.title || '';
var image = data.image || '';
var description = data.description || '';
return res.render('stories/index', {
title: "Confirm your Camper News story submission",
page: 'storySubmission',
storyURL: data.url,
storyTitle: title,
storyImage: image,
storyMetaDescription: description
}); });
return res.render('stories/index', {
page: 'stories/submit'
});
}
var title = data.title || '';
var image = data.image || '';
var description = data.description || '';
return res.render('stories/index', {
title: 'Confirm your Camper News story submission',
page: 'storySubmission',
storyURL: data.url,
storyTitle: title,
storyImage: image,
storyMetaDescription: description
});
}; };
exports.returnIndividualStory = function(req, res, next) { exports.returnIndividualStory = function(req, res, next) {
var dashedName = req.params.storyName; var dashedName = req.params.storyName;
var storyName = dashedName.replace(/\-/g, ' '); var storyName = dashedName.replace(/\-/g, ' ');
Story.find({'storyLink' : new RegExp(storyName, 'i')}, function(err, story) { Story.find({'storyLink': new RegExp(storyName, 'i')}, function(err, story) {
if (err) { if (err) {
next(err); return next(err);
} }
if (story.length < 1) { if (story.length < 1) {
req.flash('errors', { req.flash('errors', {
msg: "404: We couldn't find a story with that name. Please double check the name." msg: "404: We couldn't find a story with that name. Please double check the name."
}); });
return res.redirect('/stories/'); return res.redirect('/stories/');
} }
story = story.pop(); story = story.pop();
var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-'); var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-');
if (dashedNameFull !== dashedName) { if (dashedNameFull !== dashedName) {
return res.redirect('../stories/' + dashedNameFull); return res.redirect('../stories/' + dashedNameFull);
} }
var userVoted = false; var userVoted = false;
try { try {
var votedObj = story.upVotes.filter(function(a){ var votedObj = story.upVotes.filter(function(a) {
return a['upVotedByUsername'] === req.user['profile']['username']; return a['upVotedByUsername'] === req.user['profile']['username'];
}) });
if (votedObj.length > 0){ if (votedObj.length > 0) {
userVoted = true; userVoted = true;
} }
} catch(err){ } catch(err) {
userVoted = false; userVoted = false;
} }
res.render('stories/index', { res.render('stories/index', {
title: story.headline, title: story.headline,
link: story.link, link: story.link,
author: story.author, author: story.author,
description: story.description, description: story.description,
rank: story.upVotes.length, rank: story.upVotes.length,
upVotes: story.upVotes, upVotes: story.upVotes,
comments: story.comments, comments: story.comments,
id: story._id, id: story._id,
timeAgo: moment(story.timePosted).fromNow(), timeAgo: moment(story.timePosted).fromNow(),
image: story.image, image: story.image,
page: 'show', page: 'show',
storyMetaDescription: story.metaDescription, storyMetaDescription: story.metaDescription,
hasUserVoted: userVoted hasUserVoted: userVoted
});
}); });
});
}; };
exports.getStories = function(req, res) { exports.getStories = function(req, res, next) {
MongoClient.connect(secrets.db, function(err, database) { MongoClient.connect(secrets.db, function(err, database) {
database.collection('stories').find({ if (err) {
"$text": { return next(err);
"$search": req.body.data.searchValue }
} database.collection('stories').find({
}, { '$text': {
headline: 1, '$search': req.body.data.searchValue
timePosted: 1, }
link: 1, }, {
description: 1, headline: 1,
rank: 1, timePosted: 1,
upVotes: 1, link: 1,
author: 1, description: 1,
comments: 1, rank: 1,
image: 1, upVotes: 1,
storyLink: 1, author: 1,
metaDescription: 1, comments: 1,
textScore: { image: 1,
$meta: "textScore" storyLink: 1,
} metaDescription: 1,
}, { textScore: {
sort: { $meta: 'textScore'
textScore: { }
$meta: "textScore" }, {
} sort: {
} textScore: {
}).toArray(function(err, items) { $meta: 'textScore'
if (items !== null && items.length !== 0) { }
return res.json(items); }
} }).toArray(function(err, items) {
return res.status(404); if (err) {
}); return next(err);
}
if (items !== null && items.length !== 0) {
return res.json(items);
}
return res.sendStatus(404);
}); });
});
}; };
exports.upvote = function(req, res, next) { exports.upvote = function(req, res, next) {
var data = req.body.data; var data = req.body.data;
Story.find({'_id': data.id}, function(err, story) { Story.find({'_id': data.id}, function(err, story) {
if (err) { if (err) {
res.status(500); return next(err);
return next(err); }
} story = story.pop();
story = story.pop(); story.rank++;
story.rank++; story.upVotes.push(
story.upVotes.push( {
{ upVotedBy: data.upVoter._id,
upVotedBy: data.upVoter._id, upVotedByUsername: data.upVoter.profile.username
upVotedByUsername: data.upVoter.profile.username }
} );
); story.markModified('rank');
story.markModified('rank'); story.save();
story.save(); User.find({'_id': story.author.userId}, function(err, user) {
return res.send(story); 'use strict';
if (err) {
return next(err);
}
user = user.pop();
user.progressTimestamps.push(Date.now());
user.save();
}); });
return res.send(story);
});
}; };
exports.comments = function(req, res, next) { exports.comments = function(req, res, next) {
var data = req.params.id; var data = req.params.id;
Comment.find({'_id': data}, function(err, comment) { Comment.find({'_id': data}, function(err, comment) {
if (err) { if (err) {
res.status(500); return next(err);
return next(err); }
} comment = comment.pop();
comment = comment.pop(); return res.send(comment);
return res.send(comment); });
});
}; };
exports.newStory = function(req, res) { exports.newStory = function(req, res, next) {
if (!req.user) { if (!req.user) {
return res.status(500); return next(new Error('Must be logged in'));
}
var url = req.body.data.url;
var cleanURL = sanitizeHtml(url, {
allowedTags: [],
allowedAttributes: []
}).replace(/&quot;/g, '"');
if (cleanURL !== url) {
req.flash('errors', {
msg: "The URL you submitted doesn't appear valid"
});
return res.json({
alreadyPosted: true,
storyURL: '/stories/submit'
});
}
if (url.search(/^https?:\/\//g) === -1) {
url = 'http://' + url;
}
Story.find({'link': url}, function(err, story) {
if (err) {
return next(err);
} }
var url = req.body.data.url; if (story.length) {
var cleanURL = sanitizeHtml(url, { req.flash('errors', {
msg: "Someone's already posted that link. Here's the discussion."
});
return res.json({
alreadyPosted: true,
storyURL: '/stories/' + story.pop().storyLink
});
}
resources.getURLTitle(url, processResponse);
});
function processResponse(err, story) {
if (err) {
res.json({
alreadyPosted: false,
storyURL: url,
storyTitle: '',
storyImage: '',
storyMetaDescription: ''
});
} else {
res.json({
alreadyPosted: false,
storyURL: url,
storyTitle: story.title,
storyImage: story.image,
storyMetaDescription: story.description
});
}
}
};
exports.storySubmission = function(req, res, next) {
var data = req.body.data;
if (req.user._id.toString() !== data.author.userId.toString()) {
return next(new Error('Not authorized'));
}
var storyLink = data.headline
.replace(/\'/g, '')
.replace(/\"/g, '')
.replace(/,/g, '')
.replace(/[^a-z0-9]/gi, ' ')
.replace(/\s+/g, ' ')
.toLowerCase();
var link = data.link;
if (link.search(/^https?:\/\//g) === -1) {
link = 'http://' + link;
}
var story = new Story({
headline: sanitizeHtml(data.headline, {
allowedTags: [],
allowedAttributes: []
}).replace(/&quot;/g, '"'),
timePosted: Date.now(),
link: link,
description: sanitizeHtml(data.description, {
allowedTags: [],
allowedAttributes: []
}).replace(/&quot;/g, '"'),
rank: 1,
upVotes: data.upVotes,
author: data.author,
comments: [],
image: data.image,
storyLink: storyLink,
metaDescription: data.storyMetaDescription
});
req.user.progressTimestamps.push(Date.now());
req.user.save();
story.save(function(err) {
if (err) {
return next(err);
}
res.send(JSON.stringify({
storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase()
}));
});
};
exports.commentSubmit = function(req, res, next) {
var data = req.body.data;
if (req.user._id.toString() !== data.author.userId.toString()) {
return next(new Error('Not authorized'));
}
var sanitizedBody = sanitizeHtml(data.body,
{
allowedTags: [], allowedTags: [],
allowedAttributes: [] allowedAttributes: []
}).replace(/&quot;/g, '"'); }).replace(/&quot;/g, '"');
if (cleanURL !== url) { if (data.body !== sanitizedBody) {
req.flash('errors', { req.flash('errors', {
msg: "The URL you submitted doesn't appear valid" msg: 'HTML is not allowed'
});
return res.json({
alreadyPosted: true,
storyURL: '/stories/submit'
});
}
if (url.search(/^https?:\/\//g) === -1) {
url = 'http://' + url;
}
Story.find({'link': url}, function(err, story) {
if (err) {
return res.status(500);
}
if (story.length) {
req.flash('errors', {
msg: "Someone's already posted that link. Here's the discussion."
});
return res.json({
alreadyPosted: true,
storyURL: '/stories/' + story.pop().storyLink
});
}
resources.getURLTitle(url, processResponse);
}); });
return res.send(true);
function processResponse(err, story) { }
if (err) { var comment = new Comment({
res.json({ associatedPost: data.associatedPost,
alreadyPosted: false, body: sanitizedBody,
storyURL: url, rank: 0,
storyTitle: '', upvotes: 0,
storyImage: '', author: data.author,
storyMetaDescription: '' comments: [],
}); topLevel: true,
} else { commentOn: Date.now()
res.json({ });
alreadyPosted: false, commentSave(comment, Story, res, next);
storyURL: url,
storyTitle: story.title,
storyImage: story.image,
storyMetaDescription: story.description
});
}
}
}; };
exports.storySubmission = function(req, res) { exports.commentOnCommentSubmit = function(req, res, next) {
var data = req.body.data; var data = req.body.data;
if (req.user._id.toString() !== data.author.userId.toString()) {
return res.status(500);
}
var storyLink = data.headline
.replace(/\'/g, '')
.replace(/\"/g, '')
.replace(/,/g, '')
.replace(/[^a-z0-9]/gi, ' ')
.replace(/\s+/g, ' ')
.toLowerCase();
var link = data.link;
if (link.search(/^https?:\/\//g) === -1) {
link = 'http://' + link;
}
var story = new Story({
headline: sanitizeHtml(data.headline, {
allowedTags: [],
allowedAttributes: []
}).replace(/&quot;/g, '"'),
timePosted: Date.now(),
link: link,
description: sanitizeHtml(data.description, {
allowedTags: [],
allowedAttributes: []
}).replace(/&quot;/g, '"'),
rank: 1,
upVotes: data.upVotes,
author: data.author,
comments: [],
image: data.image,
storyLink: storyLink,
metaDescription: data.storyMetaDescription
});
story.save(function(err) { if (req.user._id.toString() !== data.author.userId.toString()) {
return next(new Error('Not authorized'));
}
var sanitizedBody = sanitizeHtml(data.body,
{
allowedTags: [],
allowedAttributes: []
}).replace(/&quot;/g, '"');
if (data.body !== sanitizedBody) {
req.flash('errors', {
msg: 'HTML is not allowed'
});
return res.send(true);
}
var comment = new Comment({
associatedPost: data.associatedPost,
body: sanitizedBody,
rank: 0,
upvotes: 0,
author: data.author,
comments: [],
topLevel: false,
commentOn: Date.now()
});
commentSave(comment, Comment, res, next);
};
function commentSave(comment, Context, res, next) {
comment.save(function(err, data) {
if (err) {
return next(err);
}
try {
Context.find({'_id': comment.associatedPost}, function (err, associatedStory) {
if (err) { if (err) {
return res.status(500); return next(err);
} }
res.send(JSON.stringify({ associatedStory = associatedStory.pop();
storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase() if (associatedStory) {
})); associatedStory.comments.push(data._id);
}); associatedStory.save(function (err) {
}; if (err) {
return next(err);
exports.commentSubmit = function(req, res) { }
var data = req.body.data; res.send(true);
if (req.user._id.toString() !== data.author.userId.toString()) { });
return res.status(500);
}
var sanitizedBody = sanitizeHtml(data.body,
{
allowedTags: [],
allowedAttributes: []
}).replace(/&quot;/g, '"');
if (data.body !== sanitizedBody) {
req.flash('errors', {
msg: 'HTML is not allowed'
});
return res.send(true);
}
var comment = new Comment({
associatedPost: data.associatedPost,
body: sanitizedBody,
rank: 0,
upvotes: 0,
author: data.author,
comments: [],
topLevel: true,
commentOn: Date.now()
});
commentSave(comment, Story, res);
};
exports.commentOnCommentSubmit = function(req, res) {
var data = req.body.data;
if (req.user._id.toString() !== data.author.userId.toString()) {
return res.status(500);
}
var sanitizedBody = sanitizeHtml(data.body,
{
allowedTags: [],
allowedAttributes: []
}).replace(/&quot;/g, '"');
if (data.body !== sanitizedBody) {
req.flash('errors', {
msg: 'HTML is not allowed'
});
return res.send(true);
}
var comment = new Comment({
associatedPost: data.associatedPost,
body: sanitizedBody,
rank: 0,
upvotes: 0,
author: data.author,
comments: [],
topLevel: false,
commentOn: Date.now()
});
commentSave(comment, Comment, res);
};
function commentSave(comment, Context, res) {
comment.save(function(err, data) {
if (err) {
return res.status(500);
} }
try { });
Context.find({'_id': comment.associatedPost}, function (err, associatedStory) { } catch (e) {
if (err) { // delete comment
return res.status(500); return next(err);
} }
associatedStory = associatedStory.pop(); });
if (associatedStory) {
associatedStory.comments.push(data._id);
associatedStory.save(function (err) {
if (err) {
res.status(500);
}
res.send(true);
});
}
});
} catch (e) {
// delete comment
return res.status(500);
}
});
} }

View File

@ -6,9 +6,9 @@ var _ = require('lodash'),
User = require('../models/User'), User = require('../models/User'),
secrets = require('../config/secrets'), secrets = require('../config/secrets'),
moment = require('moment'), moment = require('moment'),
Challenge = require('./../models/Challenge'),
debug = require('debug')('freecc:cntr:challenges'), debug = require('debug')('freecc:cntr:challenges'),
resources = require('./resources'); resources = require('./resources'),
R = require('ramda');
@ -18,7 +18,9 @@ var _ = require('lodash'),
*/ */
exports.getSignin = function(req, res) { exports.getSignin = function(req, res) {
if (req.user) return res.redirect('/'); if (req.user) {
return res.redirect('/');
}
res.render('account/signin', { res.render('account/signin', {
title: 'Free Code Camp Login' title: 'Free Code Camp Login'
}); });
@ -41,13 +43,17 @@ exports.postSignin = function(req, res, next) {
} }
passport.authenticate('local', function(err, user, info) { passport.authenticate('local', function(err, user, info) {
if (err) return next(err); if (err) {
return next(err);
}
if (!user) { if (!user) {
req.flash('errors', { msg: info.message }); req.flash('errors', { msg: info.message });
return res.redirect('/signin'); return res.redirect('/signin');
} }
req.logIn(user, function(err) { req.logIn(user, function(err) {
if (err) return next(err); if (err) {
return next(err);
}
req.flash('success', { msg: 'Success! You are logged in.' }); req.flash('success', { msg: 'Success! You are logged in.' });
res.redirect(req.session.returnTo || '/'); res.redirect(req.session.returnTo || '/');
}); });
@ -70,7 +76,9 @@ exports.signout = function(req, res) {
*/ */
exports.getEmailSignin = function(req, res) { exports.getEmailSignin = function(req, res) {
if (req.user) return res.redirect('/'); if (req.user) {
return res.redirect('/');
}
res.render('account/email-signin', { res.render('account/email-signin', {
title: 'Sign in to your Free Code Camp Account' title: 'Sign in to your Free Code Camp Account'
}); });
@ -82,7 +90,9 @@ exports.getEmailSignin = function(req, res) {
*/ */
exports.getEmailSignup = function(req, res) { exports.getEmailSignup = function(req, res) {
if (req.user) return res.redirect('/'); if (req.user) {
return res.redirect('/');
}
res.render('account/email-signup', { res.render('account/email-signup', {
title: 'Create Your Free Code Camp Account' title: 'Create Your Free Code Camp Account'
}); });
@ -98,7 +108,7 @@ exports.postEmailSignup = function(req, res, next) {
req.assert('email', 'valid email required').isEmail(); req.assert('email', 'valid email required').isEmail();
var errors = req.validationErrors(); var errors = req.validationErrors();
if (errors) { if (errors) {
req.flash('errors', errors); req.flash('errors', errors);
return res.redirect('/email-signup'); return res.redirect('/email-signup');
@ -124,7 +134,7 @@ exports.postEmailSignup = function(req, res, next) {
var user = new User({ var user = new User({
email: req.body.email.trim(), email: req.body.email.trim(),
password: req.body.password, password: req.body.password,
profile : { profile: {
username: req.body.username.trim(), username: req.body.username.trim(),
picture: 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png' picture: 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png'
} }
@ -174,7 +184,7 @@ exports.postEmailSignup = function(req, res, next) {
'Greetings from San Francisco!\n\n', 'Greetings from San Francisco!\n\n',
'Thank you for joining our community.\n', 'Thank you for joining our community.\n',
'Feel free to email us at this address if you have any questions about Free Code Camp.\n', 'Feel free to email us at this address if you have any questions about Free Code Camp.\n',
"And if you have a moment, check out our blog: blog.freecodecamp.com.\n", 'And if you have a moment, check out our blog: blog.freecodecamp.com.\n',
'Good luck with the challenges!\n\n', 'Good luck with the challenges!\n\n',
'- the Volunteer Camp Counselor Team' '- the Volunteer Camp Counselor Team'
].join('') ].join('')
@ -190,10 +200,45 @@ exports.postEmailSignup = function(req, res, next) {
* For Calendar display * For Calendar display
*/ */
exports.getStreak = function(req, res) { exports.getStreak = function(req, res, next) {
var completedStreak = req.user.challengesHash;
} req.user.progressTimestamps = req.user.progressTimestamps.sort(function(a, b) {
return a - b;
});
var timeObject = Object.create(null);
R.forEach(function(time) {
timeObject[moment(time).format('YYYY-MM-DD')] = time;
}, req.user.progressTimestamps);
var tmpLongest = 1;
var timeKeys = R.keys(timeObject);
for (var i = 1; i <= timeKeys.length; i++) {
if (moment(timeKeys[i - 1]).add(1, 'd').toString()
=== moment(timeKeys[i]).toString()) {
tmpLongest++;
if (tmpLongest > req.user.currentStreak) {
req.user.currentStreak = tmpLongest;
}
if ( req.user.currentStreak > req.user.longestStreak) {
req.user.longestStreak = req.user.currentStreak;
}
}
}
req.user.save(function(err) {
if (err) {
return next(err);
}
});
s
var payload = {
longest: req.user.longestStreak,
timeObject: timeObject
};
return res.send(payload);
};
/** /**
* GET /account * GET /account
@ -272,7 +317,7 @@ exports.returnUser = function(req, res, next) {
var data = {}; var data = {};
var progressTimestamps = user.progressTimestamps; var progressTimestamps = user.progressTimestamps;
for (var i = 0; i < progressTimestamps.length; i++) { for (var i = 0; i < progressTimestamps.length; i++) {
data[progressTimestamps[i].toString()] = 1; data[(progressTimestamps[i] / 1000).toString()] = 1;
} }
res.render('account/show', { res.render('account/show', {

View File

@ -1,7 +1,9 @@
var bcrypt = require('bcrypt-nodejs'); var bcrypt = require('bcrypt-nodejs');
var crypto = require('crypto'); var crypto = require('crypto');
var mongoose = require('mongoose'); var mongoose = require('mongoose');
require('mongoose-long')(mongoose);
var Long = mongoose.Types.Long;
var userSchema = new mongoose.Schema({ var userSchema = new mongoose.Schema({
email: { email: {
type: String, type: String,
@ -21,7 +23,7 @@ var userSchema = new mongoose.Schema({
type: Number, type: Number,
default: 0 default: 0
}, },
progressTimestamps: { type: Array, default: [Date] }, progressTimestamps: [],
challengesCompleted: { type: Array, default: [] }, challengesCompleted: { type: Array, default: [] },
pointsNeedMigration: { type: Boolean, default: true }, pointsNeedMigration: { type: Boolean, default: true },
challengesHash: { challengesHash: {
@ -332,9 +334,30 @@ var userSchema = new mongoose.Schema({
resetPasswordToken: String, resetPasswordToken: String,
resetPasswordExpires: Date, resetPasswordExpires: Date,
uncompletedBonfires: Array, uncompletedBonfires: Array,
completedBonfires: Array, completedBonfires: [
{
_id: String,
completedWith: String,
completedDate: Long,
solution: String
}
],
uncompletedCoursewares: Array, uncompletedCoursewares: Array,
completedCoursewares: Array completedCoursewares: [
{
completedDate: Long,
_id: String,
name: String
}
],
currentStreak: {
type: Number,
default: 0
},
longestStreak: {
type: Number,
default: 0
}
}); });
/** /**

View File

@ -47,11 +47,9 @@
"method-override": "^2.3.0", "method-override": "^2.3.0",
"moment": "^2.8.4", "moment": "^2.8.4",
"mongodb": "^1.4.33", "mongodb": "^1.4.33",
"mongoose": "^3.8.19", "mongoose": "^4.0.0",
"mongoose-text-search": "0.0.2",
"morgan": "^1.5.0", "morgan": "^1.5.0",
"newrelic": "^1.13.3", "newrelic": "^1.13.3",
"node": "0.0.0",
"nodemailer": "^1.3.0", "nodemailer": "^1.3.0",
"passport": "^0.2.1", "passport": "^0.2.1",
"passport-facebook": "^1.0.3", "passport-facebook": "^1.0.3",
@ -76,7 +74,6 @@
"chai": "^1.10.0", "chai": "^1.10.0",
"gulp": "^3.8.8", "gulp": "^3.8.8",
"gulp-inject": "^1.0.2", "gulp-inject": "^1.0.2",
"gulp-minify-css": "^0.5.1",
"gulp-nodemon": "^1.0.4", "gulp-nodemon": "^1.0.4",
"mocha": "^2.0.1", "mocha": "^2.0.1",
"multiline": "^1.0.1", "multiline": "^1.0.1",