From a0f4f91ec19e59ea31468c347743ef9e72a15823 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Sat, 20 Jun 2015 18:40:43 -0700 Subject: [PATCH 01/51] remove slack completely and update help and pair actions --- config/secrets.js | 4 - public/js/main_0.0.2.js | 60 -------- server/boot/randomAPIs.js | 131 +----------------- server/views/coursewares/showBonfire.jade | 7 +- server/views/coursewares/showHTML.jade | 2 - server/views/coursewares/showJS.jade | 23 ++- server/views/coursewares/showVideo.jade | 15 +- .../coursewares/showZiplineOrBasejump.jade | 23 ++- server/views/partials/challenge-modals.jade | 22 +-- server/views/partials/navbar.jade | 8 +- 10 files changed, 40 insertions(+), 255 deletions(-) diff --git a/config/secrets.js b/config/secrets.js index 2ea3cdef28..ed369d9766 100644 --- a/config/secrets.js +++ b/config/secrets.js @@ -13,10 +13,6 @@ module.exports = { key: process.env.BLOGGER_KEY }, - slack: { - key: process.env.SLACK_KEY - }, - mandrill: { user: process.env.MANDRILL_USER, password: process.env.MANDRILL_PASSWORD diff --git a/public/js/main_0.0.2.js b/public/js/main_0.0.2.js index 65ec9f170c..98ea7c23ed 100644 --- a/public/js/main_0.0.2.js +++ b/public/js/main_0.0.2.js @@ -16,67 +16,11 @@ $(document).ready(function() { setCSRFToken($('meta[name="csrf-token"]').attr('content')); - $('#i-want-help').on('click', function() { - $('#help-modal').modal('hide'); - var editorValue = editor.getValue(); - var currentLocation = window.location.href; - $.post( - '/get-help', - { - payload: { - code: editorValue, - challenge: currentLocation - } - }, - function(res) { - if (res) { - window.open('https://freecodecamp.slack.com/messages/help/', '_blank') - } - } - ); - }); - - $('#i-want-help-editorless').on('click', function() { - $('#help-editorless-modal').modal('hide'); - var currentLocation = window.location.href; - $.post( - '/get-help', - { - payload: { - challenge: currentLocation - } - }, - function(res) { - if (res) { - window.open('https://freecodecamp.slack.com/messages/help/', '_blank') - } - } - ); - }); - $('#report-issue').on('click', function() { $('#issue-modal').modal('hide'); window.open('https://github.com/freecodecamp/freecodecamp/issues/new?&body=Challenge '+ window.location.href +' has an issue. Please describe how to reproduce it, and include links to screen shots if possible.', '_blank') }); - $('#i-want-to-pair').on('click', function() { - $('#pair-modal').modal('hide'); - var currentLocation = window.location.href; - $.post( - '/get-pair', - { - payload: { - challenge: currentLocation - } - }, - function(res) { - if (res) { - window.open('https://freecodecamp.slack.com/messages/letspair/', '_blank') - } - } - ); - }); - $('.checklist-element').each(function() { var checklistElementId = $(this).attr('id'); if(!!localStorage[checklistElementId]) { @@ -150,10 +94,6 @@ $(document).ready(function() { $('#help-modal').modal('show'); }); - $('#trigger-help-editorless-modal').on('click', function() { - $('#help-editorless-modal').modal('show'); - }); - $('#trigger-issue-modal').on('click', function() { $('#issue-modal').modal('show'); }); diff --git a/server/boot/randomAPIs.js b/server/boot/randomAPIs.js index 7dd89b1b4f..c0092b558d 100644 --- a/server/boot/randomAPIs.js +++ b/server/boot/randomAPIs.js @@ -2,10 +2,8 @@ var Rx = require('rx'), Twit = require('twit'), async = require('async'), moment = require('moment'), - Slack = require('node-slack'), request = require('request'), debug = require('debug')('freecc:cntr:resources'), - constantStrings = require('../utils/constantStrings.json'), bootcampJson = require('../utils/bootcamps.json'), secrets = require('../../config/secrets'); @@ -24,8 +22,6 @@ module.exports = function(app) { 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('/coding-bootcamp-cost-calculator', bootcampCalculator); router.get('/coding-bootcamp-cost-calculator.json', bootcampCalculatorJson); @@ -39,72 +35,9 @@ module.exports = function(app) { router.get('/unsubscribe/:email', unsubscribe); router.get('/unsubscribed', unsubscribed); router.get('/cats.json', getCats); - router.get('/api/slack', slackInvite); app.use(router); - 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 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); - } - 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: 'You need to sign in to Free Code Camp before ' + - 'we can send you a Slack invite.' - }); - return res.redirect('/account'); - } - } - function twitter(req, res, next) { // sends out random tweets about javascript var T = new Twit({ @@ -134,56 +67,6 @@ module.exports = function(app) { ); } - - function getHelp(req, res) { - var userName = req.user.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.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'); @@ -317,16 +200,6 @@ module.exports = function(app) { ); } - 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 bootcampCalculator(req, res) { res.render('resources/calculator', { title: 'Coding Bootcamp Cost Calculator', @@ -338,6 +211,10 @@ module.exports = function(app) { res.send(bootcampJson); } + function chat(req, res) { + res.redirect('https://gitter.im/FreeCodeCamp/FreeCodeCamp'); + } + function jobsForm(req, res) { res.render('resources/jobs-form', { title: 'Employer Partnership Form for Job Postings,' + diff --git a/server/views/coursewares/showBonfire.jade b/server/views/coursewares/showBonfire.jade index c28f20e322..1eae48b09e 100644 --- a/server/views/coursewares/showBonfire.jade +++ b/server/views/coursewares/showBonfire.jade @@ -84,14 +84,9 @@ block content label.negative-10.btn.btn-primary.btn-block#submitButton i.fa.fa-play |   Run code (ctrl + enter) - #resetButton.btn.btn-danger.btn-big.btn-block(data-toggle='modal', data-target='#reset-modal', data-backdrop='true') Reset Code - if (user && user.sentSlackInvite) .button-spacer .btn-group.input-group.btn-group-justified - label.btn.btn-success#trigger-help-modal - i.fa.fa-refresh - |   Reset - label.btn.btn-success#trigger-help-modal + label.btn.btn-success#resetButton i.fa.fa-refresh |   Reset label.btn.btn-success#trigger-help-modal diff --git a/server/views/coursewares/showHTML.jade b/server/views/coursewares/showHTML.jade index 2ebc64d0dd..755fb92b6d 100644 --- a/server/views/coursewares/showHTML.jade +++ b/server/views/coursewares/showHTML.jade @@ -33,8 +33,6 @@ block content label.btn.btn-primary.btn-block.negative-10#next-courseware-button .ion-checkmark-circled |   Go to my next challenge (ctrl + enter) - - if (user.sentSlackInvite) .button-spacer .btn-group.input-group.btn-group-justified label.btn.btn-success#trigger-help-modal diff --git a/server/views/coursewares/showJS.jade b/server/views/coursewares/showJS.jade index 58737351ac..369e810061 100644 --- a/server/views/coursewares/showJS.jade +++ b/server/views/coursewares/showJS.jade @@ -35,18 +35,17 @@ block content span.ion-arrow-up-b | Less information #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) - if (user && user.sentSlackInvite) - .button-spacer - .btn-group.input-group.btn-group-justified - label.btn.btn-success#trigger-help-modal - i.fa.fa-medkit - |   Help - label.btn.btn-success#trigger-pair-modal - i.fa.fa-user-plus - |   Pair - label.btn.btn-success#trigger-issue-modal - i.fa.fa-bug - |   Bug + .button-spacer + .btn-group.input-group.btn-group-justified + label.btn.btn-success#trigger-help-modal + i.fa.fa-medkit + |   Help + label.btn.btn-success#trigger-pair-modal + i.fa.fa-user-plus + |   Pair + label.btn.btn-success#trigger-issue-modal + i.fa.fa-bug + |   Bug .spacer form.code .form-group.codeMirrorView diff --git a/server/views/coursewares/showVideo.jade b/server/views/coursewares/showVideo.jade index 4e9d82a6ec..ae3e049c90 100644 --- a/server/views/coursewares/showVideo.jade +++ b/server/views/coursewares/showVideo.jade @@ -21,14 +21,13 @@ block content script. var userLoggedIn = true; .button-spacer - if (user.sentSlackInvite) - .btn-group.input-group.btn-group-justified - .btn.btn-success.btn-big#trigger-help-editorless-modal - i.fa.fa-medkit - |   Get help - .btn.btn-success.btn-big#trigger-issue-modal - i.fa.fa-bug - |   Report a bug + .btn-group.input-group.btn-group-justified + .btn.btn-success.btn-big#trigger-help-modal-modal + i.fa.fa-medkit + |   Get help + .btn.btn-success.btn-big#trigger-issue-modal + i.fa.fa-bug + |   Report a bug .button-spacer else a.btn.btn-big.signup-btn.btn-block(href='/login') Sign in so you can save your progress diff --git a/server/views/coursewares/showZiplineOrBasejump.jade b/server/views/coursewares/showZiplineOrBasejump.jade index ae72c47528..6bd38d9737 100644 --- a/server/views/coursewares/showZiplineOrBasejump.jade +++ b/server/views/coursewares/showZiplineOrBasejump.jade @@ -18,18 +18,17 @@ block content br if (user) a.btn.btn-primary.btn-big.btn-block#completed-zipline-or-basejump I've completed this challenge (ctrl + enter) - if (user.sentSlackInvite) - .button-spacer - .btn-group.input-group.btn-group-justified - .btn.btn-success.btn-big#trigger-help-editorless-modal - i.fa.fa-medkit - |   Help - .btn.btn-success.btn-big#trigger-pair-modal - i.fa.fa-user-plus - |   Pair - .btn.btn-success.btn-big#trigger-issue-modal - i.fa.fa-bug - |   Bug + .button-spacer + .btn-group.input-group.btn-group-justified + .btn.btn-success.btn-big#trigger-help-modal + i.fa.fa-medkit + |   Help + .btn.btn-success.btn-big#trigger-pair-modal + i.fa.fa-user-plus + |   Pair + .btn.btn-success.btn-big#trigger-issue-modal + i.fa.fa-bug + |   Bug .button-spacer script. var userLoggedIn = true; diff --git a/server/views/partials/challenge-modals.jade b/server/views/partials/challenge-modals.jade index 5270e77cd4..ba8dc1becd 100644 --- a/server/views/partials/challenge-modals.jade +++ b/server/views/partials/challenge-modals.jade @@ -4,12 +4,12 @@ .modal-header.challenge-list-header Ready to pair program? a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body.text-center - h3 This will create a pair programming request. + h3 This will take you to our pair programming room where you can request a pair. h3 You'll need   a(href='/field-guide/how-do-i-install-screenhero' target='_blank') Screen Hero | . h3 Other campers may then message you about pair programming. - a.btn.btn-lg.btn-primary.btn-block#i-want-to-pair(name='_csrf', value=_csrf) Create my pair request + a.btn.btn-lg.btn-primary.btn-block(href='https://gitter.im/FreeCodeCamp/LetsPair', data-dismiss='modal', aria-hidden='true' target='_blank') Take me to the pair programming room a.btn.btn-lg.btn-info.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel #issue-modal.modal(tabindex='-1') @@ -33,20 +33,6 @@ a(href='/field-guide/how-do-i-get-help-when-i-get-stuck' target='_blank') RSAP | . h3 If you've already read the errors and searched Google, you should ask for help. - h3 This will open a help request in our Help chat room. - a.btn.btn-lg.btn-primary.btn-block#i-want-help(name='_csrf', value=_csrf) Ask for help - a.btn.btn-lg.btn-info.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel - -#help-editorless-modal.modal(tabindex='-1') - .modal-dialog.animated.zoomIn.fast-animation - .modal-content - .modal-header.challenge-list-header Need some help? - a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - .modal-body.text-center - h3 Remember to use   - a(href='/field-guide/how-do-i-get-help-when-i-get-stuck' target='_blank') RSAP - | . - h3 If you've already read the errors and searched Google, you should ask for help. - h3 This will open a help request in our Help chat room. - a.btn.btn-lg.btn-primary.btn-block#i-want-help-editorless(name='_csrf', value=_csrf) Ask for help + h3 This will take you to our help room. + a.btn.btn-lg.btn-primary.btn-block(href='https://gitter.im/FreeCodeCamp/LetsPair', data-dismiss='modal', aria-hidden='true' target='_blank') Take me to the help room a.btn.btn-lg.btn-info.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel diff --git a/server/views/partials/navbar.jade b/server/views/partials/navbar.jade index 317ce661d1..0316cbe202 100644 --- a/server/views/partials/navbar.jade +++ b/server/views/partials/navbar.jade @@ -13,12 +13,8 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height a(href='/challenges') Learn li a(href='/map') Map - if (user && user.sentSlackInvite) - li - a(href='/chat', target='_blank') Chat - else - li - a(href='/challenges/waypoint-join-our-chat-room') Chat + li + a(href='gitter.im/FreeCodeCamp/FreeCodeCamp', target='_blank') Chat li a(href='/stories') News li From c113c72082667a150a973c137510fb1d591d32e2 Mon Sep 17 00:00:00 2001 From: Rex Schrader Date: Sat, 20 Jun 2015 22:09:21 -0700 Subject: [PATCH 02/51] Fix missing semicolon in Symmetric Differences test The first test of Symmetric Differences is broken because it was missing a semicolon, making it impossible to pass. --- seed_data/challenges/advanced-bonfires.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed_data/challenges/advanced-bonfires.json b/seed_data/challenges/advanced-bonfires.json index 027a573200..8d62e77f47 100644 --- a/seed_data/challenges/advanced-bonfires.json +++ b/seed_data/challenges/advanced-bonfires.json @@ -79,7 +79,7 @@ "sym([1, 2, 3], [5, 2, 1, 4]);" ], "tests": [ - "expect(sym([1, 2, 3], [5, 2, 1, 4])).to.eqls([3, 5, 4])", + "expect(sym([1, 2, 3], [5, 2, 1, 4])).to.eqls([3, 5, 4]);", "assert.deepEqual(sym([1, 2, 5], [2, 3, 5], [3, 4, 5]), [1, 4, 5], 'should return the symmetric difference of the given arrays');", "assert.deepEqual(sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]), [1, 4, 5], 'should return an array of unique values');", "assert.deepEqual(sym([1, 1]), [1], 'should return an array of unique values');" From 5db883346e020406ac630a91d4c3b7f9f1036413 Mon Sep 17 00:00:00 2001 From: brandenbyers Date: Sun, 21 Jun 2015 00:56:20 -0500 Subject: [PATCH 03/51] Change eqls to equal --- seed/challenges/advanced-bonfires.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/advanced-bonfires.json b/seed/challenges/advanced-bonfires.json index 978ebe81ce..58f6602791 100644 --- a/seed/challenges/advanced-bonfires.json +++ b/seed/challenges/advanced-bonfires.json @@ -81,7 +81,7 @@ "sym([1, 2, 3], [5, 2, 1, 4]);" ], "tests": [ - "expect(sym([1, 2, 3], [5, 2, 1, 4])).to.eqls([3, 5, 4])", + "expect(sym([1, 2, 3], [5, 2, 1, 4])).to.equal([3, 5, 4]);", "assert.deepEqual(sym([1, 2, 5], [2, 3, 5], [3, 4, 5]), [1, 4, 5], 'should return the symmetric difference of the given arrays');", "assert.deepEqual(sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]), [1, 4, 5], 'should return an array of unique values');", "assert.deepEqual(sym([1, 1]), [1], 'should return an array of unique values');" From 1a502b169654ad7c3b828a34e7bc2630b549d8d1 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Sat, 20 Jun 2015 23:00:19 -0700 Subject: [PATCH 04/51] hotfix for production gitter change --- .gitignore | 2 +- seed_data/challenges/basic-bonfires.json | 2 +- .../challenges/get-set-for-free-code-camp.json | 16 +++++++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 2906662140..d808277441 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ lib-cov .floo .flooignore builtAssets/ - +pm2.js *.env pids logs diff --git a/seed_data/challenges/basic-bonfires.json b/seed_data/challenges/basic-bonfires.json index 5c62a354b1..a40b1f917b 100644 --- a/seed_data/challenges/basic-bonfires.json +++ b/seed_data/challenges/basic-bonfires.json @@ -1078,7 +1078,7 @@ ], "tests": [ "assert.strictEqual(convert('Dolce & Gabbana'), 'Dolce & Gabbana', 'should escape characters');", - "assert.strictEqual('Submit', '<input type="submit">Submit</input>', 'should escape characters');", + "assert.strictEqual('<>', '<>', 'should escape characters');", "assert.strictEqual(convert('abc'), 'abc', 'should handle strings with nothing to escape');" ], "MDNlinks": [ diff --git a/seed_data/challenges/get-set-for-free-code-camp.json b/seed_data/challenges/get-set-for-free-code-camp.json index 7518ea9f10..d3f5102f29 100644 --- a/seed_data/challenges/get-set-for-free-code-camp.json +++ b/seed_data/challenges/get-set-for-free-code-camp.json @@ -43,18 +43,20 @@ "_id": "bd7125d8c441eddfaeb5bd0f", "name": "Waypoint: Join Our Chat Room", "difficulty": 0.002, - "challengeSeed": "124555254", + "challengeSeed": ["131321596"], "description": [ "Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another camper to pair program with.", - "Make sure your Free Code Camp account includes your email address. Please note that the email address you use will be invisible to the public, but Slack will make it visible to other campers in our slack chat rooms. You can do this here: http://freecodecamp.com/account.", - "Click this link, which will email you can invite to Free Code Camp's Slack chat rooms: http://freecodecamp.com/api/slack.", - "Now check your email and click the link in the email from Slack.", - "Complete the sign up process, then update your biographical information and upload an image. A picture of your face works best. This is how people will see you in our chat rooms, so put your best foot forward.", - "Now enter the General chat room and introduce yourself to our chat room by typing: \"Hello world!\".", + "Create an account with GitHub here: https://github.com/join.", + "Click the pixel art in the upper right hand corner of GitHub, then choose settings. Upload a picture of yourself. A picture of your face works best. This is how people will see you in our chat rooms, so put your best foot forward. You can add your city and your personal website if you have one.", + "Now follow this link to enter our Welcome chat room: https://gitter.im/FreeCodeCamp/welcome.", + "Once you're in our Welcome chat room, introduce yourself by saying : \"Hello world!\".", "Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.", + "This is the best room for new campers, but feel free to join other chat rooms as well. Our main chat room: https://gitter.im/FreeCodeCamp/FreeCodeCamp.", "Keep the chat room open while you work through the other challenges. That way you ask for help if you get stuck on a challenge. You can also socialize when you feel like taking a break.", + "You can also download a desktop or mobile chat application here: https://gitter.im/apps", "You can also access this chat room by clicking the \"Chat\" button in the upper right hand corner.", - "In order to keep our community a friendly and positive place to learn to code, please read and follow our Code of Conduct: http://freecodecamp.com/field-guide/what-is-the-free-code-camp-code-of-conduct?" + "In order to keep our community a friendly and positive place to learn to code, please read and follow our Code of Conduct: http://freecodecamp.com/field-guide/what-is-the-free-code-camp-code-of-conduct?", + "Now you're ready to move on. Click the \"I've completed this challenge\" button to move on to your next challenge." ], "challengeType": 2, "tests": [], From c2d36522724b06e9c4f6d785af66a52bcc8c9ba8 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Sat, 20 Jun 2015 23:07:49 -0700 Subject: [PATCH 05/51] fix navbar for gitter --- views/partials/navbar.jade | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/views/partials/navbar.jade b/views/partials/navbar.jade index 2cacd3fc32..93b49da0ae 100644 --- a/views/partials/navbar.jade +++ b/views/partials/navbar.jade @@ -13,12 +13,8 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height a(href='/challenges') Learn li a(href='/map') Map - if (user && user.sentSlackInvite) - li - a(href='/chat', target='_blank') Chat - else - li - a(href='/challenges/waypoint-join-our-chat-room') Chat + li + a(href='//gitter.im/FreeCodeCamp/FreeCodeCamp', target='_blank') Chat li a(href='/news') News li From 9f1b6c58a5c630ba535f6dc8a411b5a89e2751ce Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 20 Jun 2015 23:16:45 -0700 Subject: [PATCH 06/51] fix /chat should go to gitter --- controllers/resources.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/controllers/resources.js b/controllers/resources.js index 0bf414954b..9101f82edf 100644 --- a/controllers/resources.js +++ b/controllers/resources.js @@ -231,13 +231,7 @@ module.exports = { }, chat: 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' - }); - } + res.redirect('//gitter.im/FreeCodeCamp/FreeCodeCamp'); }, jobsForm: function jobsForm(req, res) { From 4804d868c4f1d0c61fb045b05a6c818a61ed631a Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sun, 21 Jun 2015 18:10:17 -0700 Subject: [PATCH 07/51] wrap server.listen in if module prevents server from starting durring testing/scripting --- server/server.js | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/server/server.js b/server/server.js index 4d59ccbe7f..5ecabef369 100755 --- a/server/server.js +++ b/server/server.js @@ -301,20 +301,15 @@ if (true) { // eslint-disable-line }); } -/** - * Start Express server. - */ - - -app.listen(app.get('port'), function() { - console.log( - 'FreeCodeCamp server listening on port %d in %s mode', - app.get('port'), - app.get('env') - ); -}); +module.exports = app; // start the server if `$ node server.js` - - -module.exports = app; +if (require.main === module) { + app.listen(app.get('port'), function() { + console.log( + 'FreeCodeCamp server listening on port %d in %s mode', + app.get('port'), + app.get('env') + ); + }); +} From 5e8ed616d5da387d41b3c67d65e7f4ca605d0b29 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sun, 21 Jun 2015 18:37:29 -0700 Subject: [PATCH 08/51] add wait for db in development before listening for connections --- server/server.js | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/server/server.js b/server/server.js index 5ecabef369..3738150255 100755 --- a/server/server.js +++ b/server/server.js @@ -1,7 +1,6 @@ require('dotenv').load(); var pmx = require('pmx'); pmx.init(); -// handle uncaught exceptions. Forever will restart process on shutdown var R = require('ramda'), assign = require('lodash').assign, @@ -303,8 +302,7 @@ if (true) { // eslint-disable-line module.exports = app; -// start the server if `$ node server.js` -if (require.main === module) { +app.start = function () { app.listen(app.get('port'), function() { console.log( 'FreeCodeCamp server listening on port %d in %s mode', @@ -312,4 +310,29 @@ if (require.main === module) { app.get('env') ); }); +}; + +// start the server if `$ node server.js` +if (require.main === module) { + if (process.env.NODE_ENV === 'production') { + console.log('waiting for db to connect'); + var timeoutHandler; + var onConnect = function() { + console.log('db connected'); + if (timeoutHandler) { + clearTimeout(timeoutHandler); + } + app.start(); + }; + + var timeoutHandler = setTimeout(function() { + // purposely shutdown server + // pm2 should restart this in production + throw new Error('db did not connect, crashing hard'); + }, 5000); + + app.dataSources.db.on('connected', onConnect); + } else { + app.start(); + } } From 7df91e63102efaeee7d9077d3e7bc6fcba743ca1 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sun, 21 Jun 2015 19:11:42 -0700 Subject: [PATCH 09/51] add total time to start to logs during waitFordb --- server/server.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/server/server.js b/server/server.js index 3738150255..637892831e 100755 --- a/server/server.js +++ b/server/server.js @@ -187,6 +187,8 @@ app.use( }) ); +// track when connecting to db starts +var startTime = Date.now(); boot(app, { appRootDir: __dirname, dev: process.env.NODE_ENV @@ -315,10 +317,11 @@ app.start = function () { // start the server if `$ node server.js` if (require.main === module) { if (process.env.NODE_ENV === 'production') { - console.log('waiting for db to connect'); var timeoutHandler; + console.log('waiting for db to connect'); + var onConnect = function() { - console.log('db connected'); + console.log('db connected in %s ms', Date.now() - startTime); if (timeoutHandler) { clearTimeout(timeoutHandler); } @@ -326,9 +329,15 @@ if (require.main === module) { }; var timeoutHandler = setTimeout(function() { + var message = + 'db did not after ' + + (Date.now() - startTime) + + ' ms connect crashing hard'; + + console.log(message); // purposely shutdown server // pm2 should restart this in production - throw new Error('db did not connect, crashing hard'); + throw new Error(message); }, 5000); app.dataSources.db.on('connected', onConnect); From 6972f64697a557a31dd8d1c802284f844e9df411 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Sun, 21 Jun 2015 22:46:37 -0700 Subject: [PATCH 10/51] fix minor issue with controller --- server/boot/randomAPIs.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/boot/randomAPIs.js b/server/boot/randomAPIs.js index 84059c564e..2046db0ce1 100644 --- a/server/boot/randomAPIs.js +++ b/server/boot/randomAPIs.js @@ -8,7 +8,6 @@ var Rx = require('rx'), bootcampJson = require('../utils/bootcamps.json'), secrets = require('../../config/secrets'); -var slack = new Slack(secrets.slackHook); module.exports = function(app) { var router = app.loopback.Router(); var User = app.models.User; From 7004e81b4a29b196a659bcb5cdcef30253b71d6f Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Mon, 22 Jun 2015 10:30:26 -0400 Subject: [PATCH 11/51] Fixes #1053 This changes the link to point to Free Code Camp's "Lets Pair" room on gitter and opens the link in a new tab/window depending on user browser preference. --- seed/challenges/basic-bonfires.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/basic-bonfires.json b/seed/challenges/basic-bonfires.json index ae9b0b571a..bc75adb4ac 100644 --- a/seed/challenges/basic-bonfires.json +++ b/seed/challenges/basic-bonfires.json @@ -14,7 +14,7 @@ "Pair Programming is where two people code together on the same computer. It is an efficient way to collaborate, and widely practiced at software companies. Pair Programming is one of the core concepts of \"Agile\" Software Development, which you will hear more about later.", "Many people use Skype or Google Hangouts to pair program, but if you talk with professional software engineers, they will tell you that it's not really pair programming unless both people have the ability to use the keyboard and mouse.", "The most popular tool for pair programming is Screen Hero. You can download Screen Hero for Mac or Windows. Create your new user account from within the app.", - "We have a special chat room for people ready to pair program. Go to our http://freecodecamp.slack.com/messages/letspair and type \"Hello Pair Programmers!\"", + "We have a special chat room for people ready to pair program. Go to our LetsPair chatroom on gitter and type \"Hello Pair Programmers!\"", "If someone is available, they will be your \"pair\" - the person you pair programming with.", "If no one gets back to you in the first few minutes, don't worry. There will be lots of opportunities to pair program in the future.", "If someone does get back to you, private message them and ask for the email address they used to register Screen Hero.", From 10a5c3c92848b2d33f1b937c56a18ba98cf00953 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Mon, 22 Jun 2015 16:43:31 -0700 Subject: [PATCH 12/51] fix send 200 to non users on post to challenges fix remove random 'yo' debug --- server/boot/challenge.js | 29 ++++++++++++++++++++++------- server/utils/middleware.js | 8 ++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 02cecc639e..87f5be1611 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -41,7 +41,8 @@ var R = require('ramda'), observableQueryFromModel = require('../utils/rx').observableQueryFromModel, userMigration = require('../utils/middleware').userMigration, - ifNoUserRedirectTo = require('../utils/middleware').ifNoUserRedirectTo; + ifNoUserRedirectTo = require('../utils/middleware').ifNoUserRedirectTo, + ifNoUserSend = require('../utils/middleware').ifNoUserSend; var challengeMapWithNames = utils.getChallengeMapWithNames(); var challengeMapWithIds = utils.getChallengeMapWithIds(); @@ -75,17 +76,32 @@ module.exports = function(app) { var router = app.loopback.Router(); var Challenge = app.models.Challenge; var User = app.models.User; + var redirectNonUser = + ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works'); + var send200toNonUser = ifNoUserSend(true); - router.post('/completed-challenge/', completedChallenge); - router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump); - router.post('/completed-bonfire', completedBonfire); + router.post( + '/completed-challenge/', + send200toNonUser, + completedChallenge + ); + router.post( + '/completed-zipline-or-basejump', + send200toNonUser, + completedZiplineOrBasejump + ); + router.post( + '/completed-bonfire', + send200toNonUser, + completedBonfire + ); // the follow routes are covered by userMigration router.use(userMigration); router.get('/map', challengeMap); router.get( '/challenges/next-challenge', - ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works'), + redirectNonUser, returnNextChallenge ); @@ -93,7 +109,7 @@ module.exports = function(app) { router.get( '/challenges/', - ifNoUserRedirectTo('/challenges/learn-how-free-code-camp-works'), + redirectNonUser, returnCurrentChallenge ); @@ -308,7 +324,6 @@ module.exports = function(app) { .withLatestFrom( Rx.Observable.just(req.user), function(pairedWith, user) { - debug('yo'); return { user: user, pairedWith: pairedWith diff --git a/server/utils/middleware.js b/server/utils/middleware.js index 5af207e513..dc0219f0a4 100644 --- a/server/utils/middleware.js +++ b/server/utils/middleware.js @@ -43,3 +43,11 @@ exports.ifNoUserRedirectTo = function ifNoUserRedirectTo(url) { }; }; +exports.ifNoUserSend = function ifNoUserSend(sendThis) { + return function(req, res, next) { + if (req.user) { + return next(); + } + return res.status(200).send(sendThis); + }; +}; From d624cf364094d0830052752ce238d3f89cfc9eff Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Mon, 22 Jun 2015 17:24:55 -0700 Subject: [PATCH 13/51] factor out (un)dasherize into utils --- server/boot/challenge.js | 16 ++++------------ server/utils/index.js | 11 +++++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 87f5be1611..81219341de 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -47,21 +47,13 @@ var R = require('ramda'), var challengeMapWithNames = utils.getChallengeMapWithNames(); var challengeMapWithIds = utils.getChallengeMapWithIds(); var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames(); +var challangesRegex = /^(bonfire|waypoint|zipline|basejump)/i; + +var dasherize = utils.dasherize; +var unDasherize = utils.unDashedName; var getMDNLinks = utils.getMDNLinks; -var challangesRegex = /^(bonfire|waypoint|zipline|basejump)/i; -function dasherize(name) { - return ('' + name) - .toLowerCase() - .replace(/\s/g, '-') - .replace(/[^a-z0-9\-\.]/gi, ''); -} - -function unDasherize(name) { - return ('' + name).replace(/\-/g, ' '); -} - function updateUserProgress(user, challengeId, completedChallenge) { var index = user.uncompletedChallenges.indexOf(challengeId); if (index > -1) { diff --git a/server/utils/index.js b/server/utils/index.js index bff016241c..b49a4579f7 100644 --- a/server/utils/index.js +++ b/server/utils/index.js @@ -60,6 +60,17 @@ Array.zip = function(left, right, combinerFunction) { module.exports = { + dasherize: function dasherize(name) { + return ('' + name) + .toLowerCase() + .replace(/\s/g, '-') + .replace(/[^a-z0-9\-\.]/gi, ''); + }, + + unDasherize: function unDasherize(name) { + return ('' + name).replace(/\-/g, ' '); + }, + getChallengeMapForDisplay: function () { if (!challengeMapForDisplay) { challengeMapForDisplay = {}; From 663d559fb49682ce002a924ad0b1c116cad1a333 Mon Sep 17 00:00:00 2001 From: Steven Leiva Date: Mon, 22 Jun 2015 22:08:58 -0400 Subject: [PATCH 14/51] Change links to Gitter channel. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9770de3ebc..0029a894e3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Our campers (students) start by working through our free, self-paced, browser-ba 80% of our campers are over 25, and nearly a fifth of our campers are women. -This code is running live at [FreeCodeCamp.com](http://www.FreeCodeCamp.com). We also have [Slack](http://freecodecamp.slack.com), a [blog](http://blog.freecodecamp.com), and even a [Twitch.tv channel](http://twitch.tv/freecodecamp). +This code is running live at [FreeCodeCamp.com](http://www.FreeCodeCamp.com). We also have [Gitter](https://gitter.im/FreeCodeCamp/FreeCodeCamp), a [blog](http://blog.freecodecamp.com), and even a [Twitch.tv channel](http://twitch.tv/freecodecamp). [Join our community](http://www.freecodecamp.com/signin)! @@ -27,7 +27,7 @@ Contributing We welcome pull requests from Free Code Camp campers (our students) and seasoned JavaScript developers alike! Follow these steps to contribute: 1. Check our [public Waffle Board](https://waffle.io/freecodecamp/freecodecamp). -2. Pick an issue that nobody has claimed and start working on it. If your issue isn't on the board, open an issue. If you think you can fix it yourself, start working on it. Feel free to ask for help in our [Slack](http://freecodecamp.slack.com). +2. Pick an issue that nobody has claimed and start working on it. If your issue isn't on the board, open an issue. If you think you can fix it yourself, start working on it. Feel free to ask for help in our [Gitter](https://gitter.im/FreeCodeCamp/FreeCodeCamp) 3. Fork the project ([Need help with forking a project?](https://help.github.com/articles/fork-a-repo/)). You'll do all of your work on your forked copy. 4. Create a branch specific to the issue or feature you are working on. Push your work to that branch. ([Need help with branching?](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)) 5. Name the branch something like `user-xxx` where user is your username and xxx is the issue number you are addressing. From b383da47bb9337ec7b8a8a2d30ec7519a9cb0f48 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Mon, 22 Jun 2015 19:18:15 -0700 Subject: [PATCH 15/51] fix uncompletedBonfires should default to empty array --- common/models/user.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/models/user.json b/common/models/user.json index a7caad6227..0254ff8407 100644 --- a/common/models/user.json +++ b/common/models/user.json @@ -134,7 +134,8 @@ "type": "string" }, "uncompletedBonfires": { - "type": "array" + "type": "array", + "default": [] }, "completedBonfires": { "type": [ From 1dda8e2f87a67e6723accea24b2b23f39807cfe9 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Mon, 22 Jun 2015 19:23:07 -0700 Subject: [PATCH 16/51] refactor Rxify nonprofits router --- server/boot/nonprofits.js | 94 +++++++++++++++------------------------ server/utils/rx.js | 5 +++ 2 files changed, 42 insertions(+), 57 deletions(-) diff --git a/server/boot/nonprofits.js b/server/boot/nonprofits.js index 1d16e76b74..8411d7f31c 100644 --- a/server/boot/nonprofits.js +++ b/server/boot/nonprofits.js @@ -1,6 +1,13 @@ +var debug = require('debug')('freecc:nonprofits'); +var observeMethod = require('../utils/rx').observeMethod; +var unDasherize = require('../utils').unDasherize; +var dasherize = require('../utils').dasherize; + module.exports = function(app) { var router = app.loopback.Router(); var Nonprofit = app.models.Nonprofit; + var findNonprofits = observeMethod(Nonprofit, 'find'); + var findOneNonprofit = observeMethod(Nonprofit, 'findOne'); router.get('/nonprofits/directory', nonprofitsDirectory); router.get('/nonprofits/:nonprofitName', returnIndividualNonprofit); @@ -8,57 +15,55 @@ module.exports = function(app) { app.use(router); function nonprofitsDirectory(req, res, next) { - Nonprofit.find( - { where: { estimatedHours: { gt: 0 } } }, - function(err, nonprofits) { - if (err) { return next(err); } - + findNonprofits({ where: { estimatedHours: { gt: 0 } } }).subscribe( + function(nonprofits) { res.render('nonprofits/directory', { title: 'Nonprofits we help', nonprofits: nonprofits }); - } + }, + next ); } function returnIndividualNonprofit(req, res, next) { var dashedName = req.params.nonprofitName; - var nonprofitName = dashedName.replace(/\-/g, ' '); + var nonprofitName = unDasherize(dashedName); + var query = { where: { name: { + like: nonprofitName, + options: 'i' + } } }; - Nonprofit.find( - { where: { name: new RegExp(nonprofitName, 'i') } }, - function(err, nonprofit) { - if (err) { - return next(err); - } - - if (nonprofit.length < 1) { + debug('looking for %s', nonprofitName); + debug('query', query); + findOneNonprofit(query).subscribe( + function(nonprofit) { + if (!nonprofit) { 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, '-'); + var dashedNameFull = dasherize(nonprofit.name); 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.username; - }); - if (hasShownInterest.length === 0) { - buttonActive = true; - } - } + var buttonActive = false; + if ( + req.user && + req.user.uncompletedBonfires.length === 0 && + req.user.completedCoursewares.length > 63 + ) { + var hasShownInterest = + nonprofit.interestedCampers.filter(function(user) { + return user.username === req.user.username; + }); + + if (hasShownInterest.length === 0) { + buttonActive = true; } } @@ -97,33 +102,8 @@ module.exports = function(app) { buttonActive: buttonActive, currentStatus: nonprofit.currentStatus }); - } + }, + next ); } - - /* - 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.username, - picture: req.user.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/utils/rx.js b/server/utils/rx.js index 8a4003c00c..5bc8a36382 100644 --- a/server/utils/rx.js +++ b/server/utils/rx.js @@ -1,5 +1,6 @@ var Rx = require('rx'); var debug = require('debug')('freecc:rxUtils'); +var slice = Array.prototype.slice; exports.saveUser = function saveUser(user) { return new Rx.Observable.create(function(observer) { @@ -23,3 +24,7 @@ exports.observableQueryFromModel = function observableQueryFromModel(Model, method, query) { return Rx.Observable.fromNodeCallback(Model[method], Model)(query); }; + +exports.observeMethod = function observeMethod(Model, method) { + return Rx.Observable.fromNodeCallback(Model[method], Model); +}; From e3aee8f42101effa9ec8e4d8b6f40d9bfd35f697 Mon Sep 17 00:00:00 2001 From: Natasha Haggard Date: Mon, 22 Jun 2015 22:13:21 -0500 Subject: [PATCH 17/51] Issue 1036 - Fixed minor grammatical mistake Fixed minor grammatical mistake - changed "Your app should have an text field input element" to "Your app should have a text field input element" --- seed/challenges/basic-html5-and-css.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/basic-html5-and-css.json b/seed/challenges/basic-html5-and-css.json index 7fdae6da09..c90513cc28 100644 --- a/seed/challenges/basic-html5-and-css.json +++ b/seed/challenges/basic-html5-and-css.json @@ -1468,7 +1468,7 @@ "You can create one like this: <input type='text'>. Note that input elements are self-closing." ], "tests": [ - "assert($('input').length > 0, 'Your app should have an text field input element.')" + "assert($('input').length > 0, 'Your app should have a text field input element.')" ], "challengeSeed": [ "", From 738afa817cf0e2bcf79c6c7634a9816e8169044f Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Tue, 23 Jun 2015 08:10:20 -0700 Subject: [PATCH 18/51] update challenge copy --- .../get-set-for-free-code-camp.json | 38 +++++++++++++++++-- views/challengeMap/show.jade | 36 +++++++++--------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/seed_data/challenges/get-set-for-free-code-camp.json b/seed_data/challenges/get-set-for-free-code-camp.json index d3f5102f29..0d16109548 100644 --- a/seed_data/challenges/get-set-for-free-code-camp.json +++ b/seed_data/challenges/get-set-for-free-code-camp.json @@ -48,10 +48,10 @@ "Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another camper to pair program with.", "Create an account with GitHub here: https://github.com/join.", "Click the pixel art in the upper right hand corner of GitHub, then choose settings. Upload a picture of yourself. A picture of your face works best. This is how people will see you in our chat rooms, so put your best foot forward. You can add your city and your personal website if you have one.", - "Now follow this link to enter our Welcome chat room: https://gitter.im/FreeCodeCamp/welcome.", - "Once you're in our Welcome chat room, introduce yourself by saying : \"Hello world!\".", + "Join our main chat room: https://gitter.im/FreeCodeCamp/FreeCodeCamp.", + "Once you're in our chat room, introduce yourself by saying : \"Hello world!\".", "Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.", - "This is the best room for new campers, but feel free to join other chat rooms as well. Our main chat room: https://gitter.im/FreeCodeCamp/FreeCodeCamp.", + "We have a busy chat room, so be sure to configure your notification settings in the top right corner.", "Keep the chat room open while you work through the other challenges. That way you ask for help if you get stuck on a challenge. You can also socialize when you feel like taking a break.", "You can also download a desktop or mobile chat application here: https://gitter.im/apps", "You can also access this chat room by clicking the \"Chat\" button in the upper right hand corner.", @@ -257,10 +257,40 @@ "namePt": "", "descriptionPt": [] }, + { + "_id": "bd7126d8c431eddfaeb5bd3e", + "name": "Waypoint: Add Free Code Camp to your LinkedIn Profile", + "difficulty": 0.008, + "challengeSeed": "127358841", + "description": [ + "LinkedIn is a critical tool for your job search later on.", + "Add Free Code Camp to your LinkedIn profile by going to https://www.linkedin.com/profile/edit-education?school=Free+Code+Camp.", + "Estimate your dates. Keep in mind that Free Code Camp is a rigorous 1,600 hour program, and will probably take at least a year to complete.", + "In the \"Degree\" section, type \"Full Stack Web Development\".", + "In the \"Field of study\" section, type \"Computer Software Engineering\".", + "Click the \"Save Changes\" button.", + "Be sure to add your key word skills to LinkedIn's skills section as you learn them, such as HTML, jQuery, Linux and Node.js.", + "You can expand your LinkedIn network by inviting friends you meet through Free Code Camp to connect with you on LinkedIn.", + "Make your LinkedIn profile as complete as possible. Unlike other social networks, with LinkedIn, it's perfectly fine if you don't want to add a photo.", + "Let's keep moving. We're almost ready to start coding!" + ], + "challengeType": 2, + "tests": [], + "nameCn": "", + "descriptionCn": [], + "nameFr": "", + "descriptionFr": [], + "nameRu": "", + "descriptionRu": [], + "nameEs": "", + "descriptionEs": [], + "namePt": "", + "descriptionPt": [] + }, { "_id": "bd7137d8c441eddfaeb5bdef", "name": "Waypoint: Get Help the Hacker Way with RSAP", - "difficulty": 0.008, + "difficulty": 0.009, "challengeSeed": "125407432", "description": [ "Let's cover one last thing before you start working through our lessons: how to get help.", diff --git a/views/challengeMap/show.jade b/views/challengeMap/show.jade index 594793fbc2..173f58d7c4 100644 --- a/views/challengeMap/show.jade +++ b/views/challengeMap/show.jade @@ -83,21 +83,21 @@ block content li.large-p.negative-10 a(href="/challenges/#{challenge.name}")= challenge.name - #announcementModal.modal(tabindex='-1') - .modal-dialog.animated.fadeInUp.fast-animation - .modal-content - .modal-header.challenge-list-header Add us to your LinkedIn profile - a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - .modal-body - h3.text-left LinkedIn now recognizes Free Code Camp as a university. - img.img-responsive.img-center(src='https://www.evernote.com/l/AHTzkHwtg-BHj57bqqDL7WFF8WgrI5V8cxwB/image.png') - h3.text-left It takes less than a minute to add Free Code Camp to your LinkedIn profile. - a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='/linkedin', target='_blank') Show me how to do this - a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Thanks for the heads-up - script. - $(document).ready(function () { - if (!localStorage || !localStorage.linkedIn) { - $('#announcementModal').modal('show'); - localStorage.linkedIn = "true"; - } - }); + //#announcementModal.modal(tabindex='-1') + // .modal-dialog.animated.fadeInUp.fast-animation + // .modal-content + // .modal-header.challenge-list-header Add us to your LinkedIn profile + // a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + // .modal-body + // h3.text-left LinkedIn now recognizes Free Code Camp as a university. + // img.img-responsive.img-center(src='https://www.evernote.com/l/AHTzkHwtg-BHj57bqqDL7WFF8WgrI5V8cxwB/image.png') + // h3.text-left It takes less than a minute to add Free Code Camp to your LinkedIn profile. + // a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='/linkedin', target='_blank') Show me how to do this + // a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Thanks for the heads-up + //script. + // $(document).ready(function () { + // if (!localStorage || !localStorage.linkedIn) { + // $('#announcementModal').modal('show'); + // localStorage.linkedIn = "true"; + // } + // }); From 51faa9e80ff1a227fb2f0274d79dceb26aec1704 Mon Sep 17 00:00:00 2001 From: LumenTeun Date: Tue, 23 Jun 2015 20:15:09 +0200 Subject: [PATCH 19/51] Fixes misspelling that caused 'undefined is not function' error --- server/boot/challenge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 81219341de..c0ec900804 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -50,7 +50,7 @@ var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames(); var challangesRegex = /^(bonfire|waypoint|zipline|basejump)/i; var dasherize = utils.dasherize; -var unDasherize = utils.unDashedName; +var unDasherize = utils.unDasherize; var getMDNLinks = utils.getMDNLinks; From 135047027af493c0dc964dfa165d531089447ec3 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Tue, 23 Jun 2015 11:42:27 -0700 Subject: [PATCH 20/51] add new field guides and challenges --- public/js/main_0.0.2.js | 6 +++--- seed_data/challenges/get-set-for-free-code-camp.json | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/public/js/main_0.0.2.js b/public/js/main_0.0.2.js index 69a8dfcff2..d21a077ed8 100644 --- a/public/js/main_0.0.2.js +++ b/public/js/main_0.0.2.js @@ -30,7 +30,7 @@ $(document).ready(function() { }, function(res) { if (res) { - window.open('https://freecodecamp.slack.com/messages/help/', '_blank') + window.open('https://gitter.im/FreeCodeCamp/Help', '_blank') } } ); @@ -48,7 +48,7 @@ $(document).ready(function() { }, function(res) { if (res) { - window.open('https://freecodecamp.slack.com/messages/help/', '_blank') + window.open('https://gitter.im/FreeCodeCamp/Help', '_blank') } } ); @@ -71,7 +71,7 @@ $(document).ready(function() { }, function(res) { if (res) { - window.open('https://freecodecamp.slack.com/messages/letspair/', '_blank') + window.open('https://gitter.im/FreeCodeCamp/LetsPair', '_blank') } } ); diff --git a/seed_data/challenges/get-set-for-free-code-camp.json b/seed_data/challenges/get-set-for-free-code-camp.json index 0d16109548..b5d9712946 100644 --- a/seed_data/challenges/get-set-for-free-code-camp.json +++ b/seed_data/challenges/get-set-for-free-code-camp.json @@ -43,11 +43,14 @@ "_id": "bd7125d8c441eddfaeb5bd0f", "name": "Waypoint: Join Our Chat Room", "difficulty": 0.002, - "challengeSeed": ["131321596"], + "challengeSeed": ["131574135"], "description": [ "Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another camper to pair program with.", "Create an account with GitHub here: https://github.com/join.", "Click the pixel art in the upper right hand corner of GitHub, then choose settings. Upload a picture of yourself. A picture of your face works best. This is how people will see you in our chat rooms, so put your best foot forward. You can add your city and your personal website if you have one.", + "Go to Free Code Camp's open-source repository: https://github.com/freecodecamp/freecodecamp.", + "You can \"star\" this repository by clicking the star button in the upper right hand corner.", + "Later, you'll be able to fork this repository if you'd like to contribute to our open source codebase.", "Join our main chat room: https://gitter.im/FreeCodeCamp/FreeCodeCamp.", "Once you're in our chat room, introduce yourself by saying : \"Hello world!\".", "Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.", @@ -227,7 +230,7 @@ "challengeSeed": "127358841", "description": [ "One of the best ways to stay motivated when learning to code is to hang out with other campers.", - "Slack and Camper News are great ways to communicate with other campers, but there's no substitute for meeting people in-person.", + "Our chat room and Camper News are great ways to communicate with other campers, but there's no substitute for meeting people in-person.", "The easiest way to meet other campers in your city is to join your city's Facebook Group. Click here to view our growing list of local groups.", "Click the link to your city, then, once Facebook loads, click \"Join group\".", "Our local groups are new, so if you don't see your city on this list, you should follow the directions to create a Facebook group for your city.", @@ -261,7 +264,7 @@ "_id": "bd7126d8c431eddfaeb5bd3e", "name": "Waypoint: Add Free Code Camp to your LinkedIn Profile", "difficulty": 0.008, - "challengeSeed": "127358841", + "challengeSeed": "131574134", "description": [ "LinkedIn is a critical tool for your job search later on.", "Add Free Code Camp to your LinkedIn profile by going to https://www.linkedin.com/profile/edit-education?school=Free+Code+Camp.", @@ -297,7 +300,7 @@ "Any time you get stuck or don't know what to do next, follow this simple algorithm (procedure): RSAP (Read, Search, Ask, Post).", "First, R - Read the documentation or error message. A key skill that good coders have is the ability to interpret and then follow instructions.", "Next, S - Search Google. Good Google queries take a lot of practice. When you search Google, you usually want to include the language or framework you're using. You also want to limit the results to a recent period.", - "Then, if you still haven't found an answer to your question, A - Ask your friends. If you have trouble, you can ask your fellow campers. We have a special chat room specifically for getting help with tools you learn through these Free Code Camp Challenges. Go to https://freecodecamp.slack.com/messages/help/. Keep this chat open while you work on the remaining challenges.", + "Then, if you still haven't found an answer to your question, A - Ask your friends. If you have trouble, you can ask your fellow campers. We have a special chat room specifically for getting help with tools you learn through these Free Code Camp Challenges. Go to https://gitter.im/FreeCodeCamp/Help. Keep this chat open while you work on the remaining challenges.", "Finally, P - Post on Stack Overflow. Before you attempt to do this, read Stack Overflow's guide to asking good questions: http://stackoverflow.com/help/how-to-ask.", "Here's our detailed field guide on getting help: http://freecodecamp.com/field-guide/how-do-i-get-help-when-i-get-stuck.", "Now you have a clear algorithm to follow when you need help! Let's start coding! Move on to your next challenge." From fb04a022aee31a13652495ac736eb708a8bd4c84 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 23 Jun 2015 16:22:07 -0700 Subject: [PATCH 21/51] refactor rxify fieldguides --- server/boot/fieldGuide.js | 180 +++++++++++++++++++++----------------- server/utils/rx.js | 1 - 2 files changed, 98 insertions(+), 83 deletions(-) diff --git a/server/boot/fieldGuide.js b/server/boot/fieldGuide.js index ad142e8336..87fcdd0e69 100644 --- a/server/boot/fieldGuide.js +++ b/server/boot/fieldGuide.js @@ -1,11 +1,16 @@ -var R = require('ramda'), -// Rx = require('rx'), - debug = require('debug')('freecc:fieldguides'), - utils = require('../utils'); +var Rx = require('rx'); +var debug = require('debug')('freecc:fieldguides'); +var observeMethod = require('../utils/rx').observeMethod; +var saveUser = require('../utils/rx').saveUser; +var utils = require('../utils'); + +var allFieldGuideNamesAndIds = utils.allFieldGuideNamesAndIds(); module.exports = function(app) { var router = app.loopback.Router(); var FieldGuide = app.models.FieldGuide; + var findFieldGuideById = observeMethod(FieldGuide, 'findById'); + var findOneFieldGuide = observeMethod(FieldGuide, 'findOne'); router.get('/field-guide/all-articles', showAllFieldGuides); router.get('/field-guide/:fieldGuideName', returnIndividualFieldGuide); @@ -15,60 +20,66 @@ module.exports = function(app) { app.use(router); function returnIndividualFieldGuide(req, res, next) { - var dashedNameFromQuery = req.params.fieldGuideName; - if (req.user) { - var completed = req.user.completedFieldGuides; + var dashedName = req.params.fieldGuideName; + var userSave = Rx.Observable.just(req.user) + .filter(function(user) { + return !!user; + }) + .map(function(user) { + var completed = user.completedFieldGuides; - var uncompletedFieldGuides = utils.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); } - }); - } - - FieldGuide.find({ where: {'dashedName': dashedNameFromQuery}}, - 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.' + var uncompletedFieldGuides = utils.allFieldGuideIds() + .filter(function(id) { + if (completed.indexOf(id) === -1) { + return id; + } }); + user.uncompletedFieldGuides = uncompletedFieldGuides; + return user; + }) + .flatMap(function(user) { + return saveUser(user); + }); - return res.redirect('/'); - } + var query = { where: { dashedName: { like: dashedName, options: 'i' } } }; - var fieldGuide = R.head(fieldGuideFromMongo); - fieldGuide.name.toLowerCase().replace(/\s/g, '-').replace(/\?/g, ''); + debug('find fieldGuide', query); + Rx.Observable.combineLatest( + // find that field guide + findOneFieldGuide(query), + userSave, + Rx.helpers.identity + ) + .subscribe( + // don't care about return from userSave + function(fieldGuide) { + if (!fieldGuide) { + 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/all-articles'); + } - // if (fieldGuide.dashedName !== dashedNameFromQuery) { - // return res.redirect('../field-guide/' + fieldGuide.dashedName); - // } - res.render('field-guide/show', { - title: fieldGuide.name, - fieldGuideId: fieldGuide.id, - description: fieldGuide.description.join('') - }); - } + 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('') + }); + }, + next ); } function showAllFieldGuides(req, res) { - var allFieldGuideNamesAndIds = utils.allFieldGuideNamesAndIds(); - var completedFieldGuides = []; if (req.user && req.user.completedFieldGuides) { completedFieldGuides = req.user.completedFieldGuides; } + res.render('field-guide/all-articles', { allFieldGuideNamesAndIds: allFieldGuideNamesAndIds, completedFieldGuides: completedFieldGuides @@ -76,16 +87,19 @@ module.exports = function(app) { } function showCompletedFieldGuideFunction(req, res) { - req.flash('success', { - msg: [ - 'You\'ve read all our current Field Guide entries. ' + - 'If you have ideas for other Field Guide articles, ' + - 'please let us know on ', - 'GitHub.' - ].join('') - }); + req.flash( + 'success', + { + msg: [ + 'You\'ve read all our current Field Guide entries. ' + + 'If you have ideas for other Field Guide articles, ' + + 'please let us know on ', + 'GitHub.' + ].join('') + } + ); return res.redirect('../field-guide/how-do-i-use-this-guide'); } @@ -95,38 +109,40 @@ module.exports = function(app) { } if (!req.user.uncompletedFieldGuides.length) { - return showCompletedFieldGuideFunction(req, res, next); + return showCompletedFieldGuideFunction(req, res); } - FieldGuide.findById(req.user.uncompletedFieldGuides[0], - function(err, fieldGuide) { - - if (err) { return next(err); } + findFieldGuideById(req.user.uncompletedFieldGuides[0]).subscribe( + function(fieldGuide) { if (!fieldGuide) { - debug('bad juju in field guide %s', - req.user.uncompletedFieldGuides[0]); + debug( + 'field guide %s not found', + req.user.uncompletedFieldGuides[0] + ); return res.redirect('../field-guide/how-do-i-use-this-guide'); } return res.redirect('../field-guide/' + fieldGuide.dashedName); - }); + }, + next + ); + } + + 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); + } + + saveUser(req.user).subscribe( + function() { + res.send(true); + }, + next + ); } }; - -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); - }); -} diff --git a/server/utils/rx.js b/server/utils/rx.js index 5bc8a36382..7e98aa486b 100644 --- a/server/utils/rx.js +++ b/server/utils/rx.js @@ -1,6 +1,5 @@ var Rx = require('rx'); var debug = require('debug')('freecc:rxUtils'); -var slice = Array.prototype.slice; exports.saveUser = function saveUser(user) { return new Rx.Observable.create(function(observer) { From 31eb81bf2f0ce58b2ef0ccc4c5f1603788152ded Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 23 Jun 2015 16:28:16 -0700 Subject: [PATCH 22/51] fix many queries in story --- server/boot/story.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/server/boot/story.js b/server/boot/story.js index 7603dfa20e..b25b54769b 100755 --- a/server/boot/story.js +++ b/server/boot/story.js @@ -43,7 +43,10 @@ module.exports = function(app) { } function hotJSON(req, res, next) { - Story.find({order: 'timePosted DESC', limit: 1000}, function(err, stories) { + Story.find({ + order: 'timePosted DESC', + limit: 1000 + }, function(err, stories) { if (err) { return next(err); } @@ -127,7 +130,7 @@ module.exports = function(app) { var storyName = dashedName.replace(/\-/g, ' ').trim(); - Story.find({where: {'storyLink': storyName}}, function(err, story) { + Story.find({ where: { storyLink: storyName } }, function(err, story) { if (err) { return next(err); } @@ -225,7 +228,7 @@ module.exports = function(app) { function upvote(req, res, next) { var data = req.body.data; - Story.find({'id': data.id}, function(err, story) { + Story.find({ where: { id: data.id } }, function(err, story) { if (err) { return next(err); } @@ -267,7 +270,7 @@ module.exports = function(app) { function comments(req, res, next) { var data = req.params.id; Comment.find( - { where: {'id': data } }, + { where: { id: data } }, function(err, comment) { if (err) { return next(err); @@ -300,7 +303,7 @@ module.exports = function(app) { url = 'http://' + url; } Story.find( - { where: {'link': url} }, + { where: { link: url } }, function(err, story) { if (err) { return next(err); @@ -357,7 +360,10 @@ module.exports = function(app) { } Story.count({ - storyLink: { like: new RegExp('^' + storyLink + '(?: [0-9]+)?$', 'i') } + storyLink: { + like: ('^' + storyLink + '(?: [0-9]+)?$'), + options: 'i' + } }, function (err, storyCount) { if (err) { return next(err); @@ -495,7 +501,7 @@ module.exports = function(app) { function commentEdit(req, res, next) { - Comment.find({ id: req.params.id }, function(err, cmt) { + Comment.find({ where: { id: req.params.id } }, function(err, cmt) { if (err) { return next(err); } @@ -538,7 +544,7 @@ module.exports = function(app) { // Based on the context retrieve the parent // object of the comment (Story/Comment) Context.find({ - id: data.associatedPost + where: { id: data.associatedPost } }, function (err, associatedContext) { if (err) { return next(err); @@ -555,7 +561,7 @@ module.exports = function(app) { } // Find the author of the parent object User.findOne({ - 'profile.username': associatedContext.author.username + username: associatedContext.author.username }, function(err, recipient) { if (err) { return next(err); From 0b1e9ba279e1c5252ec839e623c2f73572fd3421 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 23 Jun 2015 16:45:35 -0700 Subject: [PATCH 23/51] fix mismatch property in challenge controller --- server/boot/challenge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 81219341de..c0ec900804 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -50,7 +50,7 @@ var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames(); var challangesRegex = /^(bonfire|waypoint|zipline|basejump)/i; var dasherize = utils.dasherize; -var unDasherize = utils.unDashedName; +var unDasherize = utils.unDasherize; var getMDNLinks = utils.getMDNLinks; From 1e84751a0f36b36754202db808aed072887ff18b Mon Sep 17 00:00:00 2001 From: Natasha Haggard Date: Tue, 23 Jun 2015 22:46:28 -0500 Subject: [PATCH 24/51] Issue 1094 - Field Guide Error "Contact Jason Ruekert - he's @jsonify in Slack." Since freecodecamp is now using Gitter again, changed it to say "Contact Jason Ruekert - he's @jsonify in Gitter." --- seed/field-guides.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/field-guides.json b/seed/field-guides.json index 9d3e906d16..24109b4813 100644 --- a/seed/field-guides.json +++ b/seed/field-guides.json @@ -279,7 +279,7 @@ "

", "

    ", "
  1. Follow this tutorial to set up your computer for streaming.
  2. ", - "
  3. Contact Jason Ruekert - he's @jsonify in Slack. He's in charge of our Twitch.tv channel. Tell him what you'd like to stream, and when you're available to stream.
  4. ", + "
  5. Contact Jason Ruekert - he's @jsonify in Gitter. He's in charge of our Twitch.tv channel. Tell him what you'd like to stream, and when you're available to stream.
  6. ", "
  7. Jason will pair with you using Screen Hero to verify your computer is configured properly to stream.
  8. ", "
", "

", From bd0a51714440c000770036715a3267431682c8ad Mon Sep 17 00:00:00 2001 From: Natasha Haggard Date: Tue, 23 Jun 2015 22:55:52 -0500 Subject: [PATCH 25/51] Issue 1080 Changed "Twitter Bootstrap" to "Bootstrap". It used to be it's name but it's not any more. It's in their brand guidelines to not refer to it as Twitter Bootstrap any more: http://getbootstrap.com/about/#name --- seed/challenges/bootstrap.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/bootstrap.json b/seed/challenges/bootstrap.json index 41a7229731..2fb852ade8 100644 --- a/seed/challenges/bootstrap.json +++ b/seed/challenges/bootstrap.json @@ -8,7 +8,7 @@ "dashedName": "waypoint-mobile-responsive-images", "difficulty": 0.047, "description": [ - "Now let's go back to our Cat Photo App. This time, we'll style it using the popular Twitter Bootstrap responsive CSS framework. First, add a new image with the src attribute of \"http://bit.ly/fcc-kittens2\", and add the \"img-responsive\" Bootstrap class to that image.", + "Now let's go back to our Cat Photo App. This time, we'll style it using the popular Bootstrap responsive CSS framework. First, add a new image with the src attribute of \"http://bit.ly/fcc-kittens2\", and add the \"img-responsive\" Bootstrap class to that image.", "It would be great if the image could be exactly the width of our phone's screen.", "Fortunately, we have access to a Responsive CSS Framework called Bootstrap. You can add Bootstrap to any app just by including it with <link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'/> at the top of your HTML. But we've gone ahead and automatically added it to your Cat Photo App for you.", "Bootstrap will figure out how wide your screen is and respond by resizing your HTML elements - hence the name Responsive Design.", From 926151ff8b53042b9cd1b6755f59dcad011557d0 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Tue, 23 Jun 2015 21:54:00 -0700 Subject: [PATCH 26/51] Add Gitter badge and use map art instead of logo --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0029a894e3..b78989b63c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ + + [![Throughput Graph](https://graphs.waffle.io/freecodecamp/freecodecamp/throughput.svg)](https://waffle.io/freecodecamp/freecodecamp/metrics) -[![Stories in Ready](https://badge.waffle.io/FreeCodeCamp/freecodecamp.png?label=ready&title=Ready)](https://waffle.io/FreeCodeCamp/freecodecamp) - +[![Join the chat at https://gitter.im/sahat/hackathon-starter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/sahat/hackathon-starter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Welcome to Free Code Camp's open source codebase! ======================= From 3a0ab673f3a5ae1e538a984a5f2f5aece173e311 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Tue, 23 Jun 2015 21:55:24 -0700 Subject: [PATCH 27/51] Update Gitter badge path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b78989b63c..8e6742b074 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Throughput Graph](https://graphs.waffle.io/freecodecamp/freecodecamp/throughput.svg)](https://waffle.io/freecodecamp/freecodecamp/metrics) -[![Join the chat at https://gitter.im/sahat/hackathon-starter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/sahat/hackathon-starter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at https://gitter.im/freecodecamp/freecodecamp](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/freecodecamp/freecodecamp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Welcome to Free Code Camp's open source codebase! ======================= From 4d620c57966a8b152145d1006d05fda5d9296e9c Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Wed, 24 Jun 2015 07:06:40 -0700 Subject: [PATCH 28/51] comment out feature map modal and camper news notification --- server/views/challengeMap/show.jade | 36 ++++++++++++++--------------- server/views/stories/news-nav.jade | 16 ++++++------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/server/views/challengeMap/show.jade b/server/views/challengeMap/show.jade index e80b209312..392e4275f7 100644 --- a/server/views/challengeMap/show.jade +++ b/server/views/challengeMap/show.jade @@ -98,21 +98,21 @@ block content li.large-p.negative-10 a(href="/challenges/#{challenge.dashedName}")= challenge.name - #announcementModal.modal(tabindex='-1') - .modal-dialog.animated.fadeInUp.fast-animation - .modal-content - .modal-header.challenge-list-header Add us to your LinkedIn profile - a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × - .modal-body - h3.text-left LinkedIn now recognizes Free Code Camp as a university. - img.img-responsive.img-center(src='https://www.evernote.com/l/AHTzkHwtg-BHj57bqqDL7WFF8WgrI5V8cxwB/image.png') - h3.text-left It takes less than a minute to add Free Code Camp to your LinkedIn profile. - a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='/linkedin', target='_blank') Show me how to do this - a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Thanks for the heads-up - script. - $(document).ready(function () { - if (!localStorage || !localStorage.linkedIn) { - $('#announcementModal').modal('show'); - localStorage.linkedIn = "true"; - } - }); + //#announcementModal.modal(tabindex='-1') + // .modal-dialog.animated.fadeInUp.fast-animation + // .modal-content + // .modal-header.challenge-list-header Add us to your LinkedIn profile + // a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + // .modal-body + // h3.text-left LinkedIn now recognizes Free Code Camp as a university. + // img.img-responsive.img-center(src='https://www.evernote.com/l/AHTzkHwtg-BHj57bqqDL7WFF8WgrI5V8cxwB/image.png') + // h3.text-left It takes less than a minute to add Free Code Camp to your LinkedIn profile. + // a.btn.btn-lg.btn-info.btn-block(name='_csrf', value=_csrf, aria-hidden='true', href='/linkedin', target='_blank') Show me how to do this + // a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Thanks for the heads-up + //script. + // $(document).ready(function () { + // if (!localStorage || !localStorage.linkedIn) { + // $('#announcementModal').modal('show'); + // localStorage.linkedIn = "true"; + // } + // }); diff --git a/server/views/stories/news-nav.jade b/server/views/stories/news-nav.jade index 48f64ae561..e808c376e3 100644 --- a/server/views/stories/news-nav.jade +++ b/server/views/stories/news-nav.jade @@ -12,14 +12,14 @@ span.input-group-btn button#searchbutton.btn.btn-big.btn-primary.btn-responsive(type='button') Search -.spacer -.row - .col-xs-12.col-sm-8.col-sm-offset-2.well - h4.text-center Which other free resources do you use? - img.img-responsive(src='https://www.evernote.com/l/AHRNhlwViM1Kh5qCm6iy7MSWrbdyxYbRkWkB/image.png') - p Link us to your favorite free coding resources. - p Use the headline: "Awesome Free Resource: (the name of the book, podcast, or video series)". We'll publish a list of the 25 most-upvoted resources (and the campers who submitted them) in Wednesday's blog post, and in an upcoming Field Guide article. Also - as always - you'll get 1 point every time someone upvotes your post. -.spacer +//.spacer +//.row +// .col-xs-12.col-sm-8.col-sm-offset-2.well +// h4.text-center Which other free resources do you use? +// img.img-responsive(src='https://www.evernote.com/l/AHRNhlwViM1Kh5qCm6iy7MSWrbdyxYbRkWkB/image.png') +// p Link us to your favorite free coding resources. +// p Use the headline: "Awesome Free Resource: (the name of the book, podcast, or video series)". We'll publish a list of the 25 most-upvoted resources (and the campers who submitted them) in Wednesday's blog post, and in an upcoming Field Guide article. Also - as always - you'll get 1 point every time someone upvotes your post. +//.spacer #search-results From c0c15cf8751214db715a1f161157c941fd189049 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Wed, 24 Jun 2015 07:15:39 -0700 Subject: [PATCH 29/51] fix define story --- server/boot/story.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/boot/story.js b/server/boot/story.js index b25b54769b..3dd1019b34 100755 --- a/server/boot/story.js +++ b/server/boot/story.js @@ -11,6 +11,7 @@ module.exports = function(app) { var router = app.loopback.Router(); var User = app.models.User; var Story = app.models.Story; + var Comment = app.models.Comment; router.get('/stories/hotStories', hotJSON); router.get('/stories/comments/:id', comments); From 5bde1839f366e1fc4900215adee4c9c874a95584 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Wed, 24 Jun 2015 07:25:56 -0700 Subject: [PATCH 30/51] add read authority to all for stories and comments --- common/models/comment.json | 6 ++++++ common/models/story.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/common/models/comment.json b/common/models/comment.json index ada3b000d6..da6bb755d1 100644 --- a/common/models/comment.json +++ b/common/models/comment.json @@ -49,6 +49,12 @@ "principalId": "$everyone", "permission": "DENY" }, + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }, { "accessType": "EXECUTE", "principalType": "ROLE", diff --git a/common/models/story.json b/common/models/story.json index bdd86a996b..4b50e3eed4 100644 --- a/common/models/story.json +++ b/common/models/story.json @@ -69,7 +69,7 @@ { "accessType": "READ", "principalType": "ROLE", - "principalId": "$authenticated", + "principalId": "$everyone", "permission": "ALLOW" }, { From 62d2c40a87a02e166923f05b896edc998f723c89 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Wed, 24 Jun 2015 08:23:49 -0700 Subject: [PATCH 31/51] Add read access to all models --- common/models/User-Identity.json | 9 ++++++++- common/models/bonfire.json | 2 +- common/models/challenge.json | 2 +- common/models/field-guide.json | 2 +- common/models/job.json | 2 +- common/models/nonprofit.json | 2 +- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/common/models/User-Identity.json b/common/models/User-Identity.json index d63e5c0af3..79c2192f1c 100644 --- a/common/models/User-Identity.json +++ b/common/models/User-Identity.json @@ -11,6 +11,13 @@ "foreignKey": "userId" } }, - "acls": [], + "acls": [ + { + "accessType": "*", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "DENY" + } + ], "methods": [] } diff --git a/common/models/bonfire.json b/common/models/bonfire.json index ae5e5db32a..859f0196ed 100644 --- a/common/models/bonfire.json +++ b/common/models/bonfire.json @@ -36,7 +36,7 @@ { "accessType": "READ", "principalType": "ROLE", - "principalId": "$authenticated", + "principalId": "$everyone", "permission": "ALLOW" } ], diff --git a/common/models/challenge.json b/common/models/challenge.json index dfc8c9c4a9..f270e7f70b 100644 --- a/common/models/challenge.json +++ b/common/models/challenge.json @@ -72,7 +72,7 @@ { "accessType": "READ", "principalType": "ROLE", - "principalId": "$authenticated", + "principalId": "$everyone", "permission": "ALLOW" } ], diff --git a/common/models/field-guide.json b/common/models/field-guide.json index b8e734247c..be3ae99a25 100644 --- a/common/models/field-guide.json +++ b/common/models/field-guide.json @@ -29,7 +29,7 @@ { "accessType": "READ", "principalType": "ROLE", - "principalId": "$authenticated", + "principalId": "$everyone", "permission": "ALLOW" } ], diff --git a/common/models/job.json b/common/models/job.json index 30f981d4df..83d50ebc7b 100644 --- a/common/models/job.json +++ b/common/models/job.json @@ -32,7 +32,7 @@ { "accessType": "READ", "principalType": "ROLE", - "principalId": "$authenticated", + "principalId": "$everyone", "permission": "ALLOW" } ], diff --git a/common/models/nonprofit.json b/common/models/nonprofit.json index 2cfad23d66..ea2e834531 100644 --- a/common/models/nonprofit.json +++ b/common/models/nonprofit.json @@ -63,7 +63,7 @@ { "accessType": "READ", "principalType": "ROLE", - "principalId": "$authenticated", + "principalId": "$everyone", "permission": "ALLOW" } ], From 3e24f218281afb60a1ecbc261af4d9e5ae8437c6 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Wed, 24 Jun 2015 08:24:13 -0700 Subject: [PATCH 32/51] fix comment model defaults --- common/models/comment.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/models/comment.json b/common/models/comment.json index da6bb755d1..ec29a05c98 100644 --- a/common/models/comment.json +++ b/common/models/comment.json @@ -22,14 +22,15 @@ }, "rank": { "type": "number", - "default": "-Infinity" + "default": 0 }, "upvotes": { "type": "array", "default": [] }, "author": { - "type": {} + "type": {}, + "default": {} }, "comments": { "type": "array", From 1f28f877cf23dcece9f21fce0e1909dfa8d2aa05 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Wed, 24 Jun 2015 08:25:34 -0700 Subject: [PATCH 33/51] fix remove mongoose specific function --- server/boot/story.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/server/boot/story.js b/server/boot/story.js index 3dd1019b34..b1b1f11b9c 100755 --- a/server/boot/story.js +++ b/server/boot/story.js @@ -234,14 +234,11 @@ module.exports = function(app) { return next(err); } story = story.pop(); - story.rank++; - story.upVotes.push( - { - upVotedBy: req.user.id, - upVotedByUsername: req.user.username - } - ); - story.markModified('rank'); + story.rank += 1; + story.upVotes.push({ + upVotedBy: req.user.id, + upVotedByUsername: req.user.username + }); 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 From c5a31baedaa404fa2452624d5b9c71aca190585c Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Wed, 24 Jun 2015 09:30:35 -0700 Subject: [PATCH 34/51] remove resources.js controller --- controllers/resources.js | 635 --------------------------------------- 1 file changed, 635 deletions(-) delete mode 100644 controllers/resources.js diff --git a/controllers/resources.js b/controllers/resources.js deleted file mode 100644 index 9101f82edf..0000000000 --- a/controllers/resources.js +++ /dev/null @@ -1,635 +0,0 @@ -var async = require('async'), - path = require('path'), - moment = require('moment'), - Twit = require('twit'), - debug = require('debug')('freecc:cntr:resources'), - cheerio = require('cheerio'), - request = require('request'), - R = require('ramda'), - _ = require('lodash'), - fs = require('fs'), - - - constantStrings = require('./constantStrings.json'), - User = require('../models/User'), - Challenge = require('./../models/Challenge'), - Story = require('./../models/Story'), - FieldGuide = require('./../models/FieldGuide'), - Nonprofit = require('./../models/Nonprofit'), - Comment = require('./../models/Comment'), - resources = require('./resources.json'), - secrets = require('./../config/secrets'), - nonprofits = require('../seed_data/nonprofits.json'), - fieldGuides = require('../seed_data/field-guides.json'), - Slack = require('node-slack'), - slack = new Slack(secrets.slackHook); - -/** - * Cached values - */ -var allFieldGuideIds, allFieldGuideNames, allNonprofitNames, - challengeMap, challengeMapForDisplay, challengeMapWithIds, - challengeMapWithNames, allChallengeIds, allChallenges; - -/** - * GET / - * Resources. - */ - -Array.zip = function(left, right, combinerFunction) { - var counter, - results = []; - - for (counter = 0; counter < Math.min(left.length, right.length); counter++) { - results.push(combinerFunction(left[counter], right[counter])); - } - - return results; -}; - -(function() { - if (!challengeMap) { - var localChallengeMap = {}; - var files = fs.readdirSync( - path.join(__dirname, '/../seed_data/challenges') - ); - var keyCounter = 0; - files = files.map(function (file) { - return require( - path.join(__dirname, '/../seed_data/challenges/' + file) - ); - }); - files = files.sort(function (a, b) { - return a.order - b.order; - }); - files.forEach(function (file) { - localChallengeMap[keyCounter++] = file; - }); - challengeMap = _.cloneDeep(localChallengeMap); - } -})(); - - -module.exports = { - - getChallengeMapForDisplay: function(completedChallengeList) { - if (!challengeMapForDisplay) { - challengeMapForDisplay = {}; - Object.keys(challengeMap).forEach(function(key) { - //TODO get ratio of completed to uncompleted for each section - //challengeMap[key].challenges.forEach(function(challenge){ - // - //} - challengeMapForDisplay[key] = { - name: challengeMap[key].name, - dashedName: challengeMap[key].name.replace(/\s/g, '-'), - challenges: challengeMap[key].challenges, - completedCount: challengeMap[key].challenges //ToDo count number of uncompleted challenges - } - }); - } - return challengeMapForDisplay; - }, - - getChallengeMapWithIds: function() { - if (!challengeMapWithIds) { - challengeMapWithIds = {}; - Object.keys(challengeMap).forEach(function (key) { - var onlyIds = challengeMap[key].challenges.map(function (elem) { - return elem._id; - }); - challengeMapWithIds[key] = onlyIds; - }); - } - return challengeMapWithIds; - }, - - allChallengeIds: function() { - - if (!allChallengeIds) { - allChallengeIds = []; - Object.keys(this.getChallengeMapWithIds()).forEach(function(key) { - allChallengeIds.push(challengeMapWithIds[key]); - }); - allChallengeIds = R.flatten(allChallengeIds); - } - return allChallengeIds; - }, - - allChallenges: function() { - if (!allChallenges) { - allChallenges = []; - Object.keys(this.getChallengeMapWithNames()).forEach(function(key) { - allChallenges.push(challengeMap[key].challenges); - }); - allChallenges = R.flatten(allChallenges); - } - return allChallenges; - }, - - getChallengeMapWithNames: function() { - if (!challengeMapWithNames) { - challengeMapWithNames = {}; - Object.keys(challengeMap). - forEach(function (key) { - var onlyNames = challengeMap[key].challenges.map(function (elem) { - return elem.name; - }); - challengeMapWithNames[key] = onlyNames; - }); - } - return challengeMapWithNames; - }, - - sitemap: 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); - } - }); - }, - - 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, results) { - if (err) { - return next(err); - } else { - setTimeout(function() { - res.header('Content-Type', 'application/xml'); - res.render('resources/sitemap', { - appUrl: appUrl, - now: now, - users: results.users, - challenges: results.challenges, - stories: results.stories, - nonprofits: results.nonprofits, - fieldGuides: results.fieldGuides - }); - }, 0); - } - } - ); - }, - - chat: function chat(req, res) { - res.redirect('//gitter.im/FreeCodeCamp/FreeCodeCamp'); - }, - - jobsForm: function jobsForm(req, res) { - res.render('resources/jobs-form', { - title: 'Employer Partnership Form for Job Postings, Recruitment and Corporate Sponsorships' - }); - }, - - catPhotoSubmit: 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.' - ); - }, - - nonprofits: function nonprofits(req, res) { - res.render('resources/nonprofits', { - title: 'A guide to our Nonprofit Projects' - }); - }, - - nonprofitsForm: function nonprofitsForm(req, res) { - res.render('resources/nonprofits-form', { - title: 'Nonprofit Projects Proposal Form' - }); - }, - - agileProjectManagers: function agileProjectManagers(req, res) { - res.render('resources/pmi-acp-agile-project-managers', { - title: 'Get Agile Project Management Experience for the PMI-ACP' - }); - }, - - agileProjectManagersForm: function agileProjectManagersForm(req, res) { - res.render('resources/pmi-acp-agile-project-managers-form', { - title: 'Agile Project Management Program Application Form' - }); - }, - - twitch: function twitch(req, res) { - res.render('resources/twitch', { - title: "Enter Free Code Camp's Chat Rooms" - }); - }, - - unsubscribe: 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'); - } - }); - }, - - unsubscribed: function unsubscribed(req, res) { - res.render('resources/unsubscribed', { - title: 'You have been unsubscribed' - }); - }, - - githubCalls: function(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 - }); - } - ); - } - ); - }, - - trelloCalls: function(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)); - }); - }, - - bloggerCalls: function(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)); - } - ); - }, - - about: function(req, res, next) { - if (req.user) { - if ( - !req.user.profile.picture || - req.user.profile.picture.indexOf('apple-touch-icon-180x180.png') !== -1 - ) { - req.user.profile.picture = - 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png'; - // TODO(berks): unhandled callback - req.user.save(); - } - } - var date1 = new Date('10/15/2014'); - var date2 = new Date(); - - var timeDiff = Math.abs(date2.getTime() - date1.getTime()); - var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24)); - var announcements = resources.announcements; - function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); - } - User.count({}, function (err, c3) { - if (err) { - debug('User err: ', err); - return next(err); - } - - res.render('resources/learn-to-code', { - title: 'About Free Code Camp', - daysRunning: daysRunning, - c3: numberWithCommas(c3), - announcements: announcements - }); - }); - }, - - randomPhrase: function() { - return resources.phrases[ - Math.floor(Math.random() * resources.phrases.length) - ]; - }, - - randomVerb: function() { - return resources.verbs[ - Math.floor(Math.random() * resources.verbs.length) - ]; - }, - - randomCompliment: function() { - return resources.compliments[ - Math.floor(Math.random() * resources.compliments.length) - ]; - }, - - allFieldGuideIds: function() { - if (allFieldGuideIds) { - return allFieldGuideIds; - } else { - allFieldGuideIds = fieldGuides.map(function (elem) { - return elem._id; - }); - return allFieldGuideIds; - } - }, - - allFieldGuideNamesAndIds: function() { - if (allFieldGuideNames) { - return allFieldGuideNames; - } else { - allFieldGuideNames = fieldGuides.map(function (elem) { - return { - name: elem.name, - dashedName: elem.dashedName, - id: elem._id }; - }); - return allFieldGuideNames; - } - }, - - allNonprofitNames: function() { - if (allNonprofitNames) { - return allNonprofitNames; - } else { - allNonprofitNames = nonprofits.map(function (elem) { - return { name: elem.name }; - }); - return allNonprofitNames; - } - }, - - whichEnvironment: function() { - return process.env.NODE_ENV; - }, - - getURLTitle: function(url, callback) { - (function () { - var result = {title: '', image: '', url: '', description: ''}; - request(url, function (error, response, body) { - if (!error && response.statusCode === 200) { - var $ = cheerio.load(body); - var metaDescription = $("meta[name='description']"); - var metaImage = $("meta[property='og:image']"); - var urlImage = metaImage.attr('content') ? - metaImage.attr('content') : - ''; - - var metaTitle = $('title'); - var description = metaDescription.attr('content') ? - metaDescription.attr('content') : - ''; - - result.title = metaTitle.text().length < 90 ? - metaTitle.text() : - metaTitle.text().slice(0, 87) + '...'; - - result.image = urlImage; - result.description = description; - callback(null, result); - } else { - callback(new Error('failed')); - } - }); - })(); - }, - - updateUserStoryPictures: function(userId, picture, username, cb) { - - var counter = 0, - foundStories, - foundComments; - - Story.find({'author.userId': userId}, function(err, stories) { - if (err) { - return cb(err); - } - foundStories = stories; - counter++; - saveStoriesAndComments(); - }); - Comment.find({'author.userId': userId}, function(err, comments) { - if (err) { - return cb(err); - } - foundComments = comments; - counter++; - saveStoriesAndComments(); - }); - - function saveStoriesAndComments() { - if (counter !== 2) { - return; - } - var tasks = []; - R.forEach(function(comment) { - comment.author.picture = picture; - comment.author.username = username; - comment.markModified('author'); - tasks.push(function(cb) { - comment.save(cb); - }); - }, foundComments); - - R.forEach(function(story) { - story.author.picture = picture; - story.author.username = username; - story.markModified('author'); - tasks.push(function(cb) { - story.save(cb); - }); - }, foundStories); - async.parallel(tasks, function(err) { - if (err) { - return cb(err); - } - cb(); - }); - } - }, - codepenResources: { - twitter: function(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); - } - ); - }, - twitterFCCStream: function() { - // sends out a tweet stream from FCC's account - }, - twitch: function() { - // exports information from the twitch account - }, - slack: function() { - - } - }, - - getHelp: function(req, res, next) { - 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); - }, - - getPair: function(req, res, next) { - 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.", - channel: '#letspair', - username: "Companion Cube", - icon_url: "https://lh3.googleusercontent.com/-f6xDPDV2rPE/AAAAAAAAAAI/AAAAAAAAAAA/mdlESXQu11Q/photo.jpg" - }); - return res.sendStatus(200); - } -}; From 750a454298c73c341a2f8caf715d9c599b64dcc5 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Wed, 24 Jun 2015 09:37:03 -0700 Subject: [PATCH 35/51] update bootcamp json --- server/utils/bootcamps.json | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/server/utils/bootcamps.json b/server/utils/bootcamps.json index 3a1a7959bf..c9895932f6 100644 --- a/server/utils/bootcamps.json +++ b/server/utils/bootcamps.json @@ -1,23 +1,5 @@ -[{ - "name": "App Academy", - "cost": "18000", - "weeks": "12", - "finance": false, - "housing": "500", - "cities": [ - "new-york-city", - "san-francisco" - ] -}, { - "name": "Viking Code School", - "cost": "18000", - "weeks": "14", - "housing": "0", - "finance": false, - "cities": [ - "online" - ] -}, { +[ +{ "name": "Hack Reactor", "cost": "17780", "housing": "500", From 95f47de9f0b2750069f36ae7e7693446c5cbbf61 Mon Sep 17 00:00:00 2001 From: LumenTeun Date: Tue, 23 Jun 2015 20:53:46 +0200 Subject: [PATCH 36/51] 'Slack' to 'Gitter' --- .../get-set-for-free-code-camp.json | 8 +++---- seed/field-guides.json | 24 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/seed/challenges/get-set-for-free-code-camp.json b/seed/challenges/get-set-for-free-code-camp.json index 379bc26f6e..4408618ae3 100644 --- a/seed/challenges/get-set-for-free-code-camp.json +++ b/seed/challenges/get-set-for-free-code-camp.json @@ -234,7 +234,7 @@ "challengeSeed": ["127358841"], "description": [ "One of the best ways to stay motivated when learning to code is to hang out with other campers.", - "Slack and Camper News are great ways to communicate with other campers, but there's no substitute for meeting people in-person.", + "Gitter and Camper News are great ways to communicate with other campers, but there's no substitute for meeting people in-person.", "The easiest way to meet other campers in your city is to join your city's Facebook Group. Click here to view our growing list of local groups.", "Click the link to your city, then, once Facebook loads, click \"Join group\".", "Our local groups are new, so if you don't see your city on this list, you should follow the directions to create a Facebook group for your city.", @@ -253,7 +253,7 @@ "nameEs": "Waypoint: Encuentrate con otros Campers en tu Ciudad", "descriptionEs": [ "Una de las mejores maneras de mantenerte motivado cuando estás aprendiendo a programar es pasar el rato con otros campers.", - "Slack y Noticias de Campers son una muy buena forma de comunicarte con otros campers, pero no hay ningún substituto para conocerlos en persona.", + "Gitter y Noticias de Campers son una muy buena forma de comunicarte con otros campers, pero no hay ningún substituto para conocerlos en persona.", "La forma más fácil de encontrarte con otros campers en tu ciudad es unirte al grupo de Facebook de tu ciudad o país. Dale click a here para ver la lista de grupos locales.", "Dale click al link de tu ciudad o país y una vez que Facebook cargue, dale click a \"Join group\".", "Nuestros grupos locales son pocos, asi que en caso no veas tu ciudad o país en la lista, solamente sigue las instrucciones para crear un grupo de Facebook para ello.", @@ -275,7 +275,7 @@ "Any time you get stuck or don't know what to do next, follow this simple algorithm (procedure): RSAP (Read, Search, Ask, Post).", "First, R - Read the documentation or error message. A key skill that good coders have is the ability to interpret and then follow instructions.", "Next, S - Search Google. Good Google queries take a lot of practice. When you search Google, you usually want to include the language or framework you're using. You also want to limit the results to a recent period.", - "Then, if you still haven't found an answer to your question, A - Ask your friends. If you have trouble, you can ask your fellow campers. We have a special chat room specifically for getting help with tools you learn through these Free Code Camp Challenges. Go to https://freecodecamp.slack.com/messages/help/. Keep this chat open while you work on the remaining challenges.", + "Then, if you still haven't found an answer to your question, A - Ask your friends. If you have trouble, you can ask your fellow campers. We have a special chat room specifically for getting help with tools you learn through these Free Code Camp Challenges. Go to https://gitter.im/FreeCodeCamp/Help. Keep this chat open while you work on the remaining challenges.", "Finally, P - Post on Stack Overflow. Before you attempt to do this, read Stack Overflow's guide to asking good questions: http://stackoverflow.com/help/how-to-ask.", "Here's our detailed field guide on getting help: http://freecodecamp.com/field-guide/how-do-i-get-help-when-i-get-stuck.", "Now you have a clear algorithm to follow when you need help! Let's start coding! Move on to your next challenge." @@ -294,7 +294,7 @@ "Cualquier momento en el que te atasques o no sepas que hacer, sigue este simple algoritmo (procedimiento): RSAP (Read, Search, Ask, Post). Que en español vendría a ser Lee, Busca, Pregunta, Publica.", "Primero, Lee - Lee la documentación o el mensaje de error. El punto fuerte de un buen programador es la habilidad de interpretar y seguir instrucciones.", "Luego, Busca - Busca en Google. Buenas búsquedas o queries requieren bastante práctica. Cuando búsques en Google, idealmente tienes que incluir el lenguaje o framework que estés usando. También tendrás que limitar los resultados de búsqueda a un periodo reciente.", - "Ahora, en caso no hayas encontrado la respuesta a tu pregunta, Pregunta - Pregunta a tus amigos. En caso estes en problemas, puedes preguntar a otros campers. Tenemos una sala de chat especificamente para obtener ayuda sobre las herramientas que utilizamos en los desafíos de Free Code Camp. Ingresa a https://freecodecamp.slack.com/messages/help/. Mantén este chat abierto mientras trabajas en los desafíos subsiguientes.", + "Ahora, en caso no hayas encontrado la respuesta a tu pregunta, Pregunta - Pregunta a tus amigos. En caso estes en problemas, puedes preguntar a otros campers. Tenemos una sala de chat especificamente para obtener ayuda sobre las herramientas que utilizamos en los desafíos de Free Code Camp. Ingresa a https://gitter.im/FreeCodeCamp/Help. Mantén este chat abierto mientras trabajas en los desafíos subsiguientes.", "Finalmente, Publica - Publica tu pregunta en Stack Overflow. Antes de hacer esto lee la guía de Stack Overflow para publicar buenas preguntas: http://stackoverflow.com/help/how-to-ask. Tendrás que hacerlo en inglés, en caso no sepas como, pide que te ayuden a traducir tu pregunta en el canal #espanol de Slack.", "Aquí está nuestra guia detallada en como obtener ayuda: http://freecodecamp.com/field-guide/how-do-i-get-help-when-i-get-stuck.", "Ahora que tienes en claro el procedimiento a seguir cuando necesites ayuda. ¡Empecémos a programar! Continua con el siguiente desafío." diff --git a/seed/field-guides.json b/seed/field-guides.json index 24109b4813..e090157e1a 100644 --- a/seed/field-guides.json +++ b/seed/field-guides.json @@ -225,7 +225,7 @@ "

", "

This is the most time-efficient way to handle being stuck, and it's the most respectful of other people's time, too.

", "

Most of the time, you'll solve your problem after just one or two steps of this algorithm.

", - "

We have a special chat room just for getting help: https://freecodecamp.slack.com/messages/help/

", + "

We have a special chat room just for getting help: https://gitter.im/FreeCodeCamp/Help

", "

Also, if you need to post on Stack Overflow, be sure to read their guide to asking good questions: http://stackoverflow.com/help/how-to-ask.

", "

Learning to code is hard. But it's a lot easier if you ask for help when you need it!

", "" @@ -492,7 +492,7 @@ " A screen shot showing you the group description box on the Facebook page.", "
  • Click the \"Upload a photo button. To start out, you'll probably just want to use Free Code Camp's banner (download it here), or a scenic shot of your city. Later you can update this with a picture from one of your city's Free Code Camp events.
  • ", " A screenshot showing the \"Upload a photo\" button.", - "
  • Message @quincylarson in Slack with a link to your city's group page and he'll include it here.
  • ", + "
  • Message @quincylarson on Gitter with a link to your city's group page and he'll include it here.
  • ", "
  • Join our Local Leaders Facebook group, where we share ideas about involving campers in your city.
  • ", " ", "

    ", @@ -634,7 +634,7 @@ "

    Here are our recommended ways of collaborating:

    ", "

    ", "

      ", - "
    • • Slack has robust private messaging functionality. It's the main way our team communicates, and we recommend it over email.
    • ", + "
    • • Gitter has robust private messaging functionality. It's the main way our team communicates, and we recommend it over email.
    • ", "
    • • Trello is great for managing projects. Work with your stakeholder to create Trello cards, and update these cards regularly as you make progress on them.
    • ", "
    • • Screen Hero or Team Viewer - These are the ideal way to pair program. Tools like TMUX are good, but difficult to use. We discourage you from using screen sharing tools where only one person has control of the keyboard and mouse - that isn't real pair programming.
    • ", "
    • • Write clear and readable code, commit messages, branch names, and pull request messages.
    • ", @@ -719,9 +719,9 @@ " ", "

      ", "

      Free Code Camp should be a harassment-free experience for everyone, regardless of gender, gender identity and expression, age, sexual orientation, disability, physical appearance, body size, race, national origin, or religion (or lack thereof).

      ", - "

      We do not tolerate harassment of campers in any form, anywhere on Free Code Camp's online media (Slack, Twitch, etc.) or during pair programming. Harassment includes sexual language and imagery, deliberate intimidation, stalking, unwelcome sexual attention, libel, and any malicious hacking or social engineering.

      ", + "

      We do not tolerate harassment of campers in any form, anywhere on Free Code Camp's online media (Gitter, Twitch, etc.) or during pair programming. Harassment includes sexual language and imagery, deliberate intimidation, stalking, unwelcome sexual attention, libel, and any malicious hacking or social engineering.

      ", "

      If a camper engages in harassing behavior, our team will take any action we deem appropriate, up to and including banning them from Free Code Camp.

      ", - "

      We want everyone to feel safe and respected. If you are being harassed or notice that someone else is being harassed, say something! Message @quincylarson, @terakilobyte and @codenonprofit in Slack (preferably with a screen shot of the offending language) so we can take fast action.

      ", + "

      We want everyone to feel safe and respected. If you are being harassed or notice that someone else is being harassed, say something! Message @quincylarson, @terakilobyte and @codenonprofit on Gitter (preferably with a screen shot of the offending language) so we can take fast action.

      ", "

      If you have questions about this code of conduct, email us at team@freecodecamp.com.

      ", "" ] @@ -777,10 +777,10 @@ "

      We're happy to do a quick interview for your publication or show. Here's whom you should contact about what, and how to best reach them:

      ", "

      ", "

        ", - "
      1. Want to talk to about Free Code Camp's curriculum or long-term vision? Reach out to Quincy Larson. He's @ossia on Twitter and @quincylarson on Slack.
      2. ", - "
      3. Want to talk about Free Code Camp's open source codebase, infrastructure, or JavaScript in general? Talk to Nathan Leniz. He's @terakilobyte on Twitter and @terakilobyte on Slack.
      4. ", - "
      5. Want to explore our efforts to empower nonprofits with code? Michael D. Johnson eats, sleeps and breathes that. He's @figitalboy on Twitter and @codenonprofit on Slack.
      6. ", - "
      7. Want to get a camper's perspective on our community? Talk with Bianca Mihai (@biancamihai on Slack and @bubuslubu on Twitter) or Suzanne Atkinson (@adventurebear on Slack and @steelcitycoach on Twitter).", + "
      8. Want to talk to about Free Code Camp's curriculum or long-term vision? Reach out to Quincy Larson. He's @ossia on Twitter and @quincylarson on Gitter.
      9. ", + "
      10. Want to talk about Free Code Camp's open source codebase, infrastructure, or JavaScript in general? Talk to Nathan Leniz. He's @terakilobyte on Twitter and @terakilobyte on Gitter.
      11. ", + "
      12. Want to explore our efforts to empower nonprofits with code? Michael D. Johnson eats, sleeps and breathes that. He's @figitalboy on Twitter and @codenonprofit on Gitter.
      13. ", + "
      14. Want to get a camper's perspective on our community? Talk with Bianca Mihai (@biancamihai on Gitter and @bubuslubu on Twitter) or Suzanne Atkinson (@adventurebear on Gitter and @steelcitycoach on Twitter).", "
      ", "

      ", "

      We strive to be helpful and transparent in everything we do. We'll do what we can to help you share our community with your audience.

      ", @@ -838,7 +838,7 @@ "

      Contributing to our field guide is a great way to establish your history on GitHub, add to your portfolio, and help other campers. If you have a question about JavaScript or programming in general that you'd like us to add to the field guide, here are two ways to get it into the guide:

      ", "

      ", "

        ", - "
      1. You can message @danraley in Slack with your question.
      2. ", + "
      3. You can message @danraley on Gitter with your question.
      4. ", "
      5. You can also contribute to this field guide directly via GitHub pull request, by cloning Free Code Camp's main repository and modifying field-guides.json.
      6. ", "
      ", "

      ", @@ -852,7 +852,7 @@ "description": [ "
      ", "

      Our translation effort is driven by bilingual campers like you.", - "

      If you're able to help us, you can join our Trello board by sending @quincylarson your email address in Slack.

      ", + "

      If you're able to help us, you can join our Trello board by sending @quincylarson your email address on Gitter.

      ", "
      " ] }, @@ -865,7 +865,7 @@ "

      Translation is an all-or-nothing proposal.", "

      We won't be able to add new languages to Free Code Camp until all of our challenges are translated into that language.

      ", "

      In addition to translating these initially, we'll also need to maintain the translation as the challenges are gradually updated.

      ", - "

      If you're able to help us, you can join our Trello board by sending @quincylarson your email address in Slack.

      ", + "

      If you're able to help us, you can join our Trello board by sending @quincylarson your email address on Gitter.

      ", "" ] }, From c8c03aae10cdb01860899e3901cb64f8243211e5 Mon Sep 17 00:00:00 2001 From: LumenTeun Date: Tue, 23 Jun 2015 20:56:25 +0200 Subject: [PATCH 37/51] 'Slack' to 'Gitter or Slack' --- seed/field-guides.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seed/field-guides.json b/seed/field-guides.json index e090157e1a..197ed132d3 100644 --- a/seed/field-guides.json +++ b/seed/field-guides.json @@ -625,8 +625,8 @@ "
    ", "

    ", "

    It's notoriously difficult to estimate how long building software projects will take, so feel free to ask our volunteer team for help.

    ", - "

    You'll continue to meet with your stakeholder at least twice a month in your project's Slack channel.

    ", - "

    You should also ask questions in your project's Slack channel as they come up throughout the week, and your stakeholder can answer them asynchronously.

    ", + "

    You'll continue to meet with your stakeholder at least twice a month in your project's Gitter or Slack channel.

    ", + "

    You should also ask questions in your project's Gitter or Slack channel as they come up throughout the week, and your stakeholder can answer them asynchronously.

    ", "

    Getting \"blocked\" on a task can take away your sense of forward momentum, so be sure to proactively seek answers to any ambiguities you encounter.

    ", "

    Ultimately, the project will be considered complete once both the stakeholder's needs have been met, and you and your pair are happy with the project. Then you can add it to your portfolio!

    ", "

    Working with your Pair

    ", From 13590a133160634c41ed4ed5ec3a8f45f7db998b Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Thu, 25 Jun 2015 15:03:46 -0700 Subject: [PATCH 38/51] refactor rxify stories fixes many bugs --- public/js/main_0.0.2.js | 5 +- server/boot/story.js | 696 ++++++++++++++++++++-------------------- server/utils/index.js | 61 ++-- server/utils/rx.js | 15 +- 4 files changed, 380 insertions(+), 397 deletions(-) diff --git a/public/js/main_0.0.2.js b/public/js/main_0.0.2.js index 472fe7351d..c4c3a338e2 100644 --- a/public/js/main_0.0.2.js +++ b/public/js/main_0.0.2.js @@ -263,10 +263,9 @@ $(document).ready(function() { .fail(function (xhr, textStatus, errorThrown) { $('#story-submit').bind('click', storySubmitButtonHandler); }) - .done(function (data, textStatus, xhr) { - window.location = '/stories/' + JSON.parse(data).storyLink; + .done(function(data, textStatus, xhr) { + window.location = '/stories/' + data.storyLink; }); - }; $('#story-submit').on('click', storySubmitButtonHandler); diff --git a/server/boot/story.js b/server/boot/story.js index b1b1f11b9c..9b34881267 100755 --- a/server/boot/story.js +++ b/server/boot/story.js @@ -1,17 +1,84 @@ -var nodemailer = require('nodemailer'), +var Rx = require('rx'), + nodemailer = require('nodemailer'), + assign = require('object.assign'), sanitizeHtml = require('sanitize-html'), moment = require('moment'), mongodb = require('mongodb'), // debug = require('debug')('freecc:cntr:story'), utils = require('../utils'), + observeMethod = require('../utils/rx').observeMethod, + saveUser = require('../utils/rx').saveUser, + saveInstance = require('../utils/rx').saveInstance, MongoClient = mongodb.MongoClient, secrets = require('../../config/secrets'); +var foundationDate = 1413298800000; +var time48Hours = 172800000; + +var unDasherize = utils.unDasherize; +var dasherize = utils.dasherize; +var getURLTitle = utils.getURLTitle; + +var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } +}); + +function sendMailWhillyNilly(mailOptions) { + transporter.sendMail(mailOptions, function(err) { + if (err) { + console.log('err sending mail whilly nilly', err); + console.log('logging err but not carring'); + } + }); +} + +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 z = Math.log(rank) / Math.log(10); + var hotness = z + (timeValue / time48Hours); + return hotness; +} + +function sortByRank(a, b) { + return hotRank(b.timePosted - foundationDate, b.rank) - + hotRank(a.timePosted - foundationDate, a.rank); +} + +function cleanData(data, opts) { + var options = assign( + {}, + { + allowedTags: [], + allowedAttributes: [] + }, + opts || {} + ); + return sanitizeHtml(data, options).replace(/";/g, '"'); +} + module.exports = function(app) { var router = app.loopback.Router(); var User = app.models.User; + var findUserById = observeMethod(User, 'findById'); + var findOneUser = observeMethod(User, 'findOne'); + var Story = app.models.Story; + var findStory = observeMethod(Story, 'find'); + var findOneStory = observeMethod(Story, 'findOne'); + var findStoryById = observeMethod(Story, 'findById'); + var countStories = observeMethod(Story, 'count'); + var Comment = app.models.Comment; + var findCommentById = observeMethod(Comment, 'findById'); router.get('/stories/hotStories', hotJSON); router.get('/stories/comments/:id', comments); @@ -29,39 +96,19 @@ module.exports = function(app) { app.use(router); - 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; - } - function hotJSON(req, res, next) { - Story.find({ + var query = { order: 'timePosted DESC', limit: 1000 - }, 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)); - - }); + }; + findStory(query).subscribe( + function(stories) { + var sliceVal = stories.length >= 100 ? 100 : stories.length; + var data = stories.sort(sortByRank).slice(0, sliceVal); + res.json(data); + }, + next + ); } function hot(req, res) { @@ -78,32 +125,11 @@ module.exports = function(app) { }); } - /* - * 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) { + var cleanedData = cleanData(data.url); + if (data.url.replace(/&/g, '&') !== cleanedData) { req.flash('errors', { msg: 'The data for this post is malformed' }); @@ -125,64 +151,53 @@ module.exports = function(app) { }); } - function returnIndividualStory(req, res, next) { var dashedName = req.params.storyName; + var storyName = unDasherize(dashedName); - var storyName = dashedName.replace(/\-/g, ' ').trim(); + findOneStory({ where: { storyLink: storyName } }).subscribe( + function(story) { + if (!story) { + req.flash('errors', { + msg: "404: We couldn't find a story with that name. " + + 'Please double check the name.' + }); - Story.find({ where: { storyLink: storyName } }, function(err, story) { - if (err) { - return next(err); - } + var dashedNameFull = story.storyLink.toLowerCase() + .replace(/\s+/g, ' ') + .replace(/\s/g, '-'); - - if (story.length < 1) { - req.flash('errors', { - msg: "404: We couldn't find a story with that name. " + - 'Please double check the name.' - }); - - return res.redirect('/stories/'); - } - - story = story.pop(); - var dashedNameFull = story.storyLink.toLowerCase() - .replace(/\s+/g, ' ') - .replace(/\s/g, '-'); - if (dashedNameFull !== dashedName) { - return res.redirect('../stories/' + dashedNameFull); - } - - var userVoted = false; - try { - var votedObj = story.upVotes.filter(function(a) { - return a['upVotedByUsername'] === req.user['profile']['username']; - }); - if (votedObj.length > 0) { - userVoted = true; + if (dashedNameFull !== dashedName) { + return res.redirect('../stories/' + dashedNameFull); + } + return res.redirect('/stories/'); } - } 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 - }); - }); + + // true if any of votes are made by user + var userVoted = story.upVotes.some(function(upvote) { + return upvote.upVotedByUsername === req.user.username; + }); + + 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 + }); + }, + next + ); } function getStories(req, res, next) { @@ -228,54 +243,50 @@ module.exports = function(app) { } function upvote(req, res, next) { - var data = req.body.data; - Story.find({ where: { id: data.id } }, function(err, story) { - if (err) { - return next(err); - } - story = story.pop(); - story.rank += 1; - story.upVotes.push({ - upVotedBy: req.user.id, - upVotedByUsername: req.user.username - }); - 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); } + var id = req.body.data.id; + var savedStory = findStoryById(id) + .flatMap(function(story) { + story.rank += 1; + story.upVotes.push({ + upVotedBy: req.user.id, + upVotedByUsername: req.user.username + }); + return saveInstance(story); + }) + .shareReplay(); - 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); - } - }); + savedStory.flatMap(function(story) { + // find story author + return findUserById(story.author.userId); + }) + .flatMap(function(user) { + // if user deletes account then this will not exist + if (user) { + user.progressTimestamps.push(Date.now()); } + return saveUser(user); + }) + .flatMap(function() { + req.user.progressTimestamps.push(Date.now()); + return saveUser(req.user); + }) + .flatMap(savedStory) + .subscribe( + function(story) { + return res.send(story); + }, + next ); - 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); - }); + var id = req.params.id; + findCommentById(id).subscribe( + function(comment) { + res.send(comment); + }, + next + ); } function newStory(req, res, next) { @@ -283,10 +294,8 @@ module.exports = function(app) { return next(new Error('Must be logged in')); } var url = req.body.data.url; - var cleanURL = sanitizeHtml(url, { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'); + var cleanURL = cleanData(url); + if (cleanURL !== url) { req.flash('errors', { msg: "The URL you submitted doesn't appear valid" @@ -300,44 +309,46 @@ module.exports = function(app) { 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: '/stories/' + story.pop().storyLink - }); - } - utils.getURLTitle(url, processResponse); - } - ); - function processResponse(err, story) { - if (err) { - res.json({ + findStory({ where: { link: url } }) + .map(function(stories) { + if (stories.length) { + return { + alreadyPosted: true, + storyURL: '/stories/' + stories.pop().storyLink + }; + } + return { alreadyPosted: false, - storyURL: url, - storyTitle: '', - storyImage: '', - storyMetaDescription: '' - }); - } else { - res.json({ - alreadyPosted: false, - storyURL: url, - storyTitle: story.title, - storyImage: story.image, - storyMetaDescription: story.description - }); - } - } + storyURL: url + }; + }) + .flatMap(function(data) { + if (data.alreadyPosted) { + return Rx.Observable.just(data); + } + return Rx.Observable.fromNodeCallback(getURLTitle)(data.storyURL) + .map(function(story) { + return { + alreadyPosted: false, + storyURL: data.storyURL, + storyTitle: story.title, + storyImage: story.image, + storyMetaDescription: story.description + }; + }); + }) + .subscribe( + function(story) { + if (story.alreadyPosted) { + req.flash('errors', { + msg: "Someone's already posted that link. Here's the discussion." + }); + } + res.json(story); + }, + next + ); } function storySubmission(req, res, next) { @@ -357,66 +368,60 @@ module.exports = function(app) { link = 'http://' + link; } - Story.count({ + var query = { storyLink: { like: ('^' + storyLink + '(?: [0-9]+)?$'), options: 'i' } - }, function (err, storyCount) { - if (err) { - return next(err); - } + }; - // if duplicate storyLink add unique number - storyLink = (storyCount === 0) ? storyLink : storyLink + ' ' + storyCount; + var savedStory = countStories(query) + .flatMap(function(storyCount) { + // 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.username - })], - author: { - picture: req.user.picture, - userId: req.user.id, - username: req.user.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); + var link = data.link; + if (link.search(/^https?:\/\//g) === -1) { + link = 'http://' + link; } - 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() - })); + var newStory = new Story({ + headline: cleanData(data.headline), + timePosted: Date.now(), + link: link, + description: cleanData(data.description), + rank: 1, + upVotes: [({ + upVotedBy: req.user.id, + upVotedByUsername: req.user.username + })], + author: { + picture: req.user.picture, + userId: req.user.id, + username: req.user.username, + email: req.user.email + }, + comments: [], + image: data.image, + storyLink: storyLink, + metaDescription: data.storyMetaDescription, + originalStoryAuthorEmail: req.user.email }); + return saveInstance(newStory); }); - }); + + req.user.progressTimestamps.push(Date.now()); + return saveUser(req.user) + .flatMap(savedStory) + .subscribe( + function(story) { + res.json({ + storyLink: dasherize(story.storyLink) + }); + }, + next + ); } function commentSubmit(req, res, next) { @@ -424,11 +429,8 @@ module.exports = function(app) { if (!req.user) { return next(new Error('Not authorized')); } - var sanitizedBody = sanitizeHtml(data.body, - { - allowedTags: [], - allowedAttributes: [] - }).replace(/"/g, '"'); + var sanitizedBody = cleanData(data.body); + if (data.body !== sanitizedBody) { req.flash('errors', { msg: 'HTML is not allowed' @@ -453,7 +455,13 @@ module.exports = function(app) { commentOn: Date.now() }); - commentSave(comment, Story, res, next); + commentSave(comment, findStoryById).subscribe( + function() {}, + next, + function() { + res.send(true); + } + ); } function commentOnCommentSubmit(req, res, next) { @@ -462,13 +470,7 @@ module.exports = function(app) { return next(new Error('Not authorized')); } - var sanitizedBody = sanitizeHtml( - data.body, - { - allowedTags: [], - allowedAttributes: [] - } - ).replace(/"/g, '"'); + var sanitizedBody = cleanData(data.body); if (data.body !== sanitizedBody) { req.flash('errors', { @@ -494,119 +496,101 @@ module.exports = function(app) { topLevel: false, commentOn: Date.now() }); - commentSave(comment, Comment, res, next); + commentSave(comment, findCommentById).subscribe( + function() {}, + next, + function() { + res.send(true); + } + ); } function commentEdit(req, res, next) { - - Comment.find({ where: { id: req.params.id } }, function(err, cmt) { - if (err) { - return next(err); - } - 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); - } - - cmt.body = sanitizedBody; - cmt.commentOn = Date.now(); - cmt.save(function(err) { - if (err) { - return next(err); + findCommentById(req.params.id) + .doOnNext(function(comment) { + if (!req.user && comment.author.userId !== req.user.id) { + throw new Error('Not authorized'); } - res.send(true); - }); - - }); - + }) + .flatMap(function(comment) { + var sanitizedBody = cleanData(req.body.body); + if (req.body.body !== sanitizedBody) { + req.flash('errors', { + msg: 'HTML is not allowed' + }); + } + comment.body = sanitizedBody; + comment.commentOn = Date.now(); + return saveInstance(comment); + }) + .subscribe( + function() { + res.send(true); + }, + next + ); } - function commentSave(comment, Context, res, next) { - comment.save(function(err, data) { - if (err) { - return next(err); - } - try { + function commentSave(comment, findContextById) { + return saveInstance(comment) + .flatMap(function(comment) { // Based on the context retrieve the parent // object of the comment (Story/Comment) - Context.find({ - where: { id: data.associatedPost } - }, function (err, associatedContext) { - 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({ - 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 - } - }); + return findContextById(comment.associatedPost); + }) + .flatMap(function(associatedContext) { + if (associatedContext) { + associatedContext.comments.push(comment.id); + } + // NOTE(berks): saveInstance is safe + // it will automatically call onNext with null and onCompleted if + // argument is falsey or has no method save + return saveInstance(associatedContext); + }) + .flatMap(function(associatedContext) { + // Find the author of the parent object + // if no username + var username = associatedContext && associatedContext.author ? + associatedContext.author.username : + null; - 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 query = { where: { username: username } }; + return findOneUser(query); + }) + // if no user is found we don't want to hit the doOnNext + // filter here will call onCompleted without running through the following + // steps + .filter(function(user) { + return !!user; + }) + // if this is called user is guarenteed to exits + // this is a side effect, hence we use do/tap observable methods + .doOnNext(function(user) { + // If the emails of both authors differ, + // only then proceed with email notification + if ( + comment.author && + comment.author.email && + user.email && + (comment.author.email !== user.email) + ) { + sendMailWhillyNilly({ + to: user.email, + from: 'Team@freecodecamp.com', + subject: comment.author.username + + ' replied to your post on Camper News', + text: [ + 'Just a quick heads-up: ', + comment.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') }); - }); - } catch (e) { - return next(err); - } - }); + } + }); } }; diff --git a/server/utils/index.js b/server/utils/index.js index b49a4579f7..ec64435a5f 100644 --- a/server/utils/index.js +++ b/server/utils/index.js @@ -20,11 +20,6 @@ var allFieldGuideIds, allFieldGuideNames, allNonprofitNames, challengeMapWithNames, allChallengeIds, challengeMapWithDashedNames; -/** - * GET / - * Resources. - */ - Array.zip = function(left, right, combinerFunction) { var counter, results = []; @@ -68,7 +63,7 @@ module.exports = { }, unDasherize: function unDasherize(name) { - return ('' + name).replace(/\-/g, ' '); + return ('' + name).replace(/\-/g, ' ').trim(); }, getChallengeMapForDisplay: function () { @@ -199,35 +194,37 @@ module.exports = { return process.env.NODE_ENV; }, - getURLTitle: function (url, callback) { - (function () { - var result = {title: '', image: '', url: '', description: ''}; - request(url, function (error, response, body) { - if (!error && response.statusCode === 200) { - var $ = cheerio.load(body); - var metaDescription = $("meta[name='description']"); - var metaImage = $("meta[property='og:image']"); - var urlImage = metaImage.attr('content') ? - metaImage.attr('content') : - ''; + getURLTitle: function(url, callback) { + var result = { + title: '', + image: '', + url: '', + description: '' + }; + request(url, function(err, response, body) { + if (err || response.statusCode !== 200) { + return callback(new Error('failed')); + } + var $ = cheerio.load(body); + var metaDescription = $("meta[name='description']"); + var metaImage = $("meta[property='og:image']"); + var urlImage = metaImage.attr('content') ? + metaImage.attr('content') : + ''; - var metaTitle = $('title'); - var description = metaDescription.attr('content') ? - metaDescription.attr('content') : - ''; + var metaTitle = $('title'); + var description = metaDescription.attr('content') ? + metaDescription.attr('content') : + ''; - result.title = metaTitle.text().length < 90 ? - metaTitle.text() : - metaTitle.text().slice(0, 87) + '...'; + result.title = metaTitle.text().length < 90 ? + metaTitle.text() : + metaTitle.text().slice(0, 87) + '...'; - result.image = urlImage; - result.description = description; - callback(null, result); - } else { - callback(new Error('failed')); - } - }); - })(); + result.image = urlImage; + result.description = description; + callback(null, result); + }); }, getMDNLinks: function(links) { diff --git a/server/utils/rx.js b/server/utils/rx.js index 7e98aa486b..8088e163e0 100644 --- a/server/utils/rx.js +++ b/server/utils/rx.js @@ -1,24 +1,27 @@ var Rx = require('rx'); var debug = require('debug')('freecc:rxUtils'); -exports.saveUser = function saveUser(user) { +exports.saveInstance = function saveInstance(instance) { return new Rx.Observable.create(function(observer) { - if (!user || typeof user.save !== 'function') { - debug('no user or save method'); + if (!instance || typeof instance.save !== 'function') { + debug('no instance or save method'); observer.onNext(); return observer.onCompleted(); } - user.save(function(err, savedUser) { + instance.save(function(err, savedInstance) { if (err) { return observer.onError(err); } - debug('user saved'); - observer.onNext(savedUser); + debug('instance saved'); + observer.onNext(savedInstance); observer.onCompleted(); }); }); }; +// alias saveInstance +exports.saveUser = exports.saveInstance; + exports.observableQueryFromModel = function observableQueryFromModel(Model, method, query) { return Rx.Observable.fromNodeCallback(Model[method], Model)(query); From e9450b93217bee2f6b863845823bee1a06f77fcb Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Thu, 25 Jun 2015 15:15:41 -0700 Subject: [PATCH 39/51] start building sponsor views --- server/boot/randomAPIs.js | 14 ++++ server/views/home.jade | 13 ---- server/views/sponsors/become-a-sponsor.jade | 10 +++ server/views/sponsors/sponsors.jade | 75 +++++++++++++++++++++ 4 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 server/views/sponsors/become-a-sponsor.jade create mode 100644 server/views/sponsors/sponsors.jade diff --git a/server/boot/randomAPIs.js b/server/boot/randomAPIs.js index 2046db0ce1..c071bd8afa 100644 --- a/server/boot/randomAPIs.js +++ b/server/boot/randomAPIs.js @@ -29,6 +29,8 @@ module.exports = function(app) { router.get('/pmi-acp-agile-project-managers-form', agileProjectManagersForm); router.get('/nonprofits', nonprofits); router.get('/nonprofits-form', nonprofitsForm); + router.get('/our-sponsors', sponsors); + router.get('/become-a-sponsor', becomeASponsor); router.get('/jobs-form', jobsForm); router.get('/submit-cat-photo', catPhotoSubmit); router.get('/unsubscribe/:email', unsubscribe); @@ -237,6 +239,18 @@ module.exports = function(app) { ); } + function sponsors(req, res) { + res.render('sponsors/sponsors', { + title: 'The Sponsors who make Free Code Camp Possible' + }); + } + + function becomeASponsor(req, res) { + res.render('sponsors/become-a-sponsor', { + title: 'The Sponsors who make Free Code Camp Possible' + }); + } + function nonprofits(req, res) { res.render('resources/nonprofits', { title: 'A guide to our Nonprofit Projects' diff --git a/server/views/home.jade b/server/views/home.jade index f1058309b5..3740c64e3b 100644 --- a/server/views/home.jade +++ b/server/views/home.jade @@ -1,19 +1,6 @@ extends layout block content .jumbotron - if (user && user.progressTimestamps.length > 0) - .col-xs-12 - .embed-responsive.embed-responsive-16by9 - iframe.embed-responsive-item(src='//player.vimeo.com/video/129168884') - br - h3 Note: If you're using Firefox and the buttons aren't working, do a full refresh (control + f5 on Windows and command + shift + r on Mac). We're working on this. - h3 Note: If you've already completed the HTML, CSS, Bootstrap Waypoints, you do not need to do these new Waypoints. - h3 Note: If you were already doing full stack JavaScript Waypoints (Node.js, Angular.js), go ahead and start the Bonfires (Basic Algorithm Scripting). - h3 Thanks for your patience everyone! We're confident these curriculum improvements will better prepare you for your nonprofit projects and for the workplace. - br - br - br - br .text-center h1.hug-top Code with Us h2 Let's learn to code by building projects for nonprofits diff --git a/server/views/sponsors/become-a-sponsor.jade b/server/views/sponsors/become-a-sponsor.jade new file mode 100644 index 0000000000..a96d0aaf67 --- /dev/null +++ b/server/views/sponsors/become-a-sponsor.jade @@ -0,0 +1,10 @@ +extends ../layout +block content + script. + .panel.panel-info + .panel-heading.text-center + h1 Sponsor the next wave of developers + .panel-body + img.img-responsive.img-center.border-radius-5(src='https://s3.amazonaws.com/freecodecamp/wide-social-banner.png') + .col-xs-12.col-md-10.col-md-offset-1 + h2.text-center Call our executive team at +1 888-888-8888 diff --git a/server/views/sponsors/sponsors.jade b/server/views/sponsors/sponsors.jade new file mode 100644 index 0000000000..fc4ac67968 --- /dev/null +++ b/server/views/sponsors/sponsors.jade @@ -0,0 +1,75 @@ +extends ../layout +block content + script. + .panel.panel-info + .panel-heading.text-center + h1 These Sponsors make Free Code Camp possible + .panel-body + img.img-responsive.img-center.border-radius-5(src='https://s3.amazonaws.com/freecodecamp/wide-social-banner.png') + .spacer + .row + .col-xs-12.col-md-10.col-md-offset-1 + a.btn.btn-cta.signup-btn.btn-block(href="/become-a-sponsor") Become a Sponsor + .spacer + h1.text-center Platinum Sponsors + .row + .col-xs-6 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-12.col-sm-10.col-md-8.col-sm-offset-1.col-md-offset-2.negative-35 + h3 + a(href='#' target='_blank') Name of Company + p.large-p Sagittis 9 lives climb the curtains lady tincidunt sleep on your face odd consequat lobortis erat eu? Malesuada eros, ac facilisis waffles reddit cute id together lacinia lived faucibus! Sed, sleeping watched diam cat ut mercedes litora? Meet cute aptent lacinia, eu likes quam neighbors tincidunt scelerisque quis loves she. + .col-xs-6 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-12.col-sm-10.col-md-8.col-sm-offset-1.col-md-offset-2.negative-35 + h3 + a(href='#' target='_blank') Name of Company + p.large-p Sagittis 9 lives climb the curtains lady tincidunt sleep on your face odd consequat lobortis erat eu? Malesuada eros, ac facilisis waffles reddit cute id together lacinia lived faucibus! Sed, sleeping watched diam cat ut mercedes litora? Meet cute aptent lacinia, eu likes quam neighbors tincidunt scelerisque quis loves she. + + h1.text-center Gold Sponsors + .row + .col-xs-4 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-12.col-sm-10.col-sm-offset-1.negative-35 + h3 + a(href='#' target='_blank') Name of Company + p.large-p Sagittis 9 lives climb the curtains lady tincidunt sleep on your face odd consequat lobortis erat eu? Malesuada eros, ac facilisis waffles reddit cute id together lacinia lived faucibus! Sed, sleeping watched diam cat ut mercedes litora? Meet cute aptent lacinia, eu likes quam neighbors tincidunt scelerisque quis loves she. + .col-xs-4 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-12.col-sm-10.col-sm-offset-1.negative-35 + h3 + a(href='#' target='_blank') Name of Company + p.large-p Sagittis 9 lives climb the curtains lady tincidunt sleep on your face odd consequat lobortis erat eu? Malesuada eros, ac facilisis waffles reddit cute id together lacinia lived faucibus! Sed, sleeping watched diam cat ut mercedes litora? Meet cute aptent lacinia, eu likes quam neighbors tincidunt scelerisque quis loves she. + .col-xs-4 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-12.col-sm-10.col-sm-offset-1.negative-35 + h3 + a(href='#' target='_blank') Name of Company + p.large-p Sagittis 9 lives climb the curtains lady tincidunt sleep on your face odd consequat lobortis erat eu? Malesuada eros, ac facilisis waffles reddit cute id together lacinia lived faucibus! Sed, sleeping watched diam cat ut mercedes litora? Meet cute aptent lacinia, eu likes quam neighbors tincidunt scelerisque quis loves she. + + h1.text-center Silver Sponsors + .row + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') + .col-xs-2 + img.img-responsive.img-center(src='http://placekitten.com/200/300') From 2422663f3499cf76b354a20f7ce6680c76d1d752 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Thu, 25 Jun 2015 21:47:25 -0700 Subject: [PATCH 40/51] add comments migration to loopback script --- seed/loopbackMigration.js | 46 +++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/seed/loopbackMigration.js b/seed/loopbackMigration.js index 02dd993b9c..1a86352e46 100644 --- a/seed/loopbackMigration.js +++ b/seed/loopbackMigration.js @@ -160,28 +160,46 @@ var storyCount = dbObservable }) .count(); +var commentCount = dbObservable + .flatMap(function(db) { + return createQuery(db, 'comments', {}); + }) + .withLatestFrom(dbObservable, function(comments, db) { + return { + comments: comments, + db: db + }; + }) + .flatMap(function(dats) { + return insertMany(dats.db, 'comment', dats.comments, { w: 1 }); + }) + .buffer(20) + .count(); + Rx.Observable.combineLatest( userIdentityCount, userSavesCount, storyCount, - function(userIdentCount, userCount, storyCount) { + commentCount, + function(userIdentCount, userCount, storyCount, commentCount) { return { userIdentCount: userIdentCount * 20, userCount: userCount * 20, - storyCount: storyCount * 20 + storyCount: storyCount * 20, + commentCount: commentCount * 20 }; }) .subscribe( - function(countObj) { - console.log('next'); - count = countObj; - }, - function(err) { - console.error('an error occured', err, err.stack); - }, - function() { + function(countObj) { + console.log('next'); + count = countObj; + }, + function(err) { + console.error('an error occured', err, err.stack); + }, + function() { - console.log('finished with ', count); - process.exit(0); - } -); + console.log('finished with ', count); + process.exit(0); + } + ); From 9c2768591d49bc0b051c52e6f03e2d47c114e4ae Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Thu, 25 Jun 2015 23:00:49 -0700 Subject: [PATCH 41/51] fix check for user existence --- server/boot/story.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/boot/story.js b/server/boot/story.js index 9b34881267..2f026626c3 100755 --- a/server/boot/story.js +++ b/server/boot/story.js @@ -173,9 +173,10 @@ module.exports = function(app) { return res.redirect('/stories/'); } + var username = req.user ? req.user.username : ''; // true if any of votes are made by user var userVoted = story.upVotes.some(function(upvote) { - return upvote.upVotedByUsername === req.user.username; + return upvote.upVotedByUsername === username; }); res.render('stories/index', { From 82c96ec0ba8e2ecf33c40eac38fc0f450d61093d Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Thu, 25 Jun 2015 23:20:16 -0700 Subject: [PATCH 42/51] bump main.js version --- public/js/{main_0.0.2.js => main_0.0.3.js} | 0 server/views/partials/universal-head.jade | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename public/js/{main_0.0.2.js => main_0.0.3.js} (100%) diff --git a/public/js/main_0.0.2.js b/public/js/main_0.0.3.js similarity index 100% rename from public/js/main_0.0.2.js rename to public/js/main_0.0.3.js diff --git a/server/views/partials/universal-head.jade b/server/views/partials/universal-head.jade index 47bc657df6..c31b46da4c 100644 --- a/server/views/partials/universal-head.jade +++ b/server/views/partials/universal-head.jade @@ -32,7 +32,7 @@ script. window.moment || document.write('