Major refactor. Add error handlers everywhere.

This commit is contained in:
Berkeley Martinez
2014-12-23 08:48:28 -08:00
parent e921b637cc
commit 86fcfe8296
18 changed files with 1431 additions and 1529 deletions

191
app.js
View File

@ -1,63 +1,61 @@
require('newrelic');
/**
* Module dependencies.
*/
require('newrelic');
var express = require('express');
var debug = require('debug')('freecc:server');
var cookieParser = require('cookie-parser');
var compress = require('compression');
var session = require('express-session');
var bodyParser = require('body-parser');
var logger = require('morgan');
var errorHandler = require('errorhandler');
var methodOverride = require('method-override');
var bodyParser = require('body-parser');
var helmet = require('helmet');
var express = require('express'),
debug = require('debug')('freecc:server'),
cookieParser = require('cookie-parser'),
compress = require('compression'),
session = require('express-session'),
bodyParser = require('body-parser'),
logger = require('morgan'),
errorHandler = require('errorhandler'),
methodOverride = require('method-override'),
bodyParser = require('body-parser'),
helmet = require('helmet'),
var _ = require('lodash');
var MongoStore = require('connect-mongo')(session);
var flash = require('express-flash');
var path = require('path');
var mongoose = require('mongoose');
var passport = require('passport');
var expressValidator = require('express-validator');
var connectAssets = require('connect-assets');
_ = require('lodash'),
MongoStore = require('connect-mongo')(session),
flash = require('express-flash'),
path = require('path'),
mongoose = require('mongoose'),
passport = require('passport'),
expressValidator = require('express-validator'),
connectAssets = require('connect-assets'),
/**
/**
* Controllers (route handlers).
*/
homeController = require('./controllers/home'),
challengesController = require('./controllers/challenges'),
resourcesController = require('./controllers/resources'),
userController = require('./controllers/user'),
contactController = require('./controllers/contact'),
var homeController = require('./controllers/home');
var challengesController = require('./controllers/challenges');
var resourcesController = require('./controllers/resources');
var userController = require('./controllers/user');
var apiController = require('./controllers/api');
var contactController = require('./controllers/contact');
/**
/**
* User model
*/
var User = require('./models/User');
/**
User = require('./models/User'),
/**
* API keys and Passport configuration.
*/
var secrets = require('./config/secrets');
var passportConf = require('./config/passport');
secrets = require('./config/secrets'),
passportConf = require('./config/passport');
/**
* Create Express server.
*/
var app = express();
/**
* Connect to MongoDB.
*/
mongoose.connect(secrets.db);
mongoose.connection.on('error', function() {
console.error('MongoDB Connection Error. Please make sure that MongoDB is running.');
console.error(
'MongoDB Connection Error. Please make sure that MongoDB is running.'
);
});
/**
@ -69,7 +67,10 @@ app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(compress());
app.use(connectAssets({
paths: [path.join(__dirname, 'public/css'), path.join(__dirname, 'public/js')],
paths: [
path.join(__dirname, 'public/css'),
path.join(__dirname, 'public/js')
],
helperContext: app.locals
}));
app.use(logger('dev'));
@ -91,38 +92,41 @@ app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
app.disable('x-powered-by');
app.use(helmet.xssFilter());
app.use(helmet.xframe());
var trusted = [
"'self'",
'"self"',
'*.freecodecamp.com',
"*.google-analytics.com",
"*.googleapis.com",
"*.gstatic.com",
"*.doubleclick.net",
"*.twitter.com",
'*.google-analytics.com',
'*.googleapis.com',
'*.gstatic.com',
'*.doubleclick.net',
'*.twitter.com',
'*.twimg.com',
"*.githubusercontent.com",
"'unsafe-eval'",
"'unsafe-inline'"
'*.githubusercontent.com',
'"unsafe-eval"',
'"unsafe-inline"'
];
//var connectSrc;
//if (process.env.NODE_ENV === 'development') {
// debug('Pushing');
// connectSrc = ['"self"', 'ws://localhost:3001/'];
//} else {
// debug('Not');
// connectSrc = [];
//}
//TODO(Berks): conditionally add localhost domains to csp;
/*var connectSrc;
if (process.env.NODE_ENV === 'development') {
debug('Pushing');
connectSrc = [''self'', 'ws://localhost:3001/'];
} else {
debug('Not');
connectSrc = [];
}*/
debug(trusted);
app.use(helmet.contentSecurityPolicy({
defaultSrc: trusted,
scriptSrc: ['*.optimizely.com'].concat(trusted),
'connect-src': process.env.NODE_ENV === 'development' ? ['ws://localhost:3001/', 'http://localhost:3001/'] : [],
'connect-src': ['ws://localhost:3001/', 'http://localhost:3001/'],
styleSrc: trusted,
imgSrc: ['*.evernote.com', '*.amazonaws.com', "data:"].concat(trusted),
fontSrc: ["'self", '*.googleapis.com'].concat(trusted),
imgSrc: ['*.evernote.com', '*.amazonaws.com', 'data:'].concat(trusted),
fontSrc: ['"self"', '*.googleapis.com'].concat(trusted),
mediaSrc: ['*.amazonaws.com', '*.twitter.com'],
frameSrc: ['*.gitter.im', '*.vimeo.com', '*.twitter.com'],
// sandbox: ['allow-forms', 'allow-scripts'],
@ -148,7 +152,9 @@ app.use(function(req, res, next) {
next();
});
app.use(express.static(path.join(__dirname, 'public'), { maxAge: 31557600000 }));
app.use(
express.static(path.join(__dirname, 'public'), { maxAge: 31557600000 })
);
/**
* Main routes.
@ -164,9 +170,19 @@ app.get('/jquery-exercises', resourcesController.jqueryExercises);
app.get('/live-pair-programming', resourcesController.livePairProgramming);
app.get('/javascript-in-your-inbox', resourcesController.javaScriptInYourInbox);
app.get('/chromebook', resourcesController.chromebook);
app.get('/pair-program-with-team-viewer', resourcesController.pairProgramWithTeamViewer);
app.get('/done-with-first-100-hours', resourcesController.doneWithFirst100Hours);
app.get('/programmer-interview-questions-app', resourcesController.programmerInterviewQuestionsApp);
app.get(
'/pair-program-with-team-viewer',
resourcesController.pairProgramWithTeamViewer
);
app.get(
'/done-with-first-100-hours',
resourcesController.doneWithFirst100Hours
);
app.get(
'/programmer-interview-questions-app',
resourcesController.programmerInterviewQuestionsApp
);
app.get('/about', resourcesController.about);
app.get('/login', userController.getLogin);
@ -187,12 +203,15 @@ app.post('/nonprofits', contactController.postContact);
app.post(
'/update-progress',
passportConf.isAuthenticated,
userController.updateProgress);
userController.updateProgress
);
app.get(
'/challenges/:challengeNumber',
passportConf.isAuthenticated,
challengesController.returnChallenge);
challengesController.returnChallenge
);
app.all('/account', passportConf.isAuthenticated);
app.get('/account', userController.getAccount);
app.post('/account/profile', userController.postUpdateProfile);
@ -207,11 +226,12 @@ app.get('/account/unlink/:provider', userController.getOauthUnlink);
*
*/
app.post('/completed_challenge', function(req, res) {
req.user.challengesHash[parseInt(req.body.challengeNumber)] = Math.round(+new Date() / 1000);
req.user.challengesHash[parseInt(req.body.challengeNumber)] =
Math.round(+ new Date() / 1000);
var ch = req.user.challengesHash;
var p = 0;
for (k in ch) {
if (ch[k] > 0) { p += 1}
for (var k in ch) {
if (ch[k] > 0) { p += 1; }
}
req.user.points = p;
req.user.save();
@ -220,7 +240,6 @@ app.post('/completed_challenge', function(req, res) {
/**
* OAuth sign-in routes.
*/
app.get('/auth/twitter', passport.authenticate('twitter'));
app.get(
'/auth/twitter/callback',
@ -246,20 +265,40 @@ app.get(
res.redirect(req.session.returnTo || '/');
});
app.get('/auth/facebook', passport.authenticate('facebook', { scope: ['email', 'user_location'] }));
app.get('/auth/facebook/callback', passport.authenticate('facebook', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) {
app.get(
'/auth/facebook',
passport.authenticate('facebook', { scope: ['email', 'user_location'] })
);
var passportOptions = {
successRedirect: '/',
failureRedirect: '/login'
};
app.get(
'/auth/facebook/callback',
passport.authenticate('facebook', passportOptions), function(req, res) {
res.redirect(req.session.returnTo || '/');
});
}
);
app.get('/auth/github', passport.authenticate('github'));
app.get('/auth/github/callback', passport.authenticate('github', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) {
app.get(
'/auth/github/callback',
passport.authenticate('github', passportOptions), function(req, res) {
res.redirect(req.session.returnTo || '/');
});
}
);
app.get('/auth/google', passport.authenticate('google', { scope: 'profile email' }));
app.get('/auth/google/callback', passport.authenticate('google', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) {
app.get(
'/auth/google',
passport.authenticate('google', { scope: 'profile email' })
);
app.get(
'/auth/google/callback',
passport.authenticate('google', passportOptions), function(req, res) {
res.redirect(req.session.returnTo || '/');
});
}
);
/**
* 500 Error Handler.

29
config/bootstrap.js vendored
View File

@ -1,22 +1,29 @@
var mongoose = require('mongoose');
var secrets = require('./secrets');
var mongoose = require('mongoose'),
debug = require('debug')('freecc:config:boot'),
secrets = require('./secrets'),
courses = require('../seed_data/courses.json'),
Course = require('./../models/Course'),
challenges = require('../seed_data/challenges.json'),
Challenge = require('./../models/Challenge');
mongoose.connect(secrets.db);
mongoose.connection.on('error', function() {
console.error('MongoDB Connection Error. Make sure MongoDB is running.');
});
var courses = require('../seed_data/courses.json');
var challenges = require('../seed_data/challenges.json');
Challenge = require ('./../models/Challenge');
Course = require ('./../models/Course');
Course.create(courses, function(err, data) {
if (err) console.log(err);
else console.log('Saved ', data );
if (err) {
debug(err);
} else {
debug('Saved ', data);
}
});
Challenge.create(challenges, function(err, data) {
if (err) console.log(err);
else console.log('Saved ', data );
if (err) {
console.log(err);
} else {
console.log('Saved ', data);
}
});

View File

@ -1,16 +1,21 @@
var _ = require('lodash');
var passport = require('passport');
var InstagramStrategy = require('passport-instagram').Strategy;
var LocalStrategy = require('passport-local').Strategy;
var FacebookStrategy = require('passport-facebook').Strategy;
var TwitterStrategy = require('passport-twitter').Strategy;
var GitHubStrategy = require('passport-github').Strategy;
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
var OAuthStrategy = require('passport-oauth').OAuthStrategy;
var OAuth2Strategy = require('passport-oauth').OAuth2Strategy;
var User = require('../models/User');
var secrets = require('./secrets');
var _ = require('lodash'),
passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
FacebookStrategy = require('passport-facebook').Strategy,
TwitterStrategy = require('passport-twitter').Strategy,
GitHubStrategy = require('passport-github').Strategy,
GoogleStrategy = require('passport-google-oauth').OAuth2Strategy,
LinkedInStrategy = require('passport-linkedin-oauth2').Strategy,
OAuthStrategy = require('passport-oauth').OAuthStrategy,
OAuth2Strategy = require('passport-oauth').OAuth2Strategy,
User = require('../models/User'),
secrets = require('./secrets');
// Login Required middleware.
module.exports = {
isAuthenticated: isAuthenticated,
isAuthorized: isAuthorized
};
passport.serializeUser(function(user, done) {
done(null, user.id);
@ -18,7 +23,8 @@ passport.serializeUser(function(user, done) {
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
if (err) { return done(err); }
done(null, user);
});
});
@ -55,23 +61,44 @@ passport.deserializeUser(function(id, done) {
*/
// Sign in with Twitter.
passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tokenSecret, profile, done) {
passport.use(
new TwitterStrategy(
secrets.twitter, function(req, accessToken, tokenSecret, profile, done) {
if (req.user) {
User.findOne({ twitter: profile.id }, function(err, existingUser) {
if (err) { return done(err); }
if (existingUser) {
req.flash('errors', { msg: 'There is already a Twitter account that belongs to you. Sign in with that account or delete it, then link it with your current account.' });
done(err);
req.flash('errors', {
msg: [
'There is already a Twitter account that belongs to you. ',
'Sign in with that account or delete it, then link it with ',
'your current account.'
].join('')
});
done();
} else {
User.findById(req.user.id, function(err, user) {
if (err) { return done(err); }
user.twitter = profile.id;
user.tokens.push({ kind: 'twitter', accessToken: accessToken, tokenSecret: tokenSecret });
user.tokens.push({
kind: 'twitter',
accessToken: accessToken,
tokenSecret: tokenSecret
});
user.profile.name = user.profile.name || profile.displayName;
user.profile.location = user.profile.location || profile._json.location;
user.profile.picture = user.profile.picture || profile._json.profile_image_url_https;
user.profile.location =
user.profile.location || profile._json.location;
user.profile.picture =
user.profile.picture || profile._json.profile_image_url_https;
user.profile.username = profile.username;
user.save(function(err) {
req.flash('info', { msg: 'Twitter account has been linked.' });
done(err, user);
done(null, user);
});
});
}
@ -79,77 +106,146 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok
} else {
User.findOne({ twitter: profile.id }, function(err, existingUser) {
if (err) { return done(err); }
//if (existingUser) return done(null, existingUser);
// Twitter will not provide an email address. Period.
// But a persons twitter username is guaranteed to be unique
// so we can "fake" a twitter email address as follows:
//user.email = profile.username + "@twitter.com";
var user = existingUser || new User;
var user = existingUser || new User();
user.twitter = profile.id;
user.email = user.email || '';
user.tokens.push({ kind: 'twitter', accessToken: accessToken, tokenSecret: tokenSecret });
user.profile.name = user.profile.name || profile.displayName;
user.profile.location = user.profile.location || profile._json.location;
user.profile.picture = user.profile.picture || profile._json.profile_image_url_https;
user.save(function(err) {
done(err, user);
user.tokens.push({
kind: 'twitter',
accessToken: accessToken,
tokenSecret: tokenSecret
});
user.profile.name = user.profile.name || profile.displayName;
user.profile.location =
user.profile.location || profile._json.location;
user.profile.picture =
user.profile.picture || profile._json.profile_image_url_https;
user.save(function(err) {
if (err) { return done(err); }
done(null, user);
});
//TODO: Twitter redirect to capture user email.
//if (!user.email) {
// req.redirect('/account');
// req.flash('errors', { msg: 'OK, you are signed in. Please add your email address to your profile.' });
// req.flash('errors', {
// msg:
// 'OK, you are signed in. ' +
// 'Please add your email address to your profile.'
// });
//}
});
}
}));
})
);
// Sign in with LinkedIn.
passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, refreshToken, profile, done) {
passport.use(
new LinkedInStrategy(
secrets.linkedin, function(req, accessToken, refreshToken, profile, done) {
if (req.user) {
User.findOne({ linkedin: profile.id }, function(err, existingUser) {
if (err) { return done(err); }
if (existingUser) {
req.flash('errors', { msg: 'There is already a LinkedIn account that belongs to you. Sign in with that account or delete it, then link it with your current account.' });
done(err);
req.flash('errors', {
msg: [
'There is already a LinkedIn account that belongs to you.',
'Sign in with that account or delete it,',
'then link it with your current account.'
].join('')
});
done();
} else {
User.findById(req.user.id, function(err, user) {
if (err) { return done(err); }
user.linkedin = profile.id;
user.tokens.push({ kind: 'linkedin', accessToken: accessToken });
user.tokens.push({
kind: 'linkedin',
accessToken: accessToken
});
user.profile.name = user.profile.name || profile.displayName;
user.profile.location = user.profile.location || profile._json.location.name;
user.profile.picture = user.profile.picture || profile._json.pictureUrl;
user.profile.website = user.profile.website || profile._json.publicProfileUrl;
user.save(function(err) {
req.flash('info', { msg: 'LinkedIn account has been linked.' });
done(err, user);
user.profile.location =
user.profile.location || profile._json.location.name;
user.profile.picture =
user.profile.picture || profile._json.pictureUrl;
user.profile.website =
user.profile.website || profile._json.publicProfileUrl;
user.save(function(err, _user) {
if (err) { return done(err); }
req.flash(
'info', { msg: 'LinkedIn account has been linked.' }
);
done(null, user);
});
});
}
});
} else {
User.findOne({ linkedin: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.emailAddress }, function(err, existingEmailUser) {
var user = existingEmailUser || new User;
if (err) { return done(err); }
if (existingUser) { return done(null, existingUser); }
User.findOne(
{ email: profile._json.emailAddress },
function(err, existingEmailUser) {
if (err) { return done(err); }
var user = existingEmailUser || new User();
user.linkedin = profile.id;
user.tokens.push({ kind: 'linkedin', accessToken: accessToken });
user.tokens.push({
kind: 'linkedin',
accessToken: accessToken
});
user.email = user.email || profile._json.emailAddress;
user.profile.name = user.profile.name || profile.displayName;
user.profile.location = user.profile.location || profile._json.location.name;
user.profile.picture = user.profile.picture || profile._json.pictureUrl;
user.profile.website = user.profile.website || profile._json.publicProfileUrl;
user.profile.location =
user.profile.location || profile._json.location.name;
user.profile.picture =
user.profile.picture || profile._json.pictureUrl;
user.profile.website =
user.profile.website || profile._json.publicProfileUrl;
user.challengesComplete = user.challengesCompleted || [];
user.save(function(err) {
done(err, user);
if (err) { return done(err); }
done(null, user);
});
});
});
}
}));
// Sign in using Email and Password.
passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, password, done) {
passport.use(
new LocalStrategy(
{ usernameField: 'email' }, function(email, password, done) {
User.findOne({ email: email }, function(err, user) {
if (!user) return done(null, false, { message: 'Email ' + email + ' not found'});
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Email ' + email + ' not found'});
}
user.comparePassword(password, function(err, isMatch) {
if (err) { return done(err); }
if (isMatch) {
return done(null, user);
} else {
@ -161,42 +257,87 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, passw
// Sign in with Facebook.
passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) {
passport.use(
new FacebookStrategy(
secrets.facebook, function(req, accessToken, refreshToken, profile, done) {
if (req.user) {
User.findOne({ facebook: profile.id }, function(err, existingUser) {
if (err) { return done(err); }
if (existingUser) {
req.flash('errors', { msg: 'There is already a Facebook account that belongs to you. Sign in with that account or delete it, then link it with your current account.' });
done(err);
req.flash('errors', {
msg: [
'There is already a Facebook account that belongs to you.',
'Sign in with that account or delete it, then link it with',
'your current account.'
].join(' ')
});
done();
} else {
User.findById(req.user.id, function(err, user) {
if (err) { return done(err); }
user.facebook = profile.id;
user.tokens.push({ kind: 'facebook', accessToken: accessToken });
user.tokens.push({
kind: 'facebook',
accessToken: accessToken
});
user.profile.name = user.profile.name || profile.displayName;
user.profile.gender = user.profile.gender || profile._json.gender;
user.profile.picture = user.profile.picture || 'https://graph.facebook.com/' + profile.id + '/picture?type=large';
user.profile.picture =
user.profile.picture ||
'https://graph.facebook.com/' +
profile.id +
'/picture?type=large';
user.save(function(err) {
req.flash('info', { msg: 'Facebook account has been linked.' });
done(err, user);
if (err) { return done(err); }
req.flash(
'info', { msg: 'Facebook account has been linked.' });
done(null, user);
});
});
}
});
} else {
User.findOne({ facebook: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
var user = existingEmailUser || new User;
if (err) { return done(err); }
if (existingUser) { return done(null, existingUser); }
User.findOne(
{ email: profile._json.email }, function(err, existingEmailUser) {
if (err) { return done(err); }
var user = existingEmailUser || new User();
user.email = user.email || profile._json.email;
user.facebook = profile.id;
user.tokens.push({ kind: 'facebook', accessToken: accessToken });
user.tokens.push({
kind: 'facebook',
accessToken: accessToken
});
user.profile.name = user.profile.name || profile.displayName;
user.profile.gender = user.profile.gender || profile._json.gender;
user.profile.picture = user.profile.picture || 'https://graph.facebook.com/' + profile.id + '/picture?type=large';
user.profile.location = user.profile.location || (profile._json.location) ? profile._json.location.name : '';
user.profile.gender =
user.profile.gender || profile._json.gender;
user.profile.picture =
user.profile.picture ||
'https://graph.facebook.com/' +
profile.id +
'/picture?type=large';
user.profile.location =
user.profile.location ||
(profile._json.location) ? profile._json.location.name : '';
user.challengesComplete = user.challengesCompleted || [];
user.save(function(err) {
done(err, user);
if (err) { return done(err); }
done(null, user);
});
});
});
@ -205,41 +346,78 @@ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, r
// Sign in with GitHub.
passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refreshToken, profile, done) {
passport.use(
new GitHubStrategy(
secrets.github, function(req, accessToken, refreshToken, profile, done) {
if (req.user) {
User.findOne({ github: profile.id }, function(err, existingUser) {
if (err) { return done(err); }
if (existingUser) {
req.flash('errors', { msg: 'There is already a GitHub account that belongs to you. Sign in with that account or delete it, then link it with your current account.' });
done(err);
req.flash('errors', {
msg: [
'There is already a GitHub account that belongs to you.',
'Sign in with that account or delete it, then link it with',
'your current account.'
].join(' ')
});
done();
} else {
User.findById(req.user.id, function(err, user) {
if (err) { return done(err); }
user.github = profile.id;
user.tokens.push({ kind: 'github', accessToken: accessToken });
user.profile.name = user.profile.name || profile.displayName;
user.profile.picture = user.profile.picture || profile._json.avatar_url;
user.profile.location = user.profile.location || profile._json.location;
user.profile.website = user.profile.website || profile._json.blog;
user.profile.picture =
user.profile.picture || profile._json.avatar_url;
user.profile.location =
user.profile.location || profile._json.location;
user.profile.website =
user.profile.website || profile._json.blog;
user.save(function(err) {
if (err) { return done(err); }
req.flash('info', { msg: 'GitHub account has been linked.' });
done(err, user);
done(null, user);
});
});
}
});
} else {
User.findOne({ github: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
var user = existingEmailUser || new User;
if (err) { return done(err); }
if (existingUser) { return done(null, existingUser); }
User.findOne(
{ email: profile._json.email }, function(err, existingEmailUser) {
if (err) { return done(err); }
var user = existingEmailUser || new User();
user.email = user.email || profile._json.email;
user.github = profile.id;
user.tokens.push({ kind: 'github', accessToken: accessToken });
user.tokens.push({
kind: 'github',
accessToken: accessToken
});
user.profile.name = user.profile.name || profile.displayName;
user.profile.picture = user.profile.picture || profile._json.avatar_url;
user.profile.location = user.profile.location || profile._json.location;
user.profile.website = user.profile.website || profile._json.blog;
user.profile.picture =
user.profile.picture || profile._json.avatar_url;
user.profile.location =
user.profile.location || profile._json.location;
user.profile.website =
user.profile.website || profile._json.blog;
user.save(function(err) {
done(err, user);
if (err) { return done(err); }
done(null, user);
});
});
});
@ -248,54 +426,87 @@ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refre
// Sign in with Google.
passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refreshToken, profile, done) {
passport.use(
new GoogleStrategy(
secrets.google, function(req, accessToken, refreshToken, profile, done) {
if (req.user) {
User.findOne({ google: profile.id }, function(err, existingUser) {
if (err) { return done(err); }
if (existingUser) {
req.flash('errors', { msg: 'There is already a Google account that belongs to you. Sign in with that account or delete it, then link it with your current account.' });
done(err);
req.flash('errors', {
msg: [
'There is already a Google account that belongs to you.',
'Sign in with that account or delete it,',
'then link it with your current account.'
].join(' ')
});
done();
} else {
User.findById(req.user.id, function(err, user) {
if (err) { return done(err); }
user.google = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken });
user.tokens.push({
kind: 'google',
accessToken: accessToken
});
user.profile.name = user.profile.name || profile.displayName;
user.profile.gender = user.profile.gender || profile._json.gender;
user.profile.picture = user.profile.picture || profile._json.picture;
user.profile.gender =
user.profile.gender || profile._json.gender;
user.profile.picture =
user.profile.picture || profile._json.picture;
user.save(function(err) {
if (err) { return done(err); }
req.flash('info', { msg: 'Google account has been linked.' });
done(err, user);
done(null, user);
});
});
}
});
} else {
User.findOne({ google: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
var user = existingEmailUser || new User;
if (err) { return done(err); }
if (existingUser) { return done(null, existingUser); }
User.findOne(
{ email: profile._json.email }, function(err, existingEmailUser) {
if (err) { return done(err); }
var user = existingEmailUser || new User();
user.email = user.email || profile._json.email;
user.google = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken });
user.profile.name = user.profile.name || profile.displayName;
user.profile.gender = user.profile.gender || profile._json.gender;
user.profile.picture = user.profile.picture || profile._json.picture;
user.tokens.push({
kind: 'google',
accessToken: accessToken
});
user.profile.name =
user.profile.name || profile.displayName;
user.profile.gender =
user.profile.gender || profile._json.gender;
user.profile.picture =
user.profile.picture || profile._json.picture;
//TODO: Greeting email
//if (!existingEmailUser) {
// sendWelcomeEmail(user);
//}
user.save(function(err) {
done(err, user);
if (err) { return done(err); }
done(null, user);
});
});
});
}
}));
// Login Required middleware.
module.exports = {
isAuthenticated: isAuthenticated,
isAuthorized: isAuthorized
};
function isAuthenticated(req, res, next) {
if (req.isAuthenticated()) return next();
@ -312,108 +523,3 @@ function isAuthorized(req, res, next) {
res.redirect('/auth/' + provider);
}
}
/*
passport.use(new InstagramStrategy(secrets.instagram,function(req, accessToken, refreshToken, profile, done) {
if (req.user) {
User.findOne({ instagram: profile.id }, function(err, existingUser) {
if (existingUser) {
req.flash('errors', { msg: 'There is already an Instagram account that belongs to you. Sign in with that account or delete it, then link it with your current account.' });
done(err);
} else {
User.findById(req.user.id, function(err, user) {
user.instagram = profile.id;
user.tokens.push({ kind: 'instagram', accessToken: accessToken });
user.profile.name = user.profile.name || profile.displayName;
user.profile.picture = user.profile.picture || profile._json.data.profile_picture;
user.profile.website = user.profile.website || profile._json.data.website;
user.save(function(err) {
req.flash('info', { msg: 'Instagram account has been linked.' });
done(err, user);
});
});
}
});
} else {
User.findOne({ instagram: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser);
var user = new User();
user.instagram = profile.id;
user.tokens.push({ kind: 'instagram', accessToken: accessToken });
user.profile.name = profile.displayName;
// Similar to Twitter API, assigns a temporary e-mail address
// to get on with the registration process. It can be changed later
// to a valid e-mail address in Profile Management.
user.email = profile.username + "@instagram.com";
user.profile.website = profile._json.data.website;
user.profile.picture = profile._json.data.profile_picture;
user.save(function(err) {
done(err, user);
});
});
}
}));
// Tumblr API setup.
passport.use('tumblr', new OAuthStrategy({
requestTokenURL: 'http://www.tumblr.com/oauth/request_token',
accessTokenURL: 'http://www.tumblr.com/oauth/access_token',
userAuthorizationURL: 'http://www.tumblr.com/oauth/authorize',
consumerKey: secrets.tumblr.consumerKey,
consumerSecret: secrets.tumblr.consumerSecret,
callbackURL: secrets.tumblr.callbackURL,
passReqToCallback: true
},
function(req, token, tokenSecret, profile, done) {
User.findById(req.user._id, function(err, user) {
user.tokens.push({ kind: 'tumblr', accessToken: token, tokenSecret: tokenSecret });
user.save(function(err) {
done(err, user);
});
});
}
));
// Foursquare API setup.
passport.use('foursquare', new OAuth2Strategy({
authorizationURL: 'https://foursquare.com/oauth2/authorize',
tokenURL: 'https://foursquare.com/oauth2/access_token',
clientID: secrets.foursquare.clientId,
clientSecret: secrets.foursquare.clientSecret,
callbackURL: secrets.foursquare.redirectUrl,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
User.findById(req.user._id, function(err, user) {
user.tokens.push({ kind: 'foursquare', accessToken: accessToken });
user.save(function(err) {
done(err, user);
});
});
}
));
// Venmo API setup.
passport.use('venmo', new OAuth2Strategy({
authorizationURL: 'https://api.venmo.com/v1/oauth/authorize',
tokenURL: 'https://api.venmo.com/v1/oauth/access_token',
clientID: secrets.venmo.clientId,
clientSecret: secrets.venmo.clientSecret,
callbackURL: secrets.venmo.redirectUrl,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
User.findById(req.user._id, function(err, user) {
user.tokens.push({ kind: 'venmo', accessToken: accessToken });
user.save(function(err) {
done(err, user);
});
});
}
));
*/

View File

@ -1,602 +0,0 @@
var secrets = require('../config/secrets');
var User = require('../models/User');
var querystring = require('querystring');
var validator = require('validator');
var async = require('async');
var cheerio = require('cheerio');
var request = require('request');
var graph = require('fbgraph');
var LastFmNode = require('lastfm').LastFmNode;
var tumblr = require('tumblr.js');
var foursquare = require('node-foursquare')({ secrets: secrets.foursquare });
var Github = require('github-api');
var Twit = require('twit');
var stripe = require('stripe')(secrets.stripe.secretKey);
var twilio = require('twilio')(secrets.twilio.sid, secrets.twilio.token);
var Linkedin = require('node-linkedin')(secrets.linkedin.clientID, secrets.linkedin.clientSecret, secrets.linkedin.callbackURL);
var clockwork = require('clockwork')({key: secrets.clockwork.apiKey});
var ig = require('instagram-node').instagram();
var Y = require('yui/yql');
var _ = require('lodash');
/**
* GET /api
* List of API examples.
*/
exports.getApi = function(req, res) {
res.render('api/index', {
title: 'API Examples'
});
};
/**
* GET /api/foursquare
* Foursquare API example.
*/
exports.getFoursquare = function(req, res, next) {
var token = _.find(req.user.tokens, { kind: 'foursquare' });
async.parallel({
trendingVenues: function(callback) {
foursquare.Venues.getTrending('40.7222756', '-74.0022724', { limit: 50 }, token.accessToken, function(err, results) {
callback(err, results);
});
},
venueDetail: function(callback) {
foursquare.Venues.getVenue('49da74aef964a5208b5e1fe3', token.accessToken, function(err, results) {
callback(err, results);
});
},
userCheckins: function(callback) {
foursquare.Users.getCheckins('self', null, token.accessToken, function(err, results) {
callback(err, results);
});
}
},
function(err, results) {
if (err) return next(err);
res.render('api/foursquare', {
title: 'Foursquare API',
trendingVenues: results.trendingVenues,
venueDetail: results.venueDetail,
userCheckins: results.userCheckins
});
});
};
/**
* GET /api/tumblr
* Tumblr API example.
*/
exports.getTumblr = function(req, res, next) {
var token = _.find(req.user.tokens, { kind: 'tumblr' });
var client = tumblr.createClient({
consumer_key: secrets.tumblr.consumerKey,
consumer_secret: secrets.tumblr.consumerSecret,
token: token.accessToken,
token_secret: token.tokenSecret
});
client.posts('withinthisnightmare.tumblr.com', { type: 'photo' }, function(err, data) {
if (err) return next(err);
res.render('api/tumblr', {
title: 'Tumblr API',
blog: data.blog,
photoset: data.posts[0].photos
});
});
};
/**
* GET /api/facebook
* Facebook API example.
*/
exports.getFacebook = function(req, res, next) {
var token = _.find(req.user.tokens, { kind: 'facebook' });
graph.setAccessToken(token.accessToken);
async.parallel({
getMe: function(done) {
graph.get(req.user.facebook, function(err, me) {
done(err, me);
});
},
getMyFriends: function(done) {
graph.get(req.user.facebook + '/friends', function(err, friends) {
done(err, friends.data);
});
}
},
function(err, results) {
if (err) return next(err);
res.render('api/facebook', {
title: 'Facebook API',
me: results.getMe,
friends: results.getMyFriends
});
});
};
/**
* GET /api/scraping
* Web scraping example using Cheerio library.
*/
exports.getScraping = function(req, res, next) {
request.get('https://news.ycombinator.com/', function(err, request, body) {
if (err) return next(err);
var $ = cheerio.load(body);
var links = [];
$('.title a[href^="http"], a[href^="https"]').each(function() {
links.push($(this));
});
res.render('api/scraping', {
title: 'Web Scraping',
links: links
});
});
};
/**
* GET /api/github
* GitHub API Example.
*/
exports.getGithub = function(req, res, next) {
var token = _.find(req.user.tokens, { kind: 'github' });
var github = new Github({ token: token.accessToken });
var repo = github.getRepo('sahat', 'requirejs-library');
repo.show(function(err, repo) {
if (err) return next(err);
res.render('api/github', {
title: 'GitHub API',
repo: repo
});
});
};
/**
* GET /api/aviary
* Aviary image processing example.
*/
exports.getAviary = function(req, res) {
res.render('api/aviary', {
title: 'Aviary API'
});
};
/**
* GET /api/nyt
* New York Times API example.
*/
exports.getNewYorkTimes = function(req, res, next) {
var query = querystring.stringify({ 'api-key': secrets.nyt.key, 'list-name': 'young-adult' });
var url = 'http://api.nytimes.com/svc/books/v2/lists?' + query;
request.get(url, function(err, request, body) {
if (err) return next(err);
if (request.statusCode === 403) return next(Error('Missing or Invalid New York Times API Key'));
var bestsellers = JSON.parse(body);
res.render('api/nyt', {
title: 'New York Times API',
books: bestsellers.results
});
});
};
/**
* GET /api/lastfm
* Last.fm API example.
*/
exports.getLastfm = function(req, res, next) {
var lastfm = new LastFmNode(secrets.lastfm);
async.parallel({
artistInfo: function(done) {
lastfm.request('artist.getInfo', {
artist: 'The Pierces',
handlers: {
success: function(data) {
done(null, data);
},
error: function(err) {
done(err);
}
}
});
},
artistTopTracks: function(done) {
lastfm.request('artist.getTopTracks', {
artist: 'The Pierces',
handlers: {
success: function(data) {
var tracks = [];
_.each(data.toptracks.track, function(track) {
tracks.push(track);
});
done(null, tracks.slice(0,10));
},
error: function(err) {
done(err);
}
}
});
},
artistTopAlbums: function(done) {
lastfm.request('artist.getTopAlbums', {
artist: 'The Pierces',
handlers: {
success: function(data) {
var albums = [];
_.each(data.topalbums.album, function(album) {
albums.push(album.image.slice(-1)[0]['#text']);
});
done(null, albums.slice(0, 4));
},
error: function(err) {
done(err);
}
}
});
}
},
function(err, results) {
if (err) return next(err.message);
var artist = {
name: results.artistInfo.artist.name,
image: results.artistInfo.artist.image.slice(-1)[0]['#text'],
tags: results.artistInfo.artist.tags.tag,
bio: results.artistInfo.artist.bio.summary,
stats: results.artistInfo.artist.stats,
similar: results.artistInfo.artist.similar.artist,
topAlbums: results.artistTopAlbums,
topTracks: results.artistTopTracks
};
res.render('api/lastfm', {
title: 'Last.fm API',
artist: artist
});
});
};
/**
* GET /api/twitter
* Twiter API example.
*/
exports.getTwitter = function(req, res, next) {
var token = _.find(req.user.tokens, { kind: 'twitter' });
var T = new Twit({
consumer_key: secrets.twitter.consumerKey,
consumer_secret: secrets.twitter.consumerSecret,
access_token: token.accessToken,
access_token_secret: token.tokenSecret
});
T.get('search/tweets', { q: 'nodejs since:2013-01-01', geocode: '40.71448,-74.00598,5mi', count: 10 }, function(err, reply) {
if (err) return next(err);
res.render('api/twitter', {
title: 'Twitter API',
tweets: reply.statuses
});
});
};
/**
* POST /api/twitter
* Post a tweet.
*/
exports.postTwitter = function(req, res, next) {
req.assert('tweet', 'Tweet cannot be empty.').notEmpty();
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
return res.redirect('/api/twitter');
}
var token = _.find(req.user.tokens, { kind: 'twitter' });
var T = new Twit({
consumer_key: secrets.twitter.consumerKey,
consumer_secret: secrets.twitter.consumerSecret,
access_token: token.accessToken,
access_token_secret: token.tokenSecret
});
T.post('statuses/update', { status: req.body.tweet }, function(err, data, response) {
if (err) return next(err);
req.flash('success', { msg: 'Tweet has been posted.'});
res.redirect('/api/twitter');
});
};
/**
* GET /api/steam
* Steam API example.
*/
exports.getSteam = function(req, res, next) {
var steamId = '76561197982488301';
var query = { l: 'english', steamid: steamId, key: secrets.steam.apiKey };
async.parallel({
playerAchievements: function(done) {
query.appid = '49520';
var qs = querystring.stringify(query);
request.get({ url: 'http://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v0001/?' + qs, json: true }, function(error, request, body) {
if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key'));
done(error, body);
});
},
playerSummaries: function(done) {
query.steamids = steamId;
var qs = querystring.stringify(query);
request.get({ url: 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?' + qs, json: true }, function(err, request, body) {
if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key'));
done(err, body);
});
},
ownedGames: function(done) {
query.include_appinfo = 1;
query.include_played_free_games = 1;
var qs = querystring.stringify(query);
request.get({ url: 'http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?' + qs, json: true }, function(err, request, body) {
if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key'));
done(err, body);
});
}
},
function(err, results) {
if (err) return next(err);
res.render('api/steam', {
title: 'Steam Web API',
ownedGames: results.ownedGames.response.games,
playerAchievemments: results.playerAchievements.playerstats,
playerSummary: results.playerSummaries.response.players[0]
});
});
};
/**
* GET /api/stripe
* Stripe API example.
*/
exports.getStripe = function(req, res) {
res.render('api/stripe', {
title: 'Stripe API',
publishableKey: secrets.stripe.publishableKey
});
};
/**
* POST /api/stripe
* Make a payment.
*/
exports.postStripe = function(req, res, next) {
var stripeToken = req.body.stripeToken;
var stripeEmail = req.body.stripeEmail;
stripe.charges.create({
amount: 395,
currency: 'usd',
card: stripeToken,
description: stripeEmail
}, function(err, charge) {
if (err && err.type === 'StripeCardError') {
req.flash('errors', { msg: 'Your card has been declined.' });
res.redirect('/api/stripe');
}
req.flash('success', { msg: 'Your card has been charged successfully.' });
res.redirect('/api/stripe');
});
};
/**
* GET /api/twilio
* Twilio API example.
*/
exports.getTwilio = function(req, res) {
res.render('api/twilio', {
title: 'Twilio API'
});
};
/**
* POST /api/twilio
* Send a text message using Twilio.
*/
exports.postTwilio = function(req, res, next) {
req.assert('number', 'Phone number is required.').notEmpty();
req.assert('message', 'Message cannot be blank.').notEmpty();
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
return res.redirect('/api/twilio');
}
var message = {
to: req.body.number,
from: '+13472235148',
body: req.body.message
};
twilio.sendMessage(message, function(err, responseData) {
if (err) return next(err.message);
req.flash('success', { msg: 'Text sent to ' + responseData.to + '.'});
res.redirect('/api/twilio');
});
};
/**
* GET /api/clockwork
* Clockwork SMS API example.
*/
exports.getClockwork = function(req, res) {
res.render('api/clockwork', {
title: 'Clockwork SMS API'
});
};
/**
* POST /api/clockwork
* Send a text message using Clockwork SMS
*/
exports.postClockwork = function(req, res, next) {
var message = {
To: req.body.telephone,
From: 'Hackathon',
Content: 'Hello from the Hackathon Starter'
};
clockwork.sendSms(message, function(err, responseData) {
if (err) return next(err.errDesc);
req.flash('success', { msg: 'Text sent to ' + responseData.responses[0].to });
res.redirect('/api/clockwork');
});
};
/**
* GET /api/venmo
* Venmo API example.
*/
exports.getVenmo = function(req, res, next) {
var token = _.find(req.user.tokens, { kind: 'venmo' });
var query = querystring.stringify({ access_token: token.accessToken });
async.parallel({
getProfile: function(done) {
request.get({ url: 'https://api.venmo.com/v1/me?' + query, json: true }, function(err, request, body) {
done(err, body);
});
},
getRecentPayments: function(done) {
request.get({ url: 'https://api.venmo.com/v1/payments?' + query, json: true }, function(err, request, body) {
done(err, body);
});
}
},
function(err, results) {
if (err) return next(err);
res.render('api/venmo', {
title: 'Venmo API',
profile: results.getProfile.data,
recentPayments: results.getRecentPayments.data
});
});
};
/**
* POST /api/venmo
* Send money.
*/
exports.postVenmo = function(req, res, next) {
req.assert('user', 'Phone, Email or Venmo User ID cannot be blank').notEmpty();
req.assert('note', 'Please enter a message to accompany the payment').notEmpty();
req.assert('amount', 'The amount you want to pay cannot be blank').notEmpty();
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
return res.redirect('/api/venmo');
}
var token = _.find(req.user.tokens, { kind: 'venmo' });
var formData = {
access_token: token.accessToken,
note: req.body.note,
amount: req.body.amount
};
if (validator.isEmail(req.body.user)) {
formData.email = req.body.user;
} else if (validator.isNumeric(req.body.user) &&
validator.isLength(req.body.user, 10, 11)) {
formData.phone = req.body.user;
} else {
formData.user_id = req.body.user;
}
request.post('https://api.venmo.com/v1/payments', { form: formData }, function(err, request, body) {
if (err) return next(err);
if (request.statusCode !== 200) {
req.flash('errors', { msg: JSON.parse(body).error.message });
return res.redirect('/api/venmo');
}
req.flash('success', { msg: 'Venmo money transfer complete' });
res.redirect('/api/venmo');
});
};
/**
* GET /api/linkedin
* LinkedIn API example.
*/
exports.getLinkedin = function(req, res, next) {
var token = _.find(req.user.tokens, { kind: 'linkedin' });
var linkedin = Linkedin.init(token.accessToken);
linkedin.people.me(function(err, $in) {
if (err) return next(err);
res.render('api/linkedin', {
title: 'LinkedIn API',
profile: $in
});
});
};
/**
* GET /api/instagram
* Instagram API example.
*/
exports.getInstagram = function(req, res, next) {
var token = _.find(req.user.tokens, { kind: 'instagram' });
ig.use({ client_id: secrets.instagram.clientID, client_secret: secrets.instagram.clientSecret });
ig.use({ access_token: token.accessToken });
async.parallel({
searchByUsername: function(done) {
ig.user_search('richellemead', function(err, users, limit) {
done(err, users);
});
},
searchByUserId: function(done) {
ig.user('175948269', function(err, user) {
done(err, user);
});
},
popularImages: function(done) {
ig.media_popular(function(err, medias) {
done(err, medias);
});
},
myRecentMedia: function(done) {
ig.user_self_media_recent(function(err, medias, pagination, limit) {
done(err, medias);
});
}
}, function(err, results) {
if (err) return next(err);
res.render('api/instagram', {
title: 'Instagram API',
usernames: results.searchByUsername,
userById: results.searchByUserId,
popularImages: results.popularImages,
myRecentMedia: results.myRecentMedia
});
});
};
/**
* GET /api/yahoo
* Yahoo API example.
*/
exports.getYahoo = function(req, res) {
Y.YQL('SELECT * FROM weather.forecast WHERE (location = 10007)', function(response) {
var location = response.query.results.channel.location;
var condition = response.query.results.channel.item.condition;
res.render('api/yahoo', {
title: 'Yahoo API',
location: location,
condition: condition
});
});
};

View File

@ -2,16 +2,21 @@
* GET /
* Challenges.
*/
var Challenge = require('./../models/Challenge');
var _ = require('lodash');
var _ = require('lodash'),
debug = require('debug')('freecc:cntr:challenges'),
Challenge = require('./../models/Challenge');
exports.returnChallenge = function(req, res, next) {
var challengeNumber = parseInt(req.params.challengeNumber) || 0;
if (challengeNumber > 59) { challengeNumber = 0; }
Challenge.findOne({challengeNumber: challengeNumber}, function (err, c) {
if (challengeNumber > 59) {
challengeNumber = 0;
}
Challenge.findOne({ challengeNumber: challengeNumber }, function(err, c) {
if (err) {
console.error('Challenge err: ', err);
next(err);
debug('Challenge err: ', err);
return next(err);
}
res.render('challenges/show', {
title: 'Challenge: ' + c.name,

View File

@ -1,5 +1,7 @@
var secrets = require('../config/secrets');
var nodemailer = require("nodemailer");
var nodemailer = require('nodemailer'),
debug = require('debug')('freecc:cntr:contact'),
secrets = require('../config/secrets');
var transporter = nodemailer.createTransport({
service: 'Mandrill',
auth: {
@ -22,9 +24,6 @@ exports.getContact = function(req, res) {
/**
* POST /contact
* Send a contact form via Nodemailer.
* @param email
* @param name
* @param message
*/
exports.postContact = function(req, res) {
@ -32,24 +31,17 @@ exports.postContact = function(req, res) {
req.assert('email', 'Email is not valid').isEmail();
req.assert('message', 'Message cannot be blank').notEmpty();
var errors = req.validationErrors();
if (errors) {
if (req.validationErrors()) {
req.flash('errors', errors);
return res.redirect('/nonprofits');
}
var from = req.body.email;
var name = req.body.name;
var body = req.body.message;
var to = 'team@freecodecamp.com';
var subject = "CodeNonprofit Project Idea from " + name;
var mailOptions = {
to: to,
from: from,
subject: subject,
text: body
to: 'team@freecodecamp.com',
name: req.body.name,
from: req.body.email,
subject: 'CodeNonprofit Project Idea from ' + name,
text: req.body.message
};
transporter.sendMail(mailOptions, function(err) {

View File

@ -7,13 +7,13 @@ exports.index = function(req, res) {
if (req.user) {
ch = req.user.challengesHash;
if (req.user.challengesHash[0] > 0) {
var max = Object.keys(ch).reduce(function(max,key){
var max = Object.keys(ch).reduce(function(max, key) {
return (max === undefined || ch[key] > ch[max]) ? +key : max;
});
nextChallenge = max + 1;
res.redirect("challenges/" + nextChallenge);
res.redirect('challenges/' + nextChallenge);
} else {
res.redirect("challenges/0");
res.redirect('challenges/0');
}
} else {
res.render('home', {

View File

@ -1,271 +1,98 @@
var User = require('../models/User'),
resources = require('./resources.json'),
questions = resources.questions,
steps = resources.steps;
var User = require('../models/User');
var totalUsers = User.count({}, function( err, count){
count;
//NOTE(BERKS): Async, total users may not available before it is used.
var totalUsers = 0;
User.count({}, function(err, count) {
totalUsers = count;
});
//var usersOverTenChallenges = User.where: "this.challengesCompleted && this.challengesCompleted.length >= 10"
/**
* GET /
* Resources.
*/
exports.learnToCode = function(req, res) {
//TODO: Stats view
module.exports = {
learnToCode: function(req, res) {
res.render('learn-to-code', {
title: 'Learn to Code'
});
}
},
exports.privacy = function(req, res) {
privacy: function privacy(req, res) {
res.render('privacy', {
title: 'Privacy'
});
}
},
exports.statistics = function(req, res) {
statistics: function statistics(req, res) {
res.render('statistics', {
title: 'Code Camper Statistics',
title: 'Code Camper Statistics'
//totalUsers: totalUsers,
//usersOverTenChallenges: usersOverTenChallenges
});
}
},
exports.chromebook = function(req, res) {
chromebook: function chromebook(req, res) {
res.render('chromebook', {
title: 'Win a Chromebook'
});
}
},
exports.jqueryExercises = function(req, res) {
jqueryExercises: function jqueryExercises(req, res) {
res.render('jquery-exercises', {
title: 'jQuery Exercises'
});
}
},
exports.livePairProgramming = function(req, res) {
livePairProgramming: function(req, res) {
res.render('live-pair-programming', {
title: 'Live Pair Programming'
});
}
},
exports.javaScriptInYourInbox = function(req, res) {
javaScriptInYourInbox: function(req, res) {
res.render('javascript-in-your-inbox', {
title: 'JavaScript in your Inbox'
});
}
},
exports.programmerInterviewQuestionsApp = function(req, res) {
programmerInterviewQuestionsApp: function(req, res) {
res.render('programmer-interview-questions-app', {
title: 'Programmer Interview Questions App'
});
}
},
exports.pairProgramWithTeamViewer = function(req, res) {
pairProgramWithTeamViewer: function(req, res) {
res.render('pair-program-with-team-viewer', {
title: 'Challenge: Pair Program with Team Viewer',
name: "Pair Program with Team Viewer",
name: 'Pair Program with Team Viewer',
video: '',
time: 30,
steps: ["In the spirit of keeping Free Code Camp 100% free, we've created directions for using a totally free pair programming tool called Team Viewer. It's not as user-friendly as Screen Hero, but it gets the job done (and works on Linux!).",
"Go to <a href='http://www.teamviewer.com/en/index.aspx' target='_blank'>http://www.teamviewer.com/en/index.aspx</a> and download Team Viewer. Be sure not to download the beta version, which isn't compatible with the current stable version everyone else will be using.",
"Install it and launch it",
"Go to the chat room and ask if anyone wants to pair program with you on a CoderByte challenge.",
"If someone is interested, they will be your \"pair\" - the person you pair programming with.",
"<strong>First, you will pair program on your pair's computer.</strong> Ask your pair for his or her Team Viewer ID and password.",
"Enter this id, and then password, to start the session.",
"Once the Team Viewer session starts, look at the the top of the screen. You will see 6 buttons. If you hover your cursor over the buttons, they will slide down so that you can read them. Click the audio/video button. This will allow you to turn on Voice Over IP and unmute your microphone, opening up your voice channel.",
"Note that you can now control your pair's keyboard and mouse, enabling you to step in and code yourself on your pair's computer when desired",
"Now you can click the X to end the pair programming session.",
"<strong>Next, you will pair program on your computer.</strong> Copy your Team Viewer ID and paste it into the private chat, so that your pair can use it to connect with you.",
"You will need to share your randomly generated password with your pair as well.",
"Once your pair connects, you will see a Team Viewer side menu. ",
"Click the audio button that drops down.",
"Click the headset icon and choose Voice over IP",
"Click the microphone button to unmute your microphone. Once your pair does the same, you two will have an open voice channel.",
"Now you can click the X to end the pair programming session.",
"Now it's time to tackle CoderByte.",
"Create a CoderByte account at <a href='http://coderbyte.com/sl/' target='_blank'>http://coderbyte.com/sl/</a>",
"Now go to <a href='http://coderbyte.com/CodingArea/Challenges/#easyChals' target='_blank'>http://coderbyte.com/CodingArea/Challenges/#easyChals</a> and start working through Coderbyte's easy algorithm scripting challenges using JavaScript.",
"When you are finished pair programming, click the X to end the session.",
"Congratulations! You have completed your first pair programming session.",
"You should pair program with different Code Campers until you've completed all the Easy, Medium and Hard CoderByte challenges. This is a big time investment, but the JavaScript practice you'll get, along with the scripting and algorithm experience, are well worth it!",
"You can complete CoderByte problems while you continue to work through Free Code Camp's challenges.",
"Be sure to pair program on these challenges, and remember to apply the RSAP methodology.",
"Click the button below to return to the Pair Programming challenge, then mark it complete."],
steps: steps,
cc: req.user.challengesHash
});
}
exports.about = function(req, res) {
},
about: function(req, res) {
res.render('about', {
title: 'About Free Code Camp and Our Team of Volunteers'
});
}
},
exports.doneWithFirst100Hours = function(req, res) {
doneWithFirst100Hours: function(req, res) {
res.render('done-with-first-100-hours', {
title: 'Congratulations on finishing the first 100 hours of Free Code Camp!'
title:
'Congratulations on finishing the first 100 hours of Free Code Camp!'
});
}
},
exports.interviewQuestions = function(req, res) {
res.json([
{
question: "Time Complexity of Accessing Array Index (int a = ARR[5];)",
answer: "O(1)"
},
{
question: "Time Complexity of Inserting a node in Linked List",
answer: "O(1)"
},
{
question: "Time Complexity of Pushing and Popping on Stack",
answer: "O(1)"
},
{
question: "Time Complexity of Insertion and Removal from Queue",
answer: "O(1)"
},
{
question: "Time Complexity of Finding out the parent or left/right child of a node in a tree stored in Array",
answer: "O(1)"
},
{
question: "Time Complexity of Jumping to Next/Previous element in Doubly Linked List",
answer: "O(1)"
},
{
question: "Time Complexity of Traversing an array",
answer: "O(n)"
},
{
question: "Time Complexity of Traversing a linked list",
answer: "O(n)"
},
{
question: "Time Complexity of Linear Search",
answer: "O(n)"
},
{
question: "Time Complexity of Deletion of a specific element in a Linked List (Not sorted)",
answer: "O(n)"
},
{
question: "Time Complexity of Comparing two strings",
answer: "O(n)"
},
{
question: "Time Complexity of Checking for Palindrome",
answer: "O(n)"
},
{
question: "Time Complexity of Counting/Bucket Sort",
answer: "O(n)"
},
{
question: "Time Complexity of Binary Search",
answer: "O(log n)"
},
{
question: "Time Complexity of Finding largest/smallest number in a binary search tree",
answer: "O(log n)"
},
{
question: "Time Complexity of Certain Divide and Conquer Algorithms based on Linear functionality",
answer: "O(log n)"
},
{
question: "Time Complexity of Calculating Fibonacci Numbers - Best Method",
answer: "O(log n)"
},
{
question: "Time Complexity of Merge Sort",
answer: "O(nlogn)"
},
{
question: "Time Complexity of Heap Sort",
answer: "O(nlogn)"
},
{
question: "Time Complexity of Quick Sort",
answer: "O(nlogn)"
},
{
question: "Time Complexity of Certain Divide and Conquer Algorithms based on optimizing O(n^2) algorithms",
answer: "O(nlogn)"
},
{
question: "Time Complexity of Bubble Sort",
answer: "O(n^2)"
},
{
question: "Time Complexity of Insertion Sort",
answer: "O(n^2)"
},
{
question: "Time Complexity of Selection Sort",
answer: "O(n^2)"
},
{
question: "Time Complexity of Traversing a simple 2D array",
answer: "O(n^2)"
},
{
question: "Latency of L1 cache reference",
answer: "0.5 nanoseconds"
},
{
question: "Latency of Branch mispredict",
answer: "5 nanoseconds"
},
{
question: "Latency of L2 cache reference",
answer: "7 nanoseconds"
},
{
question: "Latency of Mutex lock/unlock",
answer: "25 nanoseconds"
},
{
question: "Latency of Main memory reference",
answer: "100 nanoseconds"
},
{
question: "Latency of Compressing 1K bytes with Zippy",
answer: "3,000 nanoseconds"
},
{
question: "Latency of Sending 1K bytes over a 1 Gbps network",
answer: "10,000 nanoseconds"
},
{
question: "Latency of Reading 4K randomly from SSD",
answer: "150,000 nanoseconds"
},
{
question: "Latency of Reading 1 MB sequentially from memory",
answer: "250,000 nanoseconds"
},
{
question: "Latency of a Round trip within the same datacenter",
answer: "500,000 nanoseconds"
},
{
question: "Latency of Reading 1 MB sequentially from SSD",
answer: "1,000,000 nanoseconds"
},
{
question: "Latency of Disk seek",
answer: "10,000,000 nanoseconds"
},
{
question: "Latency of Reading 1 MB sequentially from disk",
answer: "20,000,000 nanoseconds"
},
{
question: "Latency of Sending a packet from California to the Netherlands and back",
answer: "150,000,000 nanoseconds"
interviewQuestions: function(req, res) {
res.json(questions);
}
]);
};

148
controllers/resources.json Normal file
View File

@ -0,0 +1,148 @@
{
"questions": [{
"question": "Time Complexity of Accessing Array Index (int a = ARR[5];)",
"answer": "O(1)"
}, {
"question": "Time Complexity of Inserting a node in Linked List",
"answer": "O(1)"
}, {
"question": "Time Complexity of Pushing and Popping on Stack",
"answer": "O(1)"
}, {
"question": "Time Complexity of Insertion and Removal from Queue",
"answer": "O(1)"
}, {
"question": "Time Complexity of Finding out the parent or left/right child of a node in a tree stored in Array",
"answer": "O(1)"
}, {
"question": "Time Complexity of Jumping to Next/Previous element in Doubly Linked List",
"answer": "O(1)"
}, {
"question": "Time Complexity of Traversing an array",
"answer": "O(n)"
}, {
"question": "Time Complexity of Traversing a linked list",
"answer": "O(n)"
}, {
"question": "Time Complexity of Linear Search",
"answer": "O(n)"
}, {
"question": "Time Complexity of Deletion of a specific element in a Linked List (Not sorted)",
"answer": "O(n)"
}, {
"question": "Time Complexity of Comparing two strings",
"answer": "O(n)"
}, {
"question": "Time Complexity of Checking for Palindrome",
"answer": "O(n)"
}, {
"question": "Time Complexity of Counting/Bucket Sort",
"answer": "O(n)"
}, {
"question": "Time Complexity of Binary Search",
"answer": "O(log n)"
}, {
"question": "Time Complexity of Finding largest/smallest number in a binary search tree",
"answer": "O(log n)"
}, {
"question": "Time Complexity of Certain Divide and Conquer Algorithms based on Linear functionality",
"answer": "O(log n)"
}, {
"question": "Time Complexity of Calculating Fibonacci Numbers - Best Method",
"answer": "O(log n)"
}, {
"question": "Time Complexity of Merge Sort",
"answer": "O(nlogn)"
}, {
"question": "Time Complexity of Heap Sort",
"answer": "O(nlogn)"
}, {
"question": "Time Complexity of Quick Sort",
"answer": "O(nlogn)"
}, {
"question": "Time Complexity of Certain Divide and Conquer Algorithms based on optimizing O(n^2) algorithms",
"answer": "O(nlogn)"
}, {
"question": "Time Complexity of Bubble Sort",
"answer": "O(n^2)"
}, {
"question": "Time Complexity of Insertion Sort",
"answer": "O(n^2)"
}, {
"question": "Time Complexity of Selection Sort",
"answer": "O(n^2)"
}, {
"question": "Time Complexity of Traversing a simple 2D array",
"answer": "O(n^2)"
}, {
"question": "Latency of L1 cache reference",
"answer": "0.5 nanoseconds"
}, {
"question": "Latency of Branch mispredict",
"answer": "5 nanoseconds"
}, {
"question": "Latency of L2 cache reference",
"answer": "7 nanoseconds"
}, {
"question": "Latency of Mutex lock/unlock",
"answer": "25 nanoseconds"
}, {
"question": "Latency of Main memory reference",
"answer": "100 nanoseconds"
}, {
"question": "Latency of Compressing 1K bytes with Zippy",
"answer": "3,000 nanoseconds"
}, {
"question": "Latency of Sending 1K bytes over a 1 Gbps network",
"answer": "10,000 nanoseconds"
}, {
"question": "Latency of Reading 4K randomly from SSD",
"answer": "150,000 nanoseconds"
}, {
"question": "Latency of Reading 1 MB sequentially from memory",
"answer": "250,000 nanoseconds"
}, {
"question": "Latency of a Round trip within the same datacenter",
"answer": "500,000 nanoseconds"
}, {
"question": "Latency of Reading 1 MB sequentially from SSD",
"answer": "1,000,000 nanoseconds"
}, {
"question": "Latency of Disk seek",
"answer": "10,000,000 nanoseconds"
}, {
"question": "Latency of Reading 1 MB sequentially from disk",
"answer": "20,000,000 nanoseconds"
}, {
"question": "Latency of Sending a packet from California to the Netherlands and back",
"answer": "150,000,000 nanoseconds"
}],
"steps": [
"In the spirit of keeping Free Code Camp 100% free, we've created directions for using a totally free pair programming tool called Team Viewer. It's not as user-friendly as Screen Hero, but it gets the job done (and works on Linux!).",
"Go to <a href='http://www.teamviewer.com/en/index.aspx' target='_blank'>http://www.teamviewer.com/en/index.aspx</a> and download Team Viewer. Be sure not to download the beta version, which isn't compatible with the current stable version everyone else will be using.",
"Install it and launch it",
"Go to the chat room and ask if anyone wants to pair program with you on a CoderByte challenge.",
"If someone is interested, they will be your \"pair\" - the person you pair programming with.",
"<strong>First, you will pair program on your pair's computer.</strong> Ask your pair for his or her Team Viewer ID and password.",
"Enter this id, and then password, to start the session.",
"Once the Team Viewer session starts, look at the the top of the screen. You will see 6 buttons. If you hover your cursor over the buttons, they will slide down so that you can read them. Click the audio/video button. This will allow you to turn on Voice Over IP and unmute your microphone, opening up your voice channel.",
"Note that you can now control your pair's keyboard and mouse, enabling you to step in and code yourself on your pair's computer when desired",
"Now you can click the X to end the pair programming session.",
"<strong>Next, you will pair program on your computer.</strong> Copy your Team Viewer ID and paste it into the private chat, so that your pair can use it to connect with you.",
"You will need to share your randomly generated password with your pair as well.",
"Once your pair connects, you will see a Team Viewer side menu. ",
"Click the audio button that drops down.",
"Click the headset icon and choose Voice over IP",
"Click the microphone button to unmute your microphone. Once your pair does the same, you two will have an open voice channel.",
"Now you can click the X to end the pair programming session.",
"Now it's time to tackle CoderByte.",
"Create a CoderByte account at <a href='http://coderbyte.com/sl/' target='_blank'>http://coderbyte.com/sl/</a>",
"Now go to <a href='http://coderbyte.com/CodingArea/Challenges/#easyChals' target='_blank'>http://coderbyte.com/CodingArea/Challenges/#easyChals</a> and start working through Coderbyte's easy algorithm scripting challenges using JavaScript.",
"When you are finished pair programming, click the X to end the session.",
"Congratulations! You have completed your first pair programming session.",
"You should pair program with different Code Campers until you've completed all the Easy, Medium and Hard CoderByte challenges. This is a big time investment, but the JavaScript practice you'll get, along with the scripting and algorithm experience, are well worth it!",
"You can complete CoderByte problems while you continue to work through Free Code Camp's challenges.",
"Be sure to pair program on these challenges, and remember to apply the RSAP methodology.",
"Click the button below to return to the Pair Programming challenge, then mark it complete."
]
}

View File

@ -7,6 +7,7 @@ var User = require('../models/User');
var secrets = require('../config/secrets');
var moment = require('moment');
//TODO(Berks): Refactor to use module.exports = {} pattern.
/**
* GET /login
* Login page.
@ -22,8 +23,6 @@ exports.getLogin = function(req, res) {
/**
* POST /login
* Sign in using email and password.
* @param email
* @param password
*/
exports.postLogin = function(req, res, next) {
@ -88,14 +87,13 @@ exports.getEmailSignup = function(req, res) {
/**
* POST /email-signup
* Create a new local account.
* @param email
* @param password
*/
exports.postEmailSignup = function(req, res, next) {
req.assert('email', 'Email is not valid').isEmail();
req.assert('password', 'Password must be at least 4 characters long').len(4);
req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);
req.assert('confirmPassword', 'Passwords do not match')
.equals(req.body.password);
var errors = req.validationErrors();
@ -110,14 +108,19 @@ exports.postEmailSignup = function(req, res, next) {
});
User.findOne({ email: req.body.email }, function(err, existingUser) {
if (err) { return next(err); }
if (existingUser) {
req.flash('errors', { msg: 'Account with that email address already exists.' });
req.flash('errors', {
msg: 'Account with that email address already exists.'
});
return res.redirect('/email-signup');
}
user.save(function(err) {
if (err) return next(err);
if (err) { return next(err); }
req.logIn(user, function(err) {
if (err) return next(err);
if (err) { return next(err); }
res.redirect('/email-signup');
});
});
@ -188,12 +191,12 @@ exports.postUpdateProfile = function(req, res, next) {
/**
* POST /account/password
* Update current password.
* @param password
*/
exports.postUpdatePassword = function(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);
req.assert('confirmPassword', 'Passwords do not match')
.equals(req.body.password);
var errors = req.validationErrors();
@ -203,12 +206,13 @@ exports.postUpdatePassword = function(req, res, next) {
}
User.findById(req.user.id, function(err, user) {
if (err) return next(err);
if (err) { return next(err); }
user.password = req.body.password;
user.save(function(err) {
if (err) return next(err);
if (err) { return next(err); }
req.flash('success', { msg: 'Password has been changed.' });
res.redirect('/account');
});
@ -222,7 +226,7 @@ exports.postUpdatePassword = function(req, res, next) {
exports.postDeleteAccount = function(req, res, next) {
User.remove({ _id: req.user.id }, function(err) {
if (err) return next(err);
if (err) { return next(err); }
req.logout();
req.flash('info', { msg: 'Your account has been deleted.' });
res.redirect('/');
@ -232,19 +236,21 @@ exports.postDeleteAccount = function(req, res, next) {
/**
* GET /account/unlink/:provider
* Unlink OAuth provider.
* @param provider
*/
exports.getOauthUnlink = function(req, res, next) {
var provider = req.params.provider;
User.findById(req.user.id, function(err, user) {
if (err) return next(err);
if (err) { return next(err); }
user[provider] = undefined;
user.tokens = _.reject(user.tokens, function(token) { return token.kind === provider; });
user.tokens =
_.reject(user.tokens, function(token) {
return token.kind === provider;
});
user.save(function(err) {
if (err) return next(err);
if (err) { return next(err); }
req.flash('info', { msg: provider + ' account has been unlinked.' });
res.redirect('/account');
});
@ -264,8 +270,11 @@ exports.getReset = function(req, res) {
.findOne({ resetPasswordToken: req.params.token })
.where('resetPasswordExpires').gt(Date.now())
.exec(function(err, user) {
if (err) { return next(err); }
if (!user) {
req.flash('errors', { msg: 'Password reset token is invalid or has expired.' });
req.flash('errors', {
msg: 'Password reset token is invalid or has expired.'
});
return res.redirect('/forgot');
}
res.render('account/reset', {
@ -277,7 +286,6 @@ exports.getReset = function(req, res) {
/**
* POST /reset/:token
* Process the reset password request.
* @param token
*/
exports.postReset = function(req, res, next) {
@ -297,8 +305,11 @@ exports.postReset = function(req, res, next) {
.findOne({ resetPasswordToken: req.params.token })
.where('resetPasswordExpires').gt(Date.now())
.exec(function(err, user) {
if (err) { return next(err); }
if (!user) {
req.flash('errors', { msg: 'Password reset token is invalid or has expired.' });
req.flash('errors', {
msg: 'Password reset token is invalid or has expired.'
});
return res.redirect('back');
}
@ -307,7 +318,7 @@ exports.postReset = function(req, res, next) {
user.resetPasswordExpires = undefined;
user.save(function(err) {
if (err) return next(err);
if (err) { return done(err); }
req.logIn(user, function(err) {
done(err, user);
});
@ -326,16 +337,25 @@ exports.postReset = function(req, res, next) {
to: user.email,
from: 'Team@freecodecamp.com',
subject: 'Your Free Code Camp password has been changed',
text: 'Hello,\n\n' +
'This email is confirming that you requested to reset your password for your Free Code Camp account. This is your email: ' + user.email + '\n'
text: [
'Hello,\n\n',
'This email is confirming that you requested to',
'reset your password for your Free Code Camp account.',
'This is your email:',
user.email,
'\n'
].join(' ')
};
transporter.sendMail(mailOptions, function(err) {
req.flash('success', { msg: 'Success! Your password has been changed.' });
done(err);
if (err) { return done(err); }
req.flash('success', {
msg: 'Success! Your password has been changed.'
});
done();
});
}
], function(err) {
if (err) return next(err);
if (err) { return next(err); }
res.redirect('/');
});
};
@ -357,7 +377,6 @@ exports.getForgot = function(req, res) {
/**
* POST /forgot
* Create a random token, then the send user an email with a reset link.
* @param email
*/
exports.postForgot = function(req, res, next) {
@ -373,14 +392,20 @@ exports.postForgot = function(req, res, next) {
async.waterfall([
function(done) {
crypto.randomBytes(16, function(err, buf) {
if (err) { return done(err); }
var token = buf.toString('hex');
done(err, token);
done(null, token);
});
},
function(token, done) {
User.findOne({ email: req.body.email.toLowerCase() }, function(err, user) {
User.findOne({
email: req.body.email.toLowerCase()
}, function(err, user) {
if (err) { return done(err); }
if (!user) {
req.flash('errors', { msg: 'No account with that email address exists.' });
req.flash('errors', {
msg: 'No account with that email address exists.'
});
return res.redirect('/forgot');
}
@ -388,7 +413,8 @@ exports.postForgot = function(req, res, next) {
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
user.save(function(err) {
done(err, token, user);
if (err) { return done(err); }
done(null, token, user);
});
});
},
@ -404,18 +430,32 @@ exports.postForgot = function(req, res, next) {
to: user.email,
from: 'Team@freecodecamp.com',
subject: 'Reset your Free Code Camp password',
text: "You are receiving this email because you (or someone else) requested we reset your Free Code Camp account's password.\n\n" +
'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
'http://' + req.headers.host + '/reset/' + token + '\n\n' +
'If you did not request this, please ignore this email and your password will remain unchanged.\n'
text: [
'You are receiving this email because you (or someone else)',
'requested we reset your Free Code Camp account\'s password.\n\n',
'Please click on the following link, or paste this into your',
'browser to complete the process:\n\n',
'http://',
req.headers.host,
'/reset/',
token,
'\n\n',
'If you did not request this, please ignore this email and',
'your password will remain unchanged.\n'
].join(' ')
};
transporter.sendMail(mailOptions, function(err) {
req.flash('info', { msg: 'An e-mail has been sent to ' + user.email + ' with further instructions.' });
done(err, 'done');
if (err) { return done(err); }
req.flash('info', {
msg: 'An e-mail has been sent to ' +
user.email +
' with further instructions.'
});
done(null, 'done');
});
}
], function(err) {
if (err) return next(err);
if (err) { return next(err); }
res.redirect('/forgot');
});
};

View File

@ -9,8 +9,9 @@ var gulp = require('gulp'),
var paths = {
server: './app.js',
serverIgnore: [],
}
serverIgnore: []
};
gulp.task('inject', function() {
gulp.src('views/home.jade')
.pipe(inject(gulp.src(bower()), {
@ -53,12 +54,9 @@ gulp.task('sync', ['serve'], function() {
sync.init(null, {
proxy: 'http://localhost:3000',
logLeval: 'debug',
files: [
'public/**/*',
],
files: ['public/**/*'],
port: 3001,
open: true,
browser: ['safari', 'google chrome'],
reloadDelay: reloadDelay
});
});

View File

@ -2,7 +2,10 @@ var mongoose = require('mongoose');
var secrets = require('../config/secrets');
var challengeSchema = new mongoose.Schema({
name: { type: String, unique: true },
name: {
type: String,
unique: true
},
link: String,
time: String,
challengeNumber: Number,
@ -10,4 +13,4 @@ var challengeSchema = new mongoose.Schema({
steps: Array
});
var Challenge = module.exports = mongoose.model('Challenge', challengeSchema);
module.exports = mongoose.model('Challenge', challengeSchema);

View File

@ -3,7 +3,12 @@ var crypto = require('crypto');
var mongoose = require('mongoose');
var userSchema = new mongoose.Schema({
//email: { type: String, unique: true, lowercase: true, match: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/ },
/*email: {
type: String,
unique: true,
lowercase: true,
match: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/
},*/
email: String,
password: String,
@ -14,77 +19,277 @@ var userSchema = new mongoose.Schema({
instagram: String,
linkedin: String,
tokens: Array,
points: { type: Number, default: 0 },
points: {
type: Number,
default: 0
},
challengesCompleted: { type: Array, default: [] },
challengesHash: {
0: { type: Number, default: 0 },
1: { type: Number, default: 0 },
2: { type: Number, default: 0 },
3: { type: Number, default: 0 },
4: { type: Number, default: 0 },
5: { type: Number, default: 0 },
6: { type: Number, default: 0 },
7: { type: Number, default: 0 },
8: { type: Number, default: 0 },
9: { type: Number, default: 0 },
10: { type: Number, default: 0 },
11: { type: Number, default: 0 },
12: { type: Number, default: 0 },
13: { type: Number, default: 0 },
14: { type: Number, default: 0 },
15: { type: Number, default: 0 },
16: { type: Number, default: 0 },
17: { type: Number, default: 0 },
18: { type: Number, default: 0 },
19: { type: Number, default: 0 },
20: { type: Number, default: 0 },
21: { type: Number, default: 0 },
22: { type: Number, default: 0 },
23: { type: Number, default: 0 },
24: { type: Number, default: 0 },
25: { type: Number, default: 0 },
26: { type: Number, default: 0 },
27: { type: Number, default: 0 },
28: { type: Number, default: 0 },
29: { type: Number, default: 0 },
30: { type: Number, default: 0 },
31: { type: Number, default: 0 },
32: { type: Number, default: 0 },
33: { type: Number, default: 0 },
34: { type: Number, default: 0 },
35: { type: Number, default: 0 },
36: { type: Number, default: 0 },
37: { type: Number, default: 0 },
38: { type: Number, default: 0 },
39: { type: Number, default: 0 },
40: { type: Number, default: 0 },
41: { type: Number, default: 0 },
42: { type: Number, default: 0 },
43: { type: Number, default: 0 },
44: { type: Number, default: 0 },
45: { type: Number, default: 0 },
46: { type: Number, default: 0 },
47: { type: Number, default: 0 },
48: { type: Number, default: 0 },
49: { type: Number, default: 0 },
50: { type: Number, default: 0 },
51: { type: Number, default: 0 },
52: { type: Number, default: 0 },
53: { type: Number, default: 0 },
54: { type: Number, default: 0 },
55: { type: Number, default: 0 },
56: { type: Number, default: 0 },
57: { type: Number, default: 0 },
58: { type: Number, default: 0 },
59: { type: Number, default: 0 }
0: {
type: Number,
default: 0
},
1: {
type: Number,
default: 0
},
2: {
type: Number,
default: 0
},
3: {
type: Number,
default: 0
},
4: {
type: Number,
default: 0
},
5: {
type: Number,
default: 0
},
6: {
type: Number,
default: 0
},
7: {
type: Number,
default: 0
},
8: {
type: Number,
default: 0
},
9: {
type: Number,
default: 0
},
10: {
type: Number,
default: 0
},
11: {
type: Number,
default: 0
},
12: {
type: Number,
default: 0
},
13: {
type: Number,
default: 0
},
14: {
type: Number,
default: 0
},
15: {
type: Number,
default: 0
},
16: {
type: Number,
default: 0
},
17: {
type: Number,
default: 0
},
18: {
type: Number,
default: 0
},
19: {
type: Number,
default: 0
},
20: {
type: Number,
default: 0
},
21: {
type: Number,
default: 0
},
22: {
type: Number,
default: 0
},
23: {
type: Number,
default: 0
},
24: {
type: Number,
default: 0
},
25: {
type: Number,
default: 0
},
26: {
type: Number,
default: 0
},
27: {
type: Number,
default: 0
},
28: {
type: Number,
default: 0
},
29: {
type: Number,
default: 0
},
30: {
type: Number,
default: 0
},
31: {
type: Number,
default: 0
},
32: {
type: Number,
default: 0
},
33: {
type: Number,
default: 0
},
34: {
type: Number,
default: 0
},
35: {
type: Number,
default: 0
},
36: {
type: Number,
default: 0
},
37: {
type: Number,
default: 0
},
38: {
type: Number,
default: 0
},
39: {
type: Number,
default: 0
},
40: {
type: Number,
default: 0
},
41: {
type: Number,
default: 0
},
42: {
type: Number,
default: 0
},
43: {
type: Number,
default: 0
},
44: {
type: Number,
default: 0
},
45: {
type: Number,
default: 0
},
46: {
type: Number,
default: 0
},
47: {
type: Number,
default: 0
},
48: {
type: Number,
default: 0
},
49: {
type: Number,
default: 0
},
50: {
type: Number,
default: 0
},
51: {
type: Number,
default: 0
},
52: {
type: Number,
default: 0
},
53: {
type: Number,
default: 0
},
54: {
type: Number,
default: 0
},
55: {
type: Number,
default: 0
},
56: {
type: Number,
default: 0
},
57: {
type: Number,
default: 0
},
58: {
type: Number,
default: 0
},
59: {
type: Number,
default: 0
}
},
profile: {
name: { type: String, default: '' },
gender: { type: String, default: '' },
location: { type: String, default: '' },
website: { type: String, default: '' },
picture: { type: String, default: '' }
//username: { type: String, default: '', unique: true, match: /^[a-zA-Z0-9_]+$/ }
name: {
type: String, default: ''
},
gender: {
type: String, default: ''
},
location: {
type: String, default: ''
},
website: {
type: String,
default: ''
},
picture: {
type: String,
default: ''
}
/*username: {
type: String,
default: '',
unique: true,
match: /^[a-zA-Z0-9_]+$/
}*/
},
resetPasswordToken: String,
@ -133,7 +338,11 @@ userSchema.methods.gravatar = function(size) {
return 'https://gravatar.com/avatar/?s=' + size + '&d=retro';
}
var md5 = crypto.createHash('md5').update(this.email).digest('hex');
var md5 = crypto
.createHash('md5')
.update(this.email)
.digest('hex');
return 'https://gravatar.com/avatar/' + md5 + '?s=' + size + '&d=retro';
};

View File

@ -4,22 +4,26 @@ $(document).ready(function() {
var setCSRFToken = function(securityToken) {
jQuery.ajaxPrefilter(function(options, _, xhr) {
if ( !xhr.crossDomain )
if (!xhr.crossDomain) {
xhr.setRequestHeader(CSRF_HEADER, securityToken);
}
});
};
setCSRFToken($('meta[name="csrf-token"]').attr('content'));
$('.start-challenge').on("click", function() {
$('.start-challenge').on('click', function() {
$(this).parent().remove();
$('.challenge-content').removeClass('hidden-element').addClass('animated fadeInDown');
$('.challenge-content')
.removeClass('hidden-element')
.addClass('animated fadeInDown');
});
$('.completed-challenge').on("click", function() {
$('.completed-challenge').on('click', function() {
$('#complete-dialog').modal('show');
l = location.pathname.split('/');
cn = l[l.length - 1]
cn = l[l.length - 1];
console.log(cn);
$.ajax({
type: 'POST',
@ -28,13 +32,13 @@ $(document).ready(function() {
});
});
$('.skip-challenge').on("click", function() {
$('.skip-challenge').on('click', function() {
$('#skip-dialog').modal('show');
});
$('.next-button').on("click", function() {
$('.next-button').on('click', function() {
l = location.pathname.split('/');
window.location = "/challenges/" + (parseInt(l[l.length-1]) + 1)
window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1);
});
});

View File

@ -1,15 +1,22 @@
var Challenge = require('../models/Challenge.js')
var mongoose = require('mongoose');
var secrets = require('../config/secrets');
var challenges = require('./challenges.json');
var Challenge = require('../models/Challenge.js'),
mongoose = require('mongoose'),
secrets = require('../config/secrets'),
challenges = require('./challenges.json');
mongoose.connect(secrets.db);
Challenge.remove({}, function(err, data){
if (err) console.log(err);
else console.log('Deleted ', data );
Challenge.remove({}, function(err, data) {
if (err) {
console.error(err);
} else {
console.log('Deleted ', data);
}
Challenge.create(challenges, function(err, data) {
if (err) console.log(err);
else console.log('Saved ', data );
if (err) {
console.log(err);
} else {
console.log('Saved ', data);
}
process.exit(0);
});
});

215
setup.js
View File

@ -50,7 +50,8 @@ var footer = blessed.text({
fg: 'white',
bg: 'blue',
tags: true,
content: ' {cyan-fg}<Up/Down>{/cyan-fg} moves | {cyan-fg}<Enter>{/cyan-fg} selects | {cyan-fg}<q>{/cyan-fg} exits'
content: ' {cyan-fg}<Up/Down>{/cyan-fg} moves |' +
' {cyan-fg}<Enter>{/cyan-fg} selects | {cyan-fg}<q>{/cyan-fg} exits'
});
var inner = blessed.form({
@ -97,7 +98,8 @@ var clusterText = blessed.text({
bg: 'red',
fg: 'white',
tags: true,
content: 'Take advantage of multi-core systems using built-in {underline}cluster{/underline} module.'
content: 'Take advantage of multi-core systems' +
' using built-in {underline}cluster{/underline} module.'
});
@ -186,38 +188,54 @@ var authForm = blessed.form({
});
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 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;");
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')");
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");
index = profileTemplate.indexOf(' if user.facebook');
profileTemplate.splice(index - 1, 5);
fs.writeFileSync('views/account/profile.jade', profileTemplate.join(os.EOL));
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'] }));");
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;");
index = passportConfig.indexOf(
"var GitHubStrategy = require('passport-github').Strategy;"
);
if (githubCheckbox.checked && index !== -1) {
console.log(index);
passportConfig.splice(index, 1);
@ -225,111 +243,162 @@ authForm.on('submit', function() {
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')");
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));
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'));");
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;");
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')");
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));
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' }));");
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;");
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')");
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));
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'));");
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;");
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')");
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));
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' }));");
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;");
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));
fs.writeFileSync(
'config/passport.js',
passportConfig.join(os.EOL)
);
index = loginTemplate.indexOf(" a.btn.btn-block.btn-instagram.btn-social(href='/auth/instagram')");
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));
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));
fs.writeFileSync(
'views/account/profile.jade',
profileTemplate.join(os.EOL)
);
index = app.indexOf("app.get('/auth/instagram', passport.authenticate('instagram'));");
index = app.indexOf(
'app.get("/auth/instagram", passport.authenticate("instagram"));'
);
app.splice(index, 4);
fs.writeFileSync('app.js', app.join(os.EOL));
@ -339,14 +408,18 @@ authForm.on('submit', function() {
home.remove(authForm);
home.append(success);
success.setContent('Selected authentication providers have been removed from passportConfig.js, User.js, app.js, login.jade and profile.jade!');
success.setContent(
'Selected authentication providers have been removed' +
'from passportConfig.js, User.js, app.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.',
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'
@ -460,8 +533,10 @@ var emailForm = blessed.form({
});
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 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) {
@ -472,20 +547,57 @@ emailForm.on('submit', function() {
choice = 'Mandrill';
}
var index = contactCtrl.indexOf('var transporter = nodemailer.createTransport({');
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');
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({');
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');
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);
@ -497,7 +609,9 @@ emailForm.on('submit', function() {
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}.',
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',
@ -592,7 +706,12 @@ home.on('select', function(child, index) {
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}app.js{/underline}');
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}app.js{/underline}'
].join(' '));
success.focus();
screen.render();
break;

View File

@ -11,7 +11,7 @@ describe('User Model', function() {
user.save(function(err) {
if (err) return done(err);
done();
})
});
});
it('should not create a user with the unique email', function(done) {