From 8165105b29608c6ace45ed205bcb46ec420a4857 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Wed, 3 Jun 2015 16:19:23 -0700 Subject: [PATCH] refactor all boot to export func and use loopback --- package.json | 1 + server/boot/fieldGuide.js | 220 +++--- server/boot/home.js | 29 +- server/boot/nonprofits.js | 236 +++--- server/boot/passport.js | 109 ++- server/boot/redirects.js | 75 +- server/boot/story.js | 1058 +++++++++++++-------------- server/boot/user.js | 1425 +++++++++++++++++++------------------ server/boot/utility.js | 826 +++++++++++---------- 9 files changed, 2028 insertions(+), 1951 deletions(-) diff --git a/package.json b/package.json index c99b623ca0..35742ea05b 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "passport-twitter": "~1.0.2", "ramda": "~0.10.0", "request": "~2.53.0", + "rx": "^2.5.3", "sanitize-html": "~1.6.1", "sitemap": "~0.7.4", "twit": "~1.1.20", diff --git a/server/boot/fieldGuide.js b/server/boot/fieldGuide.js index c3275e6f62..038af52c08 100644 --- a/server/boot/fieldGuide.js +++ b/server/boot/fieldGuide.js @@ -1,122 +1,126 @@ var R = require('ramda'), - express = require('express'), + // Rx = require('rx'), // debug = require('debug')('freecc:fieldguides'), resources = require('../resources/resources'); -var router = express.Router(); +module.exports = function(app) { + var router = app.Router(); + var FieldGuide = app.models.FieldGuide; -router.get('/field-guide/all-articles', showAllFieldGuides); -router.get('/field-guide/:fieldGuideName', returnIndividualFieldGuide); -router.get('/field-guide/', returnNextFieldGuide); -router.post('/completed-field-guide/', completedFieldGuide); + router.get('/field-guide/all-articles', showAllFieldGuides); + router.get('/field-guide/:fieldGuideName', returnIndividualFieldGuide); + router.get('/field-guide/', returnNextFieldGuide); + router.post('/completed-field-guide/', completedFieldGuide); -function returnIndividualFieldGuide(req, res, next) { - var dashedName = req.params.fieldGuideName; - if (req.user) { - var completed = req.user.completedFieldGuides; + function returnIndividualFieldGuide(req, res, next) { + var dashedName = req.params.fieldGuideName; + if (req.user) { + var completed = req.user.completedFieldGuides; - var uncompletedFieldGuides = resources.allFieldGuideIds() - .filter(function (elem) { - if (completed.indexOf(elem) === -1) { - return elem; - } + var uncompletedFieldGuides = resources.allFieldGuideIds() + .filter(function (elem) { + if (completed.indexOf(elem) === -1) { + return elem; + } + }); + req.user.uncompletedFieldGuides = uncompletedFieldGuides; + // TODO(berks): handle callback properly + req.user.save(function(err) { + if (err) { return next(err); } }); - req.user.uncompletedFieldGuides = uncompletedFieldGuides; - // TODO(berks): handle callback properly - req.user.save(); + } + + // NOTE(berks): loopback might have issue with regex here. + FieldGuide.find( + { dashedName: new RegExp(dashedName, 'i') }, + function(err, fieldGuideFromMongo) { + if (err) { + return next(err); + } + + 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.' + }); + + return res.redirect('/field-guide'); + } + + var fieldGuide = R.head(fieldGuideFromMongo); + fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); + + if (fieldGuide.dashedName !== dashedName) { + return res.redirect('../field-guide/' + fieldGuide.dashedName); + } + res.render('field-guide/show', { + title: fieldGuide.name, + fieldGuideId: fieldGuide._id, + description: fieldGuide.description.join('') + }); + } + ); } - FieldGuide.find( - { dashedName: new RegExp(dashedName, 'i') }, - function(err, fieldGuideFromMongo) { + function showAllFieldGuides(req, res) { + var allFieldGuideNamesAndIds = resources.allFieldGuideNamesAndIds(); + + var completedFieldGuides = []; + if (req.user && req.user.completedFieldGuides) { + completedFieldGuides = req.user.completedFieldGuides; + } + res.render('field-guide/all-articles', { + allFieldGuideNamesAndIds: allFieldGuideNamesAndIds, + completedFieldGuides: completedFieldGuides + }); + } + + function returnNextFieldGuide(req, res, next) { + if (!req.user) { + return res.redirect('/field-guide/how-do-i-use-this-guide'); + } + + var displayedFieldGuides = + FieldGuide.find({'_id': req.user.uncompletedFieldGuides[0]}); + + displayedFieldGuides.exec(function(err, fieldGuide) { + if (err) { return next(err); } + fieldGuide = fieldGuide.pop(); + + if (typeof fieldGuide === 'undefined') { + if (req.user.completedFieldGuides.length > 0) { + req.flash('success', { + msg: [ + "You've read all our current Field Guide entries. You can ", + 'contribute to our Field Guide ', + "here." + ].join('') + }); + } + return res.redirect('../field-guide/how-do-i-use-this-guide'); + } + var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-'); + return res.redirect('../field-guide/' + nameString); + }); + } + + function completedFieldGuide(req, res, next) { + var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId; + + req.user.completedFieldGuides.push(fieldGuideId); + + var index = req.user.uncompletedFieldGuides.indexOf(fieldGuideId); + if (index > -1) { + req.user.progressTimestamps.push(Date.now()); + req.user.uncompletedFieldGuides.splice(index, 1); + } + + req.user.save(function (err) { if (err) { return next(err); } - - 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.' - }); - - return res.redirect('/field-guide'); - } - - var fieldGuide = R.head(fieldGuideFromMongo); - fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); - - if (fieldGuide.dashedName !== dashedName) { - return res.redirect('../field-guide/' + fieldGuide.dashedName); - } - res.render('field-guide/show', { - title: fieldGuide.name, - fieldGuideId: fieldGuide._id, - description: fieldGuide.description.join('') - }); - } - ); -} - -function showAllFieldGuides(req, res) { - var allFieldGuideNamesAndIds = resources.allFieldGuideNamesAndIds(); - - var completedFieldGuides = []; - if (req.user && req.user.completedFieldGuides) { - completedFieldGuides = req.user.completedFieldGuides; + res.send(true); + }); } - res.render('field-guide/all-articles', { - allFieldGuideNamesAndIds: allFieldGuideNamesAndIds, - completedFieldGuides: completedFieldGuides - }); -} - -function returnNextFieldGuide(req, res, next) { - if (!req.user) { - return res.redirect('/field-guide/how-do-i-use-this-guide'); - } - - var displayedFieldGuides = - FieldGuide.find({'_id': req.user.uncompletedFieldGuides[0]}); - - displayedFieldGuides.exec(function(err, fieldGuide) { - if (err) { return next(err); } - fieldGuide = fieldGuide.pop(); - - if (typeof fieldGuide === 'undefined') { - if (req.user.completedFieldGuides.length > 0) { - req.flash('success', { - msg: [ - "You've read all our current Field Guide entries. You can ", - 'contribute to our Field Guide ', - "here." - ].join('') - }); - } - return res.redirect('../field-guide/how-do-i-use-this-guide'); - } - var nameString = fieldGuide.name.toLowerCase().replace(/\s/g, '-'); - return res.redirect('../field-guide/' + nameString); - }); -} - -function completedFieldGuide(req, res, next) { - var fieldGuideId = req.body.fieldGuideInfo.fieldGuideId; - - req.user.completedFieldGuides.push(fieldGuideId); - - var index = req.user.uncompletedFieldGuides.indexOf(fieldGuideId); - if (index > -1) { - req.user.progressTimestamps.push(Date.now()); - req.user.uncompletedFieldGuides.splice(index, 1); - } - - req.user.save(function (err) { - if (err) { - return next(err); - } - res.send(true); - }); -} - -module.exports = router; +}; diff --git a/server/boot/home.js b/server/boot/home.js index 16cb3434a0..036d454945 100644 --- a/server/boot/home.js +++ b/server/boot/home.js @@ -1,22 +1,21 @@ -var express = require('express'); -var router = express.Router(); var message = 'Learn to Code JavaScript and get a Coding Job by Helping Nonprofits'; -router.get('/', index); +module.exports = function(app) { + var router = app.Router(); + router.get('/', index); -function index(req, res, next) { - if (req.user && !req.user.profile.picture) { - req.user.profile.picture = - 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png'; + function index(req, res, next) { + if (req.user && !req.user.profile.picture) { + req.user.profile.picture = + 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png'; - req.user.save(function(err) { - if (err) { return next(err); } + req.user.save(function(err) { + if (err) { return next(err); } + res.render('home', { title: message }); + }); + } else { res.render('home', { title: message }); - }); - } else { - res.render('home', { title: message }); + } } -} - -module.exports = router; +}; diff --git a/server/boot/nonprofits.js b/server/boot/nonprofits.js index 2fe25a6194..507f366b49 100644 --- a/server/boot/nonprofits.js +++ b/server/boot/nonprofits.js @@ -1,126 +1,128 @@ -var express = require('express'), - Nonprofit = require('../../common/models/Nonprofit'); -var router = express.Router(); +module.exports = function(app) { + var router = app.Router(); + var Nonprofit = app.models.Nonprofit; -router.get('/nonprofits/directory', nonprofitsDirectory); -router.get('/nonprofits/:nonprofitName', returnIndividualNonprofit); + router.get('/nonprofits/directory', nonprofitsDirectory); + router.get('/nonprofits/:nonprofitName', returnIndividualNonprofit); -function nonprofitsDirectory(req, res, next) { - Nonprofit.find({ estimatedHours: { $gt: 0 } }, function(err, nonprofits) { - if (err) { return next(err); } - - res.render('nonprofits/directory', { - title: 'Nonprofits we help', - nonprofits: nonprofits - }); - }); -} - -function returnIndividualNonprofit(req, res, next) { - var dashedName = req.params.nonprofitName; - var nonprofitName = dashedName.replace(/\-/g, ' '); - - Nonprofit.find( - { name: new RegExp(nonprofitName, 'i') }, - function(err, nonprofit) { - if (err) { - return next(err); - } - - if (nonprofit.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a nonprofit with that name. " + - 'Please double check the name.' - }); - - return res.redirect('/nonprofits'); - } - - nonprofit = nonprofit.pop(); - var dashedNameFull = nonprofit.name.toLowerCase().replace(/\s/g, '-'); - if (dashedNameFull !== dashedName) { - return res.redirect('../nonprofit/' + dashedNameFull); - } - var buttonActive = false; - if (req.user) { - if (req.user.uncompletedBonfires.length === 0) { - if (req.user.completedCoursewares.length > 63) { - var hasShownInterest = - nonprofit.interestedCampers.filter(function ( obj ) { - return obj.username === req.user.profile.username; - }); - - if (hasShownInterest.length === 0) { - buttonActive = true; - } - } - } - } - - res.render('nonprofits/show', { - dashedName: dashedNameFull, - title: nonprofit.name, - logoUrl: nonprofit.logoUrl, - estimatedHours: nonprofit.estimatedHours, - projectDescription: nonprofit.projectDescription, - - approvedOther: - nonprofit.approvedDeliverables.indexOf('other') > -1, - approvedWebsite: - nonprofit.approvedDeliverables.indexOf('website') > -1, - - approvedDonor: - nonprofit.approvedDeliverables.indexOf('donor') > -1, - approvedInventory: - nonprofit.approvedDeliverables.indexOf('inventory') > -1, - - approvedVolunteer: - nonprofit.approvedDeliverables.indexOf('volunteer') > -1, - approvedForm: - nonprofit.approvedDeliverables.indexOf('form') > -1, - - approvedCommunity: - nonprofit.approvedDeliverables.indexOf('community') > -1, - approvedELearning: - nonprofit.approvedDeliverables.indexOf('eLearning') > -1, - - websiteLink: nonprofit.websiteLink, - imageUrl: nonprofit.imageUrl, - whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo, - interestedCampers: nonprofit.interestedCampers, - assignedCampers: nonprofit.assignedCampers, - buttonActive: buttonActive, - currentStatus: nonprofit.currentStatus - }); - } - ); -} - -/* -function interestedInNonprofit(req, res, next) { - if (req.user) { - Nonprofit.findOne( - { name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i') }, - function(err, nonprofit) { + function nonprofitsDirectory(req, res, next) { + Nonprofit.find( + { where: { estimatedHours: { $gt: 0 } } }, + function(err, nonprofits) { if (err) { return next(err); } - nonprofit.interestedCampers.push({ - username: req.user.profile.username, - picture: req.user.profile.picture, - timeOfInterest: Date.now() - }); - nonprofit.save(function(err) { - if (err) { return next(err); } - req.flash('success', { - msg: 'Thanks for expressing interest in this nonprofit project! ' + - "We've added you to this project as an interested camper!" - }); - res.redirect('back'); + + res.render('nonprofits/directory', { + title: 'Nonprofits we help', + nonprofits: nonprofits }); } ); } -} -*/ -module.exports = router; + function returnIndividualNonprofit(req, res, next) { + var dashedName = req.params.nonprofitName; + var nonprofitName = dashedName.replace(/\-/g, ' '); + + Nonprofit.find( + { where: { name: new RegExp(nonprofitName, 'i') } }, + function(err, nonprofit) { + if (err) { + return next(err); + } + + if (nonprofit.length < 1) { + req.flash('errors', { + msg: "404: We couldn't find a nonprofit with that name. " + + 'Please double check the name.' + }); + + return res.redirect('/nonprofits'); + } + + nonprofit = nonprofit.pop(); + var dashedNameFull = nonprofit.name.toLowerCase().replace(/\s/g, '-'); + if (dashedNameFull !== dashedName) { + return res.redirect('../nonprofit/' + dashedNameFull); + } + var buttonActive = false; + if (req.user) { + if (req.user.uncompletedBonfires.length === 0) { + if (req.user.completedCoursewares.length > 63) { + var hasShownInterest = + nonprofit.interestedCampers.filter(function ( obj ) { + return obj.username === req.user.profile.username; + }); + + if (hasShownInterest.length === 0) { + buttonActive = true; + } + } + } + } + + res.render('nonprofits/show', { + dashedName: dashedNameFull, + title: nonprofit.name, + logoUrl: nonprofit.logoUrl, + estimatedHours: nonprofit.estimatedHours, + projectDescription: nonprofit.projectDescription, + + approvedOther: + nonprofit.approvedDeliverables.indexOf('other') > -1, + approvedWebsite: + nonprofit.approvedDeliverables.indexOf('website') > -1, + + approvedDonor: + nonprofit.approvedDeliverables.indexOf('donor') > -1, + approvedInventory: + nonprofit.approvedDeliverables.indexOf('inventory') > -1, + + approvedVolunteer: + nonprofit.approvedDeliverables.indexOf('volunteer') > -1, + approvedForm: + nonprofit.approvedDeliverables.indexOf('form') > -1, + + approvedCommunity: + nonprofit.approvedDeliverables.indexOf('community') > -1, + approvedELearning: + nonprofit.approvedDeliverables.indexOf('eLearning') > -1, + + websiteLink: nonprofit.websiteLink, + imageUrl: nonprofit.imageUrl, + whatDoesNonprofitDo: nonprofit.whatDoesNonprofitDo, + interestedCampers: nonprofit.interestedCampers, + assignedCampers: nonprofit.assignedCampers, + buttonActive: buttonActive, + currentStatus: nonprofit.currentStatus + }); + } + ); + } + + /* + function interestedInNonprofit(req, res, next) { + if (req.user) { + Nonprofit.findOne( + { name: new RegExp(req.params.nonprofitName.replace(/-/, ' '), 'i') }, + function(err, nonprofit) { + if (err) { return next(err); } + nonprofit.interestedCampers.push({ + username: req.user.profile.username, + picture: req.user.profile.picture, + timeOfInterest: Date.now() + }); + nonprofit.save(function(err) { + if (err) { return next(err); } + req.flash('success', { + msg: 'Thanks for expressing interest in this nonprofit project! ' + + "We've added you to this project as an interested camper!" + }); + res.redirect('back'); + }); + } + ); + } + } + */ +}; diff --git a/server/boot/passport.js b/server/boot/passport.js index 6c3c01bef4..53918dd346 100644 --- a/server/boot/passport.js +++ b/server/boot/passport.js @@ -1,68 +1,67 @@ -var express = require('express'), - passport = require('passport'), +var passport = require('passport'), passportConf = require('../../config/passport'); -var router = express.Router(); -var passportOptions = { - successRedirect: '/', - failureRedirect: '/login' -}; - -router.all('/account', passportConf.isAuthenticated); - -router.get('/auth/twitter', passport.authenticate('twitter')); - -router.get( - '/auth/twitter/callback', - passport.authenticate('twitter', { +module.exports = function(app) { + var router = app.Router(); + var passportOptions = { successRedirect: '/', failureRedirect: '/login' - }) -); + }; -router.get( - '/auth/linkedin', - passport.authenticate('linkedin', { - state: 'SOME STATE' - }) -); + router.all('/account', passportConf.isAuthenticated); -router.get( - '/auth/linkedin/callback', - passport.authenticate('linkedin', passportOptions) -); + router.get('/auth/twitter', passport.authenticate('twitter')); -router.get( - '/auth/facebook', - passport.authenticate('facebook', {scope: ['email', 'user_location']}) -); + router.get( + '/auth/twitter/callback', + passport.authenticate('twitter', { + successRedirect: '/', + failureRedirect: '/login' + }) + ); -router.get( - '/auth/facebook/callback', - passport.authenticate('facebook', passportOptions), function (req, res) { - res.redirect(req.session.returnTo || '/'); - } -); + router.get( + '/auth/linkedin', + passport.authenticate('linkedin', { + state: 'SOME STATE' + }) + ); -router.get('/auth/github', passport.authenticate('github')); + router.get( + '/auth/linkedin/callback', + passport.authenticate('linkedin', passportOptions) + ); -router.get( - '/auth/github/callback', - passport.authenticate('github', passportOptions), function (req, res) { - res.redirect(req.session.returnTo || '/'); - } -); + router.get( + '/auth/facebook', + passport.authenticate('facebook', {scope: ['email', 'user_location']}) + ); -router.get( - '/auth/google', - passport.authenticate('google', {scope: 'profile email'}) -); + router.get( + '/auth/facebook/callback', + passport.authenticate('facebook', passportOptions), function (req, res) { + res.redirect(req.session.returnTo || '/'); + } + ); -router.get( - '/auth/google/callback', - passport.authenticate('google', passportOptions), function (req, res) { - res.redirect(req.session.returnTo || '/'); - } -); + router.get('/auth/github', passport.authenticate('github')); -module.exports = router; + router.get( + '/auth/github/callback', + passport.authenticate('github', passportOptions), function (req, res) { + res.redirect(req.session.returnTo || '/'); + } + ); + + router.get( + '/auth/google', + passport.authenticate('google', {scope: 'profile email'}) + ); + + router.get( + '/auth/google/callback', + passport.authenticate('google', passportOptions), function (req, res) { + res.redirect(req.session.returnTo || '/'); + } + ); +}; diff --git a/server/boot/redirects.js b/server/boot/redirects.js index c5422b6986..2ad49ac83c 100644 --- a/server/boot/redirects.js +++ b/server/boot/redirects.js @@ -1,47 +1,48 @@ -var express = require('express'); -var router = express.Router(); +module.exports = function(app) { + var router = app.Router(); -router.get('/nonprofit-project-instructions', function(req, res) { - res.redirect( - 301, - '/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work' - ); -}); + router.get('/nonprofit-project-instructions', function(req, res) { + res.redirect( + 301, + '/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work' + ); + }); -router.get('/agile', function(req, res) { - res.redirect(301, '/pmi-acp-agile-project-managers'); -}); + router.get('/agile', function(req, res) { + res.redirect(301, '/pmi-acp-agile-project-managers'); + }); -router.get('/live-pair-programming', function(req, res) { - res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv'); -}); + router.get('/live-pair-programming', function(req, res) { + res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv'); + }); -router.get('/install-screenhero', function(req, res) { - res.redirect(301, '/field-guide/install-screenhero'); -}); + router.get('/install-screenhero', function(req, res) { + res.redirect(301, '/field-guide/install-screenhero'); + }); -router.get('/guide-to-our-nonprofit-projects', function(req, res) { - res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects'); -}); + router.get('/guide-to-our-nonprofit-projects', function(req, res) { + res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects'); + }); -router.get('/chromebook', function(req, res) { - res.redirect(301, '/field-guide/chromebook'); -}); + router.get('/chromebook', function(req, res) { + res.redirect(301, '/field-guide/chromebook'); + }); -router.get('/deploy-a-website', function(req, res) { - res.redirect(301, '/field-guide/deploy-a-website'); -}); + router.get('/deploy-a-website', function(req, res) { + res.redirect(301, '/field-guide/deploy-a-website'); + }); -router.get('/gmail-shortcuts', function(req, res) { - res.redirect(301, '/field-guide/gmail-shortcuts'); -}); + router.get('/gmail-shortcuts', function(req, res) { + res.redirect(301, '/field-guide/gmail-shortcuts'); + }); -router.get('/nodeschool-challenges', function(req, res) { - res.redirect(301, '/field-guide/nodeschool-challenges'); -}); + router.get('/nodeschool-challenges', function(req, res) { + res.redirect(301, '/field-guide/nodeschool-challenges'); + }); -router.get('/privacy', function(req, res) { - res.redirect(301, '/field-guide/what-is-the-free-code-camp-privacy-policy?'); -}); - -module.exports = router; + router.get('/privacy', function(req, res) { + res.redirect( + 301, '/field-guide/what-is-the-free-code-camp-privacy-policy?' + ); + }); +}; diff --git a/server/boot/story.js b/server/boot/story.js index ef5fc134ac..d2dd96d7ba 100755 --- a/server/boot/story.js +++ b/server/boot/story.js @@ -1,394 +1,454 @@ var nodemailer = require('nodemailer'), sanitizeHtml = require('sanitize-html'), - express = require('express'), moment = require('moment'), mongodb = require('mongodb'), // debug = require('debug')('freecc:cntr:story'), - Story = require('../../common/models/Story'), - Comment = require('../../common/models/Comment'), - User = require('../../common/models/User'), resources = require('../resources/resources'), MongoClient = mongodb.MongoClient, - secrets = require('../../config/secrets'), - router = express.Router(); + secrets = require('../../config/secrets'); -router.get('/stories/hotStories', hotJSON); -router.get('/stories/recentStories', recentJSON); -router.get('/stories/comments/:id', comments); -router.post('/stories/comment/', commentSubmit); -router.post('/stories/comment/:id/comment', commentOnCommentSubmit); -router.put('/stories/comment/:id/edit', commentEdit); -router.get('/stories/submit', submitNew); -router.get('/stories/submit/new-story', preSubmit); -router.post('/stories/preliminary', newStory); -router.post('/stories/', storySubmission); -router.get('/news/', hot); -router.post('/stories/search', getStories); -router.get('/news/:storyName', returnIndividualStory); -router.post('/stories/upvote/', upvote); +module.exports = function(app) { + var router = app.Router(); + var User = app.models.User; + var Story = app.models.Story; -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 time48Hours = 172800000; - var hotness; - var z = Math.log(rank) / Math.log(10); - hotness = z + (timeValue / time48Hours); - return hotness; + router.get('/stories/hotStories', hotJSON); + router.get('/stories/recentStories', recentJSON); + router.get('/stories/comments/:id', comments); + router.post('/stories/comment/', commentSubmit); + router.post('/stories/comment/:id/comment', commentOnCommentSubmit); + router.put('/stories/comment/:id/edit', commentEdit); + router.get('/stories/submit', submitNew); + router.get('/stories/submit/new-story', preSubmit); + router.post('/stories/preliminary', newStory); + router.post('/stories/', storySubmission); + router.get('/news/', hot); + router.post('/stories/search', getStories); + router.get('/news/:storyName', returnIndividualStory); + router.post('/stories/upvote/', upvote); -} - -function hotJSON(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 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)); - - }); -} - -function recentJSON(req, res, next) { - var story = Story.find({}).sort({'timePosted': -1}).limit(100); - story.exec(function(err, stories) { - if (err) { - return next(err); - } - return res.json(stories); - }); -} - -function hot(req, res) { - return res.render('stories/index', { - title: 'Hot stories currently trending on Camper News', - page: 'hot' - }); -} - -function submitNew(req, res) { - return res.render('stories/index', { - title: 'Submit a new story to Camper News', - page: 'submit' - }); -} - -/* - * no used anywhere -function search(req, res) { - return res.render('stories/index', { - title: 'Search the archives of Camper News', - page: 'search' - }); -} - -function recent(req, res) { - return res.render('stories/index', { - title: 'Recently submitted stories on Camper News', - page: 'recent' - }); -} -*/ - -function preSubmit(req, res) { - - 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' - }); + 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 time48Hours = 172800000; + var hotness; + var z = Math.log(rank) / Math.log(10); + hotness = z + (timeValue / time48Hours); + return hotness; } - 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 - }); -} - - -function returnIndividualStory(req, res, next) { - var dashedName = req.params.storyName; - - var storyName = dashedName.replace(/\-/g, ' ').trim(); - - Story.find({'storyLink': storyName}, 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.' - }); - - return res.redirect('/news/'); - } - - story = story.pop(); - var dashedNameFull = story.storyLink.toLowerCase() - .replace(/\s+/g, ' ') - .replace(/\s/g, '-'); - if (dashedNameFull !== dashedName) { - return res.redirect('../news/' + 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(e) { - userVoted = false; - } - res.render('stories/index', { - title: story.headline, - link: story.link, - originalStoryLink: dashedName, - originalStoryAuthorEmail: story.author.email || '', - 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 - }); - }); -} - -function getStories(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) { + function hotJSON(req, res, next) { + var story = Story.find({}).sort({'timePosted': -1}).limit(1000); + story.exec(function(err, stories) { if (err) { return next(err); } - if (items !== null && items.length !== 0) { - return res.json(items); - } - return res.sendStatus(404); + + 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)); + }); - }); -} + } -function upvote(req, res, next) { - 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: req.user._id, - upVotedByUsername: req.user.profile.username + function recentJSON(req, res, next) { + var story = Story.find({}).sort({'timePosted': -1}).limit(100); + story.exec(function(err, stories) { + if (err) { + return next(err); } - ); - story.markModified('rank'); - story.save(); - // NOTE(Berks): This logic is full of wholes and race conditions - // this could be the source of many 'can't set headers after they are sent' - // errors. This needs cleaning - User.findOne({'_id': story.author.userId}, function(err, user) { - if (err) { return next(err); } + return res.json(stories); + }); + } - user.progressTimestamps.push(Date.now() || 0); - user.save(function (err) { - req.user.save(function (err) { - if (err) { return next(err); } + function hot(req, res) { + return res.render('stories/index', { + title: 'Hot stories currently trending on Camper News', + page: 'hot' + }); + } + + function submitNew(req, res) { + return res.render('stories/index', { + title: 'Submit a new story to Camper News', + page: 'submit' + }); + } + + /* + * no used anywhere + function search(req, res) { + return res.render('stories/index', { + title: 'Search the archives of Camper News', + page: 'search' + }); + } + + function recent(req, res) { + return res.render('stories/index', { + title: 'Recently submitted stories on Camper News', + page: 'recent' + }); + } + */ + + function preSubmit(req, res) { + + 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 + }); + } + + + function returnIndividualStory(req, res, next) { + var dashedName = req.params.storyName; + + var storyName = dashedName.replace(/\-/g, ' ').trim(); + + Story.find({'storyLink': storyName}, 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.' }); - req.user.progressTimestamps.push(Date.now() || 0); + + return res.redirect('/news/'); + } + + story = story.pop(); + var dashedNameFull = story.storyLink.toLowerCase() + .replace(/\s+/g, ' ') + .replace(/\s/g, '-'); + if (dashedNameFull !== dashedName) { + return res.redirect('../news/' + 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(e) { + userVoted = false; + } + res.render('stories/index', { + title: story.headline, + link: story.link, + originalStoryLink: dashedName, + originalStoryAuthorEmail: story.author.email || '', + 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 + }); + }); + } + + function getStories(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); }); }); - return res.send(story); - }); -} - -function comments(req, res, next) { - var data = req.params.id; - Comment.find({'_id': data}, function(err, comment) { - if (err) { - return next(err); - } - comment = comment.pop(); - return res.send(comment); - }); -} - -function newStory(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' + + function upvote(req, res, next) { + 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: req.user._id, + upVotedByUsername: req.user.profile.username + } + ); + story.markModified('rank'); + story.save(); + // NOTE(Berks): This logic is full of wholes and race conditions + // this could be the source of many 'can't set headers after + // they are sent' + // errors. This needs cleaning + User.findOne( + { where: { id: story.author.userId } }, + function(err, user) { + if (err) { return next(err); } + + user.progressTimestamps.push(Date.now() || 0); + user.save(function (err) { + req.user.save(function (err) { + if (err) { return next(err); } + }); + req.user.progressTimestamps.push(Date.now() || 0); + if (err) { + return next(err); + } + }); + } + ); + return res.send(story); }); + } + function comments(req, res, next) { + var data = req.params.id; + Comment.find( + { where: {'_id': data } }, + function(err, comment) { + if (err) { + return next(err); + } + comment = comment.pop(); + return res.send(comment); + }); } - if (url.search(/^https?:\/\//g) === -1) { - url = 'http://' + url; - } - Story.find({'link': url}, function(err, story) { - if (err) { - return next(err); + + function newStory(req, res, next) { + if (!req.user) { + return next(new Error('Must be logged in')); } - if (story.length) { + var url = req.body.data.url; + var cleanURL = sanitizeHtml(url, { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'); + if (cleanURL !== url) { req.flash('errors', { - msg: "Someone's already posted that link. Here's the discussion." + msg: "The URL you submitted doesn't appear valid" }); return res.json({ alreadyPosted: true, - storyURL: '/news/' + story.pop().storyLink + storyURL: '/stories/submit' }); - } - 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 - }); + } + if (url.search(/^https?:\/\//g) === -1) { + url = 'http://' + url; + } + Story.find( + { where: {'link': url} }, + function(err, story) { + if (err) { + return next(err); + } + if (story.length) { + req.flash('errors', { + msg: "Someone's already posted that link. Here's the discussion." + }); + return res.json({ + alreadyPosted: true, + storyURL: '/news/' + 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 + }); + } } } -} -function storySubmission(req, res, next) { - var data = req.body.data; - if (!req.user) { - return next(new Error('Not authorized')); - } - var storyLink = data.headline - .replace(/[^a-z0-9\s]/gi, '') - .replace(/\s+/g, ' ') - .toLowerCase() - .trim(); - - var link = data.link; - - if (link.search(/^https?:\/\//g) === -1) { - link = 'http://' + link; - } - - Story.count({ - storyLink: new RegExp('^' + storyLink + '(?: [0-9]+)?$', 'i') - }, function (err, storyCount) { - if (err) { - return next(err); + function storySubmission(req, res, next) { + var data = req.body.data; + if (!req.user) { + return next(new Error('Not authorized')); } - - // if duplicate storyLink add unique number - storyLink = (storyCount === 0) ? storyLink : storyLink + ' ' + storyCount; + var storyLink = data.headline + .replace(/[^a-z0-9\s]/gi, '') + .replace(/\s+/g, ' ') + .toLowerCase() + .trim(); var link = data.link; + if (link.search(/^https?:\/\//g) === -1) { link = 'http://' + link; } - var story = new Story({ - headline: sanitizeHtml(data.headline, { + + Story.count({ + storyLink: { like: new RegExp('^' + storyLink + '(?: [0-9]+)?$', 'i') } + }, function (err, storyCount) { + if (err) { + return next(err); + } + + // if duplicate storyLink add unique number + storyLink = (storyCount === 0) ? storyLink : storyLink + ' ' + storyCount; + + 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: [({ + upVotedBy: req.user._id, + upVotedByUsername: req.user.profile.username + })], + author: { + picture: req.user.profile.picture, + userId: req.user._id, + username: req.user.profile.username, + email: req.user.email + }, + comments: [], + image: data.image, + storyLink: storyLink, + metaDescription: data.storyMetaDescription, + originalStoryAuthorEmail: req.user.email + }); + story.save(function (err) { + if (err) { + return next(err); + } + req.user.progressTimestamps.push(Date.now() || 0); + req.user.save(function (err) { + if (err) { + return next(err); + } + res.send(JSON.stringify({ + storyLink: story.storyLink.replace(/\s+/g, '-').toLowerCase() + })); + }); + }); + }); + } + + function commentSubmit(req, res, next) { + var data = req.body.data; + if (!req.user) { + return next(new Error('Not authorized')); + } + var sanitizedBody = sanitizeHtml(data.body, + { allowedTags: [], allowedAttributes: [] - }).replace(/"/g, '"'), - timePosted: Date.now(), - link: link, - description: sanitizeHtml(data.description, { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'), - rank: 1, - upVotes: [({ - upVotedBy: req.user._id, - upVotedByUsername: req.user.profile.username - })], + }).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, + originalStoryLink: data.originalStoryLink, + originalStoryAuthorEmail: data.originalStoryAuthorEmail, + body: sanitizedBody, + rank: 0, + upvotes: 0, author: { picture: req.user.profile.picture, userId: req.user._id, @@ -396,214 +456,164 @@ function storySubmission(req, res, next) { email: req.user.email }, comments: [], - image: data.image, - storyLink: storyLink, - metaDescription: data.storyMetaDescription, - originalStoryAuthorEmail: req.user.email + topLevel: true, + commentOn: Date.now() }); - story.save(function (err) { - if (err) { - return next(err); - } - req.user.progressTimestamps.push(Date.now() || 0); - req.user.save(function (err) { - if (err) { - return next(err); - } - res.send(JSON.stringify({ - storyLink: story.storyLink.replace(/\s+/g, '-').toLowerCase() - })); - }); - }); - }); -} -function commentSubmit(req, res, next) { - var data = req.body.data; - if (!req.user) { - 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, - originalStoryLink: data.originalStoryLink, - originalStoryAuthorEmail: data.originalStoryAuthorEmail, - body: sanitizedBody, - rank: 0, - upvotes: 0, - author: { - picture: req.user.profile.picture, - userId: req.user._id, - username: req.user.profile.username, - email: req.user.email - }, - comments: [], - topLevel: true, - commentOn: Date.now() - }); - - commentSave(comment, Story, res, next); -} - -function commentOnCommentSubmit(req, res, next) { - var data = req.body.data; - if (!req.user) { - return next(new Error('Not authorized')); + commentSave(comment, Story, res, next); } - 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, - originalStoryLink: data.originalStoryLink, - originalStoryAuthorEmail: data.originalStoryAuthorEmail, - author: { - picture: req.user.profile.picture, - userId: req.user._id, - username: req.user.profile.username, - email: req.user.email - }, - comments: [], - topLevel: false, - commentOn: Date.now() - }); - commentSave(comment, Comment, res, next); -} - -function commentEdit(req, res, next) { - - Comment.find({'_id': req.params.id}, function(err, cmt) { - if (err) { - return next(err); - } - cmt = cmt.pop(); - - if (!req.user && cmt.author.userId !== req.user._id) { + function commentOnCommentSubmit(req, res, next) { + var data = req.body.data; + if (!req.user) { return next(new Error('Not authorized')); } + var sanitizedBody = sanitizeHtml( + data.body, + { + allowedTags: [], + allowedAttributes: [] + } + ).replace(/"/g, '"'); - var sanitizedBody = sanitizeHtml(req.body.body, { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'); - if (req.body.body !== sanitizedBody) { + if (data.body !== sanitizedBody) { req.flash('errors', { msg: 'HTML is not allowed' }); return res.send(true); } - cmt.body = sanitizedBody; - cmt.commentOn = Date.now(); - cmt.save(function (err) { + var comment = new Comment({ + associatedPost: data.associatedPost, + body: sanitizedBody, + rank: 0, + upvotes: 0, + originalStoryLink: data.originalStoryLink, + originalStoryAuthorEmail: data.originalStoryAuthorEmail, + author: { + picture: req.user.profile.picture, + userId: req.user._id, + username: req.user.profile.username, + email: req.user.email + }, + comments: [], + topLevel: false, + commentOn: Date.now() + }); + commentSave(comment, Comment, res, next); + } + + function commentEdit(req, res, next) { + + Comment.find({ id: req.params.id }, function(err, cmt) { if (err) { return next(err); } - res.send(true); - }); + cmt = cmt.pop(); - }); + if (!req.user && cmt.author.userId !== req.user._id) { + return next(new Error('Not authorized')); + } -} + var sanitizedBody = sanitizeHtml(req.body.body, { + allowedTags: [], + allowedAttributes: [] + }).replace(/"/g, '"'); + if (req.body.body !== sanitizedBody) { + req.flash('errors', { + msg: 'HTML is not allowed' + }); + return res.send(true); + } -function commentSave(comment, Context, res, next) { - comment.save(function(err, data) { - if (err) { - return next(err); - } - try { - // Based on the context retrieve the parent - // object of the comment (Story/Comment) - Context.find({ - '_id': data.associatedPost - }, function (err, associatedContext) { + cmt.body = sanitizedBody; + cmt.commentOn = Date.now(); + cmt.save(function(err) { if (err) { return next(err); } - associatedContext = associatedContext.pop(); - if (associatedContext) { - associatedContext.comments.push(data._id); - associatedContext.save(function (err) { - if (err) { - return next(err); - } - res.send(true); - }); - } - // Find the author of the parent object - User.findOne({ - 'profile.username': associatedContext.author.username - }, function(err, recipient) { + res.send(true); + }); + + }); + + } + + function commentSave(comment, Context, res, next) { + comment.save(function(err, data) { + if (err) { + return next(err); + } + try { + // Based on the context retrieve the parent + // object of the comment (Story/Comment) + Context.find({ + id: data.associatedPost + }, function (err, associatedContext) { if (err) { return next(err); } - // If the emails of both authors differ, - // only then proceed with email notification - if ( - typeof data.author !== 'undefined' && - data.author.email && - typeof recipient !== 'undefined' && - recipient.email && - (data.author.email !== recipient.email) - ) { - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - - var mailOptions = { - to: recipient.email, - from: 'Team@freecodecamp.com', - subject: data.author.username + - ' replied to your post on Camper News', - text: [ - 'Just a quick heads-up: ', - data.author.username + ' replied to you on Camper News.', - 'You can keep this conversation going.', - 'Just head back to the discussion here: ', - 'http://freecodecamp.com/news/' + data.originalStoryLink, - '- the Free Code Camp Volunteer Team' - ].join('\n') - }; - - transporter.sendMail(mailOptions, function (err) { + associatedContext = associatedContext.pop(); + if (associatedContext) { + associatedContext.comments.push(data._id); + associatedContext.save(function (err) { if (err) { - return err; + return next(err); } + res.send(true); }); } - }); - }); - } catch (e) { - return next(err); - } - }); -} + // Find the author of the parent object + User.findOne({ + 'profile.username': associatedContext.author.username + }, function(err, recipient) { + if (err) { + return next(err); + } + // If the emails of both authors differ, + // only then proceed with email notification + if ( + typeof data.author !== 'undefined' && + data.author.email && + typeof recipient !== 'undefined' && + recipient.email && + (data.author.email !== recipient.email) + ) { + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); -module.exports = router; + var mailOptions = { + to: recipient.email, + from: 'Team@freecodecamp.com', + subject: data.author.username + + ' replied to your post on Camper News', + text: [ + 'Just a quick heads-up: ', + data.author.username + ' replied to you on Camper News.', + 'You can keep this conversation going.', + 'Just head back to the discussion here: ', + 'http://freecodecamp.com/news/' + data.originalStoryLink, + '- the Free Code Camp Volunteer Team' + ].join('\n') + }; + + transporter.sendMail(mailOptions, function (err) { + if (err) { + return err; + } + }); + } + }); + }); + } catch (e) { + return next(err); + } + }); + } +}; diff --git a/server/boot/user.js b/server/boot/user.js index 8d3f7f1cfb..5317fdddad 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -5,802 +5,817 @@ var _ = require('lodash'), nodemailer = require('nodemailer'), passport = require('passport'), moment = require('moment'), - express = require('express'), debug = require('debug')('freecc:cntr:userController'), - User = require('../../common/models/User'), secrets = require('../../config/secrets'), resources = require('./../resources/resources'); -var router = express.Router(); -router.get('/login', function(req, res) { - res.redirect(301, '/signin'); -}); -router.get('/logout', function(req, res) { - res.redirect(301, '/signout'); -}); -router.get('/signin', getSignin); -router.post('/signin', postSignin); -router.get('/signout', signout); -router.get('/forgot', getForgot); -router.post('/forgot', postForgot); -router.get('/reset/:token', getReset); -router.post('/reset/:token', postReset); -router.get('/email-signup', getEmailSignup); -router.get('/email-signin', getEmailSignin); -router.post('/email-signup', postEmailSignup); -router.post('/email-signin', postSignin); -router.get('/account/api', getAccountAngular); -router.get('/api/checkUniqueUsername/:username', checkUniqueUsername); -router.get('/api/checkExistingUsername/:username', checkExistingUsername); -router.get('/api/checkUniqueEmail/:email', checkUniqueEmail); -router.post('/account/profile', postUpdateProfile); -router.post('/account/password', postUpdatePassword); -router.post('/account/delete', postDeleteAccount); -router.get('/account/unlink/:provider', getOauthUnlink); -router.get('/account', getAccount); -// Ensure this is the last route! -router.get('/:username', returnUser); +module.exports = function(app) { + var router = app.Router(); + var User = app.models.User; -/** - * GET /signin - * Siginin page. - */ - -function getSignin (req, res) { - if (req.user) { - return res.redirect('/'); - } - res.render('account/signin', { - title: 'Free Code Camp Login' + router.get('/login', function(req, res) { + res.redirect(301, '/signin'); }); -} + router.get('/logout', function(req, res) { + res.redirect(301, '/signout'); + }); + router.get('/signin', getSignin); + router.post('/signin', postSignin); + router.get('/signout', signout); + router.get('/forgot', getForgot); + router.post('/forgot', postForgot); + router.get('/reset/:token', getReset); + router.post('/reset/:token', postReset); + router.get('/email-signup', getEmailSignup); + router.get('/email-signin', getEmailSignin); + router.post('/email-signup', postEmailSignup); + router.post('/email-signin', postSignin); + router.get('/account/api', getAccountAngular); + router.get('/api/checkUniqueUsername/:username', checkUniqueUsername); + router.get('/api/checkExistingUsername/:username', checkExistingUsername); + router.get('/api/checkUniqueEmail/:email', checkUniqueEmail); + router.post('/account/profile', postUpdateProfile); + router.post('/account/password', postUpdatePassword); + router.post('/account/delete', postDeleteAccount); + router.get('/account/unlink/:provider', getOauthUnlink); + router.get('/account', getAccount); + // Ensure this is the last route! + router.get('/:username', returnUser); -/** - * POST /signin - * Sign in using email and password. - */ + /** + * GET /signin + * Siginin page. + */ -function postSignin (req, res, next) { - req.assert('email', 'Email is not valid').isEmail(); - req.assert('password', 'Password cannot be blank').notEmpty(); - - var errors = req.validationErrors(); - - if (errors) { - req.flash('errors', errors); - return res.redirect('/signin'); + function getSignin (req, res) { + if (req.user) { + return res.redirect('/'); + } + res.render('account/signin', { + title: 'Free Code Camp Login' + }); } - passport.authenticate('local', function(err, user, info) { - if (err) { - return next(err); - } - if (!user) { - req.flash('errors', { msg: info.message }); + /** + * POST /signin + * Sign in using email and password. + */ + + function postSignin (req, res, next) { + req.assert('email', 'Email is not valid').isEmail(); + req.assert('password', 'Password cannot be blank').notEmpty(); + + var errors = req.validationErrors(); + + if (errors) { + req.flash('errors', errors); return res.redirect('/signin'); } - req.logIn(user, function(err) { + + passport.authenticate('local', function(err, user, info) { if (err) { return next(err); } - req.flash('success', { msg: 'Success! You are logged in.' }); - if (/hotStories/.test(req.session.returnTo)) { - return res.redirect('../news'); + if (!user) { + req.flash('errors', { msg: info.message }); + return res.redirect('/signin'); } - if (/field-guide/.test(req.session.returnTo)) { - return res.redirect('../field-guide'); - } - return res.redirect(req.session.returnTo || '/'); - }); - })(req, res, next); -} - -/** - * GET /signout - * Log out. - */ - -function signout (req, res) { - req.logout(); - res.redirect('/'); -} - -/** - * GET /email-signup - * Signup page. - */ - -function getEmailSignin (req, res) { - if (req.user) { - return res.redirect('/'); - } - res.render('account/email-signin', { - title: 'Sign in to your Free Code Camp Account' - }); -} - -/** - * GET /signin - * Signup page. - */ - -function getEmailSignup (req, res) { - if (req.user) { - return res.redirect('/'); - } - res.render('account/email-signup', { - title: 'Create Your Free Code Camp Account' - }); -} - -/** - * POST /email-signup - * Create a new local account. - */ - -function postEmailSignup (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'); - } - - var possibleUserData = req.body; - - if (possibleUserData.password.length < 8) { - req.flash('errors', { - msg: 'Your password is too short' - }); - return res.redirect('email-signup'); - } - - if (possibleUserData.username.length < 5 || possibleUserData.length > 20) { - req.flash('errors', { - msg: 'Your username must be between 5 and 20 characters' - }); - return res.redirect('email-signup'); - } - - - var user = new User({ - email: req.body.email.trim(), - password: req.body.password, - profile: { - username: req.body.username.trim(), - picture: - 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png' - } - }); - - User.findOne({ email: req.body.email }, function(err, existingEmail) { - if (err) { - return next(err); - } - - if (existingEmail) { - req.flash('errors', { - msg: 'Account with that email address already exists.' + req.logIn(user, function(err) { + if (err) { + return next(err); + } + req.flash('success', { msg: 'Success! You are logged in.' }); + if (/hotStories/.test(req.session.returnTo)) { + return res.redirect('../news'); + } + if (/field-guide/.test(req.session.returnTo)) { + return res.redirect('../field-guide'); + } + return res.redirect(req.session.returnTo || '/'); }); - return res.redirect('/email-signup'); + })(req, res, next); + } + + /** + * GET /signout + * Log out. + */ + + function signout (req, res) { + req.logout(); + res.redirect('/'); + } + + /** + * GET /email-signup + * Signup page. + */ + + function getEmailSignin (req, res) { + if (req.user) { + return res.redirect('/'); } - User.findOne( - { 'profile.username': req.body.username }, - function(err, existingUsername) { + res.render('account/email-signin', { + title: 'Sign in to your Free Code Camp Account' + }); + } + + /** + * GET /signin + * Signup page. + */ + + function getEmailSignup (req, res) { + if (req.user) { + return res.redirect('/'); + } + res.render('account/email-signup', { + title: 'Create Your Free Code Camp Account' + }); + } + + /** + * POST /email-signup + * Create a new local account. + */ + + function postEmailSignup (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'); + } + + var possibleUserData = req.body; + + if (possibleUserData.password.length < 8) { + req.flash('errors', { + msg: 'Your password is too short' + }); + return res.redirect('email-signup'); + } + + if (possibleUserData.username.length < 5 || possibleUserData.length > 20) { + req.flash('errors', { + msg: 'Your username must be between 5 and 20 characters' + }); + return res.redirect('email-signup'); + } + + + var user = new User({ + email: req.body.email.trim(), + password: req.body.password, + profile: { + username: req.body.username.trim(), + picture: + 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png' + } + }); + + User.findOne({ email: req.body.email }, function(err, existingEmail) { if (err) { return next(err); } - if (existingUsername) { + + if (existingEmail) { req.flash('errors', { - msg: 'Account with that username already exists.' + msg: 'Account with that email address already exists.' }); return res.redirect('/email-signup'); } + User.findOne( + { 'profile.username': req.body.username }, + function(err, existingUsername) { + if (err) { + return next(err); + } + if (existingUsername) { + req.flash('errors', { + msg: 'Account with that username already exists.' + }); + return res.redirect('/email-signup'); + } - user.save(function(err) { - if (err) { return next(err); } - req.logIn(user, function(err) { + user.save(function(err) { if (err) { return next(err); } - res.redirect('/email-signup'); + req.logIn(user, function(err) { + if (err) { return next(err); } + res.redirect('/email-signup'); + }); + }); + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + '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', + 'Good luck with the challenges!\n\n', + '- the Free Code Camp Volunteer Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } }); }); - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Welcome to Free Code Camp!', - text: [ - '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', - 'Good luck with the challenges!\n\n', - '- the Free Code Camp Volunteer Team' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return err; } - }); }); - }); -} + } -/** - * GET /account - * Profile page. - */ + /** + * GET /account + * Profile page. + */ -function getAccount (req, res) { - res.render('account/account', { - title: 'Manage your Free Code Camp Account' - }); -} + function getAccount (req, res) { + res.render('account/account', { + title: 'Manage your Free Code Camp Account' + }); + } -/** - * Angular API Call - */ + /** + * Angular API Call + */ -function getAccountAngular (req, res) { - res.json({ - user: req.user - }); -} + function getAccountAngular (req, res) { + res.json({ + user: req.user + }); + } -/** - * Unique username check API Call - */ + /** + * Unique username check API Call + */ -function checkUniqueUsername (req, res, next) { - User.count( - { 'profile.username': req.params.username.toLowerCase() }, - function (err, data) { - if (err) { return next(err); } - if (data === 1) { - return res.send(true); - } else { - return res.send(false); - } - }); -} - -/** - * Existing username check - */ - -function checkExistingUsername (req, res, next) { - User.count( - { 'profile.username': req.params.username.toLowerCase() }, - function (err, data) { - if (err) { return next(err); } - if (data === 1) { - return res.send(true); - } else { - return res.send(false); - } - } - ); -} - -/** - * Unique email check API Call - */ - -function checkUniqueEmail (req, res, next) { - User.count( - { email: decodeURIComponent(req.params.email).toLowerCase() }, - function (err, data) { + function checkUniqueUsername (req, res, next) { + User.count( + { 'profile.username': req.params.username.toLowerCase() }, + function (err, data) { if (err) { return next(err); } if (data === 1) { return res.send(true); } else { return res.send(false); } - } - ); -} + }); + } + /** + * Existing username check + */ -/** - * GET /campers/:username - * Public Profile page. - */ - -function returnUser (req, res, next) { - User.find( - { 'profile.username': req.params.username.toLowerCase() }, - function(err, user) { - if (err) { - debug('Username err: ', err); - return next(err); - } - if (user[0]) { - user = user[0]; - - user.progressTimestamps = 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; - }, user.progressTimestamps); - - var tmpLongest = 1; - var timeKeys = R.keys(timeObject); - - user.longestStreak = 0; - for (var i = 1; i <= timeKeys.length; i++) { - if (moment(timeKeys[i - 1]).add(1, 'd').toString() - === moment(timeKeys[i]).toString()) { - tmpLongest++; - if (tmpLongest > user.longestStreak) { - user.longestStreak = tmpLongest; - } - } else { - tmpLongest = 1; - } + function checkExistingUsername (req, res, next) { + User.count( + { 'profile.username': req.params.username.toLowerCase() }, + function (err, data) { + if (err) { return next(err); } + if (data === 1) { + return res.send(true); + } else { + return res.send(false); } + } + ); + } - timeKeys = timeKeys.reverse(); - tmpLongest = 1; + /** + * Unique email check API Call + */ - user.currentStreak = 1; - var today = moment(Date.now()).format('YYYY-MM-DD'); + function checkUniqueEmail (req, res, next) { + User.count( + { email: decodeURIComponent(req.params.email).toLowerCase() }, + function (err, data) { + if (err) { return next(err); } + if (data === 1) { + return res.send(true); + } else { + return res.send(false); + } + } + ); + } - if ( - moment(today).toString() === moment(timeKeys[0]).toString() || - moment(today).subtract(1, 'd').toString() === - moment(timeKeys[0]).toString() - ) { - for (var _i = 1; _i <= timeKeys.length; _i++) { - if ( - moment(timeKeys[_i - 1]).subtract(1, 'd').toString() === - moment(timeKeys[_i]).toString() - ) { + /** + * GET /campers/:username + * Public Profile page. + */ + function returnUser (req, res, next) { + User.find( + { 'profile.username': req.params.username.toLowerCase() }, + function(err, user) { + if (err) { + debug('Username err: ', err); + return next(err); + } + if (user[0]) { + user = user[0]; + user.progressTimestamps = + 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; + }, user.progressTimestamps); + + var tmpLongest = 1; + var timeKeys = R.keys(timeObject); + + user.longestStreak = 0; + for (var i = 1; i <= timeKeys.length; i++) { + if (moment(timeKeys[i - 1]).add(1, 'd').toString() + === moment(timeKeys[i]).toString()) { tmpLongest++; - - if (tmpLongest > user.currentStreak) { - user.currentStreak = tmpLongest; + if (tmpLongest > user.longestStreak) { + user.longestStreak = tmpLongest; } } else { - break; + tmpLongest = 1; } } - } else { - user.currentStreak = 1; - } - user.save(function(err) { - if (err) { - return next(err); + timeKeys = timeKeys.reverse(); + tmpLongest = 1; + + user.currentStreak = 1; + var today = moment(Date.now()).format('YYYY-MM-DD'); + + if ( + moment(today).toString() === moment(timeKeys[0]).toString() || + moment(today).subtract(1, 'd').toString() === + moment(timeKeys[0]).toString() + ) { + for (var _i = 1; _i <= timeKeys.length; _i++) { + + if ( + moment(timeKeys[_i - 1]).subtract(1, 'd').toString() === + moment(timeKeys[_i]).toString() + ) { + + tmpLongest++; + + if (tmpLongest > user.currentStreak) { + user.currentStreak = tmpLongest; + } + } else { + break; + } + } + } else { + user.currentStreak = 1; } - var data = {}; - var progressTimestamps = user.progressTimestamps; - progressTimestamps.forEach(function(timeStamp) { - data[(timeStamp / 1000)] = 1; - }); + user.save(function(err) { + if (err) { + return next(err); + } - user.currentStreak = user.currentStreak || 1; - user.longestStreak = user.longestStreak || 1; - var challenges = user.completedChallenges.filter(function ( obj ) { - return obj.challengeType === 3 || obj.challengeType === 4; - }); + var data = {}; + var progressTimestamps = user.progressTimestamps; + progressTimestamps.forEach(function(timeStamp) { + data[(timeStamp / 1000)] = 1; + }); - res.render('account/show', { - title: 'Camper ' + user.profile.username + '\'s portfolio', - username: user.profile.username, - name: user.profile.name, - location: user.profile.location, - githubProfile: user.profile.githubProfile, - linkedinProfile: user.profile.linkedinProfile, - codepenProfile: user.profile.codepenProfile, - facebookProfile: user.profile.facebookProfile, - twitterHandle: user.profile.twitterHandle, - bio: user.profile.bio, - picture: user.profile.picture, - progressTimestamps: user.progressTimestamps, - website1Link: user.portfolio.website1Link, - website1Title: user.portfolio.website1Title, - website1Image: user.portfolio.website1Image, - website2Link: user.portfolio.website2Link, - website2Title: user.portfolio.website2Title, - website2Image: user.portfolio.website2Image, - website3Link: user.portfolio.website3Link, - website3Title: user.portfolio.website3Title, - website3Image: user.portfolio.website3Image, - challenges: challenges, - bonfires: user.completedChallenges.filter(function(challenge) { - return challenge.challengeType === 5; - }), - calender: data, - moment: moment, - longestStreak: user.longestStreak + - (user.longestStreak === 1 ? ' day' : ' days'), - currentStreak: user.currentStreak + - (user.currentStreak === 1 ? ' day' : ' days') + user.currentStreak = user.currentStreak || 1; + user.longestStreak = user.longestStreak || 1; + var challenges = user.completedChallenges.filter(function ( obj ) { + return obj.challengeType === 3 || obj.challengeType === 4; + }); + + res.render('account/show', { + title: 'Camper ' + user.profile.username + '\'s portfolio', + username: user.profile.username, + name: user.profile.name, + location: user.profile.location, + githubProfile: user.profile.githubProfile, + linkedinProfile: user.profile.linkedinProfile, + codepenProfile: user.profile.codepenProfile, + facebookProfile: user.profile.facebookProfile, + twitterHandle: user.profile.twitterHandle, + bio: user.profile.bio, + picture: user.profile.picture, + progressTimestamps: user.progressTimestamps, + website1Link: user.portfolio.website1Link, + website1Title: user.portfolio.website1Title, + website1Image: user.portfolio.website1Image, + website2Link: user.portfolio.website2Link, + website2Title: user.portfolio.website2Title, + website2Image: user.portfolio.website2Image, + website3Link: user.portfolio.website3Link, + website3Title: user.portfolio.website3Title, + website3Image: user.portfolio.website3Image, + challenges: challenges, + bonfires: user.completedChallenges.filter(function(challenge) { + return challenge.challengeType === 5; + }), + calender: data, + moment: moment, + longestStreak: user.longestStreak + + (user.longestStreak === 1 ? ' day' : ' days'), + currentStreak: user.currentStreak + + (user.currentStreak === 1 ? ' day' : ' days') + }); }); - }); - } else { - req.flash('errors', { - msg: "404: We couldn't find a page with that url. " + - 'Please double check the link.' - }); - return res.redirect('/'); + } else { + req.flash('errors', { + msg: "404: We couldn't find a page with that url. " + + 'Please double check the link.' + }); + return res.redirect('/'); + } } - } - ); -} + ); + } -/** - * POST /account/profile - * Update profile information. - */ + /** + * POST /account/profile + * Update profile information. + */ -function postUpdateProfile (req, res, next) { + function postUpdateProfile (req, res, next) { + + User.findById(req.user.id, function(err) { + if (err) { return next(err); } + var errors = req.validationErrors(); + if (errors) { + req.flash('errors', errors); + return res.redirect('/account'); + } + + User.findOne({ email: req.body.email }, function(err, existingEmail) { + if (err) { + return next(err); + } + var user = req.user; + if (existingEmail && existingEmail.email !== user.email) { + req.flash('errors', { + msg: 'An account with that email address already exists.' + }); + return res.redirect('/account'); + } + User.findOne( + { 'profile.username': req.body.username }, + function(err, existingUsername) { + if (err) { + return next(err); + } + var user = req.user; + if ( + existingUsername && + existingUsername.profile.username !== user.profile.username + ) { + req.flash('errors', { + msg: 'An account with that username already exists.' + }); + return res.redirect('/account'); + } + var body = req.body || {}; + user.email = body.email.trim() || ''; + user.profile.name = body.name.trim() || ''; + user.profile.username = body.username.trim() || ''; + user.profile.location = body.location.trim() || ''; + + user.profile.githubProfile = body.githubProfile.trim() || ''; + user.profile.facebookProfile = body.facebookProfile.trim() || ''; + user.profile.linkedinProfile = body.linkedinProfile.trim() || ''; + + user.profile.codepenProfile = body.codepenProfile.trim() || ''; + user.profile.twitterHandle = body.twitterHandle.trim() || ''; + user.profile.bio = body.bio.trim() || ''; + + user.profile.picture = body.picture.trim() || + 'https://s3.amazonaws.com/freecodecamp/' + + 'camper-image-placeholder.png'; + user.portfolio.website1Title = body.website1Title.trim() || ''; + user.portfolio.website1Link = body.website1Link.trim() || ''; + user.portfolio.website1Image = body.website1Image.trim() || ''; + + user.portfolio.website2Title = body.website2Title.trim() || ''; + user.portfolio.website2Link = body.website2Link.trim() || ''; + user.portfolio.website2Image = body.website2Image.trim() || ''; + + user.portfolio.website3Title = body.website3Title.trim() || ''; + user.portfolio.website3Link = body.website3Link.trim() || ''; + user.portfolio.website3Image = body.website3Image.trim() || ''; + + + user.save(function (err) { + if (err) { + return next(err); + } + resources.updateUserStoryPictures( + user._id.toString(), + user.profile.picture, + user.profile.username, + function(err) { + if (err) { return next(err); } + req.flash('success', { + msg: 'Profile information updated.' + }); + res.redirect('/account'); + } + ); + }); + } + ); + }); + }); + } + + /** + * POST /account/password + * Update current password. + */ + + function postUpdatePassword (req, res, next) { + req.assert('password', 'Password must be at least 4 characters long') + .len(4); + + req.assert('confirmPassword', 'Passwords do not match') + .equals(req.body.password); - User.findById(req.user.id, function(err) { - if (err) { return next(err); } var errors = req.validationErrors(); + if (errors) { req.flash('errors', errors); return res.redirect('/account'); } - User.findOne({ email: req.body.email }, function(err, existingEmail) { - if (err) { - return next(err); - } - var user = req.user; - if (existingEmail && existingEmail.email !== user.email) { - req.flash('errors', { - msg: 'An account with that email address already exists.' - }); - return res.redirect('/account'); - } - User.findOne( - { 'profile.username': req.body.username }, - function(err, existingUsername) { - if (err) { - return next(err); - } - var user = req.user; - if ( - existingUsername && - existingUsername.profile.username !== user.profile.username - ) { - req.flash('errors', { - msg: 'An account with that username already exists.' - }); - return res.redirect('/account'); - } - user.email = req.body.email.trim() || ''; - user.profile.name = req.body.name.trim() || ''; - user.profile.username = req.body.username.trim() || ''; - user.profile.location = req.body.location.trim() || ''; - user.profile.githubProfile = req.body.githubProfile.trim() || ''; - user.profile.facebookProfile = req.body.facebookProfile.trim() || ''; - user.profile.linkedinProfile = req.body.linkedinProfile.trim() || ''; - user.profile.codepenProfile = req.body.codepenProfile.trim() || ''; - user.profile.twitterHandle = req.body.twitterHandle.trim() || ''; - user.profile.bio = req.body.bio.trim() || ''; - - user.profile.picture = req.body.picture.trim() || - 'https://s3.amazonaws.com/freecodecamp/' + - 'camper-image-placeholder.png'; - user.portfolio.website1Title = req.body.website1Title.trim() || ''; - user.portfolio.website1Link = req.body.website1Link.trim() || ''; - user.portfolio.website1Image = req.body.website1Image.trim() || ''; - user.portfolio.website2Title = req.body.website2Title.trim() || ''; - user.portfolio.website2Link = req.body.website2Link.trim() || ''; - user.portfolio.website2Image = req.body.website2Image.trim() || ''; - user.portfolio.website3Title = req.body.website3Title.trim() || ''; - user.portfolio.website3Link = req.body.website3Link.trim() || ''; - user.portfolio.website3Image = req.body.website3Image.trim() || ''; - - - user.save(function (err) { - if (err) { - return next(err); - } - resources.updateUserStoryPictures( - user._id.toString(), - user.profile.picture, - user.profile.username, - function(err) { - if (err) { return next(err); } - req.flash('success', { - msg: 'Profile information updated.' - }); - res.redirect('/account'); - } - ); - }); - } - ); - }); - }); -} - -/** - * POST /account/password - * Update current password. - */ - -function postUpdatePassword (req, res, next) { - req.assert('password', 'Password must be at least 4 characters long').len(4); - req.assert('confirmPassword', 'Passwords do not match') - .equals(req.body.password); - - var errors = req.validationErrors(); - - if (errors) { - req.flash('errors', errors); - return res.redirect('/account'); - } - - User.findById(req.user.id, function(err, user) { - if (err) { return next(err); } - - user.password = req.body.password; - - user.save(function(err) { + User.findById(req.user.id, function(err, user) { if (err) { return next(err); } - req.flash('success', { msg: 'Password has been changed.' }); - res.redirect('/account'); - }); - }); -} + user.password = req.body.password; -/** - * POST /account/delete - * Delete user account. - */ + user.save(function(err) { + if (err) { return next(err); } -function postDeleteAccount (req, res, next) { - User.remove({ _id: req.user.id }, function(err) { - if (err) { return next(err); } - req.logout(); - req.flash('info', { msg: 'Your account has been deleted.' }); - res.redirect('/'); - }); -} - -/** - * GET /account/unlink/:provider - * Unlink OAuth provider. - */ - -function getOauthUnlink (req, res, next) { - var provider = req.params.provider; - User.findById(req.user.id, function(err, user) { - if (err) { return next(err); } - - user[provider] = null; - user.tokens = - _.reject(user.tokens, function(token) { - return token.kind === provider; - }); - - user.save(function(err) { - if (err) { return next(err); } - req.flash('info', { msg: provider + ' account has been unlinked.' }); - res.redirect('/account'); - }); - }); -} - -/** - * GET /reset/:token - * Reset Password page. - */ - -function getReset (req, res, next) { - if (req.isAuthenticated()) { - return res.redirect('/'); - } - User - .findOne({ resetPasswordToken: req.params.token }) - .where('resetPasswordExpires').gt(Date.now()) - .exec(function(err, user) { - if (err) { return next(err); } - if (!user) { - req.flash('errors', { - msg: 'Password reset token is invalid or has expired.' - }); - return res.redirect('/forgot'); - } - res.render('account/reset', { - title: 'Password Reset', - token: req.params.token + req.flash('success', { msg: 'Password has been changed.' }); + res.redirect('/account'); }); }); -} - -/** - * POST /reset/:token - * Process the reset password request. - */ - -function postReset (req, res, next) { - var errors = req.validationErrors(); - - if (errors) { - req.flash('errors', errors); - return res.redirect('back'); } - async.waterfall([ - function(done) { - User - .findOne({ resetPasswordToken: req.params.token }) - .where('resetPasswordExpires').gt(Date.now()) - .exec(function(err, user) { - if (err) { return next(err); } - if (!user) { - req.flash('errors', { - msg: 'Password reset token is invalid or has expired.' - }); - return res.redirect('back'); - } + /** + * POST /account/delete + * Delete user account. + */ - user.password = req.body.password; - user.resetPasswordToken = null; - user.resetPasswordExpires = null; + function postDeleteAccount (req, res, next) { + User.destroyById(req.user.id, function(err) { + if (err) { return next(err); } + req.logout(); + req.flash('info', { msg: 'Your account has been deleted.' }); + res.redirect('/'); + }); + } - user.save(function(err) { - if (err) { return done(err); } - req.logIn(user, function(err) { - done(err, user); - }); - }); + /** + * GET /account/unlink/:provider + * Unlink OAuth provider. + */ + + function getOauthUnlink (req, res, next) { + var provider = req.params.provider; + User.findById(req.user.id, function(err, user) { + if (err) { return next(err); } + + user[provider] = null; + user.tokens = + _.reject(user.tokens, function(token) { + return token.kind === provider; }); - }, - function(user, done) { - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Your Free Code Camp password has been changed', - text: [ - 'Hello,\n\n', - 'This email is confirming that you requested to', - 'reset your password for your Free Code Camp account.', - 'This is your email:', - user.email, - '\n' - ].join(' ') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return done(err); } - req.flash('success', { - msg: 'Success! Your password has been changed.' - }); - done(); + + user.save(function(err) { + if (err) { return next(err); } + req.flash('info', { msg: provider + ' account has been unlinked.' }); + res.redirect('/account'); }); + }); + } + + /** + * GET /reset/:token + * Reset Password page. + */ + + function getReset (req, res, next) { + if (req.isAuthenticated()) { + return res.redirect('/'); } - ], function(err) { - if (err) { return next(err); } - res.redirect('/'); - }); -} - -/** - * GET /forgot - * Forgot Password page. - */ - -function getForgot (req, res) { - if (req.isAuthenticated()) { - return res.redirect('/'); - } - res.render('account/forgot', { - title: 'Forgot Password' - }); -} - -/** - * POST /forgot - * Create a random token, then the send user an email with a reset link. - */ - -function postForgot (req, res, next) { - var errors = req.validationErrors(); - - if (errors) { - req.flash('errors', errors); - return res.redirect('/forgot'); - } - - async.waterfall([ - function(done) { - crypto.randomBytes(16, function(err, buf) { - if (err) { return done(err); } - var token = buf.toString('hex'); - done(null, token); - }); - }, - function(token, done) { - User.findOne({ - email: req.body.email.toLowerCase() - }, function(err, user) { - if (err) { return done(err); } + User.findOne( + { + where: { + resetPasswordToken: req.params.token, + resetPasswordExpires: Date.now() + } + }, + function(err, user) { + if (err) { return next(err); } if (!user) { req.flash('errors', { - msg: 'No account with that email address exists.' + msg: 'Password reset token is invalid or has expired.' }); return res.redirect('/forgot'); } - - user.resetPasswordToken = token; - // 3600000 = 1 hour - user.resetPasswordExpires = Date.now() + 3600000; - - user.save(function(err) { - if (err) { return done(err); } - done(null, token, user); + res.render('account/reset', { + title: 'Password Reset', + token: req.params.token }); }); - }, - function(token, user, done) { - var transporter = nodemailer.createTransport({ - service: 'Mandrill', - auth: { - user: secrets.mandrill.user, - pass: secrets.mandrill.password - } - }); - var mailOptions = { - to: user.email, - from: 'Team@freecodecamp.com', - subject: 'Reset your Free Code Camp password', - text: [ - 'You are receiving this email because you (or someone else)\n', - 'requested we reset your Free Code Camp account\'s password.\n\n', - 'Please click on the following link, or paste this into your\n', - 'browser to complete the process:\n\n', - 'http://', - req.headers.host, - '/reset/', - token, - '\n\n', - 'If you did not request this, please ignore this email and\n', - 'your password will remain unchanged.\n' - ].join('') - }; - transporter.sendMail(mailOptions, function(err) { - if (err) { return done(err); } - req.flash('info', { - msg: 'An e-mail has been sent to ' + - user.email + - ' with further instructions.' - }); - done(null, 'done'); - }); + } + + /** + * POST /reset/:token + * Process the reset password request. + */ + + function postReset (req, res, next) { + var errors = req.validationErrors(); + + if (errors) { + req.flash('errors', errors); + return res.redirect('back'); } - ], function(err) { - if (err) { return next(err); } - res.redirect('/forgot'); - }); -} -module.exports = router; + async.waterfall([ + function(done) { + User.findOne( + { + where: { + resetPasswordToken: req.params.token, + resetPasswordExpires: Date.now() + } + }, + function(err, user) { + if (err) { return next(err); } + if (!user) { + req.flash('errors', { + msg: 'Password reset token is invalid or has expired.' + }); + return res.redirect('back'); + } + + user.password = req.body.password; + user.resetPasswordToken = null; + user.resetPasswordExpires = null; + + user.save(function(err) { + if (err) { return done(err); } + req.logIn(user, function(err) { + done(err, user); + }); + }); + }); + }, + function(user, done) { + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Your Free Code Camp password has been changed', + text: [ + 'Hello,\n\n', + 'This email is confirming that you requested to', + 'reset your password for your Free Code Camp account.', + 'This is your email:', + user.email, + '\n' + ].join(' ') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return done(err); } + req.flash('success', { + msg: 'Success! Your password has been changed.' + }); + done(); + }); + } + ], function(err) { + if (err) { return next(err); } + res.redirect('/'); + }); + } + + /** + * GET /forgot + * Forgot Password page. + */ + + function getForgot (req, res) { + if (req.isAuthenticated()) { + return res.redirect('/'); + } + res.render('account/forgot', { + title: 'Forgot Password' + }); + } + + /** + * POST /forgot + * Create a random token, then the send user an email with a reset link. + */ + + function postForgot (req, res, next) { + var errors = req.validationErrors(); + + if (errors) { + req.flash('errors', errors); + return res.redirect('/forgot'); + } + + async.waterfall([ + function(done) { + crypto.randomBytes(16, function(err, buf) { + if (err) { return done(err); } + var token = buf.toString('hex'); + done(null, token); + }); + }, + function(token, done) { + User.findOne({ + email: req.body.email.toLowerCase() + }, function(err, user) { + if (err) { return done(err); } + if (!user) { + req.flash('errors', { + msg: 'No account with that email address exists.' + }); + return res.redirect('/forgot'); + } + + user.resetPasswordToken = token; + // 3600000 = 1 hour + user.resetPasswordExpires = Date.now() + 3600000; + + user.save(function(err) { + if (err) { return done(err); } + done(null, token, user); + }); + }); + }, + function(token, user, done) { + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Reset your Free Code Camp password', + text: [ + 'You are receiving this email because you (or someone else)\n', + 'requested we reset your Free Code Camp account\'s password.\n\n', + 'Please click on the following link, or paste this into your\n', + 'browser to complete the process:\n\n', + 'http://', + req.headers.host, + '/reset/', + token, + '\n\n', + 'If you did not request this, please ignore this email and\n', + 'your password will remain unchanged.\n' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return done(err); } + req.flash('info', { + msg: 'An e-mail has been sent to ' + + user.email + + ' with further instructions.' + }); + done(null, 'done'); + }); + } + ], function(err) { + if (err) { return next(err); } + res.redirect('/forgot'); + }); + } +}; diff --git a/server/boot/utility.js b/server/boot/utility.js index 84cb611630..1c78fb409c 100644 --- a/server/boot/utility.js +++ b/server/boot/utility.js @@ -1,254 +1,302 @@ -var express = require('express'), +var Rx = require('rx'), + Twit = require('twit'), async = require('async'), moment = require('moment'), - Twit = require('twit'), Slack = require('node-slack'), request = require('request'), debug = require('debug')('freecc:cntr:resources'), - constantStrings = require('../resources/constantStrings.json'), - User = require('../../common/models/User'), - Challenge = require('../../common/models/Challenge'), - Story = require('../../common/models/Story'), - FieldGuide = require('../../common/models/FieldGuide'), - Nonprofit = require('../../common/models/Nonprofit'), + constantStrings = require('../resources/constantStrings.json'), secrets = require('../../config/secrets'); var slack = new Slack(secrets.slackHook); -var router = express.Router(); +module.exports = function(app) { + var router = app.Router(); + var User = app.models.User; + var Challenge = app.models.Challenge; + var Story = app.models.Store; + var FieldGuide = app.models.FieldGuide; + var Nonprofit = app.models.Nonprofit; -router.get('/api/github', githubCalls); -router.get('/api/blogger', bloggerCalls); -router.get('/api/trello', trelloCalls); -router.get('/api/codepen/twitter/:screenName', twitter); -router.get('/sitemap.xml', sitemap); -router.post('/get-help', getHelp); -router.post('/get-pair', getPair); -router.get('/chat', chat); -router.get('/twitch', twitch); -router.get('/pmi-acp-agile-project-managers', agileProjectManagers); -router.get('/pmi-acp-agile-project-managers-form', agileProjectManagersForm); -router.get('/nonprofits', nonprofits); -router.get('/nonprofits-form', nonprofitsForm); -router.get('/jobs-form', jobsForm); -router.get('/submit-cat-photo', catPhotoSubmit); -router.get('/unsubscribe/:email', unsubscribe); -router.get('/unsubscribed', unsubscribed); -router.get('/cats.json', getCats); + router.get('/api/github', githubCalls); + router.get('/api/blogger', bloggerCalls); + router.get('/api/trello', trelloCalls); + router.get('/api/codepen/twitter/:screenName', twitter); + router.get('/sitemap.xml', sitemap); + router.post('/get-help', getHelp); + router.post('/get-pair', getPair); + router.get('/chat', chat); + router.get('/twitch', twitch); + router.get('/pmi-acp-agile-project-managers', agileProjectManagers); + router.get('/pmi-acp-agile-project-managers-form', agileProjectManagersForm); + router.get('/nonprofits', nonprofits); + router.get('/nonprofits-form', nonprofitsForm); + router.get('/jobs-form', jobsForm); + router.get('/submit-cat-photo', catPhotoSubmit); + router.get('/unsubscribe/:email', unsubscribe); + router.get('/unsubscribed', unsubscribed); + router.get('/cats.json', getCats); -router.get('/api/slack', slackInvite); + router.get('/api/slack', slackInvite); -function slackInvite(req, res, next) { - if (req.user) { - if (req.user.email) { - var invite = { - 'email': req.user.email, - 'token': process.env.SLACK_KEY, - 'set_active': true - }; + function slackInvite(req, res, next) { + if (req.user) { + if (req.user.email) { + var invite = { + 'email': req.user.email, + 'token': process.env.SLACK_KEY, + 'set_active': true + }; - var headers = { - 'User-Agent': 'Node Browser/0.0.1', - 'Content-Type': 'application/x-www-form-urlencoded' - }; + var headers = { + 'User-Agent': 'Node Browser/0.0.1', + 'Content-Type': 'application/x-www-form-urlencoded' + }; - var options = { - url: 'https://freecodecamp.slack.com/api/users.admin.invite', - method: 'POST', - headers: headers, - form: invite - }; + var options = { + url: 'https://freecodecamp.slack.com/api/users.admin.invite', + method: 'POST', + headers: headers, + form: invite + }; - request(options, function (error, response) { - if (!error && response.statusCode === 200) { - req.flash('success', { - msg: 'We\'ve successfully requested an invite for you.' + - ' Please check your email and follow the instructions from Slack.' - }); - req.user.sentSlackInvite = true; - req.user.save(function(err) { - if (err) { - return next(err); - } + request(options, function (error, response) { + if (!error && response.statusCode === 200) { + req.flash('success', { + msg: 'We\'ve successfully requested an invite for you.' + + ' Please check your email and follow the ' + + 'instructions from Slack.' + }); + req.user.sentSlackInvite = true; + req.user.save(function(err) { + if (err) { + return next(err); + } + return res.redirect('back'); + }); + } else { + req.flash('errors', { + msg: 'The invitation email did not go through for some reason.' + + ' Please try again or ' + + 'email us.' + }); return res.redirect('back'); - }); - } else { - req.flash('errors', { - msg: 'The invitation email did not go through for some reason.' + - ' Please try again or ' + - 'email us.' - }); - return res.redirect('back'); - } - }); + } + }); + } else { + req.flash('notice', { + msg: 'Before we can send your Slack invite, we need your email ' + + 'address. Please update your profile information here.' + }); + return res.redirect('/account'); + } } else { req.flash('notice', { - msg: 'Before we can send your Slack invite, we need your email ' + - 'address. Please update your profile information here.' + msg: 'You need to sign in to Free Code Camp before ' + + 'we can send you a Slack invite.' }); return res.redirect('/account'); } - } else { - req.flash('notice', { - msg: 'You need to sign in to Free Code Camp before ' + - 'we can send you a Slack invite.' + } + + function twitter(req, res, next) { + // sends out random tweets about javascript + var T = new Twit({ + 'consumer_key': secrets.twitter.consumerKey, + 'consumer_secret': secrets.twitter.consumerSecret, + 'access_token': secrets.twitter.token, + 'access_token_secret': secrets.twitter.tokenSecret }); - return res.redirect('/account'); - } -} -function twitter(req, res, next) { - // sends out random tweets about javascript - var T = new Twit({ - 'consumer_key': secrets.twitter.consumerKey, - 'consumer_secret': secrets.twitter.consumerSecret, - 'access_token': secrets.twitter.token, - 'access_token_secret': secrets.twitter.tokenSecret - }); - - var screenName; - if (req.params.screenName) { - screenName = req.params.screenName; - } else { - screenName = 'freecodecamp'; - } - - T.get( - 'statuses/user_timeline', - { - 'screen_name': screenName, - count: 10 - }, - function(err, data) { - if (err) { return next(err); } - return res.json(data); + var screenName; + if (req.params.screenName) { + screenName = req.params.screenName; + } else { + screenName = 'freecodecamp'; } - ); -} - -function getHelp(req, res) { - var userName = req.user.profile.username; - var code = req.body.payload.code ? '\n```\n' + - req.body.payload.code + '\n```\n' - : ''; - var challenge = req.body.payload.challenge; - - slack.send({ - text: '*@' + userName + '* wants help with ' + challenge + '. ' + - code + 'Hey, *@' + userName + '*, if no one helps you right ' + - 'away, try typing out your problem in detail to me. Like this: ' + - 'http://en.wikipedia.org/wiki/Rubber_duck_debugging', - channel: '#help', - username: 'Debuggy the Rubber Duck', - 'icon_url': 'https://pbs.twimg.com/profile_images/' + - '3609875545/569237541c920fa78d78902069615caf.jpeg' - }); - return res.sendStatus(200); -} - -function getPair(req, res) { - var userName = req.user.profile.username; - var challenge = req.body.payload.challenge; - slack.send({ - text: [ - 'Anyone want to pair with *@', - userName, - '* on ', - challenge, - '?\nMake sure you install Screen Hero here: ', - 'http://freecodecamp.com/field-guide/how-do-i-install-screenhero\n', - 'Then start your pair program session with *@', - userName, - '* by typing \"/hero @', - userName, - '\" into Slack.\n And *@', - userName, - '*, be sure to launch Screen Hero, then keep coding. ', - 'Another camper may pair with you soon.' - ].join(''), - channel: '#letspair', - username: 'Companion Cube', - 'icon_url': 'https://lh3.googleusercontent.com/-f6xDPDV2rPE/AAAAAAAAAAI/' + - 'AAAAAAAAAAA/mdlESXQu11Q/photo.jpg' - }); - return res.sendStatus(200); -} - -function sitemap(req, res, next) { - var appUrl = 'http://www.freecodecamp.com'; - var now = moment(new Date()).format('YYYY-MM-DD'); - - - 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); - callback(err); - } else { - callback(null, users[0].usernames); - } - }); + T.get( + 'statuses/user_timeline', + { + 'screen_name': screenName, + count: 10 }, - - challenges: function (callback) { - Challenge.aggregate() - .group({_id: 1, names: { $addToSet: '$name'}}) - .exec(function (err, challenges) { - if (err) { - debug('Challenge err: ', err); - callback(err); - } else { - callback(null, challenges[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, data) { + if (err) { return next(err); } + return res.json(data); } - }, function (err, results) { - if (err) { - return next(err); - } else { + ); + } + + + function getHelp(req, res) { + var userName = req.user.profile.username; + var code = req.body.payload.code ? '\n```\n' + + req.body.payload.code + '\n```\n' + : ''; + var challenge = req.body.payload.challenge; + + slack.send({ + text: '*@' + userName + '* wants help with ' + challenge + '. ' + + code + 'Hey, *@' + userName + '*, if no one helps you right ' + + 'away, try typing out your problem in detail to me. Like this: ' + + 'http://en.wikipedia.org/wiki/Rubber_duck_debugging', + channel: '#help', + username: 'Debuggy the Rubber Duck', + 'icon_url': 'https://pbs.twimg.com/profile_images/' + + '3609875545/569237541c920fa78d78902069615caf.jpeg' + }); + return res.sendStatus(200); + } + + function getPair(req, res) { + var userName = req.user.profile.username; + var challenge = req.body.payload.challenge; + slack.send({ + text: [ + 'Anyone want to pair with *@', + userName, + '* on ', + challenge, + '?\nMake sure you install Screen Hero here: ', + 'http://freecodecamp.com/field-guide/how-do-i-install-screenhero\n', + 'Then start your pair program session with *@', + userName, + '* by typing \"/hero @', + userName, + '\" into Slack.\n And *@', + userName, + '*, be sure to launch Screen Hero, then keep coding. ', + 'Another camper may pair with you soon.' + ].join(''), + channel: '#letspair', + username: 'Companion Cube', + 'icon_url': + 'https://lh3.googleusercontent.com/-f6xDPDV2rPE/AAAAAAAAAAI/' + + 'AAAAAAAAAAA/mdlESXQu11Q/photo.jpg' + }); + return res.sendStatus(200); + } + + function sitemap(req, res, next) { + var appUrl = 'http://www.freecodecamp.com'; + var now = moment(new Date()).format('YYYY-MM-DD'); + + // TODO(berks): refactor async to rx + async.parallel({ + users: function(callback) { + User.find( + { + where: { 'profile.username': { nlike: '' } }, + fields: { 'profile.username': true } + }, + function(err, users) { + if (err) { + debug('User err: ', err); + callback(err); + } else { + Rx.Observable.from(users) + .map(function(user) { + return user.profile.username; + }) + .toArray() + .subscribe( + function(usernames) { + callback(null, usernames); + }, + callback + ); + } + }); + }, + + challenges: function (callback) { + Challenge.find( + { fields: { name: true } }, + function (err, challenges) { + if (err) { + debug('Challenge err: ', err); + callback(err); + } else { + Rx.Observable.from(challenges) + .map(function(challenge) { + return challenge.name; + }) + .toArray() + .subscribe( + callback.bind(callback, null), + callback + ); + } + }); + }, + stories: function (callback) { + Story.find( + { field: { link: true } }, + function (err, stories) { + if (err) { + debug('Story err: ', err); + callback(err); + } else { + Rx.Observable.from(stories) + .map(function(story) { + return story.link; + }) + .toArray() + .subscribe( + callback.bind(callback, null), + callback + ); + } + } + ); + }, + nonprofits: function (callback) { + Nonprofit.find( + { field: { name: true } }, + function(err, nonprofits) { + if (err) { + debug('User err: ', err); + callback(err); + } else { + Rx.Observable.from(nonprofits) + .map(function(nonprofit) { + return nonprofit.name; + }) + .toArray() + .subscribe( + callback.bind(callback, null), + callback + ); + } + }); + }, + fieldGuides: function(callback) { + FieldGuide.find( + { field: { name: true } }, + function(err, fieldGuides) { + if (err) { + debug('User err: ', err); + callback(err); + } else { + Rx.Observable.from(fieldGuides) + .map(function(fieldGuide) { + return fieldGuide.name; + }) + .toArray() + .subscribe( + callback.bind(callback, null), + callback + ); + } + }); + } + }, function(err, results) { + if (err) { + return next(err); + } setTimeout(function() { res.header('Content-Type', 'application/xml'); res.render('resources/sitemap', { @@ -262,185 +310,183 @@ function sitemap(req, res, next) { }); }, 0); } - } - ); -} + ); + } -function chat(req, res) { - if (req.user && req.user.progressTimestamps.length > 5) { - res.redirect('http://freecodecamp.slack.com'); - } else { - res.render('resources/chat', { - title: 'Watch us code live on Twitch.tv' + function chat(req, res) { + if (req.user && req.user.progressTimestamps.length > 5) { + res.redirect('http://freecodecamp.slack.com'); + } else { + res.render('resources/chat', { + title: 'Watch us code live on Twitch.tv' + }); + } + } + + function jobsForm(req, res) { + res.render('resources/jobs-form', { + title: 'Employer Partnership Form for Job Postings,' + + ' Recruitment and Corporate Sponsorships' }); } -} -function jobsForm(req, res) { - res.render('resources/jobs-form', { - title: 'Employer Partnership Form for Job Postings,' + - ' Recruitment and Corporate Sponsorships' - }); -} + function catPhotoSubmit(req, res) { + res.send( + 'Success! You have submitted your cat photo. Return to your website ' + + 'by typing any letter into your code editor.' + ); + } -function catPhotoSubmit(req, res) { - res.send( - 'Success! You have submitted your cat photo. Return to your website ' + - 'by typing any letter into your code editor.' - ); -} + function nonprofits(req, res) { + res.render('resources/nonprofits', { + title: 'A guide to our Nonprofit Projects' + }); + } -function nonprofits(req, res) { - res.render('resources/nonprofits', { - title: 'A guide to our Nonprofit Projects' - }); -} + function nonprofitsForm(req, res) { + res.render('resources/nonprofits-form', { + title: 'Nonprofit Projects Proposal Form' + }); + } -function nonprofitsForm(req, res) { - res.render('resources/nonprofits-form', { - title: 'Nonprofit Projects Proposal Form' - }); -} + function agileProjectManagers(req, res) { + res.render('resources/pmi-acp-agile-project-managers', { + title: 'Get Agile Project Management Experience for the PMI-ACP' + }); + } -function agileProjectManagers(req, res) { - res.render('resources/pmi-acp-agile-project-managers', { - title: 'Get Agile Project Management Experience for the PMI-ACP' - }); -} + function agileProjectManagersForm(req, res) { + res.render('resources/pmi-acp-agile-project-managers-form', { + title: 'Agile Project Management Program Application Form' + }); + } -function agileProjectManagersForm(req, res) { - res.render('resources/pmi-acp-agile-project-managers-form', { - title: 'Agile Project Management Program Application Form' - }); -} + function twitch(req, res) { + res.render('resources/twitch', { + title: 'Enter Free Code Camp\'s Chat Rooms' + }); + } -function twitch(req, res) { - res.render('resources/twitch', { - title: 'Enter Free Code Camp\'s Chat Rooms' - }); -} - -function unsubscribe(req, res, next) { - User.findOne({ email: req.params.email }, function(err, user) { - if (user) { - if (err) { - return next(err); - } - user.sendMonthlyEmail = false; - user.save(function () { + function unsubscribe(req, res, next) { + User.findOne({ email: req.params.email }, function(err, user) { + if (user) { if (err) { return next(err); } + user.sendMonthlyEmail = false; + user.save(function () { + if (err) { + return next(err); + } + res.redirect('/unsubscribed'); + }); + } else { res.redirect('/unsubscribed'); - }); - } else { - res.redirect('/unsubscribed'); - } - }); -} - -function unsubscribed(req, res) { - res.render('resources/unsubscribed', { - title: 'You have been unsubscribed' - }); -} - -function githubCalls(req, res, next) { - var githubHeaders = { - headers: { - 'User-Agent': constantStrings.gitHubUserAgent - }, - port: 80 - }; - request( - [ - 'https://api.github.com/repos/freecodecamp/', - 'freecodecamp/pulls?client_id=', - secrets.github.clientID, - '&client_secret=', - secrets.github.clientSecret - ].join(''), - githubHeaders, - function(err, status1, pulls) { - if (err) { return next(err); } - 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 - ].join(''), - githubHeaders, - function (err, status2, issues) { - if (err) { return next(err); } - issues = ((pulls === parseInt(pulls, 10)) && issues) ? - Object.keys(JSON.parse(issues)).length - pulls : - "Can't connect to GitHub"; - res.send({ - issues: issues, - pulls: pulls - }); - } - ); - } - ); -} - -function trelloCalls(req, res, next) { - request( - 'https://trello.com/1/boards/BA3xVpz9/cards?key=' + - secrets.trello.key, - function(err, status, trello) { - if (err) { return next(err); } - trello = (status && status.statusCode === 200) ? - (JSON.parse(trello)) : - 'Can\'t connect to to Trello'; - - res.end(JSON.stringify(trello)); - }); -} - -function bloggerCalls(req, res, next) { - request( - 'https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/' + - 'posts?key=' + - secrets.blogger.key, - function (err, status, blog) { - if (err) { return next(err); } - - blog = (status && status.statusCode === 200) ? - JSON.parse(blog) : - 'Can\'t connect to Blogger'; - res.end(JSON.stringify(blog)); - } - ); -} - -function getCats(req, res) { - res.send( - [ - { - 'name': 'cute', - 'imageLink': 'https://encrypted-tbn3.gstatic.com/images' + - '?q=tbn:ANd9GcRaP1ecF2jerISkdhjr4R9yM9-8ClUy-TA36MnDiFBukd5IvEME0g' - }, - { - 'name': 'grumpy', - 'imageLink': 'http://cdn.grumpycats.com/wp-content/uploads/' + - '2012/09/GC-Gravatar-copy.png' - }, - { - 'name': 'mischievous', - 'imageLink': 'http://www.kittenspet.com/wp-content' + - '/uploads/2012/08/cat_with_funny_face_3-200x200.jpg' } - ] - ); -} + }); + } -module.exports = router; + function unsubscribed(req, res) { + res.render('resources/unsubscribed', { + title: 'You have been unsubscribed' + }); + } + + function githubCalls(req, res, next) { + var githubHeaders = { + headers: { + 'User-Agent': constantStrings.gitHubUserAgent + }, + port: 80 + }; + request( + [ + 'https://api.github.com/repos/freecodecamp/', + 'freecodecamp/pulls?client_id=', + secrets.github.clientID, + '&client_secret=', + secrets.github.clientSecret + ].join(''), + githubHeaders, + function(err, status1, pulls) { + if (err) { return next(err); } + 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 + ].join(''), + githubHeaders, + function (err, status2, issues) { + if (err) { return next(err); } + issues = ((pulls === parseInt(pulls, 10)) && issues) ? + Object.keys(JSON.parse(issues)).length - pulls : + "Can't connect to GitHub"; + res.send({ + issues: issues, + pulls: pulls + }); + } + ); + } + ); + } + + function trelloCalls(req, res, next) { + request( + 'https://trello.com/1/boards/BA3xVpz9/cards?key=' + + secrets.trello.key, + function(err, status, trello) { + if (err) { return next(err); } + trello = (status && status.statusCode === 200) ? + (JSON.parse(trello)) : + 'Can\'t connect to to Trello'; + + res.end(JSON.stringify(trello)); + }); + } + + function bloggerCalls(req, res, next) { + request( + 'https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/' + + 'posts?key=' + + secrets.blogger.key, + function (err, status, blog) { + if (err) { return next(err); } + + blog = (status && status.statusCode === 200) ? + JSON.parse(blog) : + 'Can\'t connect to Blogger'; + res.end(JSON.stringify(blog)); + } + ); + } + + function getCats(req, res) { + res.send( + [ + { + 'name': 'cute', + 'imageLink': 'https://encrypted-tbn3.gstatic.com/images' + + '?q=tbn:ANd9GcRaP1ecF2jerISkdhjr4R9yM9-8ClUy-TA36MnDiFBukd5IvEME0g' + }, + { + 'name': 'grumpy', + 'imageLink': 'http://cdn.grumpycats.com/wp-content/uploads/' + + '2012/09/GC-Gravatar-copy.png' + }, + { + 'name': 'mischievous', + 'imageLink': 'http://www.kittenspet.com/wp-content' + + '/uploads/2012/08/cat_with_funny_face_3-200x200.jpg' + } + ] + ); + } +};