diff --git a/app.js b/app.js index bd36ff727c..aec3f88d4d 100755 --- a/app.js +++ b/app.js @@ -253,12 +253,8 @@ app.use(express.static(__dirname + '/public', { maxAge: 86400000 })); app.get('/', homeController.index); -app.get('/privacy', function(req, res) { - res.redirect(301, "/field-guide/free-code-camp's-privacy-policy"); -}); - app.get('/nonprofit-project-instructions', function(req, res) { - res.redirect(301, "/field-guide/free-code-camp's-privacy-policy"); + res.redirect(301, '/field-guide/nonprofit-project-instructions'); }); app.get('/chat', resourcesController.chat); @@ -384,17 +380,17 @@ app.post( passportConf.isAuthenticated, contactController.postDoneWithFirst100Hours ); -//app.get( -// '/nonprofit-project-instructions', -// passportConf.isAuthenticated, -// resourcesController.nonprofitProjectInstructions -//); + app.post( '/update-progress', passportConf.isAuthenticated, userController.updateProgress ); +app.get('/privacy', function(req, res) { + res.redirect(301, '/field-guide/the-free-code-camp-privacy-policy'); +}); + app.get('/api/slack', function(req, res) { if (req.user) { if (req.user.email) { @@ -590,7 +586,7 @@ app.post('/completed-bonfire/', bonfireController.completedBonfire); app.get('/field-guide/:fieldGuideName', fieldGuideController.returnIndividualFieldGuide); -app.get('/field-guide', fieldGuideController.returnNextFieldGuide); +app.get('/field-guide/', fieldGuideController.returnNextFieldGuide); app.post('/completed-field-guide/', fieldGuideController.completedFieldGuide); diff --git a/controllers/bonfire.js b/controllers/bonfire.js index dbe60a0da0..88678099e1 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -63,12 +63,12 @@ exports.returnNextBonfire = function(req, res, next) { var uncompletedBonfires = req.user.uncompletedBonfires; var displayedBonfires = Bonfire.find({'_id': uncompletedBonfires[0]}); - displayedBonfires.exec(function(err, bonfire) { + displayedBonfires.exec(function(err, bonfireFromMongo) { if (err) { return next(err); } - bonfire = bonfire.pop(); - if (bonfire === undefined) { + var bonfire = bonfireFromMongo.pop(); + if (typeof bonfire === 'undefined') { req.flash('errors', { msg: "It looks like you've completed all the bonfires we have available. Good job!" }); @@ -84,13 +84,13 @@ exports.returnIndividualBonfire = function(req, res, next) { var bonfireName = dashedName.replace(/\-/g, ' '); - Bonfire.find({'name': new RegExp(bonfireName, 'i')}, function(err, bonfire) { + Bonfire.find({'name': new RegExp(bonfireName, 'i')}, function(err, bonfireFromMongo) { if (err) { next(err); } - if (bonfire.length < 1) { + if (bonfireFromMongo.length < 1) { req.flash('errors', { msg: "404: We couldn't find a bonfire with that name. Please double check the name." }); @@ -98,9 +98,9 @@ exports.returnIndividualBonfire = function(req, res, next) { return res.redirect('/bonfires'); } - bonfire = bonfire.pop(); + var bonfire = bonfireFromMongo.pop(); var dashedNameFull = bonfire.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { + if (dashedNameFull !== dashedName) { return res.redirect('../bonfires/' + dashedNameFull); } res.render('bonfire/show', { diff --git a/controllers/courseware.js b/controllers/courseware.js index a7d2b4af0c..3feb22f3d4 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -50,14 +50,14 @@ exports.returnNextCourseware = function(req, res, next) { } courseware = courseware.pop(); - if (courseware === undefined) { + if (typeof courseware === 'undefined') { req.flash('errors', { 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'); } - nameString = courseware.name.toLowerCase().replace(/\s/g, '-'); + var nameString = courseware.name.toLowerCase().replace(/\s/g, '-'); return res.redirect('../challenges/' + nameString); }); }; @@ -68,23 +68,23 @@ exports.returnIndividualCourseware = function(req, res, next) { var coursewareName = dashedName.replace(/\-/g, ' '); Courseware.find({'name': new RegExp(coursewareName, 'i')}, - function(err, courseware) { + function(err, coursewareFromMongo) { if (err) { next(err); } // Handle not found - if (courseware.length < 1) { + if (coursewareFromMongo.length < 1) { req.flash('errors', { msg: "404: We couldn't find a challenge with that name. " + "Please double check the name." }); return res.redirect('/challenges'); } - courseware = courseware.pop(); + var courseware = coursewareFromMongo.pop(); // Redirect to full name if the user only entered a partial var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { + if (dashedNameFull !== dashedName) { return res.redirect('../challenges/' + dashedNameFull); } @@ -292,7 +292,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { if (isCompletedWith) { var paired = User.find({'profile.username': isCompletedWith.toLowerCase()}).limit(1); - paired.exec(function (err, pairedWith) { + paired.exec(function (err, pairedWithFromMongo) { if (err) { return next(err); } else { @@ -301,7 +301,7 @@ exports.completedZiplineOrBasejump = function (req, res, next) { req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedCoursewares.splice(index, 1); } - pairedWith = pairedWith.pop(); + var pairedWith = pairedWithFromMongo.pop(); req.user.completedCoursewares.push({ _id: coursewareHash, diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js index d131e6436b..d4bf9a216d 100644 --- a/controllers/fieldGuide.js +++ b/controllers/fieldGuide.js @@ -9,12 +9,12 @@ exports.returnIndividualFieldGuide = function(req, res, next) { var fieldGuideName = dashedName.replace(/\-/g, ' '); - FieldGuide.find({'name': new RegExp(fieldGuideName, 'i')}, function(err, fieldGuide) { + FieldGuide.find({'name': new RegExp(fieldGuideName, 'i')}, function(err, fieldGuideFromMongo) { if (err) { next(err); } - if (fieldGuide.length < 1) { + if (fieldGuideFromMongo.length < 1) { req.flash('errors', { msg: "404: We couldn't find a field guide entry with that name. Please double check the name." }); @@ -22,9 +22,9 @@ exports.returnIndividualFieldGuide = function(req, res, next) { return res.redirect('/field-guide'); } - fieldGuide = fieldGuide.pop(); + var fieldGuide = fieldGuideFromMongo.pop(); var dashedNameFull = fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); - if (dashedNameFull != dashedName) { + if (dashedNameFull !== dashedName) { return res.redirect('../field-guide/' + dashedNameFull); } res.render('field-guide/show', { @@ -42,7 +42,7 @@ exports.showAllFieldGuides = function(req, res) { if (req.user && req.user.completedFieldGuides) { data.completedFieldGuides = req.user.completedFieldGuides; } else { - data.completedFieldGuides = [] + data.completedFieldGuides = []; } res.send(data); }; @@ -69,7 +69,7 @@ exports.returnNextFieldGuide = function(req, res, next) { return next(err); } fieldGuide = fieldGuide.pop(); - if (fieldGuide === undefined) { + if (typeof fieldGuide === 'undefined') { req.flash('success', { msg: "You've read all our current Field Guide entries. You can contribute to our Field Guide here." }); diff --git a/controllers/resources.js b/controllers/resources.js index a3f32179c7..d9e9450c6f 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -8,7 +8,6 @@ var async = require('async'), Nonprofit = require('./../models/Nonprofit'), Comment = require('./../models/Comment'), resources = require('./resources.json'), - steps = resources.steps, secrets = require('./../config/secrets'), bonfires = require('../seed_data/bonfires.json'), nonprofits = require('../seed_data/nonprofits.json'), @@ -21,6 +20,12 @@ var async = require('async'), request = require('request'), R = require('ramda'); +/** + * Cached values + */ +var allBonfireIds, allBonfireNames, allCoursewareIds, allCoursewareNames, + allFieldGuideIds, allFieldGuideNames, allNonprofitNames; + /** * GET / * Resources. @@ -38,63 +43,106 @@ Array.zip = function(left, right, combinerFunction) { }; module.exports = { - privacy: function privacy(req, res) { - res.render('resources/privacy', { - title: 'Privacy' - }); - }, - sitemap: function sitemap(req, res, next) { var appUrl = 'http://www.freecodecamp.com'; var now = moment(new Date()).format('YYYY-MM-DD'); - User.find({'profile.username': {'$ne': '' }}, function(err, users) { - if (err) { - debug('User err: ', err); - return next(err); - } - Courseware.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) { - debug('User err: ', err); - return next(err); - } - Nonprofit.find({}, function (err, nonprofits) { + + async.parallel({ + users: function(callback) { + User.aggregate() + .group({_id: 1, usernames: { $addToSet: '$profile.username'}}) + .match({'profile.username': { $ne: ''}}) + .exec(function(err, users) { if (err) { debug('User err: ', err); - return next(err); + callback(err); + } else { + callback(null, users[0].usernames); } - FieldGuide.find({}, function (err, fieldGuides) { - 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, - nonprofits: nonprofits, - fieldGuides: fieldGuides - }); - }); }); + }, + + challenges: function (callback) { + Courseware.aggregate() + .group({_id: 1, names: { $addToSet: '$name'}}) + .exec(function (err, challenges) { + if (err) { + debug('Courseware err: ', err); + callback(err); + } else { + callback(null, challenges[0].names); + } + }); + }, + bonfires: function (callback) { + Bonfire.aggregate() + .group({_id: 1, names: { $addToSet: '$name'}}) + .exec(function (err, bonfires) { + if (err) { + debug('Bonfire err: ', err); + callback(err); + } else { + callback(null, bonfires[0].names); + } }); - }); - }); - }); + }, + stories: function (callback) { + Story.aggregate() + .group({_id: 1, links: {$addToSet: '$link'}}) + .exec(function (err, stories) { + if (err) { + debug('Story err: ', err); + callback(err); + } else { + callback(null, stories[0].links); + } + }); + }, + nonprofits: function (callback) { + Nonprofit.aggregate() + .group({_id: 1, names: { $addToSet: '$name'}}) + .exec(function (err, nonprofits) { + if (err) { + debug('User err: ', err); + callback(err); + } else { + callback(null, nonprofits[0].names); + } + }); + }, + fieldGuides: function (callback) { + FieldGuide.aggregate() + .group({_id: 1, names: { $addToSet: '$name'}}) + .exec(function (err, fieldGuides) { + if (err) { + debug('User err: ', err); + callback(err); + } else { + callback(null, fieldGuides[0].names); + } + }); + } + }, function (err, results) { + if (err) { + return next(err); + } else { + setTimeout(function() { + res.header('Content-Type', 'application/xml'); + res.render('resources/sitemap', { + appUrl: appUrl, + now: now, + users: results.users, + challenges: results.challenges, + bonfires: results.bonfires, + stories: results.stories, + nonprofits: results.nonprofits, + fieldGuides: results.fieldGuides + }); + }, 0); + } + } + ); }, chat: function chat(req, res) { @@ -161,132 +209,165 @@ module.exports = { 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', - daysRunning: daysRunning, - c3: numberWithCommas(c3), - announcements: announcements - }); + res.render('resources/learn-to-code', { + title: 'About Free Code Camp', + daysRunning: daysRunning, + c3: numberWithCommas(c3), + announcements: announcements }); }); }, randomPhrase: function() { - var phrases = resources.phrases; - return phrases[Math.floor(Math.random() * phrases.length)]; + return resources.phrases[Math.floor( + Math.random() * resources.phrases.length)]; }, randomVerb: function() { - var verbs = resources.verbs; - return verbs[Math.floor(Math.random() * verbs.length)]; + return resources.verbs[Math.floor( + Math.random() * resources.verbs.length)]; }, randomCompliment: function() { - var compliments = resources.compliments; - return compliments[Math.floor(Math.random() * compliments.length)]; + return resources.compliments[Math.floor( + Math.random() * resources.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; - }); + if (allBonfireIds) { + return allBonfireIds; + } else { + allBonfireIds = 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; + }); + return allBonfireIds; + } }, allFieldGuideIds: function() { - return fieldGuides.map(function(elem) { - return { - _id: elem._id, - } - }) - .map(function(elem) { - return elem._id; - }); + if (allFieldGuideIds) { + return allFieldGuideIds; + } else { + allFieldGuideIds = fieldGuides. + map(function (elem) { + return { + _id: elem._id + }; + }); + return allFieldGuideIds; + } }, 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 - } - }); + if (allBonfireNames) { + return allBonfireNames; + } else { + allBonfireNames = 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 + }; + }); + return allBonfireNames; + } }, allFieldGuideNames: function() { - return fieldGuides.map(function(elem) { - return { - name: elem.name - } - }) + if (allFieldGuideNames) { + return allFieldGuideNames; + } else { + allFieldGuideNames = fieldGuides. + map(function (elem) { + return { + name: elem.name + }; + }); + return allFieldGuideNames; + } }, allNonprofitNames: function() { - return nonprofits.map(function(elem) { - return { - name: elem.name - } - }) + if (allNonprofitNames) { + return allNonprofitNames; + } else { + allNonprofitNames = nonprofits. + map(function (elem) { + return { + name: elem.name + }; + }); + return allNonprofitNames; + } }, 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; - }); + if (allCoursewareIds) { + return allCoursewareIds; + } else { + allCoursewareIds = 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; + }); + return allCoursewareIds; + } }, allCoursewareNames: function() { - return coursewares.map(function(elem) { - return { - name: elem.name, - difficulty: elem.difficulty, - challengeType: elem.challengeType, - _id: elem._id - }; - }) - .sort(function(a, b) { - return a.difficulty - b.difficulty; - }) - .map (function(elem) { - return { - name: elem.name, - challengeType: elem.challengeType, - _id: elem._id - }; - }); + if (allCoursewareNames) { + return allCoursewareNames; + } else { + allCoursewareNames = coursewares. + map(function (elem) { + return { + name: elem.name, + difficulty: elem.difficulty, + challengeType: elem.challengeType, + _id: elem._id + }; + }). + sort(function (a, b) { + return a.difficulty - b.difficulty; + }). + map(function (elem) { + return { + name: elem.name, + challengeType: elem.challengeType, + _id: elem._id + }; + }); + return allCoursewareNames; + } }, whichEnvironment: function() { @@ -302,8 +383,9 @@ module.exports = { var metaDescription = $("meta[name='description']"); var metaImage = $("meta[property='og:image']"); var urlImage = metaImage.attr('content') ? metaImage.attr('content') : ''; + var metaTitle = $('title'); var description = metaDescription.attr('content') ? metaDescription.attr('content') : ''; - result.title = $('title').text().length < 141 ? $('title').text() : $('title').text().slice(0, 137) + " ..."; + result.title = metaTitle.text().length < 141 ? metaTitle.text() : metaTitle.text().slice(0, 137) + " ..."; result.image = urlImage; result.description = description; callback(null, result); @@ -360,7 +442,9 @@ module.exports = { }); }, foundStories); async.parallel(tasks, function(err) { - if (err) { return cb(err); } + if (err) { + return cb(err); + } cb(); }); } diff --git a/controllers/user.js b/controllers/user.js index 034716ddcb..c00a0a1f6d 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -253,7 +253,7 @@ exports.checkExistingUsername = function(req, res, next) { exports.checkUniqueEmail = function(req, res, next) { User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) { if (err) { return next(err); } - if (data == 1) { + if (data === 1) { return res.send(true); } else { return res.send(false); @@ -271,7 +271,7 @@ exports.returnUser = function(req, res, next) { User.find({'profile.username': req.params.username.toLowerCase()}, function(err, user) { if (err) { debug('Username err: ', err); next(err); } if (user[0]) { - var user = user[0]; + user = user[0]; user.progressTimestamps = user.progressTimestamps.sort(function(a, b) { return a - b; @@ -284,6 +284,7 @@ exports.returnUser = function(req, res, next) { 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()) { @@ -305,13 +306,17 @@ exports.returnUser = function(req, res, next) { var data = {}; var progressTimestamps = user.progressTimestamps; - for (var i = 0; i < progressTimestamps.length; i++) { - data[(progressTimestamps[i] / 1000).toString()] = 1; - } + progressTimestamps.forEach(function(timeStamp) { + data[(timeStamp / 1000)] = 1; + }); + + //for (var i = 0; i < progressTimestamps.length; i++) { + // data[(progressTimestamps[i] / 1000).toString()] = 1; + //} user.currentStreak = user.currentStreak || 1; user.longestStreak = user.longestStreak || 1; - challenges = user.completedCoursewares.filter(function ( obj ) { + var challenges = user.completedCoursewares.filter(function ( obj ) { return !!obj.solution; }); res.render('account/show', { diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index d3207d38f9..24d2813064 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -644,7 +644,8 @@ "difficulty": "3.12", "description": [ "Fill in the object constructor with the methods specified in the tests.", - "Those methods are getFirstName(), getLastName(), getFullName(), setFirstName(), setLastName(), and setFullName().", + "Those methods are getFirstName(), getLastName(), getFullName(), setFirstName(first), setLastName(last), and setFullName(firstAndLast).", + "All functions that take an argument have an arity of 1, and the argument will be a string.", "These methods must be the only available means for interacting with the object." ], "challengeSeed": "var Person = function(firstAndLast) {\n return firstAndLast;\r\n};\n\nvar bob = new Person('Bob Ross');\nbob.getFullName();", diff --git a/views/resources/sitemap.jade b/views/resources/sitemap.jade index 5c9a2c7b88..7d7c7b1c1c 100644 --- a/views/resources/sitemap.jade +++ b/views/resources/sitemap.jade @@ -32,18 +32,19 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") changefreq daily priority= 0.8 - //- Users + //- User each user in users url - loc #{appUrl}/#{user.profile.username} + loc #{appUrl}/#{user} lastmod= now changefreq daily priority= 0.9 + //- Products each bonfire in bonfires url - loc #{appUrl}/bonfires/#{bonfire.name.replace(/\s/g, '-')} + loc #{appUrl}/bonfires/#{bonfire.replace(/\s/g, '-')} lastmod= now changefreq weekly priority= 0.5 @@ -51,7 +52,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Challenges each challenge in challenges url - loc #{appUrl}/challenges/#{challenge.name.replace(/\s/g, '-')} + loc #{appUrl}/challenges/#{challenge.replace(/\s/g, '-')} lastmod= now changefreq weekly priority= 0.5 @@ -59,7 +60,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Stories each story in stories url - loc #{appUrl}/stories/#{story.storyLink.replace(/\s/g, '-')} + loc #{appUrl}/stories/#{story.replace(/\s/g, '-')} lastmod= now changefreq daily priority= 0.9 @@ -67,7 +68,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Nonprofit each nonprofit in nonprofits url - loc #{appUrl}/nonprofits/#{nonprofit.name.replace(/\s/g, '-')} + loc #{appUrl}/nonprofits/#{nonprofit.replace(/\s/g, '-')} lastmod= now changefreq daily priority= 0.9 @@ -75,7 +76,7 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") //- Nonprofit each fieldGuide in fieldGuides url - loc #{appUrl}/field-guide/#{fieldGuide.name.replace(/\s/g, '-')} + loc #{appUrl}/field-guide/#{fieldGuide.replace(/\s/g, '-')} lastmod= now changefreq daily priority= 0.9