From 153970861346f3f18c9d0b35b60cefbd02cfab1a Mon Sep 17 00:00:00 2001 From: Maxim Orlov Date: Sat, 25 Apr 2015 16:05:36 +0200 Subject: [PATCH 1/7] Fix Camper News email notification logic --- controllers/story.js | 73 +++++++++++++++++++------------------ public/js/main.js | 2 + views/stories/comments.jade | 1 + views/stories/show.jade | 1 + 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/controllers/story.js b/controllers/story.js index 9047279e3f..9803a7afa0 100755 --- a/controllers/story.js +++ b/controllers/story.js @@ -155,7 +155,7 @@ exports.returnIndividualStory = function(req, res, next) { title: story.headline, link: story.link, originalStoryLink: dashedName, - originalStoryAuthorEmail: story.author.email || "", + originalStoryAuthorEmail: story.author.email || '', author: story.author, description: story.description, rank: story.upVotes.length, @@ -398,7 +398,7 @@ exports.storySubmission = function(req, res, next) { var comment = new Comment({ associatedPost: data.associatedPost, originalStoryLink: data.originalStoryLink, - originalStoryAuthorEmail: req.user.email, + originalStoryAuthorEmail: data.originalStoryAuthorEmail, body: sanitizedBody, rank: 0, upvotes: 0, @@ -496,53 +496,54 @@ exports.storySubmission = function(req, res, next) { return next(err); } try { - Context.find({'_id': comment.associatedPost}, function (err, associatedStory) { + // 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); } - associatedStory = associatedStory.pop(); - if (associatedStory) { - associatedStory.comments.push(data._id); - associatedStory.save(function (err) { + associatedContext = associatedContext.pop(); + if (associatedContext) { + associatedContext.comments.push(data._id); + associatedContext.save(function (err) { if (err) { return next(err); } res.send(true); }); } - User.findOne({'profile.username': associatedStory.author.username}, function(err, recipient) { + // Find the author of the parent object + User.findOne({'profile.username': associatedContext.author.username}, function(err, recipient) { if (err) { return next(err); } - var recipients = ''; - if (data.originalStoryAuthorEmail && (data.originalStoryAuthorEmail !== recipient.email)) { - recipients = data.originalStoryAuthorEmail + ',' + recipient.email; - } else { - recipients = recipient.email; + // If the emails of both authors differ, only then proceed with email notification + if (data.author.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/stories/' + data.originalStoryLink, + '- the Free Code Camp Volunteer Team' + ].join('\n') + }; + + 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: recipients, - from: 'Team@freecodecamp.com', - subject: associatedStory.author.username + " replied to your post on Camper News", - text: [ - "Just a quick heads-up: " + associatedStory.author.username + " replied to you on Camper News.", - "You can keep this conversation going.", - "Just head back to the discussion here: http://freecodecamp.com/stories/" + comment.originalStoryLink, - '- the Free Code Camp Volunteer Team' - ].join('\n') - }; - transporter.sendMail(mailOptions, function (err) { - if (err) { - return err; - } - }); }); }); } catch (e) { diff --git a/public/js/main.js b/public/js/main.js index ee6c9e1cb8..4525ca7a72 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -287,6 +287,8 @@ $(document).ready(function() { { data: { associatedPost: storyId, + originalStoryLink: originalStoryLink, + originalStoryAuthorEmail: originalStoryAuthorEmail, body: data } }) diff --git a/views/stories/comments.jade b/views/stories/comments.jade index 07412b59be..425d52890d 100755 --- a/views/stories/comments.jade +++ b/views/stories/comments.jade @@ -108,6 +108,7 @@ data: { associatedPost: commentId, originalStoryLink: originalStoryLink, + originalStoryAuthorEmail: originalStoryAuthorEmail, body: $('#comment-to-comment-textinput').val(), } }) diff --git a/views/stories/show.jade b/views/stories/show.jade index edb1e3c6b8..1e04c3a34f 100644 --- a/views/stories/show.jade +++ b/views/stories/show.jade @@ -2,6 +2,7 @@ script. var storyId = !{JSON.stringify(id)}; var originalStoryLink = !{JSON.stringify(originalStoryLink)}; + var originalStoryAuthorEmail = !{JSON.stringify(originalStoryAuthorEmail)}; var comments = !{JSON.stringify(comments)}; var upVotes = !{JSON.stringify(upVotes)}; var image = !{JSON.stringify(image)}; From a9feb269f6946302da34be9dcc7d60eab72e1dee Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Sat, 25 Apr 2015 12:28:14 -0400 Subject: [PATCH 2/7] Remove some debug/console.log statements. Fix allFieldGuideIds method in resources.js to return an array of _ids, not an array of objects. Field guide controller now correctly completes field guides. Closes #367. --- app.js | 1 - controllers/bonfire.js | 1 - controllers/fieldGuide.js | 11 +++++------ controllers/nonprofits.js | 1 - controllers/resources.js | 4 +--- controllers/story.js | 9 +++++---- public/js/main.js | 2 -- 7 files changed, 11 insertions(+), 18 deletions(-) diff --git a/app.js b/app.js index 1c92c7d9f1..30df332f22 100755 --- a/app.js +++ b/app.js @@ -81,7 +81,6 @@ app.set('port', process.env.PORT || 3000); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); -console.log(process.env.NODE_ENV); if (process.env.NODE_ENV === 'production') { app.all(/.*/, function (req, res, next) { diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 88678099e1..68f22d9532 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -326,7 +326,6 @@ exports.completedBonfire = function (req, res, next) { return next(err); } if (user) { - debug('Saving user'); res.send(true); } }); diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js index d4bf9a216d..65b87d3931 100644 --- a/controllers/fieldGuide.js +++ b/controllers/fieldGuide.js @@ -22,7 +22,7 @@ exports.returnIndividualFieldGuide = function(req, res, next) { return res.redirect('/field-guide'); } - var fieldGuide = fieldGuideFromMongo.pop(); + var fieldGuide = R.head(fieldGuideFromMongo); var dashedNameFull = fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); if (dashedNameFull !== dashedName) { return res.redirect('../field-guide/' + dashedNameFull); @@ -54,20 +54,20 @@ exports.returnNextFieldGuide = function(req, res, next) { var completed = req.user.completedFieldGuides; - req.user.uncompletedFieldGuides = resources.allFieldGuideIds().filter(function (elem) { + var uncompletedFieldGuides = resources.allFieldGuideIds().filter(function (elem) { if (completed.indexOf(elem) === -1) { return elem; } }); + req.user.uncompletedFieldGuides = uncompletedFieldGuides; req.user.save(); - var uncompletedFieldGuides = req.user.uncompletedFieldGuides; - var displayedFieldGuides = FieldGuide.find({'_id': uncompletedFieldGuides[0]}); displayedFieldGuides.exec(function(err, fieldGuide) { if (err) { return next(err); } + fieldGuide, fieldGuide[0]); fieldGuide = fieldGuide.pop(); if (typeof fieldGuide === 'undefined') { req.flash('success', { @@ -81,14 +81,13 @@ exports.returnNextFieldGuide = function(req, res, next) { }; exports.completedFieldGuide = function (req, res, next) { - debug('params in completedFieldGuide', req.params); 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() || 0); + req.user.progressTimestamps.push(Date.now()); req.user.uncompletedFieldGuides.splice(index, 1); } diff --git a/controllers/nonprofits.js b/controllers/nonprofits.js index a7871327a3..d3b824cf3e 100644 --- a/controllers/nonprofits.js +++ b/controllers/nonprofits.js @@ -132,7 +132,6 @@ exports.returnIndividualNonprofit = function(req, res, next) { var hasShownInterest = nonprofit.interestedCampers.filter(function ( obj ) { return obj.username === req.user.profile.username; }); - console.log(hasShownInterest); if (hasShownInterest.length === 0) { buttonActive = true; } diff --git a/controllers/resources.js b/controllers/resources.js index d9e9450c6f..29e38c67c8 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -261,9 +261,7 @@ module.exports = { } else { allFieldGuideIds = fieldGuides. map(function (elem) { - return { - _id: elem._id - }; + return elem._id; }); return allFieldGuideIds; } diff --git a/controllers/story.js b/controllers/story.js index 9047279e3f..379222d5a5 100755 --- a/controllers/story.js +++ b/controllers/story.js @@ -118,7 +118,7 @@ exports.preSubmit = function(req, res) { exports.returnIndividualStory = function(req, res, next) { var dashedName = req.params.storyName; - var storyName = dashedName.replace(/\-/g, ' '); + var storyName = dashedName.replace(/\-/g, ' ').trim(); Story.find({'storyLink': storyName}, function(err, story) { if (err) { @@ -321,9 +321,10 @@ exports.storySubmission = function(req, res, next) { .replace(/\'/g, '') .replace(/\"/g, '') .replace(/,/g, '') - .replace(/[^a-z0-9]/gi, ' ') .replace(/\s+/g, ' ') - .toLowerCase(); + .replace(/[^a-z0-9\s]/gi, '') + .toLowerCase() + .trim(); var link = data.link; if (link.search(/^https?:\/\//g) === -1) { link = 'http://' + link; @@ -334,7 +335,7 @@ exports.storySubmission = function(req, res, next) { } // if duplicate storyLink add unique number - storyLink = (storyCount == 0) ? storyLink : storyLink + ' ' + storyCount; + storyLink = (storyCount === 0) ? storyLink : storyLink + ' ' + storyCount; var link = data.link; if (link.search(/^https?:\/\//g) === -1) { diff --git a/public/js/main.js b/public/js/main.js index ee6c9e1cb8..d57425e6eb 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -100,7 +100,6 @@ $(document).ready(function() { }); $('.next-field-guide-button').on('click', function() { - console.log('click'); var fieldGuideId = $('#fieldGuideId').text(); completedFieldGuide(fieldGuideId); }); @@ -126,7 +125,6 @@ $(document).ready(function() { }); $('#next-courseware-button').on('click', function() { - console.log(passedCoursewareHash); if ($('.signup-btn-nav').length < 1) { switch (challengeType) { case 0: From 2fce595e8979eef2bbc6498ace4069b95a15ca70 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Sat, 25 Apr 2015 12:40:07 -0400 Subject: [PATCH 3/7] Add story cleanup script to ensure all story links are proper. --- seed_data/storyCleanup.js | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 seed_data/storyCleanup.js diff --git a/seed_data/storyCleanup.js b/seed_data/storyCleanup.js new file mode 100644 index 0000000000..02d24009dd --- /dev/null +++ b/seed_data/storyCleanup.js @@ -0,0 +1,50 @@ +/** + * Created by nathanleniz on 4/25/15. + */ +require('dotenv').load(); +var mongodb = require('mongodb'), + Story = require('../models/Story.js'), + secrets = require('../config/secrets'); + mongoose = require('mongoose'); + +mongoose.connect(secrets.db); + +function storyLinkCleanup(cb) { + console.log('headLineCleanup'); + var i = 1; + var stream = Story.find({}).skip(0).limit(0).batchSize(20000).stream(); + + stream.on('data', function (story) { + console.log(i++); + this.pause(); + story.storyLink = story.storyLink. + replace(/\'/g, ''). + replace(/\"/g, ''). + replace(/,/g, ''). + replace(/\s+/g, ' '). + replace(/[^a-z0-9\s]/gi, ''). + toLowerCase(). + trim(); + story.save(function (err) { + if (err) { + console.log('woops'); + } + this.resume(); + }.bind(this)); + }) + .on('error', function (err) { + console.log(err); + }).on('close', function () { + console.log('done with set'); + stream.destroy(); + cb(); + }); +} + +function done() { + console.log('Migration script has completed'); + process.exit(0); +} + + +storyLinkCleanup(done); From 7c8e5e60f4b7f300c3db5fbe9f96b5350fb1e99b Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Sat, 25 Apr 2015 12:44:34 -0400 Subject: [PATCH 4/7] Modification to script. --- seed_data/storyCleanup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed_data/storyCleanup.js b/seed_data/storyCleanup.js index 02d24009dd..645ff84206 100644 --- a/seed_data/storyCleanup.js +++ b/seed_data/storyCleanup.js @@ -33,7 +33,7 @@ function storyLinkCleanup(cb) { }.bind(this)); }) .on('error', function (err) { - console.log(err); + console.error(err); }).on('close', function () { console.log('done with set'); stream.destroy(); From ce84f3eeb10f9bd1c9524d367af40678b6544bbf Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Sat, 25 Apr 2015 14:36:34 -0400 Subject: [PATCH 5/7] Fully remove remnant of errant debug statement --- controllers/fieldGuide.js | 1 - 1 file changed, 1 deletion(-) diff --git a/controllers/fieldGuide.js b/controllers/fieldGuide.js index 65b87d3931..51f01e8529 100644 --- a/controllers/fieldGuide.js +++ b/controllers/fieldGuide.js @@ -67,7 +67,6 @@ exports.returnNextFieldGuide = function(req, res, next) { if (err) { return next(err); } - fieldGuide, fieldGuide[0]); fieldGuide = fieldGuide.pop(); if (typeof fieldGuide === 'undefined') { req.flash('success', { From b8bffae47b11324f786cf99ff82880847fcf6884 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Sat, 25 Apr 2015 18:59:15 -0700 Subject: [PATCH 6/7] @terakilobyte cleaned up event handlers --- public/js/main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/js/main.js b/public/js/main.js index 6c6fc41521..5ce2c1d9bb 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -277,7 +277,7 @@ $(document).ready(function() { $('#story-submit').on('click', storySubmitButtonHandler); var commentSubmitButtonHandler = function commentSubmitButtonHandler() { - $('comment-button').unbind('click'); + $('#comment-button').unbind('click'); var data = $('#comment-box').val(); $('#comment-button').attr('disabled', 'disabled'); @@ -292,6 +292,7 @@ $(document).ready(function() { }) .fail(function (xhr, textStatus, errorThrown) { $('#comment-button').attr('disabled', false); + $('#comment-button').bind('click', commentSubmitButtonHandler); }) .done(function (data, textStatus, xhr) { window.location.reload(); From c50ce70749a4349df7a3552fcf068eaa2b4b3acd Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Sun, 26 Apr 2015 12:46:48 -0700 Subject: [PATCH 7/7] minor updates to coursewares and field guides --- seed_data/coursewares.json | 2 +- seed_data/field-guides.json | 120 +++++++----------------------------- 2 files changed, 24 insertions(+), 98 deletions(-) diff --git a/seed_data/coursewares.json b/seed_data/coursewares.json index 1f71a32e70..5667196e77 100644 --- a/seed_data/coursewares.json +++ b/seed_data/coursewares.json @@ -821,9 +821,9 @@ "Rule #3: Reverse engineer the example project's functionality, and also feel free to personalize it.", "Here are the user stories you must enable, and optional bonus user stories:", "User Story: As a user, I can play a game of Tic Tac Toe with the computer.", + "Bonus User Story: As a user, I can never actually win against the computer - at best I can tie.", "Bonus User Story: As a user, my game will reset as soon as it's over so I can play again.", "Bonus User Story: As a user, I can choose whether I want to play as X or O.", - "Hint: Here's an example call to Twitch.tv's JSON API: https://api.twitch.tv/kraken/streams/freecodecamp.", "When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.", "If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.Click here then add your link to your tweet's text" ], diff --git a/seed_data/field-guides.json b/seed_data/field-guides.json index 5b40a05a0c..e8ee3ba70e 100644 --- a/seed_data/field-guides.json +++ b/seed_data/field-guides.json @@ -258,6 +258,29 @@ "" ] }, + { + "_id": "bd7159d9c442eddfaeb5bdef", + "name": "What does Register mean?", + "description": [ + "
", + "

", + "
", + "
", + "

These global shortcuts work everywhere on a Mac:", + "
    ", + "
  • Control + F = Forward
  • ", + "
  • Control + B = Backward
  • ", + "
  • Control + N = Next Line
  • ", + "
  • Control + P = Previous Line
  • ", + "
  • Control + H = Backspace
  • ", + "
  • Control + D = Delete
  • ", + "
  • Control + A = Beginning of Line
  • ", + "
  • Control + E = End of Line
  • ", + "
  • Control + K = Kill line
  • ", + "
", + "

" + ] + }, { "_id": "bd7158d9c445eddfaeb5bdef", "name": "Gmail Zero Inbox Shortcuts", @@ -370,103 +393,6 @@ "
" ] }, - { - "_id": "bd7158d9c448eddfaeb5bdef", - "name": "Live Stream Pair Programming on Twitch.tv", - "description": [ - "
", - "

Live Pair Programming

", - "

", - "

Watch the live stream below or on our  Twitch.tv channel.

", - "
", - "
", - "
", - " ", - "
", - "
", - "
", - "
", - "
", - " ", - "
", - "
", - "
", - "
", - " ", - "
", - "
", - "
", - "
", - "

Previous Live Pair Programming Sessions

", - "
", - "
", - " ", - "
", - "

link:  http://www.youtube.com/watch/_BErpDdmBOw

", - "
", - " ", - "
", - "

link:  http://www.youtube.com/watch/Fn9HMn79KH0

", - "
", - " ", - "
", - "

link:  http://www.youtube.com/watch/S7iRBZJwOAs

", - "
", - " ", - "
", - "

link:  http://www.youtube.com/watch/BHNRg39ZblE

", - "
", - " ", - "
", - "

link:  http://www.youtube.com/watch/YDfkHlDmehA

", - "
", - " ", - " ", - " ", - "
" - ] - }, - { - "_id": "bd7158d9c449eddfaeb5bdef", - "name": "Nodeschool Challenges", - "description": [ - "

Learn Node.js, NPM, Express.js, and advanced JavaScript like Functional Programming and Promises


", - "
", - " ", - "
", - "
" - ] - }, - { - "_id": "bd7158d9c450eddfaeb5bdef", - "name": "Nonprofit Project Instructions", - "description": [ - "
", - "

It's time to apply what you've learned here at Free Code Camp.

", - "

By the end of this process, you'll have a portfolio of live apps being used by real people.

", - "

Please do the following immediately:

", - "

", - "
    ", - "
  1. Complete this form:  http://goo.gl/forms/f61dLt67t8.
  2. ", - "
  3. Read this document, which will answer many questions you may have about our nonprofit projects:  http://freecodecamp.com/field-guide/guide-to-our-nonprofit-projects.
  4. ", - "
  5. We'll send you an invite to our Nonprofit Projects Trello board. Once we do, go there and add yourself to at least 3 nonprofit projects that interest you.
  6. ", - "
  7. Finish any unfinished Bonfire challenges. These challenges serve as the Free Code Camp \"exit test\". You must complete these before you can start working on nonprofit projects. If you completed CoderByte or CodeWars challenges instead of Bonfire, email us and we'll take a look: team@freecodecamp.com.
  8. ", - "
", - "

Please email us if you have further questions:  team@freecodecamp.com.

", - "", - "
" - ] - }, { "_id": "bd7158d9c451eddfaeb5bded", "name": "Bonfire Style Guide",