diff --git a/.eslintrc b/.eslintrc
index 418fe63976..5c50e37af5 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -15,7 +15,8 @@
"window": true,
"$": true,
"ga": true,
- "jQuery": true
+ "jQuery": true,
+ "router": true
},
"rules": {
"no-comma-dangle": 2,
diff --git a/Procfile b/Procfile
index 6e36002829..7769836542 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1 @@
-web: ./node_modules/.bin/forever -m 5 app.js
\ No newline at end of file
+web: ./node_modules/.bin/forever -m 5 server.js
\ No newline at end of file
diff --git a/README.md b/README.md
index 092c5b76e9..7b4fba7d05 100644
--- a/README.md
+++ b/README.md
@@ -126,7 +126,7 @@ Project Structure
| **views/partials**/footer.jade | Footer partial template. |
| **views**/layout.jade | Base template. |
| **views**/home.jade | Home page template. |
-| app.js | Main application file. |
+| server.js | Main application file. |
List of Packages
@@ -155,7 +155,7 @@ List of Packages
| github-api | GitHub API library. |
| jade | Template engine for Express. |
| less | LESS compiler. Used implicitly by connect-assets. |
-| helmet | Restricts Cross site requests. You can modify its settings in app.js |
+| helmet | Restricts Cross site requests. You can modify its settings in server.js |
| mongoose | MongoDB ODM. |
| nodemailer | Node.js library for sending emails. |
| passport | Simple and elegant authentication library for node.js |
diff --git a/controllers/challengeMap.js b/controllers/challengeMap.js
deleted file mode 100644
index 210586f190..0000000000
--- a/controllers/challengeMap.js
+++ /dev/null
@@ -1,53 +0,0 @@
-var R = require('ramda'),
- debug = require('debug')('freecc:cntr:challengeMap'),
- User = require('../models/User'),
- resources = require('./resources');
-
-var challengeTypes = {
- 'HTML_CSS_JQ': 0,
- 'JAVASCRIPT': 1,
- 'VIDEO': 2,
- 'ZIPLINE': 3,
- 'BASEJUMP': 4,
- 'BONFIRE': 5
-};
-
-module.exports = {
- challengeMap: 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;
- });
- var challengeList = resources.getChallengeMapForDisplay(completedChallengeList);
-
- 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/controllers/resources.js b/controllers/resources.js
deleted file mode 100644
index e80473d109..0000000000
--- a/controllers/resources.js
+++ /dev/null
@@ -1,641 +0,0 @@
-var async = require('async'),
- path = require('path'),
- moment = require('moment'),
- Twit = require('twit'),
- debug = require('debug')('freecc:cntr:resources'),
- cheerio = require('cheerio'),
- request = require('request'),
- R = require('ramda'),
- _ = require('lodash'),
- fs = require('fs'),
-
-
- constantStrings = require('./constantStrings.json'),
- User = require('../models/User'),
- Challenge = require('./../models/Challenge'),
- Story = require('./../models/Story'),
- FieldGuide = require('./../models/FieldGuide'),
- Nonprofit = require('./../models/Nonprofit'),
- Comment = require('./../models/Comment'),
- resources = require('./resources.json'),
- secrets = require('./../config/secrets'),
- nonprofits = require('../seed_data/nonprofits.json'),
- fieldGuides = require('../seed_data/field-guides.json'),
- Slack = require('node-slack'),
- slack = new Slack(secrets.slackHook);
-
-/**
- * Cached values
- */
-var allFieldGuideIds, allFieldGuideNames, allNonprofitNames,
- challengeMap, challengeMapForDisplay, challengeMapWithIds,
- challengeMapWithNames, allChallengeIds, allChallenges;
-
-/**
- * GET /
- * Resources.
- */
-
-Array.zip = function(left, right, combinerFunction) {
- var counter,
- results = [];
-
- for (counter = 0; counter < Math.min(left.length, right.length); counter++) {
- results.push(combinerFunction(left[counter], right[counter]));
- }
-
- return results;
-};
-
-(function() {
- if (!challengeMap) {
- var localChallengeMap = {};
- var files = fs.readdirSync(
- path.join(__dirname, '/../seed_data/challenges')
- );
- var keyCounter = 0;
- files = files.map(function (file) {
- return require(
- path.join(__dirname, '/../seed_data/challenges/' + file)
- );
- });
- files = files.sort(function (a, b) {
- return a.order - b.order;
- });
- files.forEach(function (file) {
- localChallengeMap[keyCounter++] = file;
- });
- challengeMap = _.cloneDeep(localChallengeMap);
- }
-})();
-
-
-module.exports = {
-
- getChallengeMapForDisplay: function(completedChallengeList) {
- if (!challengeMapForDisplay) {
- challengeMapForDisplay = {};
- Object.keys(challengeMap).forEach(function(key) {
- //TODO get ratio of completed to uncompleted for each section
- //challengeMap[key].challenges.forEach(function(challenge){
- //
- //}
- challengeMapForDisplay[key] = {
- name: challengeMap[key].name,
- dashedName: challengeMap[key].name.replace(/\s/g, '-'),
- challenges: challengeMap[key].challenges,
- completedCount: challengeMap[key].challenges //ToDo count number of uncompleted challenges
- }
- });
- }
- return challengeMapForDisplay;
- },
-
- getChallengeMapWithIds: function() {
- if (!challengeMapWithIds) {
- challengeMapWithIds = {};
- Object.keys(challengeMap).forEach(function (key) {
- var onlyIds = challengeMap[key].challenges.map(function (elem) {
- return elem._id;
- });
- challengeMapWithIds[key] = onlyIds;
- });
- }
- return challengeMapWithIds;
- },
-
- allChallengeIds: function() {
-
- if (!allChallengeIds) {
- allChallengeIds = [];
- Object.keys(this.getChallengeMapWithIds()).forEach(function(key) {
- allChallengeIds.push(challengeMapWithIds[key]);
- });
- allChallengeIds = R.flatten(allChallengeIds);
- }
- return allChallengeIds;
- },
-
- allChallenges: function() {
- if (!allChallenges) {
- allChallenges = [];
- Object.keys(this.getChallengeMapWithNames()).forEach(function(key) {
- allChallenges.push(challengeMap[key].challenges);
- });
- allChallenges = R.flatten(allChallenges);
- }
- return allChallenges;
- },
-
- getChallengeMapWithNames: function() {
- if (!challengeMapWithNames) {
- challengeMapWithNames = {};
- Object.keys(challengeMap).
- forEach(function (key) {
- var onlyNames = challengeMap[key].challenges.map(function (elem) {
- return elem.name;
- });
- challengeMapWithNames[key] = onlyNames;
- });
- }
- return challengeMapWithNames;
- },
-
- sitemap: function sitemap(req, res, next) {
- var appUrl = 'http://www.freecodecamp.com';
- var now = moment(new Date()).format('YYYY-MM-DD');
-
-
- async.parallel({
- users: function(callback) {
- User.aggregate()
- .group({_id: 1, usernames: { $addToSet: '$profile.username'}})
- .match({'profile.username': { $ne: ''}})
- .exec(function(err, users) {
- if (err) {
- debug('User err: ', err);
- callback(err);
- } else {
- callback(null, users[0].usernames);
- }
- });
- },
-
- challenges: function (callback) {
- Challenge.aggregate()
- .group({_id: 1, names: { $addToSet: '$name'}})
- .exec(function (err, challenges) {
- if (err) {
- debug('Challenge err: ', err);
- callback(err);
- } else {
- callback(null, challenges[0].names);
- }
- });
- },
- stories: function (callback) {
- Story.aggregate()
- .group({_id: 1, links: {$addToSet: '$link'}})
- .exec(function (err, stories) {
- if (err) {
- debug('Story err: ', err);
- callback(err);
- } else {
- callback(null, stories[0].links);
- }
- });
- },
- nonprofits: function (callback) {
- Nonprofit.aggregate()
- .group({_id: 1, names: { $addToSet: '$name'}})
- .exec(function (err, nonprofits) {
- if (err) {
- debug('User err: ', err);
- callback(err);
- } else {
- callback(null, nonprofits[0].names);
- }
- });
- },
- fieldGuides: function (callback) {
- FieldGuide.aggregate()
- .group({_id: 1, names: { $addToSet: '$name'}})
- .exec(function (err, fieldGuides) {
- if (err) {
- debug('User err: ', err);
- callback(err);
- } else {
- callback(null, fieldGuides[0].names);
- }
- });
- }
- }, function (err, results) {
- if (err) {
- return next(err);
- } else {
- setTimeout(function() {
- res.header('Content-Type', 'application/xml');
- res.render('resources/sitemap', {
- appUrl: appUrl,
- now: now,
- users: results.users,
- challenges: results.challenges,
- stories: results.stories,
- nonprofits: results.nonprofits,
- fieldGuides: results.fieldGuides
- });
- }, 0);
- }
- }
- );
- },
-
- chat: function chat(req, res) {
- if (req.user && req.user.progressTimestamps.length > 5) {
- res.redirect('http://freecode.slack.com');
- } else {
- res.render('resources/chat', {
- title: 'Watch us code live on Twitch.tv'
- });
- }
- },
-
- jobsForm: function jobsForm(req, res) {
- res.render('resources/jobs-form', {
- title: 'Employer Partnership Form for Job Postings, Recruitment and Corporate Sponsorships'
- });
- },
-
- catPhotoSubmit: function catPhotoSubmit(req, res) {
- res.send(
- 'Success! You have submitted your cat photo. Return to your website ' +
- 'by typing any letter into your code editor.'
- );
- },
-
- nonprofits: function nonprofits(req, res) {
- res.render('resources/nonprofits', {
- title: 'A guide to our Nonprofit Projects'
- });
- },
-
- nonprofitsForm: function nonprofitsForm(req, res) {
- res.render('resources/nonprofits-form', {
- title: 'Nonprofit Projects Proposal Form'
- });
- },
-
- agileProjectManagers: function agileProjectManagers(req, res) {
- res.render('resources/pmi-acp-agile-project-managers', {
- title: 'Get Agile Project Management Experience for the PMI-ACP'
- });
- },
-
- agileProjectManagersForm: function agileProjectManagersForm(req, res) {
- res.render('resources/pmi-acp-agile-project-managers-form', {
- title: 'Agile Project Management Program Application Form'
- });
- },
-
- twitch: function twitch(req, res) {
- res.render('resources/twitch', {
- title: "Enter Free Code Camp's Chat Rooms"
- });
- },
-
- unsubscribe: function unsubscribe(req, res, next) {
- User.findOne({ email: req.params.email }, function(err, user) {
- if (user) {
- if (err) {
- return next(err);
- }
- user.sendMonthlyEmail = false;
- user.save(function () {
- if (err) {
- return next(err);
- }
- res.redirect('/unsubscribed');
- });
- } else {
- res.redirect('/unsubscribed');
- }
- });
- },
-
- unsubscribed: function unsubscribed(req, res) {
- res.render('resources/unsubscribed', {
- title: 'You have been unsubscribed'
- });
- },
-
- githubCalls: function(req, res, next) {
- var githubHeaders = {
- headers: {
- 'User-Agent': constantStrings.gitHubUserAgent
- },
- port: 80
- };
- request(
- [
- 'https://api.github.com/repos/freecodecamp/',
- 'freecodecamp/pulls?client_id=',
- secrets.github.clientID,
- '&client_secret=',
- secrets.github.clientSecret
- ].join(''),
- githubHeaders,
- function(err, status1, pulls) {
- if (err) { return next(err); }
- pulls = pulls ?
- Object.keys(JSON.parse(pulls)).length :
- "Can't connect to github";
-
- request(
- [
- 'https://api.github.com/repos/freecodecamp/',
- 'freecodecamp/issues?client_id=',
- secrets.github.clientID,
- '&client_secret=',
- secrets.github.clientSecret
- ].join(''),
- githubHeaders,
- function (err, status2, issues) {
- if (err) { return next(err); }
- issues = ((pulls === parseInt(pulls, 10)) && issues) ?
- Object.keys(JSON.parse(issues)).length - pulls :
- "Can't connect to GitHub";
- res.send({
- issues: issues,
- pulls: pulls
- });
- }
- );
- }
- );
- },
-
- trelloCalls: function(req, res, next) {
- request(
- 'https://trello.com/1/boards/BA3xVpz9/cards?key=' +
- secrets.trello.key,
- function(err, status, trello) {
- if (err) { return next(err); }
- trello = (status && status.statusCode === 200) ?
- (JSON.parse(trello)) :
- "Can't connect to to Trello";
-
- res.end(JSON.stringify(trello));
- });
- },
-
- bloggerCalls: function(req, res, next) {
- request(
- 'https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/' +
- 'posts?key=' +
- secrets.blogger.key,
- function (err, status, blog) {
- if (err) { return next(err); }
-
- blog = (status && status.statusCode === 200) ?
- JSON.parse(blog) :
- "Can't connect to Blogger";
- res.end(JSON.stringify(blog));
- }
- );
- },
-
- about: function(req, res, next) {
- if (req.user) {
- if (
- !req.user.profile.picture ||
- req.user.profile.picture.indexOf('apple-touch-icon-180x180.png') !== -1
- ) {
- req.user.profile.picture =
- 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png';
- // TODO(berks): unhandled callback
- req.user.save();
- }
- }
- var date1 = new Date('10/15/2014');
- var date2 = new Date();
-
- var timeDiff = Math.abs(date2.getTime() - date1.getTime());
- var daysRunning = Math.ceil(timeDiff / (1000 * 3600 * 24));
- var announcements = resources.announcements;
- function numberWithCommas(x) {
- return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
- }
- User.count({}, function (err, c3) {
- if (err) {
- debug('User err: ', err);
- return next(err);
- }
-
- res.render('resources/learn-to-code', {
- title: 'About Free Code Camp',
- daysRunning: daysRunning,
- c3: numberWithCommas(c3),
- announcements: announcements
- });
- });
- },
-
- randomPhrase: function() {
- return resources.phrases[
- Math.floor(Math.random() * resources.phrases.length)
- ];
- },
-
- randomVerb: function() {
- return resources.verbs[
- Math.floor(Math.random() * resources.verbs.length)
- ];
- },
-
- randomCompliment: function() {
- return resources.compliments[
- Math.floor(Math.random() * resources.compliments.length)
- ];
- },
-
- allFieldGuideIds: function() {
- if (allFieldGuideIds) {
- return allFieldGuideIds;
- } else {
- allFieldGuideIds = fieldGuides.map(function (elem) {
- return elem._id;
- });
- return allFieldGuideIds;
- }
- },
-
- allFieldGuideNamesAndIds: function() {
- if (allFieldGuideNames) {
- return allFieldGuideNames;
- } else {
- allFieldGuideNames = fieldGuides.map(function (elem) {
- return {
- name: elem.name,
- dashedName: elem.dashedName,
- id: elem._id };
- });
- return allFieldGuideNames;
- }
- },
-
- allNonprofitNames: function() {
- if (allNonprofitNames) {
- return allNonprofitNames;
- } else {
- allNonprofitNames = nonprofits.map(function (elem) {
- return { name: elem.name };
- });
- return allNonprofitNames;
- }
- },
-
- whichEnvironment: function() {
- return process.env.NODE_ENV;
- },
-
- getURLTitle: function(url, callback) {
- (function () {
- var result = {title: '', image: '', url: '', description: ''};
- request(url, function (error, response, body) {
- if (!error && response.statusCode === 200) {
- var $ = cheerio.load(body);
- var metaDescription = $("meta[name='description']");
- var metaImage = $("meta[property='og:image']");
- var urlImage = metaImage.attr('content') ?
- metaImage.attr('content') :
- '';
-
- var metaTitle = $('title');
- var description = metaDescription.attr('content') ?
- metaDescription.attr('content') :
- '';
-
- result.title = metaTitle.text().length < 90 ?
- metaTitle.text() :
- metaTitle.text().slice(0, 87) + '...';
-
- result.image = urlImage;
- result.description = description;
- callback(null, result);
- } else {
- callback(new Error('failed'));
- }
- });
- })();
- },
-
- updateUserStoryPictures: function(userId, picture, username, cb) {
-
- var counter = 0,
- foundStories,
- foundComments;
-
- Story.find({'author.userId': userId}, function(err, stories) {
- if (err) {
- return cb(err);
- }
- foundStories = stories;
- counter++;
- saveStoriesAndComments();
- });
- Comment.find({'author.userId': userId}, function(err, comments) {
- if (err) {
- return cb(err);
- }
- foundComments = comments;
- counter++;
- saveStoriesAndComments();
- });
-
- function saveStoriesAndComments() {
- if (counter !== 2) {
- return;
- }
- var tasks = [];
- R.forEach(function(comment) {
- comment.author.picture = picture;
- comment.author.username = username;
- comment.markModified('author');
- tasks.push(function(cb) {
- comment.save(cb);
- });
- }, foundComments);
-
- R.forEach(function(story) {
- story.author.picture = picture;
- story.author.username = username;
- story.markModified('author');
- tasks.push(function(cb) {
- story.save(cb);
- });
- }, foundStories);
- async.parallel(tasks, function(err) {
- if (err) {
- return cb(err);
- }
- cb();
- });
- }
- },
- codepenResources: {
- twitter: function(req, res, next) {
- // sends out random tweets about javascript
- var T = new Twit({
- 'consumer_key': secrets.twitter.consumerKey,
- 'consumer_secret': secrets.twitter.consumerSecret,
- 'access_token': secrets.twitter.token,
- 'access_token_secret': secrets.twitter.tokenSecret
- });
-
- var screenName;
- if (req.params.screenName) {
- screenName = req.params.screenName;
- } else {
- screenName = 'freecodecamp';
- }
-
- T.get(
- 'statuses/user_timeline',
- {
- 'screen_name': screenName,
- count: 10
- },
- function(err, data) {
- if (err) { return next(err); }
- return res.json(data);
- }
- );
- },
- twitterFCCStream: function() {
- // sends out a tweet stream from FCC's account
- },
- twitch: function() {
- // exports information from the twitch account
- },
- slack: function() {
-
- }
- },
-
- getHelp: function(req, res, next) {
- var userName = req.user.profile.username;
- var code = req.body.payload.code ? '\n```\n' +
- req.body.payload.code + '\n```\n'
- : '';
- var challenge = req.body.payload.challenge;
-
- slack.send({
- text: "*@" + userName + "* wants help with " + challenge + ". " +
- code + "Hey, *@" + userName + "*, if no one helps you right " +
- "away, try typing out your problem in detail to me. Like this: " +
- "http://en.wikipedia.org/wiki/Rubber_duck_debugging",
- channel: '#help',
- username: "Debuggy the Rubber Duck",
- icon_url: "https://pbs.twimg.com/profile_images/3609875545/569237541c920fa78d78902069615caf.jpeg"
- });
- return res.sendStatus(200);
- },
-
- getPair: function(req, res, next) {
- var userName = req.user.profile.username;
- var challenge = req.body.payload.challenge;
- slack.send({
- text: "Anyone want to pair with *@" + userName + "* on " + challenge +
- "?\nMake sure you install Screen Hero here: " +
- "http://freecodecamp.com/field-guide/how-do-i-install-screenhero\n" +
- "Then start your pair program session with *@" + userName +
- "* by typing \"/hero @" + userName + "\" into Slack.\n And *@"+ userName +
- "*, be sure to launch Screen Hero, then keep coding. " +
- "Another camper may pair with you soon.",
- channel: '#letspair',
- username: "Companion Cube",
- icon_url: "https://lh3.googleusercontent.com/-f6xDPDV2rPE/AAAAAAAAAAI/AAAAAAAAAAA/mdlESXQu11Q/photo.jpg"
- });
- return res.sendStatus(200);
- }
-};
diff --git a/gulpfile.js b/gulpfile.js
index b8568baefc..7a657a2a49 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -9,7 +9,7 @@ var gulp = require('gulp'),
eslint = require('gulp-eslint');
var paths = {
- server: './app.js',
+ server: './server.js',
serverIgnore: []
};
diff --git a/controllers/challenge.js b/server/boot/challenge.js
similarity index 98%
rename from controllers/challenge.js
rename to server/boot/challenge.js
index 12a3c3655e..3b546826e9 100644
--- a/controllers/challenge.js
+++ b/server/boot/challenge.js
@@ -31,10 +31,10 @@
*/
var R = require('ramda'),
- Challenge = require('./../models/Challenge'),
- User = require('./../models/User'),
- resources = require('./resources'),
- MDNlinks = require('./../seed_data/bonfireMDNlinks');
+ Challenge = require('./../../models/Challenge'),
+ User = require('./../../models/User'),
+ resources = require('./../resources/resources'),
+ MDNlinks = require('./../../seed_data/bonfireMDNlinks');
var challengeMapWithNames = resources.getChallengeMapWithNames();
var challengeMapWithIds = resources.getChallengeMapWithIds();
diff --git a/server/boot/challengeMap.js b/server/boot/challengeMap.js
new file mode 100644
index 0000000000..c61c6785a0
--- /dev/null
+++ b/server/boot/challengeMap.js
@@ -0,0 +1,63 @@
+var R = require('ramda'),
+ debug = require('debug')('freecc:cntr:challengeMap'), //eslint-disable-line
+ User = require('../../models/User'),
+ resources = require('./../resources/resources'),
+ middleware = require('../resources/middleware'),
+ express = require('express'),
+ router = express.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');
+});
+
+
+
+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;
+ });
+ var challengeList = resources.
+ getChallengeMapForDisplay(completedChallengeList);
+
+ 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
+ });
+ });
+}
+
+module.exports = router;
diff --git a/controllers/fieldGuide.js b/server/boot/fieldGuide.js
similarity index 96%
rename from controllers/fieldGuide.js
rename to server/boot/fieldGuide.js
index 5bbd7235b9..c74e993dbd 100644
--- a/controllers/fieldGuide.js
+++ b/server/boot/fieldGuide.js
@@ -1,6 +1,6 @@
var R = require('ramda'),
- FieldGuide = require('./../models/FieldGuide'),
- resources = require('./resources'),
+ FieldGuide = require('./../../models/FieldGuide'),
+ resources = require('./../resources/resources'),
debug = require('debug')('freecc:fieldguides');
exports.returnIndividualFieldGuide = function(req, res, next) {
diff --git a/controllers/home.js b/server/boot/home.js
similarity index 73%
rename from controllers/home.js
rename to server/boot/home.js
index 86b4a784c3..44efd6c958 100644
--- a/controllers/home.js
+++ b/server/boot/home.js
@@ -1,7 +1,13 @@
var message =
'Learn to Code JavaScript and get a Coding Job by Helping Nonprofits';
+var express = require('express');
+var router = express.Router();
-exports.index = function(req, res, next) {
+router.get('/', index);
+
+
+
+function index(req, res, next) {
if (req.user && !req.user.profile.picture) {
req.user.profile.picture =
'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png';
@@ -13,4 +19,6 @@ exports.index = function(req, res, next) {
} else {
res.render('home', { title: message });
}
-};
+}
+
+module.exports = router;
diff --git a/controllers/jobs.js b/server/boot/jobs.js
similarity index 75%
rename from controllers/jobs.js
rename to server/boot/jobs.js
index 5d0cd348cf..1802f4cdf3 100644
--- a/controllers/jobs.js
+++ b/server/boot/jobs.js
@@ -1,6 +1,6 @@
var moment = require('moment'),
- Job = require('./../models/Job'),
- resources = require('./resources');
+ Job = require('./../../models/Job'),
+ resources = require('./../resources/resources');
exports.jobsDirectory = function(req, res, next) {
Job.find({}, function(err, jobs) {
diff --git a/controllers/nonprofits.js b/server/boot/nonprofits.js
similarity index 97%
rename from controllers/nonprofits.js
rename to server/boot/nonprofits.js
index d0a3aa0798..176c50cfff 100644
--- a/controllers/nonprofits.js
+++ b/server/boot/nonprofits.js
@@ -1,6 +1,6 @@
var moment = require('moment'),
- Nonprofit = require('./../models/Nonprofit'),
- resources = require('./resources');
+ Nonprofit = require('./../../models/Nonprofit'),
+ resources = require('./../resources/resources');
exports.nonprofitsDirectory = function(req, res, next) {
Nonprofit.find({estimatedHours: { $gt: 0 } }, function(err, nonprofits) {
diff --git a/server/boot/redirects.js b/server/boot/redirects.js
new file mode 100644
index 0000000000..674ae9750b
--- /dev/null
+++ b/server/boot/redirects.js
@@ -0,0 +1,45 @@
+var express = require('express');
+var router = express.Router();
+
+
+router.get('/nonprofit-project-instructions', function(req, res) {
+ res.redirect(301, '/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work');
+});
+
+router.get('/agile', function(req, res) {
+ res.redirect(301, '/pmi-acp-agile-project-managers');
+});
+
+router.get('/live-pair-programming', function(req, res) {
+ res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv');
+});
+
+router.get('/install-screenhero', function(req, res) {
+ res.redirect(301, '/field-guide/install-screenhero');
+});
+
+router.get('/guide-to-our-nonprofit-projects', function(req, res) {
+ res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects');
+});
+
+router.get('/chromebook', function(req, res) {
+ res.redirect(301, '/field-guide/chromebook');
+});
+
+router.get('/deploy-a-website', function(req, res) {
+ res.redirect(301, '/field-guide/deploy-a-website');
+});
+
+router.get('/gmail-shortcuts', function(req, res) {
+ res.redirect(301, '/field-guide/gmail-shortcuts');
+});
+
+router.get('/nodeschool-challenges', function(req, res) {
+ res.redirect(301, '/field-guide/nodeschool-challenges');
+});
+
+router.get('/privacy', function(req, res) {
+ res.redirect(301, '/field-guide/what-is-the-free-code-camp-privacy-policy?');
+});
+
+module.exports = router;
diff --git a/controllers/story.js b/server/boot/story.js
similarity index 61%
rename from controllers/story.js
rename to server/boot/story.js
index a828f70d05..0902c756ab 100755
--- a/controllers/story.js
+++ b/server/boot/story.js
@@ -1,16 +1,33 @@
/* eslint-disable no-catch-shadow, no-unused-vars */
var R = require('ramda'),
debug = require('debug')('freecc:cntr:story'),
- Story = require('./../models/Story'),
- Comment = require('./../models/Comment'),
- User = require('./../models/User'),
+ Story = require('./../../models/Story'),
+ Comment = require('./../../models/Comment'),
+ User = require('./../../models/User'),
moment = require('moment'),
- resources = require('./resources'),
+ resources = require('./../resources/resources'),
mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient,
- secrets = require('../config/secrets'),
+ secrets = require('../../config/secrets'),
nodemailer = require('nodemailer'),
- sanitizeHtml = require('sanitize-html');
+ sanitizeHtml = require('sanitize-html'),
+ express = require('express'),
+ router = express.Router();
+
+router.get('/stories/hotStories', hotJSON);
+router.get('/stories/recentStories', recentJSON);
+router.get('/stories/comments/:id', comments);
+router.post('/stories/comment/', commentSubmit);
+router.post('/stories/comment/:id/comment', commentOnCommentSubmit);
+router.put('/stories/comment/:id/edit', commentEdit);
+router.get('/stories/submit', submitNew);
+router.get('/stories/submit/new-story', preSubmit);
+router.post('/stories/preliminary', newStory);
+router.post('/stories/', storySubmission);
+router.get('/news/', hot);
+router.post('/stories/search', getStories);
+router.get('/news/:storyName', returnIndividualStory);
+router.post('/stories/upvote/', upvote);
function hotRank(timeValue, rank) {
/*
@@ -27,7 +44,7 @@ function hotRank(timeValue, rank) {
}
-exports.hotJSON = function(req, res, next) {
+function hotJSON(req, res, next) {
var story = Story.find({}).sort({'timePosted': -1}).limit(1000);
story.exec(function(err, stories) {
if (err) {
@@ -45,9 +62,9 @@ exports.hotJSON = function(req, res, next) {
}).slice(0, sliceVal));
});
-};
+}
-exports.recentJSON = function(req, res, next) {
+function recentJSON(req, res, next) {
var story = Story.find({}).sort({'timePosted': -1}).limit(100);
story.exec(function(err, stories) {
if (err) {
@@ -55,37 +72,37 @@ exports.recentJSON = function(req, res, next) {
}
return res.json(stories);
});
-};
+}
-exports.hot = function(req, res) {
+function hot(req, res) {
return res.render('stories/index', {
title: 'Hot stories currently trending on Camper News',
page: 'hot'
});
-};
+}
-exports.submitNew = function(req, res) {
+function submitNew(req, res) {
return res.render('stories/index', {
title: 'Submit a new story to Camper News',
page: 'submit'
});
-};
+}
-exports.search = function(req, res) {
+function search(req, res) {
return res.render('stories/index', {
title: 'Search the archives of Camper News',
page: 'search'
});
-};
+}
-exports.recent = function(req, res) {
+function recent(req, res) {
return res.render('stories/index', {
title: 'Recently submitted stories on Camper News',
page: 'recent'
});
-};
+}
-exports.preSubmit = function(req, res) {
+function preSubmit(req, res) {
var data = req.query;
var cleanData = sanitizeHtml(data.url, {
@@ -113,10 +130,10 @@ exports.preSubmit = function(req, res) {
storyImage: image,
storyMetaDescription: description
});
-};
+}
-exports.returnIndividualStory = function(req, res, next) {
+function returnIndividualStory(req, res, next) {
var dashedName = req.params.storyName;
var storyName = dashedName.replace(/\-/g, ' ').trim();
@@ -130,7 +147,7 @@ exports.returnIndividualStory = function(req, res, next) {
if (story.length < 1) {
req.flash('errors', {
msg: "404: We couldn't find a story with that name. " +
- 'Please double check the name.'
+ 'Please double check the name.'
});
return res.redirect('/news/');
@@ -173,9 +190,9 @@ exports.returnIndividualStory = function(req, res, next) {
hasUserVoted: userVoted
});
});
-};
+}
-exports.getStories = function(req, res, next) {
+function getStories(req, res, next) {
MongoClient.connect(secrets.db, function(err, database) {
if (err) {
return next(err);
@@ -215,9 +232,9 @@ exports.getStories = function(req, res, next) {
return res.sendStatus(404);
});
});
-};
+}
-exports.upvote = function(req, res, next) {
+function upvote(req, res, next) {
var data = req.body.data;
Story.find({'_id': data.id}, function(err, story) {
if (err) {
@@ -252,9 +269,9 @@ exports.upvote = function(req, res, next) {
});
return res.send(story);
});
-};
+}
-exports.comments = function(req, res, next) {
+function comments(req, res, next) {
var data = req.params.id;
Comment.find({'_id': data}, function(err, comment) {
if (err) {
@@ -263,9 +280,9 @@ exports.comments = function(req, res, next) {
comment = comment.pop();
return res.send(comment);
});
-};
+}
-exports.newStory = function(req, res, next) {
+function newStory(req, res, next) {
if (!req.user) {
return next(new Error('Must be logged in'));
}
@@ -322,9 +339,9 @@ exports.newStory = function(req, res, next) {
});
}
}
-};
+}
-exports.storySubmission = function(req, res, next) {
+function storySubmission(req, res, next) {
var data = req.body.data;
if (!req.user) {
return next(new Error('Not authorized'));
@@ -393,189 +410,190 @@ exports.storySubmission = function(req, res, next) {
}));
});
});
-};
+}
- exports.commentSubmit = function(req, res, next) {
- var data = req.body.data;
- if (!req.user) {
+function commentSubmit(req, res, next) {
+ var data = req.body.data;
+ if (!req.user) {
+ return next(new Error('Not authorized'));
+ }
+ var sanitizedBody = sanitizeHtml(data.body,
+ {
+ allowedTags: [],
+ allowedAttributes: []
+ }).replace(/"/g, '"');
+ if (data.body !== sanitizedBody) {
+ req.flash('errors', {
+ msg: 'HTML is not allowed'
+ });
+ return res.send(true);
+ }
+ var comment = new Comment({
+ associatedPost: data.associatedPost,
+ originalStoryLink: data.originalStoryLink,
+ originalStoryAuthorEmail: data.originalStoryAuthorEmail,
+ body: sanitizedBody,
+ rank: 0,
+ upvotes: 0,
+ author: {
+ picture: req.user.profile.picture,
+ userId: req.user._id,
+ username: req.user.profile.username,
+ email: req.user.email
+ },
+ comments: [],
+ topLevel: true,
+ commentOn: Date.now()
+ });
+
+ commentSave(comment, Story, res, next);
+}
+
+function commentOnCommentSubmit(req, res, next) {
+ var data = req.body.data;
+ if (!req.user) {
+ return next(new Error('Not authorized'));
+ }
+
+ var sanitizedBody = sanitizeHtml(data.body,
+ {
+ allowedTags: [],
+ allowedAttributes: []
+ }).replace(/"/g, '"');
+ if (data.body !== sanitizedBody) {
+ req.flash('errors', {
+ msg: 'HTML is not allowed'
+ });
+ return res.send(true);
+ }
+ var comment = new Comment({
+ associatedPost: data.associatedPost,
+ body: sanitizedBody,
+ rank: 0,
+ upvotes: 0,
+ originalStoryLink: data.originalStoryLink,
+ originalStoryAuthorEmail: data.originalStoryAuthorEmail,
+ author: {
+ picture: req.user.profile.picture,
+ userId: req.user._id,
+ username: req.user.profile.username,
+ email: req.user.email
+ },
+ comments: [],
+ topLevel: false,
+ commentOn: Date.now()
+ });
+ commentSave(comment, Comment, res, next);
+}
+
+function commentEdit(req, res, next) {
+
+ Comment.find({'_id': req.params.id}, function(err, cmt) {
+ if (err) {
+ return next(err);
+ }
+ cmt = cmt.pop();
+
+ if (!req.user && cmt.author.userId !== req.user._id) {
return next(new Error('Not authorized'));
}
- var sanitizedBody = sanitizeHtml(data.body,
- {
- allowedTags: [],
- allowedAttributes: []
- }).replace(/"/g, '"');
- if (data.body !== sanitizedBody) {
+
+
+ var sanitizedBody = sanitizeHtml(req.body.body, {
+ allowedTags: [],
+ allowedAttributes: []
+ }).replace(/"/g, '"');
+ if (req.body.body !== sanitizedBody) {
req.flash('errors', {
msg: 'HTML is not allowed'
});
return res.send(true);
}
- var comment = new Comment({
- associatedPost: data.associatedPost,
- originalStoryLink: data.originalStoryLink,
- originalStoryAuthorEmail: data.originalStoryAuthorEmail,
- body: sanitizedBody,
- rank: 0,
- upvotes: 0,
- author: {
- picture: req.user.profile.picture,
- userId: req.user._id,
- username: req.user.profile.username,
- email: req.user.email
- },
- comments: [],
- topLevel: true,
- commentOn: Date.now()
- });
- commentSave(comment, Story, res, next);
- };
-
- exports.commentOnCommentSubmit = function(req, res, next) {
- var data = req.body.data;
- if (!req.user) {
- return next(new Error('Not authorized'));
- }
-
- var sanitizedBody = sanitizeHtml(data.body,
- {
- allowedTags: [],
- allowedAttributes: []
- }).replace(/"/g, '"');
- if (data.body !== sanitizedBody) {
- req.flash('errors', {
- msg: 'HTML is not allowed'
- });
- return res.send(true);
- }
- var comment = new Comment({
- associatedPost: data.associatedPost,
- body: sanitizedBody,
- rank: 0,
- upvotes: 0,
- originalStoryLink: data.originalStoryLink,
- originalStoryAuthorEmail: data.originalStoryAuthorEmail,
- author: {
- picture: req.user.profile.picture,
- userId: req.user._id,
- username: req.user.profile.username,
- email: req.user.email
- },
- comments: [],
- topLevel: false,
- commentOn: Date.now()
- });
- commentSave(comment, Comment, res, next);
- };
-
- exports.commentEdit = function(req, res, next) {
-
- Comment.find({'_id': req.params.id}, function(err, cmt) {
+ cmt.body = sanitizedBody;
+ cmt.commentOn = Date.now();
+ cmt.save(function (err) {
if (err) {
return next(err);
}
- cmt = cmt.pop();
+ res.send(true);
+ });
- if (!req.user && cmt.author.userId !== req.user._id) {
- return next(new Error('Not authorized'));
- }
+ });
+}
- var sanitizedBody = sanitizeHtml(req.body.body, {
- allowedTags: [],
- allowedAttributes: []
- }).replace(/"/g, '"');
- if (req.body.body !== sanitizedBody) {
- req.flash('errors', {
- msg: 'HTML is not allowed'
- });
- return res.send(true);
- }
-
- cmt.body = sanitizedBody;
- cmt.commentOn = Date.now();
- cmt.save(function (err) {
+function commentSave(comment, Context, res, next) {
+ comment.save(function(err, data) {
+ if (err) {
+ return next(err);
+ }
+ try {
+ // Based on the context retrieve the parent
+ // object of the comment (Story/Comment)
+ Context.find({'_id': data.associatedPost}, function (err, associatedContext) {
if (err) {
return next(err);
}
- res.send(true);
- });
-
- });
-
- };
-
- function commentSave(comment, Context, res, next) {
- comment.save(function(err, data) {
- if (err) {
- return next(err);
- }
- try {
- // Based on the context retrieve the parent
- // object of the comment (Story/Comment)
- Context.find({'_id': data.associatedPost}, function (err, associatedContext) {
- if (err) {
- return next(err);
- }
- associatedContext = associatedContext.pop();
- if (associatedContext) {
- associatedContext.comments.push(data._id);
- associatedContext.save(function (err) {
- if (err) {
- return next(err);
- }
- res.send(true);
- });
- }
- // Find the author of the parent object
- User.findOne({'profile.username': associatedContext.author.username}, function(err, recipient) {
+ associatedContext = associatedContext.pop();
+ if (associatedContext) {
+ associatedContext.comments.push(data._id);
+ associatedContext.save(function (err) {
if (err) {
return next(err);
}
- // If the emails of both authors differ,
- // only then proceed with email notification
- if (
- typeof data.author !== 'undefined' &&
- data.author.email &&
- typeof recipient !== 'undefined' &&
- recipient.email &&
- (data.author.email !== recipient.email)
- ) {
- var transporter = nodemailer.createTransport({
- service: 'Mandrill',
- auth: {
- user: secrets.mandrill.user,
- pass: secrets.mandrill.password
- }
- });
-
- var mailOptions = {
- to: recipient.email,
- from: 'Team@freecodecamp.com',
- subject: data.author.username +
- ' replied to your post on Camper News',
- text: [
- 'Just a quick heads-up: ',
- data.author.username + ' replied to you on Camper News.',
- 'You can keep this conversation going.',
- 'Just head back to the discussion here: ',
- 'http://freecodecamp.com/news/' + data.originalStoryLink,
- '- the Free Code Camp Volunteer Team'
- ].join('\n')
- };
-
- transporter.sendMail(mailOptions, function (err) {
- if (err) {
- return err;
- }
- });
- }
+ res.send(true);
});
+ }
+ // Find the author of the parent object
+ User.findOne({'profile.username': associatedContext.author.username}, function(err, recipient) {
+ if (err) {
+ return next(err);
+ }
+ // If the emails of both authors differ,
+ // only then proceed with email notification
+ if (
+ typeof data.author !== 'undefined' &&
+ data.author.email &&
+ typeof recipient !== 'undefined' &&
+ recipient.email &&
+ (data.author.email !== recipient.email)
+ ) {
+ var transporter = nodemailer.createTransport({
+ service: 'Mandrill',
+ auth: {
+ user: secrets.mandrill.user,
+ pass: secrets.mandrill.password
+ }
+ });
+
+ var mailOptions = {
+ to: recipient.email,
+ from: 'Team@freecodecamp.com',
+ subject: data.author.username +
+ ' replied to your post on Camper News',
+ text: [
+ 'Just a quick heads-up: ',
+ data.author.username + ' replied to you on Camper News.',
+ 'You can keep this conversation going.',
+ 'Just head back to the discussion here: ',
+ 'http://freecodecamp.com/news/' + data.originalStoryLink,
+ '- the Free Code Camp Volunteer Team'
+ ].join('\n')
+ };
+
+ transporter.sendMail(mailOptions, function (err) {
+ if (err) {
+ return err;
+ }
+ });
+ }
});
- } catch (e) {
- // delete comment
- return next(err);
- }
- });
- }
+ });
+ } catch (e) {
+ return next(err);
+ }
+ });
+}
+
+module.exports = router;
diff --git a/controllers/user.js b/server/boot/user.js
similarity index 88%
rename from controllers/user.js
rename to server/boot/user.js
index f83ac9e0be..affcf95e51 100644
--- a/controllers/user.js
+++ b/server/boot/user.js
@@ -3,68 +3,62 @@ var _ = require('lodash'),
crypto = require('crypto'),
nodemailer = require('nodemailer'),
passport = require('passport'),
- User = require('../models/User'),
- secrets = require('../config/secrets'),
+ User = require('../../models/User'),
+ secrets = require('../../config/secrets'),
moment = require('moment'),
debug = require('debug')('freecc:cntr:userController'),
- resources = require('./resources'),
+ resources = require('./../resources/resources'),
R = require('ramda');
-
-/**
- *
- * @param req
- * @param res
- * @returns null
- * Middleware to migrate users from fragmented challenge structure to unified
- * challenge structure
- */
-exports.userMigration = function(req, res, next) {
- if (req.user && req.user.completedChallenges.length === 0) {
- req.user.completedChallenges = R.filter(function (elem) {
- return elem; // getting rid of undefined
- }, R.concat(
- req.user.completedCoursewares,
- req.user.completedBonfires.map(function (bonfire) {
- return ({
- completedDate: bonfire.completedDate,
- _id: bonfire._id,
- name: bonfire.name,
- completedWith: bonfire.completedWith,
- solution: bonfire.solution,
- githubLink: '',
- verified: false,
- challengeType: 5
- });
- })
- ));
- next();
- } else {
- next();
- }
-};
+router.get('/login', function(req, res) {
+ res.redirect(301, '/signin');
+});
+router.get('/logout', function(req, res) {
+ res.redirect(301, '/signout');
+});
+router.get('/signin', getSignin);
+router.post('/signin', postSignin);
+router.get('/signout', signout);
+router.get('/forgot', getForgot);
+router.post('/forgot', postForgot);
+router.get('/reset/:token', getReset);
+router.post('/reset/:token', postReset);
+router.get('/email-signup', getEmailSignup);
+router.get('/email-signin', getEmailSignin);
+router.post('/email-signup', postEmailSignup);
+router.post('/email-signin', postSignin);
+router.get('/account/api', getAccountAngular);
+router.get('/api/checkUniqueUsername/:username', checkUniqueUsername);
+router.get('/api/checkExistingUsername/:username', checkExistingUsername);
+router.get('/api/checkUniqueEmail/:email', checkUniqueEmail);
+router.post('/account/profile', postUpdateProfile);
+router.post('/account/password', postUpdatePassword);
+router.post('/account/delete', postDeleteAccount);
+router.get('/account/unlink/:provider', getOauthUnlink);
+router.get('/account', getAccount);
+router.get('/:username', returnUser); // Ensure this is the last route!
/**
* GET /signin
* Siginin page.
*/
-exports.getSignin = function(req, res) {
+function getSignin (req, res) {
if (req.user) {
return res.redirect('/');
}
res.render('account/signin', {
title: 'Free Code Camp Login'
});
-};
+}
/**
* POST /signin
* Sign in using email and password.
*/
-exports.postSignin = function(req, res, next) {
+function postSignin (req, res, next) {
req.assert('email', 'Email is not valid').isEmail();
req.assert('password', 'Password cannot be blank').notEmpty();
@@ -97,53 +91,52 @@ exports.postSignin = function(req, res, next) {
return res.redirect(req.session.returnTo || '/');
});
})(req, res, next);
-};
+}
/**
* GET /signout
* Log out.
*/
-exports.signout = function(req, res) {
+function signout (req, res) {
req.logout();
res.redirect('/');
-};
+}
/**
* GET /email-signup
* Signup page.
*/
-exports.getEmailSignin = function(req, res) //noinspection Eslint
-{
+function getEmailSignin (req, res) {
if (req.user) {
return res.redirect('/');
}
res.render('account/email-signin', {
title: 'Sign in to your Free Code Camp Account'
});
-};
+}
/**
* GET /signin
* Signup page.
*/
-exports.getEmailSignup = function(req, res) {
+function getEmailSignup (req, res) {
if (req.user) {
return res.redirect('/');
}
res.render('account/email-signup', {
title: 'Create Your Free Code Camp Account'
});
-};
+}
/**
* POST /email-signup
* Create a new local account.
*/
-exports.postEmailSignup = function(req, res, next) {
+function postEmailSignup (req, res, next) {
req.assert('email', 'valid email required').isEmail();
var errors = req.validationErrors();
@@ -237,34 +230,34 @@ exports.postEmailSignup = function(req, res, next) {
});
});
});
-};
+}
/**
* GET /account
* Profile page.
*/
-exports.getAccount = function(req, res) {
+function getAccount (req, res) {
res.render('account/account', {
title: 'Manage your Free Code Camp Account'
});
-};
+}
/**
* Angular API Call
*/
-exports.getAccountAngular = function(req, res) {
+function getAccountAngular (req, res) {
res.json({
user: req.user
});
-};
+}
/**
* Unique username check API Call
*/
-exports.checkUniqueUsername = function(req, res, next) {
+function checkUniqueUsername (req, res, next) {
User.count(
{ 'profile.username': req.params.username.toLowerCase() },
function (err, data) {
@@ -275,12 +268,12 @@ exports.checkUniqueUsername = function(req, res, next) {
return res.send(false);
}
});
-};
+}
/**
* Existing username check
*/
-exports.checkExistingUsername = function(req, res, next) {
+function checkExistingUsername (req, res, next) {
User.count(
{ 'profile.username': req.params.username.toLowerCase() },
function (err, data) {
@@ -292,13 +285,13 @@ exports.checkExistingUsername = function(req, res, next) {
}
}
);
-};
+}
/**
* Unique email check API Call
*/
-exports.checkUniqueEmail = function(req, res, next) {
+function checkUniqueEmail (req, res, next) {
User.count(
{ email: decodeURIComponent(req.params.email).toLowerCase() },
function (err, data) {
@@ -310,7 +303,7 @@ exports.checkUniqueEmail = function(req, res, next) {
}
}
);
-};
+}
/**
@@ -318,7 +311,7 @@ exports.checkUniqueEmail = function(req, res, next) {
* Public Profile page.
*/
-exports.returnUser = function(req, res, next) {
+function returnUser (req, res, next) {
User.find(
{ 'profile.username': req.params.username.toLowerCase() },
function(err, user) {
@@ -445,37 +438,14 @@ exports.returnUser = function(req, res, next) {
}
}
);
-};
-
-
-/**
- * POST /update-progress
- * Update profile information.
- */
-
-exports.updateProgress = function(req, res, next) {
- User.findById(req.user.id, function(err, user) {
- if (err) { return next(err); }
- user.email = req.body.email || '';
- user.profile.name = req.body.name || '';
- user.profile.gender = req.body.gender || '';
- user.profile.location = req.body.location || '';
- user.profile.website = req.body.website || '';
-
- user.save(function(err) {
- if (err) { return next(err); }
- req.flash('success', { msg: 'Profile information updated.' });
- res.redirect('/account');
- });
- });
-};
+}
/**
* POST /account/profile
* Update profile information.
*/
-exports.postUpdateProfile = function(req, res, next) {
+function postUpdateProfile (req, res, next) {
User.findById(req.user.id, function(err) {
if (err) { return next(err); }
@@ -558,14 +528,14 @@ exports.postUpdateProfile = function(req, res, next) {
);
});
});
-};
+}
/**
* POST /account/password
* Update current password.
*/
-exports.postUpdatePassword = function(req, res, next) {
+function postUpdatePassword (req, res, next) {
req.assert('password', 'Password must be at least 4 characters long').len(4);
req.assert('confirmPassword', 'Passwords do not match')
.equals(req.body.password);
@@ -589,28 +559,28 @@ exports.postUpdatePassword = function(req, res, next) {
res.redirect('/account');
});
});
-};
+}
/**
* POST /account/delete
* Delete user account.
*/
-exports.postDeleteAccount = function(req, res, next) {
+function postDeleteAccount (req, res, next) {
User.remove({ _id: req.user.id }, function(err) {
if (err) { return next(err); }
req.logout();
req.flash('info', { msg: 'Your account has been deleted.' });
res.redirect('/');
});
-};
+}
/**
* GET /account/unlink/:provider
* Unlink OAuth provider.
*/
-exports.getOauthUnlink = function(req, res, next) {
+function getOauthUnlink (req, res, next) {
var provider = req.params.provider;
User.findById(req.user.id, function(err, user) {
if (err) { return next(err); }
@@ -627,14 +597,14 @@ exports.getOauthUnlink = function(req, res, next) {
res.redirect('/account');
});
});
-};
+}
/**
* GET /reset/:token
* Reset Password page.
*/
-exports.getReset = function(req, res, next) {
+function getReset (req, res, next) {
if (req.isAuthenticated()) {
return res.redirect('/');
}
@@ -654,14 +624,14 @@ exports.getReset = function(req, res, next) {
token: req.params.token
});
});
-};
+}
/**
* POST /reset/:token
* Process the reset password request.
*/
-exports.postReset = function(req, res, next) {
+function postReset (req, res, next) {
var errors = req.validationErrors();
if (errors) {
@@ -728,28 +698,28 @@ exports.postReset = function(req, res, next) {
if (err) { return next(err); }
res.redirect('/');
});
-};
+}
/**
* GET /forgot
* Forgot Password page.
*/
-exports.getForgot = function(req, res) {
+function getForgot (req, res) {
if (req.isAuthenticated()) {
return res.redirect('/');
}
res.render('account/forgot', {
title: 'Forgot Password'
});
-};
+}
/**
* POST /forgot
* Create a random token, then the send user an email with a reset link.
*/
-exports.postForgot = function(req, res, next) {
+function postForgot (req, res, next) {
var errors = req.validationErrors();
if (errors) {
@@ -826,4 +796,6 @@ exports.postForgot = function(req, res, next) {
if (err) { return next(err); }
res.redirect('/forgot');
});
-};
+}
+
+module.exports = router;
diff --git a/server/boot/utility.js b/server/boot/utility.js
new file mode 100644
index 0000000000..2565d64b16
--- /dev/null
+++ b/server/boot/utility.js
@@ -0,0 +1,435 @@
+var express = require('express'),
+ async = require('async'),
+ moment = require('moment'),
+ Twit = require('twit'),
+ debug = require('debug')('freecc:cntr:resources'),
+ request = require('request'),
+ constantStrings = require('./constantStrings.json'),
+ User = require('../../models/User'),
+ Challenge = require('./../../models/Challenge'),
+ Story = require('./../../models/Story'),
+ FieldGuide = require('./../../models/FieldGuide'),
+ Nonprofit = require('./../../models/Nonprofit'),
+ secrets = require('./../../config/secrets'),
+ Slack = require('node-slack'),
+ slack = new Slack(secrets.slackHook);
+router = express.Router();
+
+router.get('/api/github', githubCalls);
+router.get('/api/blogger', bloggerCalls);
+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('/twitch', twitch);
+router.get('/pmi-acp-agile-project-managers', agileProjectManagers);
+router.get('/pmi-acp-agile-project-managers-form', agileProjectManagersForm);
+router.get('/nonprofits', nonprofits);
+router.get('/nonprofits-form', nonprofitsForm);
+router.get('/jobs-form', jobsForm);
+router.get('/submit-cat-photo', catPhotoSubmit);
+router.get('/unsubscribe/:email', unsubscribe);
+router.get('/unsubscribed', unsubscribed);
+router.get('/cats.json', getCats);
+
+router.get('/api/slack', slackInvite);
+
+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://freecode.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({
+ 'consumer_key': secrets.twitter.consumerKey,
+ 'consumer_secret': secrets.twitter.consumerSecret,
+ 'access_token': secrets.twitter.token,
+ 'access_token_secret': secrets.twitter.tokenSecret
+ });
+
+ var screenName;
+ if (req.params.screenName) {
+ screenName = req.params.screenName;
+ } else {
+ screenName = 'freecodecamp';
+ }
+
+ T.get(
+ 'statuses/user_timeline',
+ {
+ 'screen_name': screenName,
+ count: 10
+ },
+ function(err, data) {
+ if (err) { return next(err); }
+ return res.json(data);
+ }
+ );
+}
+
+
+function getHelp(req, res) {
+ var userName = req.user.profile.username;
+ var code = req.body.payload.code ? '\n```\n' +
+ req.body.payload.code + '\n```\n'
+ : '';
+ var challenge = req.body.payload.challenge;
+
+ slack.send({
+ text: '*@' + userName + '* wants help with ' + challenge + '. ' +
+ code + 'Hey, *@' + userName + '*, if no one helps you right ' +
+ 'away, try typing out your problem in detail to me. Like this: ' +
+ 'http://en.wikipedia.org/wiki/Rubber_duck_debugging',
+ channel: '#help',
+ username: 'Debuggy the Rubber Duck',
+ 'icon_url': 'https://pbs.twimg.com/profile_images/' +
+ '3609875545/569237541c920fa78d78902069615caf.jpeg'
+ });
+ return res.sendStatus(200);
+}
+
+function getPair(req, res) {
+ var userName = req.user.profile.username;
+ var challenge = req.body.payload.challenge;
+ slack.send({
+ text: 'Anyone want to pair with *@' + userName + '* on ' + challenge +
+ '?\nMake sure you install Screen Hero here: ' +
+ 'http://freecodecamp.com/field-guide/how-do-i-install-screenhero\n' +
+ 'Then start your pair program session with *@' + userName +
+ '* by typing \"/hero @' + userName + '\" into Slack.\n And *@'+ userName +
+ '*, be sure to launch Screen Hero, then keep coding. ' +
+ 'Another camper may pair with you soon.',
+ channel: '#letspair',
+ username: 'Companion Cube',
+ 'icon_url': 'https://lh3.googleusercontent.com/-f6xDPDV2rPE/AAAAAAAAAAI/' +
+ 'AAAAAAAAAAA/mdlESXQu11Q/photo.jpg'
+ });
+ return res.sendStatus(200);
+}
+
+function sitemap(req, res, next) {
+ var appUrl = 'http://www.freecodecamp.com';
+ var now = moment(new Date()).format('YYYY-MM-DD');
+
+
+ async.parallel({
+ users: function(callback) {
+ User.aggregate()
+ .group({_id: 1, usernames: { $addToSet: '$profile.username'}})
+ .match({'profile.username': { $ne: ''}})
+ .exec(function(err, users) {
+ if (err) {
+ debug('User err: ', err);
+ callback(err);
+ } else {
+ callback(null, users[0].usernames);
+ }
+ });
+ },
+
+ challenges: function (callback) {
+ Challenge.aggregate()
+ .group({_id: 1, names: { $addToSet: '$name'}})
+ .exec(function (err, challenges) {
+ if (err) {
+ debug('Challenge err: ', err);
+ callback(err);
+ } else {
+ callback(null, challenges[0].names);
+ }
+ });
+ },
+ stories: function (callback) {
+ Story.aggregate()
+ .group({_id: 1, links: {$addToSet: '$link'}})
+ .exec(function (err, stories) {
+ if (err) {
+ debug('Story err: ', err);
+ callback(err);
+ } else {
+ callback(null, stories[0].links);
+ }
+ });
+ },
+ nonprofits: function (callback) {
+ Nonprofit.aggregate()
+ .group({_id: 1, names: { $addToSet: '$name'}})
+ .exec(function (err, nonprofits) {
+ if (err) {
+ debug('User err: ', err);
+ callback(err);
+ } else {
+ callback(null, nonprofits[0].names);
+ }
+ });
+ },
+ fieldGuides: function (callback) {
+ FieldGuide.aggregate()
+ .group({_id: 1, names: { $addToSet: '$name'}})
+ .exec(function (err, fieldGuides) {
+ if (err) {
+ debug('User err: ', err);
+ callback(err);
+ } else {
+ callback(null, fieldGuides[0].names);
+ }
+ });
+ }
+ }, function (err, results) {
+ if (err) {
+ return next(err);
+ } else {
+ setTimeout(function() {
+ res.header('Content-Type', 'application/xml');
+ res.render('resources/sitemap', {
+ appUrl: appUrl,
+ now: now,
+ users: results.users,
+ challenges: results.challenges,
+ stories: results.stories,
+ nonprofits: results.nonprofits,
+ fieldGuides: results.fieldGuides
+ });
+ }, 0);
+ }
+ }
+ );
+}
+
+function chat(req, res) {
+ if (req.user && req.user.progressTimestamps.length > 5) {
+ res.redirect('http://freecode.slack.com');
+ } else {
+ res.render('resources/chat', {
+ title: 'Watch us code live on Twitch.tv'
+ });
+ }
+}
+
+function jobsForm(req, res) {
+ res.render('resources/jobs-form', {
+ title: 'Employer Partnership Form for Job Postings,' +
+ ' Recruitment and Corporate Sponsorships'
+ });
+}
+
+function catPhotoSubmit(req, res) {
+ res.send(
+ 'Success! You have submitted your cat photo. Return to your website ' +
+ 'by typing any letter into your code editor.'
+ );
+}
+
+function nonprofits(req, res) {
+ res.render('resources/nonprofits', {
+ title: 'A guide to our Nonprofit Projects'
+ });
+}
+
+function nonprofitsForm(req, res) {
+ res.render('resources/nonprofits-form', {
+ title: 'Nonprofit Projects Proposal Form'
+ });
+}
+
+function agileProjectManagers(req, res) {
+ res.render('resources/pmi-acp-agile-project-managers', {
+ title: 'Get Agile Project Management Experience for the PMI-ACP'
+ });
+}
+
+function agileProjectManagersForm(req, res) {
+ res.render('resources/pmi-acp-agile-project-managers-form', {
+ title: 'Agile Project Management Program Application Form'
+ });
+}
+
+function twitch(req, res) {
+ res.render('resources/twitch', {
+ title: 'Enter Free Code Camp\'s Chat Rooms'
+ });
+}
+
+function unsubscribe(req, res, next) {
+ User.findOne({ email: req.params.email }, function(err, user) {
+ if (user) {
+ if (err) {
+ return next(err);
+ }
+ user.sendMonthlyEmail = false;
+ user.save(function () {
+ if (err) {
+ return next(err);
+ }
+ res.redirect('/unsubscribed');
+ });
+ } else {
+ res.redirect('/unsubscribed');
+ }
+ });
+}
+
+function unsubscribed(req, res) {
+ res.render('resources/unsubscribed', {
+ title: 'You have been unsubscribed'
+ });
+}
+
+function githubCalls(req, res, next) {
+ var githubHeaders = {
+ headers: {
+ 'User-Agent': constantStrings.gitHubUserAgent
+ },
+ port: 80
+ };
+ request(
+ [
+ 'https://api.github.com/repos/freecodecamp/',
+ 'freecodecamp/pulls?client_id=',
+ secrets.github.clientID,
+ '&client_secret=',
+ secrets.github.clientSecret
+ ].join(''),
+ githubHeaders,
+ function(err, status1, pulls) {
+ if (err) { return next(err); }
+ pulls = pulls ?
+ Object.keys(JSON.parse(pulls)).length :
+ 'Can\'t connect to github';
+
+ request(
+ [
+ 'https://api.github.com/repos/freecodecamp/',
+ 'freecodecamp/issues?client_id=',
+ secrets.github.clientID,
+ '&client_secret=',
+ secrets.github.clientSecret
+ ].join(''),
+ githubHeaders,
+ function (err, status2, issues) {
+ if (err) { return next(err); }
+ issues = ((pulls === parseInt(pulls, 10)) && issues) ?
+ Object.keys(JSON.parse(issues)).length - pulls :
+ "Can't connect to GitHub";
+ res.send({
+ issues: issues,
+ pulls: pulls
+ });
+ }
+ );
+ }
+ );
+}
+
+function trelloCalls(req, res, next) { //eslint-disable-line
+ request(
+ 'https://trello.com/1/boards/BA3xVpz9/cards?key=' +
+ secrets.trello.key,
+ function(err, status, trello) {
+ if (err) { return next(err); }
+ trello = (status && status.statusCode === 200) ?
+ (JSON.parse(trello)) :
+ 'Can\'t connect to to Trello';
+
+ res.end(JSON.stringify(trello));
+ });
+}
+
+function bloggerCalls(req, res, next) { //eslint-disable-line
+ request(
+ 'https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/' +
+ 'posts?key=' +
+ secrets.blogger.key,
+ function (err, status, blog) {
+ if (err) { return next(err); }
+
+ blog = (status && status.statusCode === 200) ?
+ JSON.parse(blog) :
+ 'Can\'t connect to Blogger';
+ res.end(JSON.stringify(blog));
+ }
+ );
+}
+
+function getCats(req, res) { //eslint-disable-line
+ res.send(
+ [
+ {
+ 'name': 'cute',
+ 'imageLink': 'https://encrypted-tbn3.gstatic.com/images' +
+ '?q=tbn:ANd9GcRaP1ecF2jerISkdhjr4R9yM9-8ClUy-TA36MnDiFBukd5IvEME0g'
+ },
+ {
+ 'name': 'grumpy',
+ 'imageLink': 'http://cdn.grumpycats.com/wp-content/uploads/' +
+ '2012/09/GC-Gravatar-copy.png'
+ },
+ {
+ 'name': 'mischievous',
+ 'imageLink': 'http://www.kittenspet.com/wp-content' +
+ '/uploads/2012/08/cat_with_funny_face_3-200x200.jpg'
+ }
+ ]
+ );
+}
+
+module.exports = router;
diff --git a/controllers/constantStrings.json b/server/resources/constantStrings.json
similarity index 100%
rename from controllers/constantStrings.json
rename to server/resources/constantStrings.json
diff --git a/server/resources/middleware.js b/server/resources/middleware.js
new file mode 100644
index 0000000000..0f2766bfe2
--- /dev/null
+++ b/server/resources/middleware.js
@@ -0,0 +1,34 @@
+var R = require('ramda');
+
+/**
+ *
+ * @param req
+ * @param res
+ * @returns null
+ * Middleware to migrate users from fragmented challenge structure to unified
+ * challenge structure
+ */
+exports.userMigration = function(req, res, next) {
+ if (!req.user || req.user.completedChallenges.length !== 0) {
+ return next();
+ }
+ req.user.completedChallenges = R.filter(function (elem) {
+ return elem; // getting rid of undefined
+ }, R.concat(
+ req.user.completedCoursewares,
+ req.user.completedBonfires.map(function (bonfire) {
+ return ({
+ completedDate: bonfire.completedDate,
+ _id: bonfire._id,
+ name: bonfire.name,
+ completedWith: bonfire.completedWith,
+ solution: bonfire.solution,
+ githubLink: '',
+ verified: false,
+ challengeType: 5
+ });
+ })
+ )
+ );
+ return next();
+};
diff --git a/server/resources/resources.js b/server/resources/resources.js
new file mode 100644
index 0000000000..8729b560eb
--- /dev/null
+++ b/server/resources/resources.js
@@ -0,0 +1,273 @@
+var async = require('async'),
+ path = require('path'),
+ debug = require('debug')('freecc:cntr:resources'), // eslint-disable-line
+ cheerio = require('cheerio'),
+ request = require('request'),
+ R = require('ramda'),
+ _ = require('lodash'),
+ fs = require('fs'),
+
+
+ Story = require('./../../models/Story'),
+ Comment = require('./../../models/Comment'),
+ resources = require('./resources.json'),
+ nonprofits = require('../../seed_data/nonprofits.json'),
+ fieldGuides = require('../../seed_data/field-guides.json');
+
+/**
+ * Cached values
+ */
+var allFieldGuideIds, allFieldGuideNames, allNonprofitNames,
+ challengeMap, challengeMapForDisplay, challengeMapWithIds,
+ challengeMapWithNames, allChallengeIds, allChallenges;
+
+/**
+ * GET /
+ * Resources.
+ */
+
+Array.zip = function(left, right, combinerFunction) {
+ var counter,
+ results = [];
+
+ for (counter = 0; counter < Math.min(left.length, right.length); counter++) {
+ results.push(combinerFunction(left[counter], right[counter]));
+ }
+
+ return results;
+};
+
+(function() {
+ if (!challengeMap) {
+ var localChallengeMap = {};
+ var files = fs.readdirSync(
+ path.join(__dirname, '/../seed_data/challenges')
+ );
+ var keyCounter = 0;
+ files = files.map(function (file) {
+ return require(
+ path.join(__dirname, '/../seed_data/challenges/' + file)
+ );
+ });
+ files = files.sort(function (a, b) {
+ return a.order - b.order;
+ });
+ files.forEach(function (file) {
+ localChallengeMap[keyCounter++] = file;
+ });
+ challengeMap = _.cloneDeep(localChallengeMap);
+ }
+})();
+
+
+module.exports = {
+ getChallengeMapForDisplay: function () {
+ if (!challengeMapForDisplay) {
+ challengeMapForDisplay = {};
+ Object.keys(challengeMap).forEach(function (key) {
+ challengeMapForDisplay[key] = {
+ name: challengeMap[key].name,
+ dashedName: challengeMap[key].name.replace(/\s/g, '-'),
+ challenges: challengeMap[key].challenges,
+ completedCount: challengeMap[key].challenges
+ };
+ });
+ }
+ return challengeMapForDisplay;
+ },
+
+ getChallengeMapWithIds: function () {
+ if (!challengeMapWithIds) {
+ challengeMapWithIds = {};
+ Object.keys(challengeMap).forEach(function (key) {
+ var onlyIds = challengeMap[key].challenges.map(function (elem) {
+ return elem._id;
+ });
+ challengeMapWithIds[key] = onlyIds;
+ });
+ }
+ return challengeMapWithIds;
+ },
+
+ allChallengeIds: function () {
+
+ if (!allChallengeIds) {
+ allChallengeIds = [];
+ Object.keys(this.getChallengeMapWithIds()).forEach(function (key) {
+ allChallengeIds.push(challengeMapWithIds[key]);
+ });
+ allChallengeIds = R.flatten(allChallengeIds);
+ }
+ return allChallengeIds;
+ },
+
+ allChallenges: function () {
+ if (!allChallenges) {
+ allChallenges = [];
+ Object.keys(this.getChallengeMapWithNames()).forEach(function (key) {
+ allChallenges.push(challengeMap[key].challenges);
+ });
+ allChallenges = R.flatten(allChallenges);
+ }
+ return allChallenges;
+ },
+
+ getChallengeMapWithNames: function () {
+ if (!challengeMapWithNames) {
+ challengeMapWithNames = {};
+ Object.keys(challengeMap).
+ forEach(function (key) {
+ var onlyNames = challengeMap[key].challenges.map(function (elem) {
+ return elem.name;
+ });
+ challengeMapWithNames[key] = onlyNames;
+ });
+ }
+ return challengeMapWithNames;
+ },
+
+
+ randomPhrase: function () {
+ return resources.phrases[
+ Math.floor(Math.random() * resources.phrases.length)
+ ];
+ },
+
+ randomVerb: function () {
+ return resources.verbs[
+ Math.floor(Math.random() * resources.verbs.length)
+ ];
+ },
+
+ randomCompliment: function () {
+ return resources.compliments[
+ Math.floor(Math.random() * resources.compliments.length)
+ ];
+ },
+
+ allFieldGuideIds: function () {
+ if (allFieldGuideIds) {
+ return allFieldGuideIds;
+ } else {
+ allFieldGuideIds = fieldGuides.map(function (elem) {
+ return elem._id;
+ });
+ return allFieldGuideIds;
+ }
+ },
+
+ allFieldGuideNamesAndIds: function () {
+ if (allFieldGuideNames) {
+ return allFieldGuideNames;
+ } else {
+ allFieldGuideNames = fieldGuides.map(function (elem) {
+ return {
+ name: elem.name,
+ dashedName: elem.dashedName,
+ id: elem._id
+ };
+ });
+ return allFieldGuideNames;
+ }
+ },
+
+ allNonprofitNames: function () {
+ if (allNonprofitNames) {
+ return allNonprofitNames;
+ } else {
+ allNonprofitNames = nonprofits.map(function (elem) {
+ return {name: elem.name};
+ });
+ return allNonprofitNames;
+ }
+ },
+
+ whichEnvironment: function () {
+ return process.env.NODE_ENV;
+ },
+
+ getURLTitle: function (url, callback) {
+ (function () {
+ var result = {title: '', image: '', url: '', description: ''};
+ request(url, function (error, response, body) {
+ if (!error && response.statusCode === 200) {
+ var $ = cheerio.load(body);
+ var metaDescription = $("meta[name='description']");
+ var metaImage = $("meta[property='og:image']");
+ var urlImage = metaImage.attr('content') ?
+ metaImage.attr('content') :
+ '';
+
+ var metaTitle = $('title');
+ var description = metaDescription.attr('content') ?
+ metaDescription.attr('content') :
+ '';
+
+ result.title = metaTitle.text().length < 90 ?
+ metaTitle.text() :
+ metaTitle.text().slice(0, 87) + '...';
+
+ result.image = urlImage;
+ result.description = description;
+ callback(null, result);
+ } else {
+ callback(new Error('failed'));
+ }
+ });
+ })();
+ },
+
+ updateUserStoryPictures: function (userId, picture, username, cb) {
+
+ var counter = 0,
+ foundStories,
+ foundComments;
+
+ Story.find({'author.userId': userId}, function (err, stories) {
+ if (err) {
+ return cb(err);
+ }
+ foundStories = stories;
+ counter++;
+ saveStoriesAndComments();
+ });
+ Comment.find({'author.userId': userId}, function (err, comments) {
+ if (err) {
+ return cb(err);
+ }
+ foundComments = comments;
+ counter++;
+ saveStoriesAndComments();
+ });
+
+ function saveStoriesAndComments() {
+ if (counter !== 2) {
+ return;
+ }
+ var tasks = [];
+ R.forEach(function (comment) {
+ comment.author.picture = picture;
+ comment.author.username = username;
+ comment.markModified('author');
+ tasks.push(function (cb) {
+ comment.save(cb);
+ });
+ }, foundComments);
+
+ R.forEach(function (story) {
+ story.author.picture = picture;
+ story.author.username = username;
+ story.markModified('author');
+ tasks.push(function (cb) {
+ story.save(cb);
+ });
+ }, foundStories);
+ async.parallel(tasks, function (err) {
+ if (err) {
+ return cb(err);
+ }
+ cb();
+ });
+ }
+ }
+};
diff --git a/controllers/resources.json b/server/resources/resources.json
similarity index 100%
rename from controllers/resources.json
rename to server/resources/resources.json
diff --git a/app.js b/server/server.js
similarity index 52%
rename from app.js
rename to server/server.js
index e431ac361f..107d155398 100755
--- a/app.js
+++ b/server/server.js
@@ -21,8 +21,8 @@ var express = require('express'),
methodOverride = require('method-override'),
bodyParser = require('body-parser'),
helmet = require('helmet'),
- frameguard = require('frameguard'),
- csp = require('helmet-csp'),
+ //frameguard = require('frameguard'),
+ //csp = require('helmet-csp'),
MongoStore = require('connect-mongo')(session),
flash = require('express-flash'),
path = require('path'),
@@ -36,25 +36,23 @@ var express = require('express'),
/**
* Controllers (route handlers).
*/
- homeController = require('./controllers/home'),
- resourcesController = require('./controllers/resources'),
- userController = require('./controllers/user'),
- nonprofitController = require('./controllers/nonprofits'),
- fieldGuideController = require('./controllers/fieldGuide'),
- challengeMapController = require('./controllers/challengeMap'),
- challengeController = require('./controllers/challenge'),
- jobsController = require('./controllers/jobs'),
-
- /**
- * Stories
- */
- storyController = require('./controllers/story'),
+ homeController = require('./boot/home'),
+ resourcesController = require('./resources/resources'),
+ userController = require('./boot/user'),
+ nonprofitController = require('./boot/nonprofits'),
+ fieldGuideController = require('./boot/fieldGuide'),
+ challengeMapController = require('./boot/challengeMap'),
+ challengeController = require('./boot/challenge'),
+ jobsController = require('./boot/jobs'),
+ redirects = require('./boot/redirects'),
+ utility = require('./boot/utility'),
+ storyController = require('./boot/story'),
/**
* API keys and Passport configuration.
*/
- secrets = require('./config/secrets'),
- passportConf = require('./config/passport');
+ secrets = require('./../config/secrets'),
+ passportConf = require('./../config/passport');
/**
* Create Express server.
@@ -216,128 +214,9 @@ app.use(function (req, res, next) {
next();
});
-/**
- * Main routes.
- */
-
-app.get('/', homeController.index);
-
-app.get('/nonprofit-project-instructions', function(req, res) {
- res.redirect(301, '/field-guide/how-do-free-code-camp\'s-nonprofit-projects-work');
-});
-
-app.post('/get-help', resourcesController.getHelp);
-
-app.post('/get-pair', resourcesController.getPair);
-
-app.get('/chat', resourcesController.chat);
-
-app.get('/twitch', resourcesController.twitch);
-
-app.get('/cats.json', function(req, res) {
- res.send(
- [
- {
- "name": "cute",
- "imageLink": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcRaP1ecF2jerISkdhjr4R9yM9-8ClUy-TA36MnDiFBukd5IvEME0g"
- },
- {
- "name": "grumpy",
- "imageLink": "http://cdn.grumpycats.com/wp-content/uploads/2012/09/GC-Gravatar-copy.png"
- },
- {
- "name": "mischievous",
- "imageLink": "http://www.kittenspet.com/wp-content/uploads/2012/08/cat_with_funny_face_3-200x200.jpg"
- }
- ]
- )
-});
-
-// Agile Project Manager Onboarding
-
-app.get('/pmi-acp-agile-project-managers',
- resourcesController.agileProjectManagers);
-
-app.get('/agile', function(req, res) {
- res.redirect(301, '/pmi-acp-agile-project-managers');
-});
-
-app.get('/pmi-acp-agile-project-managers-form',
- resourcesController.agileProjectManagersForm);
-
-// Nonprofit Onboarding
-
-app.get('/nonprofits', resourcesController.nonprofits);
-
-app.get('/nonprofits-form', resourcesController.nonprofitsForm);
-
-app.get('/map',
- userController.userMigration,
- challengeMapController.challengeMap
-);
-
-app.get('/live-pair-programming', function(req, res) {
- res.redirect(301, '/field-guide/live-stream-pair-programming-on-twitch.tv');
-});
-
-app.get('/install-screenhero', function(req, res) {
- res.redirect(301, '/field-guide/install-screenhero');
-});
-
-app.get('/guide-to-our-nonprofit-projects', function(req, res) {
- res.redirect(301, '/field-guide/a-guide-to-our-nonprofit-projects');
-});
-
-app.get('/chromebook', function(req, res) {
- res.redirect(301, '/field-guide/chromebook');
-});
-
-app.get('/deploy-a-website', function(req, res) {
- res.redirect(301, '/field-guide/deploy-a-website');
-});
-
-app.get('/gmail-shortcuts', function(req, res) {
- res.redirect(301, '/field-guide/gmail-shortcuts');
-});
-
-app.get('/nodeschool-challenges', function(req, res) {
- res.redirect(301, '/field-guide/nodeschool-challenges');
-});
-app.get('/learn-to-code', challengeMapController.challengeMap);
-app.get('/about', function(req, res) {
- res.redirect(301, '/map');
-});
-app.get('/signin', userController.getSignin);
-app.get('/login', function(req, res) {
- res.redirect(301, '/signin');
-});
-
-app.post('/signin', userController.postSignin);
-
-app.get('/signout', userController.signout);
-
-app.get('/logout', function(req, res) {
- res.redirect(301, '/signout');
-});
-
-app.get('/forgot', userController.getForgot);
-
-app.post('/forgot', userController.postForgot);
-
-app.get('/reset/:token', userController.getReset);
-
-app.post('/reset/:token', userController.postReset);
-
-app.get('/email-signup', userController.getEmailSignup);
-
-app.get('/email-signin', userController.getEmailSignin);
-
-app.post('/email-signup', userController.postEmailSignup);
-
-app.post('/email-signin', userController.postSignin);
/**
* Nonprofit Project routes.
@@ -355,169 +234,25 @@ app.get(
jobsController.jobsDirectory
);
-app.get(
- '/jobs-form',
- resourcesController.jobsForm
-);
-app.get('/privacy', function(req, res) {
- res.redirect(301, '/field-guide/what-is-the-free-code-camp-privacy-policy?');
-});
-app.get('/submit-cat-photo', resourcesController.catPhotoSubmit);
-app.get('/api/slack', function(req, res) {
- 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://freecode.slack.com/api/users.admin.invite',
- method: 'POST',
- headers: headers,
- form: invite
- };
-
- request(options, function (error, response, body) {
- 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, user) {
- if (err) {
- 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