Merge branch 'staging' of github.com:FreeCodeCamp/freecodecamp into staging

This commit is contained in:
Quincy Larson
2015-06-19 16:18:56 -07:00
15 changed files with 161 additions and 892 deletions

View File

@ -47,7 +47,6 @@
"express-session": "~1.9.2",
"express-validator": "~2.8.0",
"font-awesome": "~4.3.0",
"forcedomain": "~0.4.0",
"forever": "~0.14.1",
"frameguard": "^0.2.2",
"github-api": "~0.7.0",

12
pm2Start.js Normal file
View File

@ -0,0 +1,12 @@
var pm2 = require('pm2');
pm2.connect(function() {
pm2.start({
name: 'server',
script: 'server/server.js',
exec_mode: 'cluster',
instances: '2',
max_memory_restart: '900M'
}, function(err, apps) {
pm2.disconnect();
});
});

View File

@ -18,7 +18,7 @@ editor.setSize("100%", "auto");
// Hijack tab key to enter two spaces intead
editor.setOption("extraKeys", {
Tab: function(cm) {
if (cm.somethingSelected()){
if (cm.somethingSelected()) {
cm.indentSelection("add");
} else {
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
@ -26,7 +26,7 @@ editor.setOption("extraKeys", {
}
},
"Shift-Tab": function(cm) {
if (cm.somethingSelected()){
if (cm.somethingSelected()) {
cm.indentSelection("subtract");
} else {
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
@ -121,7 +121,10 @@ codeOutput.setValue('/**\n' +
' */');
codeOutput.setSize("100%", "100%");
var info = editor.getScrollInfo();
var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top;
var after = editor.charCoords({
line: editor.getCursor().line + 1,
ch: 0
}, "local").top;
if (info.top + info.clientHeight < after)
editor.scrollTo(null, after - info.clientHeight + 3);
@ -143,8 +146,8 @@ editorValue = (localBonfire.isAlive())? localBonfire.getEditorValue() : allSeeds
myCodeMirror.setValue(editorValue);
function doLinting () {
editor.operation(function () {
function doLinting() {
editor.operation(function() {
for (var i = 0; i < widgets.length; ++i)
editor.removeLineWidget(widgets[i]);
widgets.length = 0;
@ -166,14 +169,14 @@ function doLinting () {
});
};
$('#submitButton').on('click', function () {
$('#submitButton').on('click', function() {
bonfireExecute();
});
function bonfireExecute() {
attempts++;
ga('send', 'event', 'Challenge', 'ran-code', challenge_Name);
userTests= null;
ga('send', 'event', 'Challenge', 'ran-code', challenge_Name);
userTests = null;
$('#codeOutput').empty();
var userJavaScript = myCodeMirror.getValue();
userJavaScript = removeComments(userJavaScript);
@ -205,16 +208,23 @@ var scrapeTests = function(userJavaScript) {
}
var counter = 0;
var regex = new RegExp(/(expect(\s+)?\(.*\;)|(assert(\s+)?\(.*\;)|(assert\.\w.*\;)|(.*\.should\..*\;)/);
var regex = new RegExp(
/(expect(\s+)?\(.*\;)|(assert(\s+)?\(.*\;)|(assert\.\w.*\;)|(.*\.should\..*\;)/
);
var match = regex.exec(userJavaScript);
while (match != null) {
var replacement = '//' + counter + testSalt;
userJavaScript = userJavaScript.substring(0, match.index) + replacement + userJavaScript.substring(match.index + match[0].length);
userJavaScript = userJavaScript.substring(0, match.index) + replacement +
userJavaScript.substring(match.index + match[0].length);
if (!userTests) {
userTests= [];
userTests = [];
}
userTests.push({"text": match[0], "line": counter, "err": null});
userTests.push({
"text": match[0],
"line": counter,
"err": null
});
counter++;
match = regex.exec(userJavaScript);
}
@ -236,17 +246,22 @@ var createTestDisplay = function() {
if (pushed) {
userTests.pop();
}
for (var i = 0; i < userTests.length;i++) {
for (var i = 0; i < userTests.length; i++) {
var test = userTests[i];
var testDoc = document.createElement("div");
if (test.err != null) {
console.log('Should be displaying bad tests');
$(testDoc)
.html("<div class='row'><div class='col-xs-2 text-center'><i class='ion-close-circled big-error-icon'></i></div><div class='col-xs-10 test-output wrappable test-vertical-center grayed-out-test-output'>" + test.text + "</div><div class='col-xs-10 test-output wrappable'>" + test.err + "</div></div><div class='ten-pixel-break'/>")
.html(
"<div class='row'><div class='col-xs-2 text-center'><i class='ion-close-circled big-error-icon'></i></div><div class='col-xs-10 test-output wrappable test-vertical-center grayed-out-test-output'>" +
test.text + "</div><div class='col-xs-10 test-output wrappable'>" +
test.err + "</div></div><div class='ten-pixel-break'/>")
.appendTo($('#testSuite'));
} else {
$(testDoc)
.html("<div class='row'><div class='col-xs-2 text-center'><i class='ion-checkmark-circled big-success-icon'></i></div><div class='col-xs-10 test-output test-vertical-center wrappable grayed-out-test-output'>" + test.text + "</div></div><div class='ten-pixel-break'/>")
.html(
"<div class='row'><div class='col-xs-2 text-center'><i class='ion-checkmark-circled big-success-icon'></i></div><div class='col-xs-10 test-output test-vertical-center wrappable grayed-out-test-output'>" +
test.text + "</div></div><div class='ten-pixel-break'/>")
.appendTo($('#testSuite'));
}
};
@ -268,18 +283,21 @@ var runTests = function(err, data) {
pushed = false;
$('#testSuite').children().remove();
if (err && userTests.length > 0) {
userTests= [{text:"Program Execution Failure", err: "No user tests were run."}];
userTests = [{
text: "Program Execution Failure",
err: "No user tests were run."
}];
createTestDisplay();
} else if (userTests) {
userTests.push(false);
pushed = true;
userTests.forEach(function(chaiTestFromJSON, indexOfTestArray, __testArray){
userTests.forEach(function(chaiTestFromJSON, indexOfTestArray,
__testArray) {
try {
if (chaiTestFromJSON) {
var output = eval(reassembleTest(chaiTestFromJSON, data));
debugger;
}
} catch(error) {
} catch (error) {
allTestsPassed = false;
__testArray[indexOfTestArray].err = error.message;
} finally {
@ -299,12 +317,12 @@ var runTests = function(err, data) {
function showCompletion() {
var time = Math.floor(Date.now()) - started;
ga('send', 'event', 'Challenge', 'solved', challenge_Name + ', Time: ' + time +', Attempts: ' + attempts);
ga('send', 'event', 'Challenge', 'solved', challenge_Name + ', Time: ' + time +
', Attempts: ' + attempts);
var bonfireSolution = myCodeMirror.getValue();
var didCompleteWith = $('#completed-with').val() || null;
$.post(
'/completed-bonfire/',
{
'/completed-bonfire/', {
challengeInfo: {
challengeId: challenge_Id,
challengeName: challenge_Name,
@ -312,10 +330,11 @@ function showCompletion() {
challengeType: challengeType,
solution: bonfireSolution
}
}, function(res) {
},
function(res) {
if (res) {
$('#complete-courseware-dialog').modal('show');
$('#complete-courseware-dialog').keydown(function (e) {
$('#complete-courseware-dialog').keydown(function(e) {
if (e.ctrlKey && e.keyCode == 13) {
$('#next-courseware-button').click();
}

View File

@ -180,7 +180,8 @@
"assert.deepEqual(palindrome(\"not a palindrome\"), false);",
"assert.deepEqual(palindrome(\"A man, a plan, a canal. Panama\"), true);",
"assert.deepEqual(palindrome(\"never odd or even\"), true);",
"assert.deepEqual(palindrome(\"nope\"), false);"
"assert.deepEqual(palindrome(\"nope\"), false);",
"assert.deepEqual(palindrome(\"almostomla\"), false);"
],
"challengeSeed": [
"function palindrome(str) {",
@ -754,8 +755,8 @@
},
{
"id": "a5de63ebea8dbee56860f4f2",
"name": "bonfire-diff-two-arrays",
"dashedName": "Bonfire: Diff Two Arrays",
"name": "Bonfire: Diff Two Arrays",
"dashedName": "bonfire-diff-two-arrays",
"difficulty": "2.01",
"description": [
"Compare two arrays and return a new array with any items not found in both of the original arrays.",
@ -1105,6 +1106,10 @@
],
"tests": [
"assert.strictEqual(convert('Dolce & Gabbana'), 'Dolce &amp; Gabbana', 'should escape characters');",
"assert.strictEqual(convert('Hamburgers < Pizza < Tacos'), 'Hamburgers &lt; Pizza &lt; Tacos', 'should escape characters');",
"assert.strictEqual(convert('Sixty > twelve'), 'Sixty &gt; twelve', 'should escape characters');",
"assert.strictEqual(convert('Stuff in \"quotation marks\"'), 'Stuff in &quot;quotation marks&quot;', 'should escape characters');",
"assert.strictEqual(convert(\"Shindler's List\"), 'Shindler&apos;s List', 'should escape characters');",
"assert.strictEqual(convert('abc'), 'abc', 'should handle strings with nothing to escape');"
],
"MDNlinks": [

View File

@ -1005,7 +1005,7 @@
],
"tests": [
"assert((/cat photos/gi).test($('a').text()), 'Your <code>a</code> element should have the anchor text of \"cat photos\"')",
"assert($('a').filter(function(index) { return /com/gi.test($('a').attr('href')); }).length > 0, 'You need an <code>a</code> element that links to <code>http&#58;//catphotoapp.com<code>.')",
"assert(/http:\\/\\/catphotoapp\\.com/gi.test($('a').attr('href')), 'You need an <code>a</code> element that links to <code>http&#58;//catphotoapp.com<code>.')",
"assert(editor.match(/<\\/a>/g) && editor.match(/<\\/a>/g).length === editor.match(/<a/g).length, 'Make sure your <code>a</code> element has a closing tag.')"
],
"challengeSeed": [

View File

@ -626,7 +626,7 @@
"The \"row\" class is applied to a <code>div</code>, and the buttons themselves can be wrapped within it."
],
"tests": [
"assert($('div.row:has(button)'), 'Your buttons should all be wrapped within the same <code>div</code> element with the class \"row\".')",
"assert($('div.row:has(button)').length > 0, 'Your buttons should all be wrapped within the same <code>div</code> element with the class \"row\".')",
"assert($('div.col-xs-4:has(button)').length > 2, 'Each of your Bootstrap buttons should be wrapped within its own a <code>div</code> element with the class \"col-xs-4\".')",
"assert(editor.match(/<\\/button>/g) && editor.match(/<button/g) && editor.match(/<\\/button>/g).length === editor.match(/<button/g).length, 'Make sure each of your <code>button</code> elements has a closing tag.')",
"assert(editor.match(/<\\/div>/g) && editor.match(/<div/g) && editor.match(/<\\/div>/g).length === editor.match(/<div/g).length, 'Make sure each of your <code>div</code> elements has a closing tag.')"

View File

@ -56,7 +56,7 @@
"<span class='text-info'>Hint:</span> Here's an example call to Twitch.tv's JSON API: <code>https://api.twitch.tv/kraken/streams/freecodecamp</code>.",
"<span class='text-info'>Hint:</span> The relevant documentation about this API call is here: <a href='https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel' target='_blank'>https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel</a>.",
"<span class='text-info'>Hint:</span> Here's an array of the Twitch.tv usernames of people who regularly stream coding: <code>[\"freecodecamp\", \"storbeck\", \"terakilobyte\", \"habathcx\",\"RobotCaleb\",\"comster404\",\"brunofin\",\"thomasballinger\",\"noobs2ninjas\",\"beohoff\"]</code>",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -87,7 +87,7 @@
"Here are the <a href='http://en.wikipedia.org/wiki/User_story' target='_blank'>user stories</a> you must enable, and optional bonus user stories:",
"<span class='text-info'>User Story:</span> As a user, I can click a button to show me a new random quote.",
"<span class='text-info'>Bonus User Story:</span> As a user, I can press a button to tweet out a quote.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -120,7 +120,7 @@
"<span class='text-info'>Bonus User Story:</span> As a user, I can see an icon depending on the temperature..",
"<span class='text-info'>Bonus User Story:</span> As a user, I see a different background image depending on the temperature (e.g. snowy mountain, hot desert).",
"<span class='text-info'>Bonus User Story:</span> As a user, I can push a button to toggle between Fahrenheit and Celsius.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -154,7 +154,7 @@
"<span class='text-info'>User Story:</span> As a user, I can click a link to go directly to the post's discussion page.",
"<span class='text-info'>Bonus User Story:</span> As a user, I can see how many upvotes each story has.",
"<span class='text-info'>Hint:</span> Here's the Camper News Hot Stories API endpoint: <code>http://www.freecodecamp.com/stories/hotStories</code>.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],
@ -187,7 +187,7 @@
"<span class='text-info'>Bonus User Story:</span>As a user, I can click a button to see a random Wikipedia entry.",
"<span class='text-info'>Bonus User Story:</span>As a user, when I type in the search box, I can see a dropdown menu with autocomplete options for matching Wikipedia entries.",
"<span class='text-info'>Hint:</span> Here's an entry on using Wikipedia's API: <code>http://www.mediawiki.org/wiki/API:Main_page</code>.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.each/'>jQuery's $.getJSON()</a>to consume APIs.",
"Remember to use <a href='/field-guide/how-do-i-get-help-when-I-get-stuck' target='_blank'>RSAP</a> if you get stuck. Try using <a href='http://api.jquery.com/jquery.getjson/'>jQuery's $.getJSON()</a>to consume APIs.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. If you pair programmed, you should also include the Free Code Camp username of your pair.",
"If you'd like immediate feedback on your project, click this button and paste in a link to your CodePen project. Otherwise, we'll review it before you start your nonprofit projects.<br><br><a class='btn btn-primary btn-block' href='https://twitter.com/intent/tweet?text=Check%20out%20the%20project%20I%20just%20built%20with%20%40FreeCodeCamp:%20%0A%20%23LearnToCode%20%23JavaScript' target='_blank'>Click here then add your link to your tweet's text</a>"
],

View File

@ -32,50 +32,30 @@
var R = require('ramda'),
utils = require('../utils'),
userMigration = require('../utils/middleware').userMigration,
MDNlinks = require('../../seed/bonfireMDNlinks');
userMigration = require('../utils/middleware').userMigration;
var challengeMapWithNames = utils.getChallengeMapWithNames();
var challengeMapWithIds = utils.getChallengeMapWithIds();
var challengeMapWithDashedNames = utils.getChallengeMapWithDashedNames();
function getMDNlinks(links) {
// takes in an array of links, which are strings
var populatedLinks = [];
// for each key value, push the corresponding link
// from the MDNlinks object into a new array
if (links) {
links.forEach(function (value) {
populatedLinks.push(MDNlinks[value]);
});
}
return populatedLinks;
}
var getMDNLinks = utils.getMDNLinks;
module.exports = function(app) {
var router = app.loopback.Router();
var Challenge = app.models.Challenge;
var User = app.models.User;
router.get(
'/challenges/next-challenge',
userMigration,
returnNextChallenge
);
router.get(
'/challenges/:challengeName',
userMigration,
returnIndividualChallenge
);
router.get('/challenges/', userMigration, returnCurrentChallenge);
router.post('/completed-challenge/', completedChallenge);
router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump);
router.post('/completed-bonfire', completedBonfire);
// the follow routes are covered by userMigration
router.use(userMigration);
router.get('/challenges/next-challenge', returnNextChallenge);
router.get('/challenges/:challengeName', returnIndividualChallenge);
router.get('/challenges/', returnCurrentChallenge);
router.get('/map', challengeMap);
app.use(router);
function returnNextChallenge(req, res, next) {
@ -295,7 +275,7 @@ module.exports = function(app) {
bonfires: challenge,
challengeId: challenge.id,
MDNkeys: challenge.MDNlinks,
MDNlinks: getMDNlinks(challenge.MDNlinks),
MDNlinks: getMDNLinks(challenge.MDNlinks),
challengeType: challenge.challengeType
});
}
@ -547,4 +527,51 @@ module.exports = function(app) {
});
}
}
function challengeMap(req, res, next) {
var completedList = [];
if (req.user) {
completedList = req.user.completedChallenges;
}
var noDuplicatedChallenges = R.uniq(completedList);
var completedChallengeList = noDuplicatedChallenges
.map(function(challenge) {
// backwards compatibility
return (challenge.id || challenge._id);
});
var challengeList = utils.
getChallengeMapForDisplay(completedChallengeList);
Object.keys(challengeList).forEach(function(key) {
challengeList[key].completed = challengeList[key]
.challenges.filter(function(elem) {
// backwards compatibility hack
return completedChallengeList.indexOf(elem.id || elem._id) > -1;
});
});
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
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));
User.count(function(err, camperCount) {
if (err) { return next(err); }
res.render('challengeMap/show', {
daysRunning: daysRunning,
camperCount: numberWithCommas(camperCount),
title: "A map of all Free Code Camp's Challenges",
challengeList: challengeList,
completedChallengeList: completedChallengeList
});
});
}
};

View File

@ -1,66 +0,0 @@
var R = require('ramda'),
// debug = require('debug')('freecc:cntr:challengeMap'),
utils = require('./../utils'),
middleware = require('../utils/middleware');
module.exports = function(app) {
var User = app.models.User;
var router = app.loopback.Router();
router.get('/map', middleware.userMigration, challengeMap);
router.get('/learn-to-code', function(req, res) {
res.redirect(301, '/map');
});
router.get('/about', function(req, res) {
res.redirect(301, '/map');
});
app.use(router);
function challengeMap(req, res, next) {
var completedList = [];
if (req.user) {
completedList = req.user.completedChallenges;
}
var noDuplicatedChallenges = R.uniq(completedList);
var completedChallengeList = noDuplicatedChallenges
.map(function(challenge) {
return (challenge.id || challenge._id); // backwards compatibility
});
var challengeList = utils.
getChallengeMapForDisplay(completedChallengeList);
Object.keys(challengeList).forEach(function(key) {
challengeList[key].completed = challengeList[key]
.challenges.filter(function(elem) {
return completedChallengeList.indexOf(elem.id || elem._id) > -1;
//backwards compatibility hack
});
});
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
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));
User.count(function(err, camperCount) {
if (err) { return next(err); }
res.render('challengeMap/show', {
daysRunning: daysRunning,
camperCount: numberWithCommas(camperCount),
title: "A map of all Free Code Camp's Challenges",
challengeList: challengeList,
completedChallengeList: completedChallengeList
});
});
}
};

View File

@ -18,5 +18,13 @@ module.exports = function(app) {
);
});
router.get('/learn-to-code', function(req, res) {
res.redirect(301, '/map');
});
router.get('/about', function(req, res) {
res.redirect(301, '/map');
});
app.use(router);
};

View File

@ -3,6 +3,7 @@ var secrets = require('../config/secrets');
module.exports = {
db: {
connector: 'mongodb',
connectionTimeout: 15000,
url: process.env.MONGOHQ_URL
},
mail: {

View File

@ -41,7 +41,7 @@ module.exports = {
},
'google-login': {
provider: 'google',
authScheme: 'oauth2',
authScheme: 'oauth',
module: 'passport-google-oauth2',
clientID: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
@ -55,7 +55,7 @@ module.exports = {
},
'google-link': {
provider: 'google',
authScheme: 'oauth2',
authScheme: 'oauth',
module: 'passport-google-oauth2',
clientID: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,

View File

@ -1,5 +1,6 @@
require('dotenv').load();
require('pmx').init();
var pmx = require('pmx');
pmx.init();
// handle uncaught exceptions. Forever will restart process on shutdown
var R = require('ramda'),
@ -20,7 +21,6 @@ var R = require('ramda'),
path = require('path'),
expressValidator = require('express-validator'),
lessMiddleware = require('less-middleware'),
pmx = require('pmx'),
passportProviders = require('./passport-providers'),
/**
@ -42,12 +42,6 @@ app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
//if (process.env.NODE_ENV === 'production') {
// app.use(forceDomain({
// hostname: 'www.freecodecamp.com'
// }));
//}
app.use(compress());
app.use(lessMiddleware(path.join(__dirname, '/public')));
app.use(logger('dev'));
@ -98,6 +92,8 @@ var trusted = [
'https://freecodecamp.com',
'https://freecodecamp.org',
'*.freecodecamp.org',
// NOTE(berks): add the following as the blob above was not covering www
'http://www.freecodecamp.org',
'ws://freecodecamp.com/',
'ws://www.freecodecamp.com/',
'*.gstatic.com',
@ -143,7 +139,8 @@ app.use(helmet.csp({
'*.aspnetcdn.com',
'*.d3js.org',
'https://cdn.inspectlet.com/inspectlet.js',
'http://cdn.inspectlet.com/inspectlet.js'
'http://cdn.inspectlet.com/inspectlet.js',
'http://www.freecodecamp.org'
].concat(trusted),
'connect-src': [].concat(trusted),
styleSrc: [
@ -259,6 +256,8 @@ R.keys(passportProviders).map(function(strategy) {
// if (process.env.NODE_ENV === 'development') {
if (true) { // eslint-disable-line
// NOTE(berks): adding pmx here for Beta test. Remove for production
app.use(pmx.expressErrorHandler());
app.use(errorHandler({
log: true
}));

View File

@ -7,6 +7,7 @@ var path = require('path'),
fs = require('fs'),
MDNlinks = require('../../seed/bonfireMDNlinks'),
resources = require('./resources.json'),
nonprofits = require('../../seed/nonprofits.json'),
fieldGuides = require('../../seed/field-guides.json');
@ -16,7 +17,7 @@ var path = require('path'),
*/
var allFieldGuideIds, allFieldGuideNames, allNonprofitNames,
challengeMap, challengeMapForDisplay, challengeMapWithIds,
challengeMapWithNames, allChallengeIds, allChallenges,
challengeMapWithNames, allChallengeIds,
challengeMapWithDashedNames;
/**
@ -216,5 +217,18 @@ module.exports = {
}
});
})();
},
getMDNLinks: function(links) {
if (!links) {
return [];
}
// takes in an array of links, which are strings
// for each key value, push the corresponding link
// from the MDNlinks object into a new array
return links.map(function(value) {
return MDNlinks[value];
});
}
};

749
setup.js
View File

@ -1,749 +0,0 @@
var fs = require('fs');
var os = require('os');
var blessed = require('blessed');
var multiline = require('multiline');
if (os.platform() === 'win32') {
console.log('**************************************************************');
console.log('Hackathon Starter Generator has been disabled on Windows until');
console.log('https://github.com/chjj/blessed is fixed or until I find a');
console.log('better CLI module.');
console.log('**************************************************************');
process.exit();
}
var screen = blessed.screen({
autoPadding: true
});
screen.key('q', function() {
process.exit(0);
});
var home = blessed.list({
parent: screen,
padding: { top: 2 },
mouse: true,
keys: true,
fg: 'white',
bg: 'blue',
selectedFg: 'blue',
selectedBg: 'white',
items: [
'» REMOVE AUTHENTICATION PROVIDER',
'» CHANGE EMAIL SERVICE',
'» ADD NODE.JS CLUSTER SUPPORT',
'» EXIT'
]
});
var homeTitle = blessed.text({
parent: screen,
align: 'center',
fg: 'blue',
bg: 'white',
content: 'Hackathon Starter (c) 2014'
});
var footer = blessed.text({
parent: screen,
bottom: 0,
fg: 'white',
bg: 'blue',
tags: true,
content: ' {cyan-fg}<Up/Down>{/cyan-fg} moves |' +
' {cyan-fg}<Enter>{/cyan-fg} selects | {cyan-fg}<q>{/cyan-fg} exits'
});
var inner = blessed.form({
top: 'center',
left: 'center',
mouse: true,
keys: true,
width: 33,
height: 10,
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
fg: 'white',
bg: 'red'
});
var success = blessed.box({
top: 'center',
left: 'center',
mouse: true,
keys: true,
tags: true,
width: '50%',
height: '40%',
border: {
type: 'line',
fg: 'white',
bg: 'green'
},
fg: 'white',
bg: 'green',
padding: 1
});
success.on('keypress', function() {
home.focus();
home.remove(success);
});
var clusterText = blessed.text({
top: 'top',
bg: 'red',
fg: 'white',
tags: true,
content: 'Take advantage of multi-core systems' +
' using built-in {underline}cluster{/underline} module.'
});
var enable = blessed.button({
parent: inner,
bottom: 0,
mouse: true,
shrink: true,
name: 'enable',
content: ' ENABLE ',
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
style: {
fg: 'white',
bg: 'red',
focus: {
fg: 'red',
bg: 'white'
}
}
});
var disable = blessed.button({
parent: inner,
bottom: 0,
left: 10,
mouse: true,
shrink: true,
name: 'disable',
content: ' DISABLE ',
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
style: {
fg: 'white',
bg: 'red',
focus: {
fg: 'red',
bg: 'white'
}
}
});
var cancel = blessed.button({
parent: inner,
bottom: 0,
left: 21,
mouse: true,
shrink: true,
name: 'cancel',
content: ' CANCEL ',
border: {
type: 'line',
fg: 'white',
bg: 'red'
},
style: {
fg: 'white',
bg: 'red',
focus: {
fg: 'red',
bg: 'white'
}
}
});
cancel.on('press', function() {
home.focus();
home.remove(inner);
screen.render();
});
var authForm = blessed.form({
mouse: true,
keys: true,
fg: 'white',
bg: 'blue',
padding: { left: 1, right: 1 }
});
authForm.on('submit', function() {
var passportConfig = fs.readFileSync('config/passport.js')
.toString().split(os.EOL);
var loginTemplate = fs.readFileSync('views/account/login.jade')
.toString().split(os.EOL);
var profileTemplate = fs.readFileSync('views/account/profile.jade')
.toString().split(os.EOL);
var userModel = fs.readFileSync('models/User.js').toString().split(os.EOL);
var app = fs.readFileSync('app.js').toString().split(os.EOL);
var secrets = fs.readFileSync('config/secrets.js').toString().split(os.EOL);
var index = passportConfig
.indexOf('var FacebookStrategy = require("passport-facebook").Strategy;');
if (facebookCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Facebook.');
passportConfig.splice(index, 47);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-facebook.btn-social(href="/auth/facebook")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.facebook');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' facebook: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf([
'app.get("/auth/facebook"',
'passport.authenticate("facebook",',
' { scope: ["email", "user_location"] }));'
].join(' '));
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
"var GitHubStrategy = require('passport-github').Strategy;"
);
if (githubCheckbox.checked && index !== -1) {
console.log(index);
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with GitHub.');
passportConfig.splice(index, 48);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
" a.btn.btn-block.btn-github.btn-social(href='/auth/github')"
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.github');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' github: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
'app.get("/auth/github", passport.authenticate("github"));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var GoogleStrategy = require("passport-google-oauth").OAuth2Strategy;'
);
if (googleCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Google.');
passportConfig.splice(index, 46);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
" a.btn.btn-block.btn-google-plus.btn-social(href='/auth/google')"
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.google');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' google: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
'app.get("/auth/google",' +
' passport.authenticate("google", { scope: "profile email" }));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var TwitterStrategy = require("passport-twitter").Strategy;'
);
if (twitterCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Twitter.');
passportConfig.splice(index, 43);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-twitter.btn-social(href="/auth/twitter")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.twitter');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade', profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' twitter: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
"app.get('/auth/twitter', passport.authenticate('twitter'));"
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var LinkedInStrategy = require("passport-linkedin-oauth2").Strategy;'
);
if (linkedinCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with LinkedIn.');
passportConfig.splice(index, 47);
fs.writeFileSync('config/passport.js', passportConfig.join(os.EOL));
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-linkedin.btn-social(href="/auth/linkedin")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync('views/account/login.jade', loginTemplate.join(os.EOL));
index = profileTemplate.indexOf(' if user.linkedin');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = userModel.indexOf(' linkedin: String,');
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
index = app.indexOf(
'app.get("/auth/linkedin",',
' passport.authenticate("linkedin", { state: "SOME STATE" }));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
}
index = passportConfig.indexOf(
'var InstagramStrategy = require("passport-instagram").Strategy;'
);
if (instagramCheckbox.checked && index !== -1) {
passportConfig.splice(index, 1);
index = passportConfig.indexOf('// Sign in with Instagram.');
passportConfig.splice(index, 43);
fs.writeFileSync(
'config/passport.js',
passportConfig.join(os.EOL)
);
index = loginTemplate.indexOf(
' a.btn.btn-block.btn-instagram.btn-social(href="/auth/instagram")'
);
loginTemplate.splice(index, 3);
fs.writeFileSync(
'views/account/login.jade',
loginTemplate.join(os.EOL)
);
index = profileTemplate.indexOf(' if user.instagram');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = app.indexOf(
'app.get("/auth/instagram", passport.authenticate("instagram"));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
userModel.splice(index, 1);
fs.writeFileSync('models/User.js', userModel.join(os.EOL));
}
home.remove(authForm);
home.append(success);
success.setContent(
'Selected authentication providers have been removed' +
'from passportConfig.js, User.js, server.js, login.jade and profile.jade!'
);
success.focus();
screen.render();
});
var authText = blessed.text({
parent: authForm,
content: 'Selecting a checkbox removes an authentication provider.' +
' If authentication provider is already removed, no action will be taken.',
padding: 1,
bg: 'magenta',
fg: 'white'
});
var facebookCheckbox = blessed.checkbox({
parent: authForm,
top: 6,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Facebook'
});
var githubCheckbox = blessed.checkbox({
parent: authForm,
top: 7,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'GitHub'
});
var googleCheckbox = blessed.checkbox({
parent: authForm,
top: 8,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Google'
});
var twitterCheckbox = blessed.checkbox({
parent: authForm,
top: 9,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Twitter'
});
var linkedinCheckbox = blessed.checkbox({
parent: authForm,
top: 10,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'LinkedIn'
});
var instagramCheckbox = blessed.checkbox({
parent: authForm,
top: 11,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Instagram'
});
var authSubmit = blessed.button({
parent: authForm,
top: 13,
mouse: true,
shrink: true,
name: 'submit',
content: ' SUBMIT ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
authSubmit.on('press', function() {
authForm.submit();
});
var authCancel = blessed.button({
parent: authForm,
top: 13,
left: 9,
mouse: true,
shrink: true,
name: 'cancel',
content: ' CANCEL ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
authCancel.on('press', function() {
home.focus();
home.remove(authForm);
screen.render();
});
var emailForm = blessed.form({
mouse: true,
keys: true,
fg: 'white',
bg: 'blue',
padding: { left: 1, right: 1 }
});
emailForm.on('submit', function() {
var contactCtrl = fs.readFileSync('controllers/contact.js')
.toString().split(os.EOL);
var userCtrl = fs.readFileSync('controllers/user.js')
.toString().split(os.EOL);
var choice = null;
if (sendgridRadio.checked) {
choice = 'SendGrid';
} else if (mailgunRadio.checked) {
choice = 'Mailgun';
} else if (mandrillRadio.checked) {
choice = 'Mandrill';
}
var index = contactCtrl.indexOf(
'var transporter = nodemailer.createTransport({'
);
contactCtrl.splice(index + 1, 1, " service: '" + choice + "',");
contactCtrl.splice(
index + 3,
1,
' user: secrets.' + choice.toLowerCase() + '.user,'
);
contactCtrl.splice(
index + 4,
1,
' pass: secrets.' + choice.toLowerCase() + '.password'
);
fs.writeFileSync('controllers/contact.js', contactCtrl.join(os.EOL));
index = userCtrl.indexOf(
' var transporter = nodemailer.createTransport({'
);
userCtrl.splice(index + 1, 1, " service: '" + choice + "',");
userCtrl.splice(
index + 3,
1,
' user: secrets.' + choice.toLowerCase() + '.user,'
);
userCtrl.splice(
index + 4,
1,
' pass: secrets.' + choice.toLowerCase() + '.password'
);
index = userCtrl.indexOf(
' var transporter = nodemailer.createTransport({',
(index + 1)
);
userCtrl.splice(
index + 1,
1,
' service: "' + choice + '",'
);
userCtrl.splice(
index + 3,
1,
' user: secrets.' + choice.toLowerCase() + '.user,'
);
userCtrl.splice(
index + 4,
1,
' pass: secrets.' + choice.toLowerCase() + '.password'
);
fs.writeFileSync('controllers/user.js', userCtrl.join(os.EOL));
home.remove(emailForm);
home.append(success);
success.setContent('Email Service has been switched to ' + choice);
success.focus();
screen.render();
});
var emailText = blessed.text({
parent: emailForm,
content: 'Select one of the following email service providers ' +
'for {underline}contact form{/underline}' +
' and {underline}password reset{/underline}.',
padding: 1,
bg: 'red',
fg: 'white',
tags: true
});
var sendgridRadio = blessed.radiobutton({
parent: emailForm,
top: 5,
checked: true,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'SendGrid'
});
var mailgunRadio = blessed.radiobutton({
parent: emailForm,
top: 6,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Mailgun'
});
var mandrillRadio = blessed.radiobutton({
parent: emailForm,
top: 7,
mouse: true,
fg: 'white',
bg: 'blue',
content: 'Mandrill'
});
var emailSubmit = blessed.button({
parent: emailForm,
top: 9,
mouse: true,
shrink: true,
name: 'submit',
content: ' SUBMIT ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
emailSubmit.on('press', function() {
emailForm.submit();
});
var emailCancel = blessed.button({
parent: emailForm,
top: 9,
left: 9,
mouse: true,
shrink: true,
name: 'cancel',
content: ' CANCEL ',
style: {
fg: 'blue',
bg: 'white',
focus: {
fg: 'white',
bg: 'red'
}
}
});
emailCancel.on('press', function() {
home.focus();
home.remove(emailForm);
screen.render();
});
home.on('select', function(child, index) {
switch (index) {
case 0:
home.append(authForm);
authForm.focus();
screen.render();
break;
case 1:
home.append(emailForm);
emailForm.focus();
break;
case 2:
addClusterSupport();
home.append(success);
success.setContent([
'New file {underline}cluster_app.js{/underline} has been created.',
'Your app is now able to use more than 1 CPU by running',
'{underline}node cluster_app.js{/underline}, which in turn',
'spawns multiple instances of {underline}server.js{/underline}'
].join(' '));
success.focus();
screen.render();
break;
default:
process.exit(0);
}
});
screen.render();
function addClusterSupport() {
var fileContents = multiline(function() {
/*
var os = require('os');
var cluster = require('cluster');
cluster.setupMaster({
exec: 'server.js'
});
cluster.on('exit', function(worker) {
console.log('worker ' + worker.id + ' died');
cluster.fork();
});
for (var i = 0; i < os.cpus().length; i++) {
cluster.fork();
}
*/
});
fs.writeFileSync('cluster_app.js', fileContents);
}