diff --git a/app.js b/app.js index ff745098db..07314c65dc 100644 --- a/app.js +++ b/app.js @@ -398,6 +398,8 @@ app.post( app.all('/account', passportConf.isAuthenticated); app.get('/account/api', userController.getAccountAngular); +app.get('/user/streak', userController.getStreak); + /** * API routes */ @@ -449,7 +451,6 @@ app.post('/account/password', userController.postUpdatePassword); app.post('/account/delete', userController.postDeleteAccount); app.get('/account/unlink/:provider', userController.getOauthUnlink); app.get('/sitemap.xml', resourcesController.sitemap); - /** * OAuth sign-in routes. */ diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 6a4712f3b1..3fecaa89f7 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -236,6 +236,7 @@ exports.completedBonfire = function (req, res) { } else { var index = req.user.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedBonfires.splice(index, 1) } @@ -243,6 +244,7 @@ exports.completedBonfire = function (req, res) { index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { + pairedWith.progressTimestamps.push(Date.now() || 0); pairedWith.uncompletedBonfires.splice(index, 1); @@ -285,6 +287,7 @@ exports.completedBonfire = function (req, res) { var index = req.user.uncompletedBonfires.indexOf(bonfireHash); if (index > -1) { + req.user.progressTimestamps.push(Date.now() || 0); req.user.uncompletedBonfires.splice(index, 1) } diff --git a/controllers/courseware.js b/controllers/courseware.js index ecbb6b2341..3b932894f8 100644 --- a/controllers/courseware.js +++ b/controllers/courseware.js @@ -1,271 +1,263 @@ var _ = require('lodash'), - debug = require('debug')('freecc:cntr:courseware'), - Courseware = require('./../models/Courseware'), - User = require('./../models/User'), - resources = require('./resources'), - R = require('ramda'); + debug = require('debug')('freecc:cntr:courseware'), + Courseware = require('./../models/Courseware'), + User = require('./../models/User'), + resources = require('./resources'), + R = require('ramda'), + moment = require('moment'); /** * Courseware controller */ exports.showAllCoursewares = function(req, res) { - var completedCoursewares = req.user.completedCoursewares.map(function(elem) { - return elem._id; - }); + var completedCoursewares = req.user.completedCoursewares.map(function(elem) { + return elem._id; + }); - var noDuplicatedCoursewares = R.uniq(completedCoursewares); - var data = {}; - data.coursewareList = resources.allCoursewareNames(); - data.completedList = noDuplicatedCoursewares; - res.send(data); + var noDuplicatedCoursewares = R.uniq(completedCoursewares); + var data = {}; + data.coursewareList = resources.allCoursewareNames(); + data.completedList = noDuplicatedCoursewares; + res.send(data); }; exports.returnNextCourseware = function(req, res) { - if (!req.user) { - return res.redirect('../challenges/learn-how-free-code-camp-works'); + if (!req.user) { + 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) { - if (completed.indexOf(elem) === -1) { - return elem; - } - }); - req.user.save(); - - var uncompletedCoursewares = req.user.uncompletedCoursewares.shift(); + var uncompletedCoursewares = req.user.uncompletedCoursewares.shift(); - var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares}); - displayedCoursewares.exec(function(err, courseware) { - if (err) { - next(err); - } + var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares}); + displayedCoursewares.exec(function(err, courseware) { + if (err) { + next(err); + } - courseware = courseware.pop(); - if (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, '-'); - return res.redirect('../challenges/' + nameString); - }); + courseware = courseware.pop(); + if (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, '-'); + return res.redirect('../challenges/' + nameString); + }); }; 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) { - if (err) { - next(err); - } - // Handle not found - if (courseware.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a challenge with that name. Please double check the name." + Courseware.find({"name" : new RegExp(coursewareName, 'i')}, function(err, courseware) { + if (err) { + next(err); + } + // Handle not found + if (courseware.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(); + + // 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 - var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull != dashedName) { - return res.redirect('../challenges/' + dashedNameFull); - } + 1 : function() { + res.render('coursewares/showJS', { + 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, - 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() { - res.render('coursewares/showJS', { - 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, + 2: 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: '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() { - 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: 'video' - }); - }, + 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' + }); + } + }; - 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' - }); - }, + return challengeType[courseware.challengeType](); - 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) { - var coursewareName = req.body.name, - coursewareTests = req.body.tests, - coursewareDifficulty = req.body.difficulty, - coursewareDescription = req.body.description, - coursewareEntryPoint = req.body.challengeEntryPoint, - coursewareChallengeSeed = req.body.challengeSeed; - coursewareTests = coursewareTests.split('\r\n'); - coursewareDescription = coursewareDescription.split('\r\n'); - coursewareTests.filter(getRidOfEmpties); - coursewareDescription.filter(getRidOfEmpties); - coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); - res.render('courseware/show', { - completedWith: null, - title: coursewareName, - name: coursewareName, - difficulty: +coursewareDifficulty, - brief: coursewareDescription[0], - details: coursewareDescription.slice(1), - tests: coursewareTests, - challengeSeed: coursewareChallengeSeed, - challengeEntryPoint: coursewareEntryPoint, - cc: req.user ? req.user.coursewaresHash : undefined, - progressTimestamps: req.user ? req.user.progressTimestamps : undefined, - verb: resources.randomVerb(), - phrase: resources.randomPhrase(), - compliment: resources.randomCompliment(), - coursewares: [], - coursewareHash: "test" - }); + var coursewareName = req.body.name, + coursewareTests = req.body.tests, + coursewareDifficulty = req.body.difficulty, + coursewareDescription = req.body.description, + coursewareEntryPoint = req.body.challengeEntryPoint, + coursewareChallengeSeed = req.body.challengeSeed; + coursewareTests = coursewareTests.split('\r\n'); + coursewareDescription = coursewareDescription.split('\r\n'); + coursewareTests.filter(getRidOfEmpties); + coursewareDescription.filter(getRidOfEmpties); + coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); + res.render('courseware/show', { + completedWith: null, + title: coursewareName, + name: coursewareName, + difficulty: +coursewareDifficulty, + brief: coursewareDescription[0], + details: coursewareDescription.slice(1), + tests: coursewareTests, + challengeSeed: coursewareChallengeSeed, + challengeEntryPoint: coursewareEntryPoint, + cc: req.user ? req.user.coursewaresHash : undefined, + progressTimestamps: req.user ? req.user.progressTimestamps : undefined, + verb: resources.randomVerb(), + phrase: resources.randomPhrase(), + compliment: resources.randomCompliment(), + coursewares: [], + coursewareHash: "test" + }); }; function getRidOfEmpties(elem) { - if (elem.length > 0) { - return elem; - } + if (elem.length > 0) { + return elem; + } }; exports.publicGenerator = function(req, res) { - res.render('courseware/public-generator'); + res.render('courseware/public-generator'); }; exports.generateChallenge = function(req, res) { - var coursewareName = req.body.name, - coursewareTests = req.body.tests, - coursewareDifficulty = req.body.difficulty, - coursewareDescription = req.body.description, - coursewareEntryPoint = req.body.challengeEntryPoint, - coursewareChallengeSeed = req.body.challengeSeed; - coursewareTests = coursewareTests.split('\r\n'); - coursewareDescription = coursewareDescription.split('\r\n'); - coursewareTests.filter(getRidOfEmpties); - coursewareDescription.filter(getRidOfEmpties); - coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); + var coursewareName = req.body.name, + coursewareTests = req.body.tests, + coursewareDifficulty = req.body.difficulty, + coursewareDescription = req.body.description, + coursewareEntryPoint = req.body.challengeEntryPoint, + coursewareChallengeSeed = req.body.challengeSeed; + coursewareTests = coursewareTests.split('\r\n'); + coursewareDescription = coursewareDescription.split('\r\n'); + coursewareTests.filter(getRidOfEmpties); + coursewareDescription.filter(getRidOfEmpties); + coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', ''); - var response = { - _id: randomString(), - name: coursewareName, - difficulty: coursewareDifficulty, - description: coursewareDescription, - challengeEntryPoint: coursewareEntryPoint, - challengeSeed: coursewareChallengeSeed, - tests: coursewareTests - }; - res.send(response); + var response = { + _id: randomString(), + name: coursewareName, + difficulty: coursewareDifficulty, + description: coursewareDescription, + challengeEntryPoint: coursewareEntryPoint, + challengeSeed: coursewareChallengeSeed, + tests: coursewareTests + }; + res.send(response); }; exports.completedCourseware = function (req, res, next) { - var isCompletedDate = Math.round(+new Date()); - var coursewareHash = req.body.coursewareInfo.coursewareHash; + var isCompletedDate = Math.round(+new Date()); + 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({ - _id: coursewareHash, - completedDate: isCompletedDate, - name: req.body.coursewareInfo.coursewareName - }); + req.user.completedCoursewares.push({ + _id: coursewareHash, + completedDate: isCompletedDate, + name: req.body.coursewareInfo.coursewareName + }); - var index = req.user.completedCoursewares.indexOf(coursewareHash); + var index = req.user.completedCoursewares.indexOf(coursewareHash); - if (index === -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedCoursewares.splice(index, 1); - } + if (index === -1) { - req.user.save(function (err, user) { - if (err) { - return next(err); - } - if (user) { - res.send(true); - } - }); -}; + req.user.progressTimestamps.push(Date.now() || 0); + req.user.uncompletedCoursewares.splice(index, 1); + } exports.completedBasejump = function (req, res, next) { var isCompletedWith = req.body.bonfireInfo.completedWith || undefined; @@ -275,76 +267,94 @@ exports.completedBasejump = function (req, res, next) { if(!solutionLink) { // flash error and redirect } + if (user) { + res.send(true); + } + }; +}; - if (isCompletedWith) { - var paired = User.find({"profile.username": isCompletedWith.toLowerCase()}).limit(1); - paired.exec(function (err, pairedWith) { - if (err) { - return err; - } else { - var index = req.user.uncompletedBonfires.indexOf(bonfireHash); - if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedBonfires.splice(index, 1) - } - pairedWith = pairedWith.pop(); +exports.completedZiplineOrBasejump = function (req, res, next) { + var isCompletedWith = req.body.bonfireInfo.completedWith || false; + var isCompletedDate = Math.round(+new Date()); + var coursewareHash = req.body.coursewareInfo.coursewareHash; + var solutionLink = req.body.coursewareInfo.solutionLink; + if (!solutionLink) { + // flash error and redirect + return next(new Error('No solution provided')); + } - index = pairedWith.uncompletedBonfires.indexOf(bonfireHash); - if (index > -1) { - pairedWith.progressTimestamps.push(Date.now() || 0); - pairedWith.uncompletedBonfires.splice(index, 1); - - } - - 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 (isCompletedWith) { + var paired = User.find({'profile.username': isCompletedWith.toLowerCase()}).limit(1); + paired.exec(function (err, pairedWith) { + if (err) { + return next(err); + } else { + var index = req.user.uncompletedCoursewares.indexOf(coursewareHash); if (index > -1) { - req.user.progressTimestamps.push(Date.now() || 0); - req.user.uncompletedBonfires.splice(index, 1) + req.user.progressTimestamps.push(Date.now() || 0); + 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) { - if (err) { - throw err; - } - if (user) { - debug('Saving user'); - res.send(true) - } + pairedWith.completedCoursewares.push({ + _id: coursewareHash, + completedWith: req.user._id, + completedDate: isCompletedDate, + solution: solutionLink }); + + 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); } -}; \ No newline at end of file + + req.user.save(function (err, user) { + if (err) { + return next(err); + } + if (user) { + debug('Saving user'); + return res.send(true); + } + }); + } +}; diff --git a/controllers/resources.js b/controllers/resources.js index 8cdd70e6d5..ff66bfeb7e 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -1,20 +1,20 @@ var async = require('async'), - User = require('../models/User'), - Challenge = require('./../models/Challenge'), - Bonfire = require('./../models/Bonfire'), - Story = require('./../models/Story'), - Comment = require('./../models/Comment'), - resources = require('./resources.json'), - steps = resources.steps, - secrets = require('./../config/secrets'), - bonfires = require('../seed_data/bonfires.json'), - coursewares = require('../seed_data/coursewares.json'), - moment = require('moment'), - https = require('https'), - debug = require('debug')('freecc:cntr:resources'), - cheerio = require('cheerio'), - request = require('request'), - R = require('ramda'); + User = require('../models/User'), + Challenge = require('./../models/Challenge'), + Bonfire = require('./../models/Bonfire'), + Story = require('./../models/Story'), + Comment = require('./../models/Comment'), + resources = require('./resources.json'), + steps = resources.steps, + secrets = require('./../config/secrets'), + bonfires = require('../seed_data/bonfires.json'), + coursewares = require('../seed_data/coursewares.json'), + moment = require('moment'), + https = require('https'), + debug = require('debug')('freecc:cntr:resources'), + cheerio = require('cheerio'), + request = require('request'), + R = require('ramda'); /** * GET / @@ -26,370 +26,381 @@ Array.zip = function(left, right, combinerFunction) { results = []; 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; }; module.exports = { - privacy: function privacy(req, res) { - res.render('resources/privacy', { - title: 'Privacy' - }); - }, + 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'); + 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) { + 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) { - debug('User err: ', err); - return next(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) { - 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 - }); - }); - }); + 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) { - res.render('resources/deploy-a-website', { - title: 'Deploy a Dynamic Website in 7 Minutes' - }); - }, + deployAWebsite: function deployAWebsite(req, res) { + res.render('resources/deploy-a-website', { + title: 'Deploy a Dynamic Website in 7 Minutes' + }); + }, - chat: function chat(req, res) { - res.render('resources/chat', { - title: "Enter Free Code Camp's Chat Rooms" - }); - }, + chat: function chat(req, res) { + res.render('resources/chat', { + title: "Enter Free Code Camp's Chat Rooms" + }); + }, - nonprofitProjectInstructions: function nonprofitProjectInstructions(req, res) { - res.render('resources/nonprofit-project-instructions', { - title: 'Nonprofit Project Instructions' - }); - }, + nonprofitProjectInstructions: function nonprofitProjectInstructions(req, res) { + res.render('resources/nonprofit-project-instructions', { + title: 'Nonprofit Project Instructions' + }); + }, - gmailShortcuts: function gmailShortcuts(req, res) { - res.render('resources/gmail-shortcuts', { - title: 'These Gmail Shortcuts will save you Hours' - }); - }, + gmailShortcuts: function gmailShortcuts(req, res) { + res.render('resources/gmail-shortcuts', { + title: 'These Gmail Shortcuts will save you Hours' + }); + }, - guideToOurNonprofitProjects: function guideToOurNonprofitProjects(req, res) { - res.render('resources/guide-to-our-nonprofit-projects', { - title: 'A guide to our Nonprofit Projects' - }); - }, + guideToOurNonprofitProjects: function guideToOurNonprofitProjects(req, res) { + res.render('resources/guide-to-our-nonprofit-projects', { + title: 'A guide to our Nonprofit Projects' + }); + }, - controlShortcuts: function controlShortcuts(req, res) { - res.render('resources/control-shortcuts', { - title: 'These Control Shortcuts will save you Hours' - }); - }, + controlShortcuts: function controlShortcuts(req, res) { + res.render('resources/control-shortcuts', { + title: 'These Control Shortcuts will save you Hours' + }); + }, - chromebook: function chromebook(req, res) { - res.render('resources/chromebook', { - title: 'Win a Chromebook' - }); - }, + chromebook: function chromebook(req, res) { + res.render('resources/chromebook', { + title: 'Win a Chromebook' + }); + }, - jqueryExercises: function jqueryExercises(req, res) { - res.render('resources/jquery-exercises', { - title: 'jQuery Exercises' - }); - }, + jqueryExercises: function jqueryExercises(req, res) { + res.render('resources/jquery-exercises', { + title: 'jQuery Exercises' + }); + }, - livePairProgramming: function(req, res) { - res.render('resources/live-pair-programming', { - title: 'Live Pair Programming' - }); - }, + livePairProgramming: function(req, res) { + res.render('resources/live-pair-programming', { + title: 'Live Pair Programming' + }); + }, - installScreenHero: function(req, res) { - res.render('resources/install-screenhero', { - title: 'Install ScreenHero' - }); - }, + installScreenHero: function(req, res) { + res.render('resources/install-screenhero', { + title: 'Install ScreenHero' + }); + }, - javaScriptInYourInbox: function(req, res) { - res.render('resources/javascript-in-your-inbox', { - title: 'JavaScript in your Inbox' - }); - }, + javaScriptInYourInbox: function(req, res) { + res.render('resources/javascript-in-your-inbox', { + title: 'JavaScript in your Inbox' + }); + }, - nodeSchoolChallenges: function(req, res) { - res.render('resources/nodeschool-challenges', { - title: 'NodeSchool Challenges' - }); - }, + nodeSchoolChallenges: function(req, res) { + res.render('resources/nodeschool-challenges', { + title: 'NodeSchool Challenges' + }); + }, - 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 }; - 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"; - 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"; - res.send({"issues": issues, "pulls" : pulls}); - }); - }); - }, + 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 }; + 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"; + 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"; + res.send({"issues": issues, "pulls" : pulls}); + }); + }); + }, - about: function(req, res, next) { - if (req.user) { - 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.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(); - }); - } + about: function(req, res, next) { + if (req.user) { + 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.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 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(); + }); + } + } }; diff --git a/controllers/story.js b/controllers/story.js index 82032ba752..27bbf47916 100644 --- a/controllers/story.js +++ b/controllers/story.js @@ -1,431 +1,447 @@ +/* eslint-disable no-catch-shadow, no-unused-vars */ var R = require('ramda'), - debug = require('debug')('freecc:cntr:story'), - Story = require('./../models/Story'), - Comment = require('./../models/Comment'), - User = require('./../models/User'), - moment = require('../public/js/lib/moment/moment.js'), - resources = require('./resources'), - mongodb = require('mongodb'), - MongoClient = mongodb.MongoClient, - secrets = require('../config/secrets'), - sanitizeHtml = require('sanitize-html'); + debug = require('debug')('freecc:cntr:story'), + Story = require('./../models/Story'), + Comment = require('./../models/Comment'), + User = require('./../models/User'), + moment = require('../public/js/lib/moment/moment.js'), + resources = require('./resources'), + mongodb = require('mongodb'), + MongoClient = mongodb.MongoClient, + secrets = require('../config/secrets'), + sanitizeHtml = require('sanitize-html'); function hotRank(timeValue, rank) { - /* - * Hotness ranking algorithm: http://amix.dk/blog/post/19588 - * tMS = postedOnDate - foundationTime; - * Ranking... - * f(ts, 1, rank) = log(10)z + (ts)/45000; - */ - var hotness; - var z = Math.log(rank) / Math.log(10); - hotness = z + (timeValue / 115200000); - return hotness; + /* + * Hotness ranking algorithm: http://amix.dk/blog/post/19588 + * tMS = postedOnDate - foundationTime; + * Ranking... + * f(ts, 1, rank) = log(10)z + (ts)/45000; + */ + var hotness; + var z = Math.log(rank) / Math.log(10); + hotness = z + (timeValue / 115200000); + return hotness; } -exports.hotJSON = function(req, res) { - var story = Story.find({}).sort({'timePosted': -1}).limit(1000); - story.exec(function(err, stories) { - if (err) { - res.send(500); - return next(err); - } +exports.hotJSON = function(req, res, next) { + var story = Story.find({}).sort({'timePosted': -1}).limit(1000); + story.exec(function(err, stories) { + if (err) { + return next(err); + } - var foundationDate = 1413298800000; + var foundationDate = 1413298800000; - var sliceVal = stories.length >= 100 ? 100 : stories.length; - return res.json(stories.map(function(elem) { - return elem; - }).sort(function(a, b) { - return hotRank(b.timePosted - foundationDate, b.rank, b.headline) - hotRank(a.timePosted - foundationDate, a.rank, a.headline); - }).slice(0, sliceVal)); + var sliceVal = stories.length >= 100 ? 100 : stories.length; + return res.json(stories.map(function(elem) { + return elem; + }).sort(function(a, b) { + return hotRank(b.timePosted - foundationDate, b.rank, b.headline) + - hotRank(a.timePosted - foundationDate, a.rank, a.headline); + }).slice(0, sliceVal)); - }); + }); }; exports.recentJSON = function(req, res, next) { - var story = Story.find({}).sort({'timePosted': -1}).limit(100); - story.exec(function(err, stories) { - if (err) { - res.status(500); - return next(err); - } - res.json(stories); - }); + var story = Story.find({}).sort({'timePosted': -1}).limit(100); + story.exec(function(err, stories) { + if (err) { + return next(err); + } + return res.json(stories); + }); }; exports.hot = function(req, res) { - res.render('stories/index', { - title: 'Hot stories currently trending on Camper News', - page: 'hot' - }); + return res.render('stories/index', { + title: 'Hot stories currently trending on Camper News', + page: 'hot' + }); }; exports.submitNew = function(req, res) { - res.render('stories/index', { - title: 'Submit a new story to Camper News', - page: 'submit' - }); + return res.render('stories/index', { + title: 'Submit a new story to Camper News', + page: 'submit' + }); }; exports.search = function(req, res) { - res.render('stories/index', { - title: 'Search the archives of Camper News', - page: 'search' - }); + return res.render('stories/index', { + title: 'Search the archives of Camper News', + page: 'search' + }); }; exports.recent = function(req, res) { - res.render('stories/index', { - title: 'Recently submitted stories on Camper News', - page: 'recent' - }); + return res.render('stories/index', { + title: 'Recently submitted stories on Camper News', + page: 'recent' + }); }; exports.preSubmit = function(req, res) { - var data = req.query; - var cleanData = sanitizeHtml(data.url, { - allowedTags: [], - allowedAttributes: [] - }).replace(/";/g, '"'); - if (data.url.replace(/&/g, '&') !== cleanData) { + var data = req.query; + var cleanData = sanitizeHtml(data.url, { + allowedTags: [], + allowedAttributes: [] + }).replace(/";/g, '"'); + if (data.url.replace(/&/g, '&') !== cleanData) { - req.flash('errors', { - 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 + req.flash('errors', { + 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 + }); }; 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) { - if (err) { - next(err); - } + Story.find({'storyLink': new RegExp(storyName, 'i')}, function(err, story) { + if (err) { + return next(err); + } - if (story.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a story with that name. Please double check the name." - }); + if (story.length < 1) { + req.flash('errors', { + 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(); - var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull !== dashedName) { - return res.redirect('../stories/' + dashedNameFull); - } + story = story.pop(); + var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull !== dashedName) { + return res.redirect('../stories/' + dashedNameFull); + } - var userVoted = false; - try { - var votedObj = story.upVotes.filter(function(a){ - return a['upVotedByUsername'] === req.user['profile']['username']; - }) - if (votedObj.length > 0){ - userVoted = true; - } - } catch(err){ - userVoted = false; - } - res.render('stories/index', { - title: story.headline, - link: story.link, - author: story.author, - description: story.description, - rank: story.upVotes.length, - upVotes: story.upVotes, - comments: story.comments, - id: story._id, - timeAgo: moment(story.timePosted).fromNow(), - image: story.image, - page: 'show', - storyMetaDescription: story.metaDescription, - hasUserVoted: userVoted - }); + var userVoted = false; + try { + var votedObj = story.upVotes.filter(function(a) { + return a['upVotedByUsername'] === req.user['profile']['username']; + }); + if (votedObj.length > 0) { + userVoted = true; + } + } catch(err) { + userVoted = false; + } + res.render('stories/index', { + title: story.headline, + link: story.link, + author: story.author, + description: story.description, + rank: story.upVotes.length, + upVotes: story.upVotes, + comments: story.comments, + id: story._id, + timeAgo: moment(story.timePosted).fromNow(), + image: story.image, + page: 'show', + storyMetaDescription: story.metaDescription, + hasUserVoted: userVoted }); + }); }; -exports.getStories = function(req, res) { - MongoClient.connect(secrets.db, function(err, database) { - database.collection('stories').find({ - "$text": { - "$search": req.body.data.searchValue - } - }, { - headline: 1, - timePosted: 1, - link: 1, - description: 1, - rank: 1, - upVotes: 1, - author: 1, - comments: 1, - image: 1, - storyLink: 1, - metaDescription: 1, - textScore: { - $meta: "textScore" - } - }, { - sort: { - textScore: { - $meta: "textScore" - } - } - }).toArray(function(err, items) { - if (items !== null && items.length !== 0) { - return res.json(items); - } - return res.status(404); - }); +exports.getStories = function(req, res, next) { + MongoClient.connect(secrets.db, function(err, database) { + if (err) { + return next(err); + } + database.collection('stories').find({ + '$text': { + '$search': req.body.data.searchValue + } + }, { + headline: 1, + timePosted: 1, + link: 1, + description: 1, + rank: 1, + upVotes: 1, + author: 1, + comments: 1, + image: 1, + storyLink: 1, + metaDescription: 1, + textScore: { + $meta: 'textScore' + } + }, { + sort: { + textScore: { + $meta: 'textScore' + } + } + }).toArray(function(err, items) { + 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) { - var data = req.body.data; - Story.find({'_id': data.id}, function(err, story) { - if (err) { - res.status(500); - return next(err); - } - story = story.pop(); - story.rank++; - story.upVotes.push( - { - upVotedBy: data.upVoter._id, - upVotedByUsername: data.upVoter.profile.username - } - ); - story.markModified('rank'); - story.save(); - return res.send(story); + var data = req.body.data; + Story.find({'_id': data.id}, function(err, story) { + if (err) { + return next(err); + } + story = story.pop(); + story.rank++; + story.upVotes.push( + { + upVotedBy: data.upVoter._id, + upVotedByUsername: data.upVoter.profile.username + } + ); + story.markModified('rank'); + story.save(); + User.find({'_id': story.author.userId}, function(err, user) { + '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) { - var data = req.params.id; - Comment.find({'_id': data}, function(err, comment) { - if (err) { - res.status(500); - return next(err); - } - comment = comment.pop(); - return res.send(comment); - }); + var data = req.params.id; + Comment.find({'_id': data}, function(err, comment) { + if (err) { + return next(err); + } + comment = comment.pop(); + return res.send(comment); + }); }; -exports.newStory = function(req, res) { - if (!req.user) { - return res.status(500); +exports.newStory = function(req, res, next) { + if (!req.user) { + return next(new Error('Must be logged in')); + } + var url = req.body.data.url; + var cleanURL = sanitizeHtml(url, { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/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; - var cleanURL = sanitizeHtml(url, { + 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); + }); + + 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(/"/g, '"'), + timePosted: Date.now(), + link: link, + description: sanitizeHtml(data.description, { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/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: [], allowedAttributes: [] }).replace(/"/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 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); + if (data.body !== sanitizedBody) { + req.flash('errors', { + msg: 'HTML is not allowed' }); - - 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 - }); - } - } + 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, next); }; -exports.storySubmission = function(req, res) { - 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(/"/g, '"'), - timePosted: Date.now(), - link: link, - description: sanitizeHtml(data.description, { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'), - rank: 1, - upVotes: data.upVotes, - author: data.author, - comments: [], - image: data.image, - storyLink: storyLink, - metaDescription: data.storyMetaDescription - }); +exports.commentOnCommentSubmit = function(req, res, next) { + var data = req.body.data; - 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(/"/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) { - return res.status(500); + return next(err); } - res.send(JSON.stringify({ - storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase() - })); - }); -}; - -exports.commentSubmit = 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(/"/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(/"/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); + associatedStory = associatedStory.pop(); + if (associatedStory) { + associatedStory.comments.push(data._id); + associatedStory.save(function (err) { + if (err) { + return next(err); + } + res.send(true); + }); } - try { - Context.find({'_id': comment.associatedPost}, function (err, associatedStory) { - if (err) { - return res.status(500); - } - 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); - } - }); + }); + } catch (e) { + // delete comment + return next(err); + } + }); } diff --git a/controllers/user.js b/controllers/user.js index 50ccf2ba47..28d8d38a11 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -6,9 +6,9 @@ var _ = require('lodash'), User = require('../models/User'), secrets = require('../config/secrets'), moment = require('moment'), - Challenge = require('./../models/Challenge'), 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) { - if (req.user) return res.redirect('/'); + if (req.user) { + return res.redirect('/'); + } res.render('account/signin', { title: 'Free Code Camp Login' }); @@ -41,13 +43,17 @@ exports.postSignin = function(req, res, next) { } passport.authenticate('local', function(err, user, info) { - if (err) return next(err); + if (err) { + return next(err); + } if (!user) { req.flash('errors', { msg: info.message }); return res.redirect('/signin'); } req.logIn(user, function(err) { - if (err) return next(err); + if (err) { + return next(err); + } req.flash('success', { msg: 'Success! You are logged in.' }); res.redirect(req.session.returnTo || '/'); }); @@ -70,7 +76,9 @@ exports.signout = 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', { title: 'Sign in to your Free Code Camp Account' }); @@ -82,7 +90,9 @@ exports.getEmailSignin = 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', { title: 'Create Your Free Code Camp Account' }); @@ -98,7 +108,7 @@ exports.postEmailSignup = function(req, res, next) { req.assert('email', 'valid email required').isEmail(); var errors = req.validationErrors(); - + if (errors) { req.flash('errors', errors); return res.redirect('/email-signup'); @@ -124,7 +134,7 @@ exports.postEmailSignup = function(req, res, next) { var user = new User({ email: req.body.email.trim(), password: req.body.password, - profile : { + profile: { username: req.body.username.trim(), 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', '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', - "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', '- the Volunteer Camp Counselor Team' ].join('') @@ -190,10 +200,45 @@ exports.postEmailSignup = function(req, res, next) { * For Calendar display */ -exports.getStreak = function(req, res) { - var completedStreak = req.user.challengesHash; +exports.getStreak = function(req, res, next) { -} + 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 @@ -272,7 +317,7 @@ exports.returnUser = function(req, res, next) { var data = {}; var progressTimestamps = user.progressTimestamps; for (var i = 0; i < progressTimestamps.length; i++) { - data[progressTimestamps[i].toString()] = 1; + data[(progressTimestamps[i] / 1000).toString()] = 1; } res.render('account/show', { diff --git a/models/User.js b/models/User.js index 6019e7d83b..1e1d828853 100644 --- a/models/User.js +++ b/models/User.js @@ -1,7 +1,9 @@ var bcrypt = require('bcrypt-nodejs'); var crypto = require('crypto'); var mongoose = require('mongoose'); +require('mongoose-long')(mongoose); +var Long = mongoose.Types.Long; var userSchema = new mongoose.Schema({ email: { type: String, @@ -21,7 +23,7 @@ var userSchema = new mongoose.Schema({ type: Number, default: 0 }, - progressTimestamps: { type: Array, default: [Date] }, + progressTimestamps: [], challengesCompleted: { type: Array, default: [] }, pointsNeedMigration: { type: Boolean, default: true }, challengesHash: { @@ -332,9 +334,30 @@ var userSchema = new mongoose.Schema({ resetPasswordToken: String, resetPasswordExpires: Date, uncompletedBonfires: Array, - completedBonfires: Array, + completedBonfires: [ + { + _id: String, + completedWith: String, + completedDate: Long, + solution: String + } + ], uncompletedCoursewares: Array, - completedCoursewares: Array + completedCoursewares: [ + { + completedDate: Long, + _id: String, + name: String + } + ], + currentStreak: { + type: Number, + default: 0 + }, + longestStreak: { + type: Number, + default: 0 + } }); /** diff --git a/package.json b/package.json index ffe8c6281b..634fada27b 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,9 @@ "method-override": "^2.3.0", "moment": "^2.8.4", "mongodb": "^1.4.33", - "mongoose": "^3.8.19", - "mongoose-text-search": "0.0.2", + "mongoose": "^4.0.0", "morgan": "^1.5.0", "newrelic": "^1.13.3", - "node": "0.0.0", "nodemailer": "^1.3.0", "passport": "^0.2.1", "passport-facebook": "^1.0.3", @@ -76,7 +74,6 @@ "chai": "^1.10.0", "gulp": "^3.8.8", "gulp-inject": "^1.0.2", - "gulp-minify-css": "^0.5.1", "gulp-nodemon": "^1.0.4", "mocha": "^2.0.1", "multiline": "^1.0.1",