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. 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": [ 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/package.json b/package.json index 8213e9ac72..7fe7c38bd8 100644 --- a/package.json +++ b/package.json @@ -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", @@ -71,6 +70,7 @@ "node-slack": "0.0.7", "node-uuid": "^1.4.3", "nodemailer": "~1.3.0", + "object.assign": "^3.0.0", "passport-facebook": "^2.0.0", "passport-google-oauth2": "^0.1.6", "passport-linkedin-oauth2": "^1.2.1", diff --git a/pm2Start.js b/pm2Start.js new file mode 100644 index 0000000000..052f3bdbfa --- /dev/null +++ b/pm2Start.js @@ -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(); + }); +}); diff --git a/public/js/calculator.js b/public/js/calculator.js index 8786b78bea..9cd991a9e7 100644 --- a/public/js/calculator.js +++ b/public/js/calculator.js @@ -154,6 +154,10 @@ $(document).ready(function () { }, 1000); }); + d3.selectAll("#chart").on("click", function () { + change(); + }); + function change() { if ($("body").data("state") === "stacked") { transitionGrouped(); diff --git a/public/js/lib/coursewares/coursewaresJSFramework_0.0.5.js b/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js similarity index 57% rename from public/js/lib/coursewares/coursewaresJSFramework_0.0.5.js rename to public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js index 2183c226d7..ae7237ef5d 100644 --- a/public/js/lib/coursewares/coursewaresJSFramework_0.0.5.js +++ b/public/js/lib/coursewares/coursewaresJSFramework_0.0.6.js @@ -1,3 +1,7 @@ +$(document).ready(function() { + $('#reset-button').on('click', resetEditor); +}); + var widgets = []; var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { lineNumbers: true, @@ -18,7 +22,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 +30,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(" "); @@ -40,12 +44,92 @@ editor.setOption("extraKeys", { }); +/* + Local Storage Update System By Andrew Cay(Resto) + codeStorage: singleton object that contains properties and methods related to + dealing with the localStorage system. + The keys work off of the variable challenge_name to make unique identifiers per bonfire + + Two extra functionalities: + Added anonymous version checking system incase of future updates to the system + Added keyup listener to editor(myCodeMirror) so the last update has been saved to storage +*/ +var codeStorage = { + version: 0.01, + keyVersion:"saveVersion", + keyValue: null,//where the value of the editor is saved + updateWait: 2000,// 2 seconds + updateTimeoutId: null, + eventArray: []//for firing saves +}; +// Returns true if the editor code was saved since last key press (use this if you want to make a "saved" notification somewhere") +codeStorage.hasSaved = function(){ + return ( updateTimeoutId === null ); +}; +codeStorage.onSave = function(func){ + codeStorage.eventArray.push(func); +}; +codeStorage.setSaveKey = function(key){ + codeStorage.keyValue = key + 'Val'; +}; +codeStorage.getEditorValue = function(){ + return ('' + localStorage.getItem(codeStorage.keyValue)); +}; + +codeStorage.isAlive = function() { + var val = this.getEditorValue() + return val !== 'null' && + val !== 'undefined' && + (val && val.length > 0); +} +codeStorage.updateStorage = function(){ + if(typeof(Storage) !== undefined) { + var value = editor.getValue(); + localStorage.setItem(codeStorage.keyValue, value); + } else { + var debugging = false; + if( debugging ){ + console.log('no web storage'); + } + } + codeStorage.updateTimeoutId = null; + codeStorage.eventArray.forEach(function(func){ + func(); + }); +}; +//Update Version +(function(){ + var savedVersion = localStorage.getItem('saveVersion'); + if( savedVersion === null ){ + localStorage.setItem(codeStorage.keyVersion, codeStorage.version);//just write current version + }else{ + if( savedVersion !== codeStorage.version ){ + //Update version + } + } +})(); + + + +///Set everything up one page +/// Update local save when editor has changed +codeStorage.setSaveKey(challenge_Name); +editor.on('keyup', function(){ + window.clearTimeout(codeStorage.updateTimeoutId); + codeStorage.updateTimeoutId = window.setTimeout(codeStorage.updateStorage, codeStorage.updateWait); +}); + var attempts = 0; if (attempts) { attempts = 0; } +var resetEditor = function resetEditor() { + editor.setValue(allSeeds); + codeStorage.updateStorage(); + +}; var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { lineNumbers: false, @@ -61,7 +145,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); @@ -71,20 +158,20 @@ var editorValue; var challengeSeed = challengeSeed || null; var tests = tests || []; + var allSeeds = ''; (function() { challengeSeed.forEach(function(elem) { - allSeeds += elem + '\n'; + allSeeds += elem + '\n'; }); })(); -editorValue = allSeeds; - +editorValue = (codeStorage.isAlive())? codeStorage.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; @@ -106,14 +193,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); @@ -145,16 +232,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); } @@ -176,17 +270,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("
border-radius
using a percentage."
],
"tests": [
- "assert(parseInt($('img').css('border-top-left-radius')) > 48, 'Your image should have a border radius of 50 percent, making it perfectly circular.')"
+ "assert(parseInt($('img').css('border-top-left-radius')) > 48, 'Your image should have a border radius of 50 percent, making it perfectly circular.')",
+ "assert(editor.match(/50%/g), 'Be sure to use a percentage instead of a pixel value.')"
],
"challengeSeed": [
"",
@@ -1005,7 +1006,7 @@
],
"tests": [
"assert((/cat photos/gi).test($('a').text()), 'Your a
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 a
element that links to http://catphotoapp.com.')",
+ "assert(/http:\\/\\/catphotoapp\\.com/gi.test($('a').attr('href')), 'You need an a
element that links to http://catphotoapp.com.')",
"assert(editor.match(/<\\/a>/g) && editor.match(/<\\/a>/g).length === editor.match(/a
element has a closing tag.')"
],
"challengeSeed": [
@@ -1540,7 +1541,7 @@
],
"tests": [
"assert($('input[placeholder]').length > 0, 'Add a placeholder
attribute text input
element.')",
- "assert($('input').attr('placeholder').match(/cat\\s+photo\\s+URL/gi), 'Set the value of your placeholder attribute to \"cat photo URL\".')"
+ "assert($('input') && $('input').attr('placeholder') && $('input').attr('placeholder').match(/cat\\s+photo\\s+URL/gi), 'Set the value of your placeholder attribute to \"cat photo URL\".')"
],
"challengeSeed": [
"",
@@ -1613,7 +1614,7 @@
"For example: <form action=\"/url-where-you-want-to-submit-form-data\"></form>
."
],
"tests": [
- "assert($('form').length > 0, 'Wrap your text input element within a form
element.')",
+ "assert($('form') && $('form').children('input') && $('form').children('input').length > 0, 'Wrap your text input element within a form
element.')",
"assert($('form').attr('action'), 'Your form
element should have an action
attribute.')",
"assert(editor.match(/<\\/form>/g) && editor.match(/
element has a closing tag.')",
"assert(editor.match(/\\/submit-cat-photo/ig), 'Make sure your form
action is set to /submit-cat-photo
.')"
diff --git a/seed/challenges/bootstrap.json b/seed/challenges/bootstrap.json
index eeac669f73..41a7229731 100644
--- a/seed/challenges/bootstrap.json
+++ b/seed/challenges/bootstrap.json
@@ -626,7 +626,7 @@
"The \"row\" class is applied to a div
, and the buttons themselves can be wrapped within it."
],
"tests": [
- "assert($('div.row:has(button)'), 'Your buttons should all be wrapped within the same div
element with the class \"row\".')",
+ "assert($('div.row:has(button)').length > 0, 'Your buttons should all be wrapped within the same div
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 div
element with the class \"col-xs-4\".')",
"assert(editor.match(/<\\/button>/g) && editor.match(/
elements has a closing tag.')",
"assert(editor.match(/<\\/div>/g) && editor.match(//g).length === editor.match(/div elements has a closing tag.')"
@@ -1264,7 +1264,7 @@
"tests": [
"assert($('button[type=\\'submit\\']').hasClass('btn btn-primary'), 'Give the submit button in your form the classes \"btn btn-primary\".')",
"assert($('button[type=\\'submit\\']:has(i.fa.fa-paper-plane)').length > 0, 'Add a <i class=\"fa fa-paper-plane\"></i>
within your submit button
element.')",
- "assert($('input[type=\\'text\\']').hasClass('form-control'), 'Give the text input in your form the class \"form-control\".')",
+ "assert($('input[type=\\'text\\']').hasClass('form-control'), 'Give the text input
in your form the class \"form-control\".')",
"assert(editor.match(/<\\/i>/g) && editor.match(/<\\/i/g).length > 3, 'Make sure each of your i
elements has a closing tag.')"
],
"challengeSeed": [
diff --git a/seed/challenges/get-set-for-free-code-camp.json b/seed/challenges/get-set-for-free-code-camp.json
index 3a9d366102..379bc26f6e 100644
--- a/seed/challenges/get-set-for-free-code-camp.json
+++ b/seed/challenges/get-set-for-free-code-camp.json
@@ -45,18 +45,20 @@
"name": "Waypoint: Join Our Chat Room",
"dashedName": "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": [],
@@ -195,7 +197,7 @@
"Click \"News\" in the upper right hand corner.",
"You'll see a variety of links that have been submitted. Click on the \"Discuss\" button under one of them.",
"You can upvote links. This will push the link up the rankings of hot links.",
- "You an also comment on a link. If someone responds to your comment, you'll get an email notification so you can come back and respond to them.",
+ "You can also comment on a link. If someone responds to your comment, you'll get an email notification so you can come back and respond to them.",
"You can also submit links. You can modify the link's headline and also leave an initial comment about the link.",
"You can view the portfolio pages of any camper who has posted links or comments on Camper News. Just click on their photo.",
"When you submit a link, you'll get a point. You will also get a point each time someone upvotes your link.",
diff --git a/seed/challenges/ziplines.json b/seed/challenges/ziplines.json
index 3779c06ff7..11b1b15f9f 100644
--- a/seed/challenges/ziplines.json
+++ b/seed/challenges/ziplines.json
@@ -56,7 +56,7 @@
"Hint: Here's an example call to Twitch.tv's JSON API: https://api.twitch.tv/kraken/streams/freecodecamp
.",
"Hint: The relevant documentation about this API call is here: https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md#get-streamschannel.",
"Hint: Here's an array of the Twitch.tv usernames of people who regularly stream coding: [\"freecodecamp\", \"storbeck\", \"terakilobyte\", \"habathcx\",\"RobotCaleb\",\"comster404\",\"brunofin\",\"thomasballinger\",\"noobs2ninjas\",\"beohoff\"]
",
- "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() to consume APIs.",
+ "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() 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.
Click here then add your link to your tweet's text"
],
@@ -87,7 +87,7 @@
"Here are the user stories you must enable, and optional bonus user stories:",
"User Story: As a user, I can click a button to show me a new random quote.",
"Bonus User Story: As a user, I can press a button to tweet out a quote.",
- "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() to consume APIs.",
+ "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() 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.
Click here then add your link to your tweet's text"
],
@@ -120,7 +120,7 @@
"Bonus User Story: As a user, I can see an icon depending on the temperature..",
"Bonus User Story: As a user, I see a different background image depending on the temperature (e.g. snowy mountain, hot desert).",
"Bonus User Story: As a user, I can push a button to toggle between Fahrenheit and Celsius.",
- "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() to consume APIs.",
+ "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() 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.
Click here then add your link to your tweet's text"
],
@@ -154,7 +154,7 @@
"User Story: As a user, I can click a link to go directly to the post's discussion page.",
"Bonus User Story: As a user, I can see how many upvotes each story has.",
"Hint: Here's the Camper News Hot Stories API endpoint: http://www.freecodecamp.com/stories/hotStories
.",
- "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() to consume APIs.",
+ "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() 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.
Click here then add your link to your tweet's text"
],
@@ -187,7 +187,7 @@
"Bonus User Story:As a user, I can click a button to see a random Wikipedia entry.",
"Bonus User Story:As a user, when I type in the search box, I can see a dropdown menu with autocomplete options for matching Wikipedia entries.",
"Hint: Here's an entry on using Wikipedia's API: http://www.mediawiki.org/wiki/API:Main_page
.",
- "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() to consume APIs.",
+ "Remember to use RSAP if you get stuck. Try using jQuery's $.getJSON() 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.
Click here then add your link to your tweet's text"
],
diff --git a/seed/field-guides.json b/seed/field-guides.json
index 2de429b569..9d3e906d16 100644
--- a/seed/field-guides.json
+++ b/seed/field-guides.json
@@ -863,7 +863,7 @@
"description": [
"",
" 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 langauge.
",
+ " 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.
",
""
diff --git a/server/boot/challenge.js b/server/boot/challenge.js
index dfa2f98ef2..81219341de 100644
--- a/server/boot/challenge.js
+++ b/server/boot/challenge.js
@@ -31,63 +31,89 @@
*/
var R = require('ramda'),
+ Rx = require('rx'),
+ assign = require('object.assign'),
+ debug = require('debug')('freecc:challenges'),
utils = require('../utils'),
+
+ // this would be so much cleaner with destructering...
+ saveUser = require('../utils/rx').saveUser,
+ observableQueryFromModel = require('../utils/rx').observableQueryFromModel,
+
userMigration = require('../utils/middleware').userMigration,
- MDNlinks = require('../../seed/bonfireMDNlinks');
+ ifNoUserRedirectTo = require('../utils/middleware').ifNoUserRedirectTo,
+ ifNoUserSend = require('../utils/middleware').ifNoUserSend;
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;
-function getMDNlinks(links) {
- // takes in an array of links, which are strings
- var populatedLinks = [];
+var getMDNLinks = utils.getMDNLinks;
- // 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]);
- });
+function updateUserProgress(user, challengeId, completedChallenge) {
+ var index = user.uncompletedChallenges.indexOf(challengeId);
+ if (index > -1) {
+ user.progressTimestamps.push(Date.now());
+ user.uncompletedChallenges.splice(index, 1);
}
- return populatedLinks;
+ user.completedChallenges.push(completedChallenge);
+ return user;
}
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/',
+ 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',
- userMigration,
+ redirectNonUser,
returnNextChallenge
);
- router.get(
- '/challenges/:challengeName',
- userMigration,
- returnIndividualChallenge
- );
+ router.get('/challenges/:challengeName', returnIndividualChallenge);
- router.get('/challenges/', userMigration, returnCurrentChallenge);
- router.post('/completed-challenge/', completedChallenge);
- router.post('/completed-zipline-or-basejump', completedZiplineOrBasejump);
- router.post('/completed-bonfire', completedBonfire);
+ router.get(
+ '/challenges/',
+ redirectNonUser,
+ returnCurrentChallenge
+ );
app.use(router);
function returnNextChallenge(req, res, next) {
- if (!req.user) {
- return res.redirect('../challenges/learn-how-free-code-camp-works');
- }
var completed = req.user.completedChallenges.map(function (elem) {
return elem.id;
});
req.user.uncompletedChallenges = utils.allChallengeIds()
- .filter(function (elem) {
+ .filter(function(elem) {
if (completed.indexOf(elem) === -1) {
return elem;
}
@@ -120,18 +146,17 @@ module.exports = function(app) {
nextChallengeName = R.head(challengeMapWithDashedNames[0].challenges);
}
- req.user.save(function(err) {
- if (err) {
- return next(err);
- }
- return res.redirect('../challenges/' + nextChallengeName);
- });
+ saveUser(req.user)
+ .subscribe(
+ function() {},
+ next,
+ function() {
+ res.redirect('/challenges/' + nextChallengeName);
+ }
+ );
}
function returnCurrentChallenge(req, res, next) {
- if (!req.user) {
- return res.redirect('../challenges/learn-how-free-code-camp-works');
- }
var completed = req.user.completedChallenges.map(function (elem) {
return elem.id;
});
@@ -142,6 +167,7 @@ module.exports = function(app) {
return elem;
}
});
+
if (!req.user.currentChallenge) {
req.user.currentChallenge = {};
req.user.currentChallenge.challengeId = challengeMapWithIds['0'][0];
@@ -153,42 +179,60 @@ module.exports = function(app) {
var nameString = req.user.currentChallenge.dashedName;
- req.user.save(function(err) {
- if (err) {
- return next(err);
- }
- return res.redirect('../challenges/' + nameString);
- });
+ saveUser(req.user)
+ .subscribe(
+ function() {},
+ next,
+ function() {
+ res.redirect('/challenges/' + nameString);
+ }
+ );
}
function returnIndividualChallenge(req, res, next) {
- var dashedName = req.params.challengeName;
+ var origChallengeName = req.params.challengeName;
+ var unDashedName = unDasherize(origChallengeName);
+ var challengeName = challangesRegex.test(unDashedName) ?
+ // remove first word if matches
+ unDashedName.split(' ').slice(1).join(' ') :
+ unDashedName;
+
+ debug('looking for ', challengeName);
Challenge.findOne(
- { where: { dashedName: dashedName }},
+ { where: { name: { like: challengeName, options: 'i' } } },
function(err, challenge) {
if (err) { return next(err); }
// Handle not found
if (!challenge) {
+ debug('did not find challenge for ' + origChallengeName);
req.flash('errors', {
- msg: '404: We couldn\'t find a challenge with that name. ' +
- 'Please double check the name.'
+ msg:
+ '404: We couldn\'t find a challenge with the name `' +
+ origChallengeName +
+ '` Please double check the name.'
});
return res.redirect('/challenges');
}
// Redirect to full name if the user only entered a partial
+ if (dasherize(challenge.name) !== origChallengeName) {
+ debug('redirecting to fullname');
+ return res.redirect('/challenges/' + dasherize(challenge.name));
+ }
+
if (req.user) {
req.user.currentChallenge = {
challengeId: challenge.id,
challengeName: challenge.name,
dashedName: challenge.dashedName,
- challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds).
- map(function (key) {
+ challengeBlock: R.head(R.flatten(Object.keys(challengeMapWithIds)
+ .map(function (key) {
return challengeMapWithIds[key]
.filter(function (elem) {
- return String(elem) === String(challenge.id);
- }).map(function () {
+ return elem === ('' + challenge.id);
+ })
+ .map(function () {
return key;
});
})
@@ -196,261 +240,168 @@ module.exports = function(app) {
};
}
- var challengeType = {
- 0: function() {
- res.render('coursewares/showHTML', {
- title: challenge.name,
- dashedName: dashedName,
- name: challenge.name,
- brief: challenge.description[0],
- details: challenge.description.slice(1),
- tests: challenge.tests,
- challengeSeed: challenge.challengeSeed,
- verb: utils.randomVerb(),
- phrase: utils.randomPhrase(),
- compliment: utils.randomCompliment(),
- challengeId: challenge.id,
- environment: utils.whichEnvironment(),
- challengeType: challenge.challengeType
- });
- },
-
- 1: function() {
- res.render('coursewares/showJS', {
- title: challenge.name,
- dashedName: dashedName,
- name: challenge.name,
- brief: challenge.description[0],
- details: challenge.description.slice(1),
- tests: challenge.tests,
- challengeSeed: challenge.challengeSeed,
- verb: utils.randomVerb(),
- phrase: utils.randomPhrase(),
- compliment: utils.randomCompliment(),
- challengeId: challenge.id,
- challengeType: challenge.challengeType
- });
- },
-
- 2: function() {
- res.render('coursewares/showVideo', {
- title: challenge.name,
- dashedName: dashedName,
- name: challenge.name,
- details: challenge.description,
- tests: challenge.tests,
- video: challenge.challengeSeed[0],
- verb: utils.randomVerb(),
- phrase: utils.randomPhrase(),
- compliment: utils.randomCompliment(),
- challengeId: challenge.id,
- challengeType: challenge.challengeType
- });
- },
-
- 3: function() {
- res.render('coursewares/showZiplineOrBasejump', {
- title: challenge.name,
- dashedName: dashedName,
- name: challenge.name,
- details: challenge.description,
- video: challenge.challengeSeed[0],
- verb: utils.randomVerb(),
- phrase: utils.randomPhrase(),
- compliment: utils.randomCompliment(),
- challengeId: challenge.id,
- challengeType: challenge.challengeType
- });
- },
-
- 4: function() {
- res.render('coursewares/showZiplineOrBasejump', {
- title: challenge.name,
- dashedName: dashedName,
- name: challenge.name,
- details: challenge.description,
- video: challenge.challengeSeed[0],
- verb: utils.randomVerb(),
- phrase: utils.randomPhrase(),
- compliment: utils.randomCompliment(),
- challengeId: challenge.id,
- challengeType: challenge.challengeType
- });
- },
-
- 5: function() {
- res.render('coursewares/showBonfire', {
- completedWith: null,
- title: challenge.name,
- dashedName: dashedName,
- name: challenge.name,
- difficulty: Math.floor(+challenge.difficulty),
- brief: challenge.description.shift(),
- details: challenge.description,
- tests: challenge.tests,
- challengeSeed: challenge.challengeSeed,
- verb: utils.randomVerb(),
- phrase: utils.randomPhrase(),
- compliment: utils.randomCompliment(),
- bonfires: challenge,
- challengeId: challenge.id,
- MDNkeys: challenge.MDNlinks,
- MDNlinks: getMDNlinks(challenge.MDNlinks),
- challengeType: challenge.challengeType
- });
- }
+ var commonLocals = {
+ title: challenge.name,
+ dashedName: origChallengeName,
+ name: challenge.name,
+ details: challenge.description.slice(1),
+ tests: challenge.tests,
+ challengeSeed: challenge.challengeSeed,
+ verb: utils.randomVerb(),
+ phrase: utils.randomPhrase(),
+ compliment: utils.randomCompliment(),
+ challengeId: challenge.id,
+ challengeType: challenge.challengeType,
+ // video challenges
+ video: challenge.challengeSeed[0],
+ // bonfires specific
+ difficulty: Math.floor(+challenge.difficulty),
+ brief: challenge.description.shift(),
+ bonfires: challenge,
+ MDNkeys: challenge.MDNlinks,
+ MDNlinks: getMDNLinks(challenge.MDNlinks),
+ // htmls specific
+ environment: utils.whichEnvironment()
};
- if (req.user) {
- req.user.save(function (err) {
- if (err) {
- return next(err);
+
+ var challengeView = {
+ 0: 'coursewares/showHTML',
+ 1: 'coursewares/showJS',
+ 2: 'coursewares/showVideo',
+ 3: 'coursewares/showZiplineOrBasejump',
+ 4: 'coursewares/showZiplineOrBasejump',
+ 5: 'coursewares/showBonfire'
+ };
+
+ saveUser(req.user)
+ .subscribe(
+ function() {},
+ next,
+ function() {
+ var view = challengeView[challenge.challengeType];
+ res.render(view, commonLocals);
}
- return challengeType[challenge.challengeType]();
- });
- } else {
- return challengeType[challenge.challengeType]();
- }
+ );
});
}
function completedBonfire(req, res, next) {
- var isCompletedWith = req.body.challengeInfo.completedWith || '';
- var isCompletedDate = Math.round(+new Date());
+ debug('compltedBonfire');
+ var completedWith = req.body.challengeInfo.completedWith || false;
var challengeId = req.body.challengeInfo.challengeId;
- var isSolution = req.body.challengeInfo.solution;
- var challengeName = req.body.challengeInfo.challengeName;
- if (isCompletedWith) {
- User.find({
- where: { 'profile.username': isCompletedWith.toLowerCase() },
- limit: 1
- }, function (err, pairedWith) {
- if (err) { return next(err); }
+ var challengeData = {
+ id: challengeId,
+ name: req.body.challengeInfo.challengeName,
+ completedDate: Math.round(+new Date()),
+ solution: req.body.challengeInfo.solution,
+ challengeType: 5
+ };
- var index = req.user.uncompletedChallenges.indexOf(challengeId);
- if (index > -1) {
- req.user.progressTimestamps.push(Date.now() || 0);
- req.user.uncompletedChallenges.splice(index, 1);
- }
- pairedWith = pairedWith.pop();
+ observableQueryFromModel(
+ User,
+ 'findOne',
+ { where: { username: ('' + completedWith).toLowerCase() } }
+ )
+ .doOnNext(function(pairedWith) {
+ debug('paired with ', pairedWith);
if (pairedWith) {
-
- index = pairedWith.uncompletedChallenges.indexOf(challengeId);
- if (index > -1) {
- pairedWith.progressTimestamps.push(Date.now() || 0);
- pairedWith.uncompletedChallenges.splice(index, 1);
-
- }
-
- pairedWith.completedChallenges.push({
- id: challengeId,
- name: challengeName,
- completedWith: req.user.id,
- completedDate: isCompletedDate,
- solution: isSolution,
- challengeType: 5
- });
-
- req.user.completedChallenges.push({
- id: challengeId,
- name: challengeName,
- completedWith: pairedWith.id,
- completedDate: isCompletedDate,
- solution: isSolution,
- challengeType: 5
- });
+ updateUserProgress(
+ pairedWith,
+ challengeId,
+ assign({ completedWith: req.user.id }, challengeData)
+ );
}
- // User said they paired, but pair wasn't found
- req.user.completedChallenges.push({
- id: challengeId,
- name: challengeName,
- completedWith: null,
- completedDate: isCompletedDate,
- solution: isSolution,
- challengeType: 5
- });
-
- req.user.save(function (err, user) {
- if (err) { return next(err); }
-
- if (pairedWith) {
- pairedWith.save(function (err, paired) {
- if (err) {
- return next(err);
- }
- if (user && paired) {
- return res.send(true);
- }
- });
- } else if (user) {
- res.send(true);
+ })
+ .withLatestFrom(
+ Rx.Observable.just(req.user),
+ function(pairedWith, user) {
+ return {
+ user: user,
+ pairedWith: pairedWith
+ };
+ }
+ )
+ // side effects should always be done in do's and taps
+ .doOnNext(function(dats) {
+ updateUserProgress(
+ dats.user,
+ challengeId,
+ dats.pairedWith ?
+ // paired programmer found and adding to data
+ assign({ completedWith: dats.pairedWith.id }, challengeData) :
+ // user said they paired, but pair wasn't found
+ challengeData
+ );
+ })
+ // not iterate users
+ .flatMap(function(dats) {
+ debug('flatmap');
+ return Rx.Observable.from([dats.user, dats.pairedWith]);
+ })
+ // save user
+ .flatMap(function(user) {
+ // save user will do nothing if user is falsey
+ return saveUser(user);
+ })
+ .subscribe(
+ function(user) {
+ debug('onNext');
+ if (user) {
+ debug('user %s saved', user.username);
}
- });
- });
- } else {
- req.user.completedChallenges.push({
- id: challengeId,
- name: challengeName,
- completedWith: null,
- completedDate: isCompletedDate,
- solution: isSolution,
- challengeType: 5
- });
-
- var index = req.user.uncompletedChallenges.indexOf(challengeId);
- if (index > -1) {
-
- req.user.progressTimestamps.push(Date.now() || 0);
- req.user.uncompletedChallenges.splice(index, 1);
- }
-
- req.user.save(function (err) {
- if (err) { return next(err); }
- res.send(true);
- });
- }
+ },
+ next,
+ function() {
+ debug('completed');
+ return res.status(200).send(true);
+ }
+ );
}
function completedChallenge(req, res, next) {
- var isCompletedDate = Math.round(+new Date());
+ var completedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
- req.user.completedChallenges.push({
- id: challengeId,
- completedDate: isCompletedDate,
- name: req.body.challengeInfo.challengeName,
- solution: null,
- githubLink: null,
- verified: true
- });
- var index = req.user.uncompletedChallenges.indexOf(challengeId);
-
- if (index > -1) {
- req.user.progressTimestamps.push(Date.now() || 0);
- req.user.uncompletedChallenges.splice(index, 1);
- }
-
- req.user.save(function (err, user) {
- if (err) {
- return next(err);
+ updateUserProgress(
+ req.user,
+ challengeId,
+ {
+ id: challengeId,
+ completedDate: completedDate,
+ name: req.body.challengeInfo.challengeName,
+ solution: null,
+ githubLink: null,
+ verified: true
}
- if (user) {
- res.sendStatus(200);
- }
- });
+ );
+
+ saveUser(req.user)
+ .subscribe(
+ function() { },
+ next,
+ function() {
+ res.sendStatus(200);
+ }
+ );
}
function completedZiplineOrBasejump(req, res, next) {
- var isCompletedWith = req.body.challengeInfo.completedWith || false;
- var isCompletedDate = Math.round(+new Date());
+ var completedWith = req.body.challengeInfo.completedWith || false;
+ var completedDate = Math.round(+new Date());
var challengeId = req.body.challengeInfo.challengeId;
var solutionLink = req.body.challengeInfo.publicURL;
- var githubLink = req.body.challengeInfo.challengeType === '4'
- ? req.body.challengeInfo.githubURL : true;
+
+ var githubLink = req.body.challengeInfo.challengeType === '4' ?
+ req.body.challengeInfo.githubURL :
+ true;
+
var challengeType = req.body.challengeInfo.challengeType === '4' ?
- 4 : 3;
+ 4 :
+ 3;
+
if (!solutionLink || !githubLink) {
req.flash('errors', {
msg: 'You haven\'t supplied the necessary URLs for us to inspect ' +
@@ -459,92 +410,110 @@ module.exports = function(app) {
return res.sendStatus(403);
}
- if (isCompletedWith) {
- User.find({
- where: { 'profile.username': isCompletedWith.toLowerCase() },
- limit: 1
- }, function (err, pairedWithFromMongo) {
- if (err) { return next(err); }
- var index = req.user.uncompletedChallenges.indexOf(challengeId);
- if (index > -1) {
- req.user.progressTimestamps.push(Date.now() || 0);
- req.user.uncompletedChallenges.splice(index, 1);
+ var challengeData = {
+ id: challengeId,
+ name: req.body.challengeInfo.challengeName,
+ completedDate: completedDate,
+ solution: solutionLink,
+ githubLink: githubLink,
+ challengeType: challengeType,
+ verified: false
+ };
+
+ observableQueryFromModel(
+ User,
+ 'findOne',
+ { where: { username: completedWith.toLowerCase() } }
+ )
+ .doOnNext(function(pairedWith) {
+ if (pairedWith) {
+ updateUserProgress(
+ pairedWith,
+ challengeId,
+ assign({ completedWith: req.user.id }, challengeData)
+ );
}
- var pairedWith = pairedWithFromMongo.pop();
-
- req.user.completedChallenges.push({
- id: challengeId,
- name: req.body.challengeInfo.challengeName,
- completedWith: pairedWith.id,
- completedDate: isCompletedDate,
- solution: solutionLink,
- githubLink: githubLink,
- challengeType: challengeType,
- verified: false
- });
-
- req.user.save(function (err, user) {
- if (err) { return next(err); }
-
- if (req.user.id.toString() === pairedWith.id.toString()) {
- return res.sendStatus(200);
+ })
+ .withLatestFrom(Rx.Observable.just(req.user), function(user, pairedWith) {
+ return {
+ user: user,
+ pairedWith: pairedWith
+ };
+ })
+ .doOnNext(function(dats) {
+ updateUserProgress(
+ dats.user,
+ challengeId,
+ dats.pairedWith ?
+ assign({ completedWith: dats.pairedWith.id }, challengeData) :
+ challengeData
+ );
+ })
+ .flatMap(function(dats) {
+ return Rx.Observable.from([dats.user, dats.pairedWith]);
+ })
+ // save users
+ .flatMap(function(user) {
+ // save user will do nothing if user is falsey
+ return saveUser(user);
+ })
+ .subscribe(
+ function(user) {
+ if (user) {
+ debug('user %s saved', user.username);
}
- index = pairedWith.uncompletedChallenges.indexOf(challengeId);
- if (index > -1) {
- pairedWith.progressTimestamps.push(Date.now() || 0);
- pairedWith.uncompletedChallenges.splice(index, 1);
-
- }
-
- pairedWith.completedChallenges.push({
- id: challengeId,
- name: req.body.challengeInfo.coursewareName,
- completedWith: req.user.id,
- completedDate: isCompletedDate,
- solution: solutionLink,
- githubLink: githubLink,
- challengeType: challengeType,
- verified: false
- });
- pairedWith.save(function (err, paired) {
- if (err) {
- return next(err);
- }
- if (user && paired) {
- return res.sendStatus(200);
- }
- });
- });
- });
- } else {
-
- req.user.completedChallenges.push({
- id: challengeId,
- name: req.body.challengeInfo.challengeName,
- completedWith: null,
- completedDate: isCompletedDate,
- solution: solutionLink,
- githubLink: githubLink,
- challengeType: challengeType,
- verified: false
- });
-
- var index = req.user.uncompletedChallenges.indexOf(challengeId);
- if (index > -1) {
- req.user.progressTimestamps.push(Date.now() || 0);
- req.user.uncompletedChallenges.splice(index, 1);
- }
-
- req.user.save(function (err, user) {
- if (err) {
- return next(err);
+ },
+ next,
+ function() {
+ return res.status(200).send(true);
}
- // NOTE(berks): under certain conditions this will not close
- // the response.
- if (user) {
- return res.sendStatus(200);
- }
- });
+ );
+ }
+
+ 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
+ });
+ });
}
};
diff --git a/server/boot/challengeMap.js b/server/boot/challengeMap.js
deleted file mode 100644
index 3b7adf2489..0000000000
--- a/server/boot/challengeMap.js
+++ /dev/null
@@ -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
- });
- });
- }
-};
diff --git a/server/boot/nonprofits.js b/server/boot/nonprofits.js
index 988ded5f84..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/boot/randomAPIs.js b/server/boot/randomAPIs.js
index 7dd89b1b4f..2046db0ce1 100644
--- a/server/boot/randomAPIs.js
+++ b/server/boot/randomAPIs.js
@@ -2,20 +2,17 @@ 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');
-var slack = new Slack(secrets.slackHook);
module.exports = function(app) {
var router = app.loopback.Router();
var User = app.models.User;
var Challenge = app.models.Challenge;
- var Story = app.models.Store;
+ var Story = app.models.Story;
var FieldGuide = app.models.FieldGuide;
var Nonprofit = app.models.Nonprofit;
@@ -24,8 +21,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 +34,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 +66,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');
@@ -193,15 +75,15 @@ module.exports = function(app) {
users: function(callback) {
User.find(
{
- where: { 'profile.username': { nlike: '' } },
- fields: { 'profile.username': true }
+ where: { username: { nlike: '' } },
+ fields: { username: true }
},
function(err, users) {
if (err) {
debug('User err: ', err);
callback(err);
} else {
- Rx.Observable.from(users)
+ Rx.Observable.from(users, null, null, Rx.Scheduler.default)
.map(function(user) {
return user.username;
})
@@ -224,7 +106,7 @@ module.exports = function(app) {
debug('Challenge err: ', err);
callback(err);
} else {
- Rx.Observable.from(challenges)
+ Rx.Observable.from(challenges, null, null, Rx.Scheduler.default)
.map(function(challenge) {
return challenge.name;
})
@@ -244,7 +126,7 @@ module.exports = function(app) {
debug('Story err: ', err);
callback(err);
} else {
- Rx.Observable.from(stories)
+ Rx.Observable.from(stories, null, null, Rx.Scheduler.default)
.map(function(story) {
return story.link;
})
@@ -265,7 +147,7 @@ module.exports = function(app) {
debug('User err: ', err);
callback(err);
} else {
- Rx.Observable.from(nonprofits)
+ Rx.Observable.from(nonprofits, null, null, Rx.Scheduler.default)
.map(function(nonprofit) {
return nonprofit.name;
})
@@ -285,7 +167,12 @@ module.exports = function(app) {
debug('User err: ', err);
callback(err);
} else {
- Rx.Observable.from(fieldGuides)
+ Rx.Observable.from(
+ fieldGuides,
+ null,
+ null,
+ Rx.Scheduler.default
+ )
.map(function(fieldGuide) {
return fieldGuide.name;
})
@@ -301,7 +188,7 @@ module.exports = function(app) {
if (err) {
return next(err);
}
- setTimeout(function() {
+ process.nextTick(function() {
res.header('Content-Type', 'application/xml');
res.render('resources/sitemap', {
appUrl: appUrl,
@@ -312,19 +199,13 @@ module.exports = function(app) {
nonprofits: results.nonprofits,
fieldGuides: results.fieldGuides
});
- }, 0);
+ });
}
);
}
function chat(req, res) {
- if (req.user && req.user.progressTimestamps.length > 5) {
- res.redirect('http://freecodecamp.slack.com');
- } else {
- res.render('resources/chat', {
- title: 'Watch us code live on Twitch.tv'
- });
- }
+ res.redirect('//gitter.im/FreeCodeCamp/FreeCodeCamp');
}
function bootcampCalculator(req, res) {
@@ -338,6 +219,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,' +
@@ -383,7 +268,7 @@ module.exports = function(app) {
}
function unsubscribe(req, res, next) {
- User.findOne({ email: req.params.email }, function(err, user) {
+ User.findOne({ where: { email: req.params.email } }, function(err, user) {
if (user) {
if (err) {
return next(err);
diff --git a/server/boot/redirects.js b/server/boot/redirects.js
index 8c622cb501..b07ac3b13e 100644
--- a/server/boot/redirects.js
+++ b/server/boot/redirects.js
@@ -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);
};
diff --git a/server/boot/user.js b/server/boot/user.js
index f36fbe56a2..4575a6decf 100644
--- a/server/boot/user.js
+++ b/server/boot/user.js
@@ -4,7 +4,7 @@ var _ = require('lodash'),
crypto = require('crypto'),
nodemailer = require('nodemailer'),
moment = require('moment'),
- //debug = require('debug')('freecc:cntr:userController'),
+ // debug = require('debug')('freecc:cntr:userController'),
secrets = require('../../config/secrets');
diff --git a/server/config.local.js b/server/config.local.js
index c781159696..9b9fc132d6 100644
--- a/server/config.local.js
+++ b/server/config.local.js
@@ -17,4 +17,4 @@ module.exports = {
clientID: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET
}
-};
\ No newline at end of file
+};
diff --git a/server/datasources.local.js b/server/datasources.local.js
index 03a6648e94..8ad9d08abc 100644
--- a/server/datasources.local.js
+++ b/server/datasources.local.js
@@ -3,7 +3,8 @@ var secrets = require('../config/secrets');
module.exports = {
db: {
connector: 'mongodb',
- url: process.env.MONGOHQ_URL
+ connectionTimeout: 5000,
+ url: secrets.db
},
mail: {
connector: 'mail',
diff --git a/server/passport-providers.js b/server/passport-providers.js
index a17d59a068..b60ad8f51e 100644
--- a/server/passport-providers.js
+++ b/server/passport-providers.js
@@ -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,
diff --git a/server/server.js b/server/server.js
index 27ec95b289..637892831e 100755
--- a/server/server.js
+++ b/server/server.js
@@ -1,6 +1,6 @@
require('dotenv').load();
-require('pmx').init();
-// handle uncaught exceptions. Forever will restart process on shutdown
+var pmx = require('pmx');
+pmx.init();
var R = require('ramda'),
assign = require('lodash').assign,
@@ -20,7 +20,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 +41,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 +91,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 +138,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: [
@@ -191,6 +187,8 @@ app.use(
})
);
+// track when connecting to db starts
+var startTime = Date.now();
boot(app, {
appRootDir: __dirname,
dev: process.env.NODE_ENV
@@ -259,6 +257,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
}));
@@ -302,20 +302,46 @@ if (true) { // eslint-disable-line
});
}
-/**
- * Start Express server.
- */
+module.exports = app;
-
-app.listen(app.get('port'), function() {
- console.log(
- 'FreeCodeCamp server listening on port %d in %s mode',
- app.get('port'),
- app.get('env')
- );
-});
+app.start = function () {
+ app.listen(app.get('port'), function() {
+ console.log(
+ 'FreeCodeCamp server listening on port %d in %s mode',
+ app.get('port'),
+ app.get('env')
+ );
+ });
+};
// start the server if `$ node server.js`
+if (require.main === module) {
+ if (process.env.NODE_ENV === 'production') {
+ var timeoutHandler;
+ console.log('waiting for db to connect');
+ var onConnect = function() {
+ console.log('db connected in %s ms', Date.now() - startTime);
+ if (timeoutHandler) {
+ clearTimeout(timeoutHandler);
+ }
+ app.start();
+ };
-module.exports = app;
+ 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(message);
+ }, 5000);
+
+ app.dataSources.db.on('connected', onConnect);
+ } else {
+ app.start();
+ }
+}
diff --git a/server/utils/index.js b/server/utils/index.js
index 6b7b342e56..b49a4579f7 100644
--- a/server/utils/index.js
+++ b/server/utils/index.js
@@ -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;
/**
@@ -59,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 = {};
@@ -216,5 +228,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];
+ });
}
};
diff --git a/server/utils/middleware.js b/server/utils/middleware.js
index 5d2d12741c..dc0219f0a4 100644
--- a/server/utils/middleware.js
+++ b/server/utils/middleware.js
@@ -33,3 +33,21 @@ exports.userMigration = function userMigration(req, res, next) {
);
return next();
};
+
+exports.ifNoUserRedirectTo = function ifNoUserRedirectTo(url) {
+ return function(req, res, next) {
+ if (req.user) {
+ return next();
+ }
+ return res.redirect(url);
+ };
+};
+
+exports.ifNoUserSend = function ifNoUserSend(sendThis) {
+ return function(req, res, next) {
+ if (req.user) {
+ return next();
+ }
+ return res.status(200).send(sendThis);
+ };
+};
diff --git a/server/utils/rx.js b/server/utils/rx.js
new file mode 100644
index 0000000000..5bc8a36382
--- /dev/null
+++ b/server/utils/rx.js
@@ -0,0 +1,30 @@
+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) {
+ if (!user || typeof user.save !== 'function') {
+ debug('no user or save method');
+ observer.onNext();
+ return observer.onCompleted();
+ }
+ user.save(function(err, savedUser) {
+ if (err) {
+ return observer.onError(err);
+ }
+ debug('user saved');
+ observer.onNext(savedUser);
+ observer.onCompleted();
+ });
+ });
+};
+
+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);
+};
diff --git a/server/views/account/show.jade b/server/views/account/show.jade
index 0f7add7096..f1edc4d3c7 100644
--- a/server/views/account/show.jade
+++ b/server/views/account/show.jade
@@ -47,88 +47,32 @@ block content
.background-svg.img-center
.points-on-top
= "[ " + (progressTimestamps.length) + " ]"
-
-
- .row
- .col-xs-12
- if (website1Title && website1Link && website1Image)
- .row
- .col-xs-12.col-md-5
- img.img-center.img-responsive.portfolio-image(src=website1Image, alt="@#{username}'s #{website1Title}")
- .col-xs-12.col-md-7
- h3.text-center.wrappable.flat-top= website1Title
- a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank')
- i.fa.icon-beaker
- | Try it out
- br
- if (website1Title && website1Link && !website1Image)
- .col-xs-12.col-md-12
- h3.text-center.wrappable.flat-top= website1Title
- a.btn.btn-lg.btn-block.btn-info(href=website1Link, target='_blank')
- i.fa.icon-beaker
- | Try it out
- br
- if (website2Title && website2Link && website2Image)
- .row
- .col-xs-12.col-md-5
- img.img-responsive.portfolio-image.img-center(src=website2Image, alt="@#{username}'s #{website2Title}")
- .col-xs-12.col-md-7
- h3.text-center.wrappable.flat-top= website2Title
- a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank')
- i.fa.icon-beaker
- | Try it out
- br
- if (website2Title && website2Link && !website2Image)
- .col-xs-12.col-md-12
- h3.text-center.wrappable.flat-top= website2Title
- a.btn.btn-lg.btn-block.btn-info(href=website2Link, target='_blank')
- i.fa.icon-beaker
- | Try it out
- br
- if (website3Title && website3Link && website3Image)
- .row
- .col-xs-12.col-md-5
- img.img-responsive.portfolio-image.img-center(src=website3Image, alt="@#{username}'s #{website1Title}")
- .col-xs-12.col-md-7
- h3.text-center.wrappable.flat-top= website3Title
- a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank')
- i.fa.icon-beaker
- | Try it out
- if (website3Title && website3Link && !website3Image)
- .col-xs-12.col-md-12
- h3.text-center.wrappable.flat-top= website3Title
- a.btn.btn-lg.btn-block.btn-info(href=website3Link, target='_blank')
- i.fa.icon-beaker
- | Try it out
-
.spacer
.hidden-xs.hidden-sm.col-md-12
#cal-heatmap.d3-centered
script.
$(document).ready(function () {
- setTimeout(function () {
- var cal = new CalHeatMap();
- var calendar = !{JSON.stringify(calender)};
- cal.init({
- itemSelector: "#cal-heatmap",
- domain: "month",
- subDomain: "x_day",
- domainGutter: 10,
- data: calendar,
- cellSize: 15,
- align: 'center',
- cellRadius: 3,
- cellPadding: 2,
- tooltip: true,
- range: 6,
- start: new Date().setDate(new Date().getDate() - 150),
- legendColors: ["#cccccc", "#215f1e"],
- legend: [1, 2, 3],
- label: {
- position: "top"
- }
- });
- }, 300);
+ var cal = new CalHeatMap();
+ var calendar = !{JSON.stringify(calender)};
+ cal.init({
+ itemSelector: "#cal-heatmap",
+ domain: "month",
+ subDomain: "x_day",
+ domainGutter: 10,
+ data: calendar,
+ cellSize: 15,
+ align: 'center',
+ cellRadius: 3,
+ cellPadding: 2,
+ tooltip: true,
+ range: 6,
+ start: new Date().setDate(new Date().getDate() - 150),
+ legendColors: ["#cccccc", "#215f1e"],
+ legend: [1, 2, 3],
+ label: {
+ position: "top"
+ }
+ });
});
.row
.hidden-xs.col-sm-12.text-center
diff --git a/server/views/coursewares/showBonfire.jade b/server/views/coursewares/showBonfire.jade
index d18abe290e..cec1c955e7 100644
--- a/server/views/coursewares/showBonfire.jade
+++ b/server/views/coursewares/showBonfire.jade
@@ -84,19 +84,20 @@ block content
label.negative-10.btn.btn-primary.btn-block#submitButton
i.fa.fa-play
| 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-reset-modal
+ i.fa.fa-refresh
+ | Reset
+ 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
form.code
.form-group.codeMirrorView
@@ -119,7 +120,7 @@ block content
form.code
.form-group.codeMirrorView
textarea#codeEditor(autofocus=true, style='display: none;')
- script(src='/js/lib/coursewares/coursewaresJSFramework_0.0.5.js')
+ script(src='/js/lib/coursewares/coursewaresJSFramework_0.0.6.js')
#complete-courseware-dialog.modal(tabindex='-1')
.modal-dialog.animated.zoomIn.fast-animation
@@ -140,6 +141,15 @@ block content
= phrase
else
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
+ #reset-modal.modal(tabindex='-1')
+ .modal-dialog.animated.fadeInUp.fast-animation
+ .modal-content
+ .modal-header.challenge-list-header Clear your code?
+ a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
+ .modal-body
+ h3 This will restore your code editor to its original state.
+ a.btn.btn-lg.btn-info.btn-block#reset-button(href='#', data-dismiss='modal', aria-hidden='true') Clear my code
+ a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel
include ../partials/challenge-modals
script.
var MDNlinks = !{JSON.stringify(MDNlinks)};
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..e35b9c5e8e 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
diff --git a/server/views/resources/calculator.jade b/server/views/resources/calculator.jade
index 5e25352139..4373653140 100644
--- a/server/views/resources/calculator.jade
+++ b/server/views/resources/calculator.jade
@@ -3,6 +3,7 @@ block content
script(src="../../../js/calculator.js")
.row
.col-xs-12.col-sm-10.col-md-8.col-lg-6.col-sm-offset-1.col-md-offset-2.col-lg-offset-3
+ h1.text-center Coding Bootcamp Cost Calculator
h3.text-center.text-primary#chosen Coming from _______, and making $_______, your true costs will be:
#city-buttons
.spacer
@@ -102,3 +103,13 @@ block content
a(href='https://en.wikipedia.org/wiki/Economic_cost' target='_blank') here
| .
li.large-li Free Code Camp. We don't charge tuition or garnish wages. We're fully online so you don't have to move. We're self-paced so you don't have to quit your job. Thus, your true cost of attending Free Code Camp will be $0.
+ .spacer
+ .row
+ .col-xs-12.col-sm-4.col-md-3
+ img.img-responsive.testimonial-image(src='https://www.evernote.com/l/AHRIBndcq-5GwZVnSy1_D7lskpH4OcJcUKUB/image.png')
+ .col-xs-12.col-sm-8.col-md-9
+ h3 Built by Suzanne Atkinson
+ p.large-p Suzanne is an emergency medicine physician, triathlon coach and web developer from Pittsburgh. You should
+ a(href='https://twitter.com/intent/user?screen_name=SteelCityCoach' target='_blank') follow her on Twitter
+ | .
+ .spacer
diff --git a/setup.js b/setup.js
deleted file mode 100644
index 652e4ce228..0000000000
--- a/setup.js
+++ /dev/null
@@ -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}{/cyan-fg} moves |' +
- ' {cyan-fg}{/cyan-fg} selects | {cyan-fg}{/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);
-}