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 9047279e3f0..9803a7afa0c 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 ee6c9e1cb8f..4525ca7a72d 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 07412b59be0..425d52890de 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 edb1e3c6b86..1e04c3a34f4 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 1c92c7d9f14..30df332f225 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 88678099e1a..68f22d95321 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 d4bf9a216d9..65b87d3931b 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 a7871327a37..d3b824cf3ea 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 d9e9450c6f0..29e38c67c8d 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 9047279e3f0..379222d5a5c 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 ee6c9e1cb8f..d57425e6ebd 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 00000000000..02d24009ddc --- /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 02d24009ddc..645ff842066 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 65b87d3931b..51f01e8529c 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 6c6fc41521b..5ce2c1d9bb7 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 1f71a32e704..5667196e77f 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 5b40a05a0ca..e8ee3ba70e2 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",