Merge pull request #180 from terakilobyte/master

MOre news updates
This commit is contained in:
Quincy Larson
2015-03-09 16:39:04 -07:00
12 changed files with 579 additions and 464 deletions

2
app.js
View File

@ -324,7 +324,7 @@ app.get(
); );
app.get( app.get(
'/stories/submit/:newStory', '/stories/submit/new-story',
storyController.preSubmit storyController.preSubmit
); );

View File

@ -4,7 +4,6 @@ var User = require('../models/User'),
Story = require('./../models/Story'), Story = require('./../models/Story'),
Comment = require('./../models/Comment'), Comment = require('./../models/Comment'),
resources = require('./resources.json'), resources = require('./resources.json'),
questions = resources.questions,
steps = resources.steps, steps = resources.steps,
secrets = require('./../config/secrets'), secrets = require('./../config/secrets'),
bonfires = require('../seed_data/bonfires.json'), bonfires = require('../seed_data/bonfires.json'),
@ -154,7 +153,7 @@ module.exports = {
}, },
bloggerCalls: function(req, res) { bloggerCalls: function(req, res) {
request('https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/posts?key=' + secrets.blogger.key, function (err, status, blog) { request('https://www.googleapis.com/blogger/v3/blogs/2421288658305323950/posts?key=' + secrets.blogger.key, function (err, status, blog) {
var blog = blog.length > 100 ? JSON.parse(blog) : ""; blog = blog.length > 100 ? JSON.parse(blog) : '';
res.send({ res.send({
blog1Title: blog ? blog["items"][0]["title"] : "Can't connect to Blogger", blog1Title: blog ? blog["items"][0]["title"] : "Can't connect to Blogger",
blog1Link: blog ? blog["items"][0]["url"] : "http://blog.freecodecamp.com", blog1Link: blog ? blog["items"][0]["url"] : "http://blog.freecodecamp.com",
@ -225,10 +224,6 @@ module.exports = {
return compliments[Math.floor(Math.random() * compliments.length)]; return compliments[Math.floor(Math.random() * compliments.length)];
}, },
numberOfBonfires: function() {
return bonfires.length - 1;
},
allBonfireIds: function() { allBonfireIds: function() {
return bonfires.map(function(elem) { return bonfires.map(function(elem) {
return { return {
@ -290,14 +285,19 @@ module.exports = {
return process.env.NODE_ENV; return process.env.NODE_ENV;
}, },
getURLTitle: function(url, callback) { getURLTitle: function(url, callback) {
debug('got url in meta scraping function', url);
(function () { (function () {
var result = {title: ''}; var result = {title: '', image: '', url: '', description: ''};
request(url, function (error, response, body) { request(url, function (error, response, body) {
if (!error && response.statusCode === 200) { if (!error && response.statusCode === 200) {
var $ = cheerio.load(body); var $ = cheerio.load(body);
var title = $('title').text(); var metaDescription = $("meta[name='description']");
result.title = title; var metaImage = $("meta[property='og:image']");
var urlImage = metaImage.attr('content') ? metaImage.attr('content') : '';
var description = metaDescription.attr('content') ? metaDescription.attr('content') : '';
result.title = $('title').text();
result.image = urlImage;
result.description = description;
callback(null, result); callback(null, result);
} else { } else {
callback('failed'); callback('failed');

View File

@ -8,9 +8,9 @@ var R = require('ramda'),
mongodb = require('mongodb'), mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient, MongoClient = mongodb.MongoClient,
secrets = require('../config/secrets'), secrets = require('../config/secrets'),
User = require('./../models/User'); sanitizeHtml = require('sanitize-html');
function hotRank(timeValue, rank, headline) { function hotRank(timeValue, rank) {
/* /*
* Hotness ranking algorithm: http://amix.dk/blog/post/19588 * Hotness ranking algorithm: http://amix.dk/blog/post/19588
* tMS = postedOnDate - foundationTime; * tMS = postedOnDate - foundationTime;
@ -24,21 +24,20 @@ function hotRank(timeValue, rank, headline) {
} }
exports.hotJSON = function(req, res, next) { exports.hotJSON = function(req, res) {
var story = Story.find({}).sort({'timePosted': -1}).limit(1000); var story = Story.find({}).sort({'timePosted': -1}).limit(1000);
story.exec(function(err, stories) { story.exec(function(err, stories) {
if (err) { if (err) {
throw err; res.send(500);
return next(err);
} }
var foundationDate = 1413298800000; var foundationDate = 1413298800000;
var sliceVal = stories.length >= 100 ? 100 : stories.length; var sliceVal = stories.length >= 100 ? 100 : stories.length;
var rankedStories = stories; return res.json(stories.map(function(elem) {
return res.json(rankedStories.map(function(elem) {
return elem; return elem;
}).sort(function(a, b) { }).sort(function(a, b) {
debug('a rank and b rank', hotRank(a.timePosted - foundationDate, a.rank, a.headline), hotRank(b.timePosted - foundationDate, b.rank, b.headline));
return hotRank(b.timePosted - foundationDate, b.rank, b.headline) - hotRank(a.timePosted - foundationDate, a.rank, a.headline); return hotRank(b.timePosted - foundationDate, b.rank, b.headline) - hotRank(a.timePosted - foundationDate, a.rank, a.headline);
}).slice(0, sliceVal)); }).slice(0, sliceVal));
@ -49,48 +48,61 @@ exports.recentJSON = function(req, res, next) {
var story = Story.find({}).sort({'timePosted': -1}).limit(100); var story = Story.find({}).sort({'timePosted': -1}).limit(100);
story.exec(function(err, stories) { story.exec(function(err, stories) {
if (err) { if (err) {
throw err; res.status(500);
return next(err);
} }
res.json(stories); res.json(stories);
}); });
}; };
exports.hot = function(req, res, next) { exports.hot = function(req, res) {
res.render('stories/index', { res.render('stories/index', {
page: 'hot' page: 'hot'
}); });
}; };
exports.submitNew = function(req,res, next) { exports.submitNew = function(req, res) {
res.render('stories/index', { res.render('stories/index', {
page: 'submit' page: 'submit'
}); });
}; };
exports.search = function(req, res, next) { exports.search = function(req, res) {
res.render('stories/index', { res.render('stories/index', {
page: 'search' page: 'search'
}); });
}; };
exports.recent = function(req, res, next) { exports.recent = function(req, res) {
res.render('stories/index', { res.render('stories/index', {
page: 'recent' page: 'recent'
}); });
}; };
exports.preSubmit = function(req, res, next) { exports.preSubmit = function(req, res) {
var data = req.params.newStory; var data = req.query;
var cleanData = sanitizeHtml(data.url);
if (data.url.replace(/&/g, '&') !== cleanData) {
debug('data and cleandata', data, cleanData, data.url === cleanData);
req.flash('errors', {
msg: 'The data for this post is malformed'
});
return res.render('stories/index', {
page: 'stories/submit'
});
}
data = data.replace(/url=/gi, '').replace(/&title=/gi, ',').split(','); var title = data.title || '';
var url = data[0]; var image = data.image || '';
var title = data[1]; var description = data.description || '';
res.render('stories/index', { return res.render('stories/index', {
page: 'storySubmission', page: 'storySubmission',
storyURL: url, storyURL: data.url,
storyTitle: title storyTitle: title,
storyImage: image,
storyMetaDescription: description
}); });
}; };
@ -132,15 +144,15 @@ exports.returnIndividualStory = function(req, res, next) {
user: req.user, user: req.user,
timeAgo: moment(story.timePosted).fromNow(), timeAgo: moment(story.timePosted).fromNow(),
image: story.image, image: story.image,
page: 'show' page: 'show',
storyMetaDescription: story.metaDescription
}); });
}); });
}; };
exports.getStories = function(req, res, next) { exports.getStories = function(req, res) {
MongoClient.connect(secrets.db, function(err, database) { MongoClient.connect(secrets.db, function(err, database) {
var db = database; database.collection('stories').find({
db.collection('stories').find({
"$text": { "$text": {
"$search": req.body.data.searchValue "$search": req.body.data.searchValue
} }
@ -155,6 +167,7 @@ exports.getStories = function(req, res, next) {
comments: 1, comments: 1,
image: 1, image: 1,
storyLink: 1, storyLink: 1,
metaDescription: 1,
textScore: { textScore: {
$meta: "textScore" $meta: "textScore"
} }
@ -177,7 +190,8 @@ exports.upvote = function(req, res, next) {
var data = req.body.data; var data = req.body.data;
Story.find({'_id': data.id}, function(err, story) { Story.find({'_id': data.id}, function(err, story) {
if (err) { if (err) {
throw err; res.status(500);
return next(err);
} }
story = story.pop(); story = story.pop();
story.rank++; story.rank++;
@ -197,59 +211,68 @@ exports.comments = function(req, res, next) {
var data = req.params.id; var data = req.params.id;
Comment.find({'_id': data}, function(err, comment) { Comment.find({'_id': data}, function(err, comment) {
if (err) { if (err) {
throw err; res.status(500);
return next(err);
} }
comment = comment.pop(); comment = comment.pop();
return res.send(comment); return res.send(comment);
}); });
}; };
exports.newStory = function(req, res, next) { exports.newStory = function(req, res) {
var url = req.body.data.url; var url = req.body.data.url;
var cleanURL = sanitizeHtml(url);
if (cleanURL !== url) {
req.flash('errors', {
msg: "The URL you submitted doesn't appear valid"
});
return res.json({
alreadyPosted: true,
storyURL: '/stories/submit'
});
}
if (url.search(/^https?:\/\//g) === -1) { if (url.search(/^https?:\/\//g) === -1) {
url = 'http://' + url; url = 'http://' + url;
} }
debug('In pre submit with a url', url);
Story.find({'link': url}, function(err, story) { Story.find({'link': url}, function(err, story) {
debug('Attempting to find a story');
if (err) { if (err) {
debug('oops');
return res.status(500); return res.status(500);
} }
if (story.length) { if (story.length) {
debug('Found a story already, here\'s the return from find', story);
req.flash('errors', { req.flash('errors', {
msg: "Someone's already posted that link. Here's the discussion." msg: "Someone's already posted that link. Here's the discussion."
}); });
debug('Redirecting the user with', story[0].storyLink);
return res.json({ return res.json({
alreadyPosted: true, alreadyPosted: true,
storyURL: story.pop().storyLink storyURL: '/stories/' + story.pop().storyLink
}); });
} }
resources.getURLTitle(url, processResponse); resources.getURLTitle(url, processResponse);
}); });
function processResponse(err, storyTitle) { function processResponse(err, story) {
if (err) { if (err) {
res.json({ res.json({
alreadyPosted: false, alreadyPosted: false,
storyURL: url, storyURL: url,
storyTitle: '' storyTitle: '',
storyImage: '',
storyMetaDescription: ''
}); });
} else { } else {
storyTitle = storyTitle ? storyTitle : '';
res.json({ res.json({
alreadyPosted: false, alreadyPosted: false,
storyURL: url, storyURL: url,
storyTitle: storyTitle.title storyTitle: story.title,
storyImage: story.image,
storyMetaDescription: story.description
}); });
} }
} }
}; };
exports.storySubmission = function(req, res, next) { exports.storySubmission = function(req, res) {
var data = req.body.data; var data = req.body.data;
var storyLink = data.headline var storyLink = data.headline
.replace(/\'/g, '') .replace(/\'/g, '')
@ -263,19 +286,20 @@ exports.storySubmission = function(req, res, next) {
link = 'http://' + link; link = 'http://' + link;
} }
var story = new Story({ var story = new Story({
headline: data.headline, headline: sanitizeHtml(data.headline),
timePosted: Date.now(), timePosted: Date.now(),
link: link, link: link,
description: data.description, description: sanitizeHtml(data.description),
rank: 1, rank: 1,
upVotes: data.upVotes, upVotes: data.upVotes,
author: data.author, author: data.author,
comments: [], comments: [],
image: data.image, image: data.image,
storyLink: storyLink storyLink: storyLink,
metaDescription: data.storyMetaDescription
}); });
story.save(function(err, data) { story.save(function(err) {
if (err) { if (err) {
return res.status(500); return res.status(500);
} }
@ -285,12 +309,22 @@ exports.storySubmission = function(req, res, next) {
}); });
}; };
exports.commentSubmit = function(req, res, next) { exports.commentSubmit = function(req, res) {
debug('comment submit fired');
var data = req.body.data; var data = req.body.data;
var sanitizedBody = sanitizeHtml(data.body,
{
allowedTags: [],
allowedAttributes: []
});
if (data.body !== sanitizedBody) {
req.flash('errors', {
msg: 'HTML is not allowed'
});
return res.send(true);
}
var comment = new Comment({ var comment = new Comment({
associatedPost: data.associatedPost, associatedPost: data.associatedPost,
body: data.body, body: sanitizedBody,
rank: 0, rank: 0,
upvotes: 0, upvotes: 0,
author: data.author, author: data.author,
@ -301,13 +335,22 @@ exports.commentSubmit = function(req, res, next) {
commentSave(comment, Story, res); commentSave(comment, Story, res);
}; };
exports.commentOnCommentSubmit = function(req, res, next) { exports.commentOnCommentSubmit = function(req, res) {
debug('comment on comment submit');
var idToFind = req.params.id;
var data = req.body.data; var data = req.body.data;
var sanitizedBody = sanitizeHtml(data.body,
{
allowedTags: [],
allowedAttributes: []
});
if (data.body !== sanitizedBody) {
req.flash('errors', {
msg: 'HTML is not allowed'
});
return res.send(true);
}
var comment = new Comment({ var comment = new Comment({
associatedPost: data.associatedPost, associatedPost: data.associatedPost,
body: data.body, body: sanitizedBody,
rank: 0, rank: 0,
upvotes: 0, upvotes: 0,
author: data.author, author: data.author,
@ -331,7 +374,7 @@ function commentSave(comment, Context, res) {
associatedStory = associatedStory.pop(); associatedStory = associatedStory.pop();
if (associatedStory) { if (associatedStory) {
associatedStory.comments.push(data._id); associatedStory.comments.push(data._id);
associatedStory.save(function (err, data) { associatedStory.save(function (err) {
if (err) { if (err) {
res.status(500); res.status(500);
} }

View File

@ -7,10 +7,10 @@ var _ = require('lodash'),
secrets = require('../config/secrets'), secrets = require('../config/secrets'),
moment = require('moment'), moment = require('moment'),
Challenge = require('./../models/Challenge'), Challenge = require('./../models/Challenge'),
debug = require('debug')('freecc:cntr:challenges') debug = require('debug')('freecc:cntr:challenges'),
resources = require('./resources'); resources = require('./resources');
//TODO(Berks): Refactor to use module.exports = {} pattern.
/** /**
* GET /signin * GET /signin
@ -18,10 +18,10 @@ var _ = require('lodash'),
*/ */
exports.getSignin = function(req, res) { exports.getSignin = function(req, res) {
if (req.user) return res.redirect('/'); if (req.user) return res.redirect('/');
res.render('account/signin', { res.render('account/signin', {
title: 'Free Code Camp Login' title: 'Free Code Camp Login'
}); });
}; };
/** /**
@ -30,28 +30,28 @@ exports.getSignin = function(req, res) {
*/ */
exports.postSignin = function(req, res, next) { exports.postSignin = function(req, res, next) {
req.assert('email', 'Email is not valid').isEmail(); req.assert('email', 'Email is not valid').isEmail();
req.assert('password', 'Password cannot be blank').notEmpty(); req.assert('password', 'Password cannot be blank').notEmpty();
var errors = req.validationErrors(); var errors = req.validationErrors();
if (errors) { if (errors) {
req.flash('errors', errors); req.flash('errors', errors);
return res.redirect('/signin'); return res.redirect('/signin');
}
passport.authenticate('local', function(err, user, info) {
if (err) return next(err);
if (!user) {
req.flash('errors', { msg: info.message });
return res.redirect('/signin');
} }
req.logIn(user, function(err) {
if (err) return next(err); passport.authenticate('local', function(err, user, info) {
req.flash('success', { msg: 'Success! You are logged in.' }); if (err) return next(err);
res.redirect(req.session.returnTo || '/'); if (!user) {
}); req.flash('errors', { msg: info.message });
})(req, res, next); return res.redirect('/signin');
}
req.logIn(user, function(err) {
if (err) return next(err);
req.flash('success', { msg: 'Success! You are logged in.' });
res.redirect(req.session.returnTo || '/');
});
})(req, res, next);
}; };
/** /**
@ -60,8 +60,8 @@ exports.postSignin = function(req, res, next) {
*/ */
exports.signout = function(req, res) { exports.signout = function(req, res) {
req.logout(); req.logout();
res.redirect('/'); res.redirect('/');
}; };
/** /**
@ -70,10 +70,10 @@ exports.signout = function(req, res) {
*/ */
exports.getEmailSignin = function(req, res) { exports.getEmailSignin = function(req, res) {
if (req.user) return res.redirect('/'); if (req.user) return res.redirect('/');
res.render('account/email-signin', { res.render('account/email-signin', {
title: 'Sign in to your Free Code Camp Account' title: 'Sign in to your Free Code Camp Account'
}); });
}; };
/** /**
@ -82,10 +82,10 @@ exports.getEmailSignin = function(req, res) {
*/ */
exports.getEmailSignup = function(req, res) { exports.getEmailSignup = function(req, res) {
if (req.user) return res.redirect('/'); if (req.user) return res.redirect('/');
res.render('account/email-signup', { res.render('account/email-signup', {
title: 'Create Your Free Code Camp Account' title: 'Create Your Free Code Camp Account'
}); });
}; };
/** /**
@ -94,64 +94,93 @@ exports.getEmailSignup = function(req, res) {
*/ */
exports.postEmailSignup = function(req, res, next) { exports.postEmailSignup = function(req, res, next) {
var errors = req.validationErrors(); var errors = req.validationErrors();
if (errors) { if (errors) {
req.flash('errors', errors); req.flash('errors', errors);
return res.redirect('/email-signup'); return res.redirect('/email-signup');
debug(errors);
}
var user = new User({
email: req.body.email.trim(),
password: req.body.password,
profile : {
username: req.body.username.trim(),
picture: 'https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png'
} }
});
User.findOne({ email: req.body.email }, function(err, existingUser) { var possibleUserData = req.body;
if (err) { return next(err); }
if (existingUser) { if (possibleUserData.password.length < 8) {
req.flash('errors', { req.flash('errors', {
msg: 'Account with that email address already exists.' msg: 'Your password is too short'
}); });
return res.redirect('/email-signup'); return res.redirect('email-signup');
} }
user.save(function(err) {
if (err) { return next(err); }
req.logIn(user, function(err) { if (possibleUserData.username.length < 5 || possibleUserData.length > 20) {
if (err) { return next(err); } req.flash('errors', {
res.redirect('/email-signup'); msg: 'Your username must be between 5 and 20 characters'
}); });
return res.redirect('email-signup');
}
var user = new User({
email: req.body.email.trim(),
password: req.body.password,
profile : {
username: req.body.username.trim(),
picture: 'https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png'
}
}); });
var transporter = nodemailer.createTransport({
service: 'Mandrill', User.findOne({ email: req.body.email }, function(err, existingEmail) {
auth: { if (err) {
user: secrets.mandrill.user, return next(err);
pass: secrets.mandrill.password }
}
if (existingEmail) {
req.flash('errors', {
msg: 'Account with that email address already exists.'
});
return res.redirect('/email-signup');
}
User.findOne({'profile.username': req.body.username }, function(err, existingUsername) {
if (err) {
return next(err);
}
if (existingUsername) {
req.flash('errors', {
msg: 'Account with that username already exists.'
});
return res.redirect('/email-signup');
}
user.save(function(err) {
if (err) { return next(err); }
req.logIn(user, function(err) {
if (err) { return next(err); }
res.redirect('/email-signup');
});
});
var transporter = nodemailer.createTransport({
service: 'Mandrill',
auth: {
user: secrets.mandrill.user,
pass: secrets.mandrill.password
}
});
var mailOptions = {
to: user.email,
from: 'Team@freecodecamp.com',
subject: 'Welcome to Free Code Camp!',
text: [
'Greetings from San Francisco!\n\n',
'Thank you for joining our community.\n',
'Feel free to email us at this address if you have any questions about Free Code Camp.\n',
"And if you have a moment, check out our blog: blog.freecodecamp.com.\n",
'Good luck with the challenges!\n\n',
'- the Volunteer Camp Counselor Team'
].join('')
};
transporter.sendMail(mailOptions, function(err) {
if (err) { return err; }
});
});
}); });
var mailOptions = {
to: user.email,
from: 'Team@freecodecamp.com',
subject: 'Welcome to Free Code Camp!',
text: [
'Greetings from San Francisco!\n\n',
'Thank you for joining our community.\n',
'Feel free to email us at this address if you have any questions about Free Code Camp.\n',
"And if you have a moment, check out our blog: blog.freecodecamp.com.\n",
'Good luck with the challenges!\n\n',
'- the Volunteer Camp Counselor Team'
].join('')
};
transporter.sendMail(mailOptions, function(err) {
if (err) { return err; }
});
});
}; };
/** /**
@ -161,7 +190,7 @@ exports.postEmailSignup = function(req, res, next) {
exports.getAccount = function(req, res) { exports.getAccount = function(req, res) {
res.render('account/account', { res.render('account/account', {
title: 'Manage your Free Code Camp Account' title: 'Manage your Free Code Camp Account'
}); });
}; };
@ -169,9 +198,9 @@ exports.getAccount = function(req, res) {
* Angular API Call * Angular API Call
*/ */
exports.getAccountAngular = function(req, res) { exports.getAccountAngular = function(req, res) {
res.json({ res.json({
user: req.user user: req.user
}); });
}; };
@ -180,13 +209,13 @@ exports.getAccount = function(req, res) {
*/ */
exports.checkUniqueUsername = function(req, res) { exports.checkUniqueUsername = function(req, res) {
User.count({'profile.username': req.params.username.toLowerCase()}, function (err, data) { User.count({'profile.username': req.params.username.toLowerCase()}, function (err, data) {
if (data == 1) { if (data == 1) {
return res.send(true); return res.send(true);
} else { } else {
return res.send(false); return res.send(false);
} }
}); });
}; };
/** /**
@ -207,13 +236,13 @@ exports.checkExistingUsername = function(req, res) {
*/ */
exports.checkUniqueEmail = function(req, res) { exports.checkUniqueEmail = function(req, res) {
User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) { User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) {
if (data == 1) { if (data == 1) {
return res.send(true); return res.send(true);
} else { } else {
return res.send(false); return res.send(false);
} }
}); });
}; };
@ -223,44 +252,44 @@ exports.checkUniqueEmail = function(req, res) {
*/ */
exports.returnUser = function(req, res, next) { exports.returnUser = function(req, res, next) {
User.find({'profile.username': req.params.username.toLowerCase()}, function(err, user) { User.find({'profile.username': req.params.username.toLowerCase()}, function(err, user) {
if (err) { debug('Username err: ', err); next(err); } if (err) { debug('Username err: ', err); next(err); }
if (user[0]) { if (user[0]) {
var user = user[0]; var user = user[0];
Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) { Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) {
res.render('account/show', { res.render('account/show', {
title: 'Camper: ', title: 'Camper: ',
username: user.profile.username, username: user.profile.username,
name: user.profile.name, name: user.profile.name,
location: user.profile.location, location: user.profile.location,
githubProfile: user.profile.githubProfile, githubProfile: user.profile.githubProfile,
linkedinProfile: user.profile.linkedinProfile, linkedinProfile: user.profile.linkedinProfile,
codepenProfile: user.profile.codepenProfile, codepenProfile: user.profile.codepenProfile,
twitterHandle: user.profile.twitterHandle, twitterHandle: user.profile.twitterHandle,
bio: user.profile.bio, bio: user.profile.bio,
picture: user.profile.picture, picture: user.profile.picture,
points: user.points, points: user.points,
website1Link: user.portfolio.website1Link, website1Link: user.portfolio.website1Link,
website1Title: user.portfolio.website1Title, website1Title: user.portfolio.website1Title,
website1Image: user.portfolio.website1Image, website1Image: user.portfolio.website1Image,
website2Link: user.portfolio.website2Link, website2Link: user.portfolio.website2Link,
website2Title: user.portfolio.website2Title, website2Title: user.portfolio.website2Title,
website2Image: user.portfolio.website2Image, website2Image: user.portfolio.website2Image,
website3Link: user.portfolio.website3Link, website3Link: user.portfolio.website3Link,
website3Title: user.portfolio.website3Title, website3Title: user.portfolio.website3Title,
website3Image: user.portfolio.website3Image, website3Image: user.portfolio.website3Image,
challenges: c, challenges: c,
ch: user.challengesHash, ch: user.challengesHash,
moment: moment moment: moment
}); });
}); });
} else { } else {
req.flash('errors', { req.flash('errors', {
msg: "404: We couldn't find a page with that url. Please double check the link." msg: "404: We couldn't find a page with that url. Please double check the link."
}); });
return res.redirect('/'); return res.redirect('/');
} }
}); });
}; };
@ -292,69 +321,70 @@ exports.updateProgress = function(req, res) {
*/ */
exports.postUpdateProfile = function(req, res, next) { exports.postUpdateProfile = function(req, res, next) {
User.findById(req.user.id, function(err, user) {
if (err) return next(err);
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
return res.redirect('/account');
}
User.findOne({ email: req.body.email }, function(err, existingEmail) { // What does this do?
if (err) { User.findById(req.user.id, function(err, user) {
return next(err); if (err) return next(err);
} var errors = req.validationErrors();
var user = req.user; if (errors) {
if (existingEmail && existingEmail.email != user.email) { req.flash('errors', errors);
req.flash('errors', { return res.redirect('/account');
msg: "An account with that email address already exists."
});
return res.redirect('/account');
}
User.findOne({ username: req.body.username }, function(err, existingUsername) {
if (err) {
return next(err);
} }
var user = req.user;
if (existingUsername && existingUsername.profile.username !== user.profile.username) {
req.flash('errors', {
msg: 'An account with that username already exists.'
});
return res.redirect('/account');
}
var user = req.user;
user.email = req.body.email.trim() || '';
user.profile.name = req.body.name.trim() || '';
user.profile.username = req.body.username.trim() || '';
user.profile.location = req.body.location.trim() || '';
user.profile.githubProfile = req.body.githubProfile.trim() || '';
user.profile.linkedinProfile = req.body.linkedinProfile.trim() || '';
user.profile.codepenProfile = req.body.codepenProfile.trim() || '';
user.profile.twitterHandle = req.body.twitterHandle.trim() || '';
user.profile.bio = req.body.bio.trim() || '';
user.profile.picture = req.body.picture.trim() || 'https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png';
user.portfolio.website1Title = req.body.website1Title.trim() || '';
user.portfolio.website1Link = req.body.website1Link.trim() || '';
user.portfolio.website1Image = req.body.website1Image.trim() || '';
user.portfolio.website2Title = req.body.website2Title.trim() || '';
user.portfolio.website2Link = req.body.website2Link.trim() || '';
user.portfolio.website2Image = req.body.website2Image.trim() || '';
user.portfolio.website3Title = req.body.website3Title.trim() || '';
user.portfolio.website3Link = req.body.website3Link.trim() || '';
user.portfolio.website3Image = req.body.website3Image.trim() || '';
User.findOne({ email: req.body.email }, function(err, existingEmail) {
user.save(function (err) {
if (err) { if (err) {
return next(err); return next(err);
} }
req.flash('success', {msg: 'Profile information updated.'}); var user = req.user;
res.redirect('/account'); if (existingEmail && existingEmail.email != user.email) {
resources.updateUserStoryPictures(user._id.toString(), user.profile.picture, user.profile.username); req.flash('errors', {
msg: "An account with that email address already exists."
});
return res.redirect('/account');
}
User.findOne({ username: req.body.username }, function(err, existingUsername) {
if (err) {
return next(err);
}
var user = req.user;
if (existingUsername && existingUsername.profile.username !== user.profile.username) {
req.flash('errors', {
msg: 'An account with that username already exists.'
});
return res.redirect('/account');
}
user.email = req.body.email.trim() || '';
user.profile.name = req.body.name.trim() || '';
user.profile.username = req.body.username.trim() || '';
user.profile.location = req.body.location.trim() || '';
user.profile.githubProfile = req.body.githubProfile.trim() || '';
user.profile.linkedinProfile = req.body.linkedinProfile.trim() || '';
user.profile.codepenProfile = req.body.codepenProfile.trim() || '';
user.profile.twitterHandle = req.body.twitterHandle.trim() || '';
user.profile.bio = req.body.bio.trim() || '';
user.profile.picture = req.body.picture.trim() || 'https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png';
user.portfolio.website1Title = req.body.website1Title.trim() || '';
user.portfolio.website1Link = req.body.website1Link.trim() || '';
user.portfolio.website1Image = req.body.website1Image.trim() || '';
user.portfolio.website2Title = req.body.website2Title.trim() || '';
user.portfolio.website2Link = req.body.website2Link.trim() || '';
user.portfolio.website2Image = req.body.website2Image.trim() || '';
user.portfolio.website3Title = req.body.website3Title.trim() || '';
user.portfolio.website3Link = req.body.website3Link.trim() || '';
user.portfolio.website3Image = req.body.website3Image.trim() || '';
user.save(function (err) {
if (err) {
return next(err);
}
req.flash('success', {msg: 'Profile information updated.'});
res.redirect('/account');
resources.updateUserStoryPictures(user._id.toString(), user.profile.picture, user.profile.username);
});
});
}); });
});
}); });
});
}; };
/** /**
@ -363,29 +393,29 @@ exports.postUpdateProfile = function(req, res, next) {
*/ */
exports.postUpdatePassword = function(req, res, next) { exports.postUpdatePassword = function(req, res, next) {
req.assert('password', 'Password must be at least 4 characters long').len(4); req.assert('password', 'Password must be at least 4 characters long').len(4);
req.assert('confirmPassword', 'Passwords do not match') req.assert('confirmPassword', 'Passwords do not match')
.equals(req.body.password); .equals(req.body.password);
var errors = req.validationErrors(); var errors = req.validationErrors();
if (errors) { if (errors) {
req.flash('errors', errors); req.flash('errors', errors);
return res.redirect('/account'); return res.redirect('/account');
} }
User.findById(req.user.id, function(err, user) { User.findById(req.user.id, function(err, user) {
if (err) { return next(err); } if (err) { return next(err); }
user.password = req.body.password; user.password = req.body.password;
user.save(function(err) { user.save(function(err) {
if (err) { return next(err); } if (err) { return next(err); }
req.flash('success', { msg: 'Password has been changed.' }); req.flash('success', { msg: 'Password has been changed.' });
res.redirect('/account'); res.redirect('/account');
});
}); });
});
}; };
/** /**
@ -394,12 +424,12 @@ exports.postUpdatePassword = function(req, res, next) {
*/ */
exports.postDeleteAccount = function(req, res, next) { exports.postDeleteAccount = function(req, res, next) {
User.remove({ _id: req.user.id }, function(err) { User.remove({ _id: req.user.id }, function(err) {
if (err) { return next(err); } if (err) { return next(err); }
req.logout(); req.logout();
req.flash('info', { msg: 'Your account has been deleted.' }); req.flash('info', { msg: 'Your account has been deleted.' });
res.redirect('/'); res.redirect('/');
}); });
}; };
/** /**
@ -408,22 +438,22 @@ exports.postDeleteAccount = function(req, res, next) {
*/ */
exports.getOauthUnlink = function(req, res, next) { exports.getOauthUnlink = function(req, res, next) {
var provider = req.params.provider; var provider = req.params.provider;
User.findById(req.user.id, function(err, user) { User.findById(req.user.id, function(err, user) {
if (err) { return next(err); } if (err) { return next(err); }
user[provider] = undefined; user[provider] = undefined;
user.tokens = user.tokens =
_.reject(user.tokens, function(token) { _.reject(user.tokens, function(token) {
return token.kind === provider; return token.kind === provider;
}); });
user.save(function(err) { user.save(function(err) {
if (err) { return next(err); } if (err) { return next(err); }
req.flash('info', { msg: provider + ' account has been unlinked.' }); req.flash('info', { msg: provider + ' account has been unlinked.' });
res.redirect('/account'); res.redirect('/account');
});
}); });
});
}; };
/** /**
@ -432,25 +462,25 @@ exports.getOauthUnlink = function(req, res, next) {
*/ */
exports.getReset = function(req, res) { exports.getReset = function(req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
return res.redirect('/'); return res.redirect('/');
} }
User User
.findOne({ resetPasswordToken: req.params.token }) .findOne({ resetPasswordToken: req.params.token })
.where('resetPasswordExpires').gt(Date.now()) .where('resetPasswordExpires').gt(Date.now())
.exec(function(err, user) { .exec(function(err, user) {
if (err) { return next(err); } if (err) { return next(err); }
if (!user) { if (!user) {
req.flash('errors', { req.flash('errors', {
msg: 'Password reset token is invalid or has expired.' msg: 'Password reset token is invalid or has expired.'
});
return res.redirect('/forgot');
}
res.render('account/reset', {
title: 'Password Reset',
token: req.params.token
});
}); });
return res.redirect('/forgot');
}
res.render('account/reset', {
title: 'Password Reset',
token: req.params.token
});
});
}; };
/** /**
@ -459,72 +489,72 @@ exports.getReset = function(req, res) {
*/ */
exports.postReset = function(req, res, next) { exports.postReset = function(req, res, next) {
var errors = req.validationErrors(); var errors = req.validationErrors();
if (errors) { if (errors) {
req.flash('errors', errors); req.flash('errors', errors);
return res.redirect('back'); return res.redirect('back');
}
async.waterfall([
function(done) {
User
.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.'
});
return res.redirect('back');
}
user.password = req.body.password;
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;
user.save(function(err) {
if (err) { return done(err); }
req.logIn(user, function(err) {
done(err, user);
});
});
});
},
function(user, done) {
var transporter = nodemailer.createTransport({
service: 'Mandrill',
auth: {
user: secrets.mandrill.user,
pass: secrets.mandrill.password
}
});
var mailOptions = {
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'
].join(' ')
};
transporter.sendMail(mailOptions, function(err) {
if (err) { return done(err); }
req.flash('success', {
msg: 'Success! Your password has been changed.'
});
done();
});
} }
], function(err) {
if (err) { return next(err); } async.waterfall([
res.redirect('/'); function(done) {
}); User
.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.'
});
return res.redirect('back');
}
user.password = req.body.password;
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;
user.save(function(err) {
if (err) { return done(err); }
req.logIn(user, function(err) {
done(err, user);
});
});
});
},
function(user, done) {
var transporter = nodemailer.createTransport({
service: 'Mandrill',
auth: {
user: secrets.mandrill.user,
pass: secrets.mandrill.password
}
});
var mailOptions = {
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'
].join(' ')
};
transporter.sendMail(mailOptions, function(err) {
if (err) { return done(err); }
req.flash('success', {
msg: 'Success! Your password has been changed.'
});
done();
});
}
], function(err) {
if (err) { return next(err); }
res.redirect('/');
});
}; };
/** /**
@ -533,12 +563,12 @@ exports.postReset = function(req, res, next) {
*/ */
exports.getForgot = function(req, res) { exports.getForgot = function(req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
return res.redirect('/'); return res.redirect('/');
} }
res.render('account/forgot', { res.render('account/forgot', {
title: 'Forgot Password' title: 'Forgot Password'
}); });
}; };
/** /**
@ -547,80 +577,80 @@ exports.getForgot = function(req, res) {
*/ */
exports.postForgot = function(req, res, next) { exports.postForgot = function(req, res, next) {
var errors = req.validationErrors(); var errors = req.validationErrors();
if (errors) { if (errors) {
req.flash('errors', errors); req.flash('errors', errors);
return res.redirect('/forgot'); return res.redirect('/forgot');
}
async.waterfall([
function(done) {
crypto.randomBytes(16, function(err, buf) {
if (err) { return done(err); }
var token = buf.toString('hex');
done(null, token);
});
},
function(token, done) {
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.'
});
return res.redirect('/forgot');
}
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
user.save(function(err) {
if (err) { return done(err); }
done(null, token, user);
});
});
},
function(token, user, done) {
var transporter = nodemailer.createTransport({
service: 'Mandrill',
auth: {
user: secrets.mandrill.user,
pass: secrets.mandrill.password
}
});
var mailOptions = {
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)\n',
'requested we reset your Free Code Camp account\'s password.\n\n',
'Please click on the following link, or paste this into your\n',
'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\n',
'your password will remain unchanged.\n'
].join('')
};
transporter.sendMail(mailOptions, function(err) {
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); } async.waterfall([
res.redirect('/forgot'); function(done) {
}); crypto.randomBytes(16, function(err, buf) {
if (err) { return done(err); }
var token = buf.toString('hex');
done(null, token);
});
},
function(token, done) {
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.'
});
return res.redirect('/forgot');
}
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
user.save(function(err) {
if (err) { return done(err); }
done(null, token, user);
});
});
},
function(token, user, done) {
var transporter = nodemailer.createTransport({
service: 'Mandrill',
auth: {
user: secrets.mandrill.user,
pass: secrets.mandrill.password
}
});
var mailOptions = {
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)\n',
'requested we reset your Free Code Camp account\'s password.\n\n',
'Please click on the following link, or paste this into your\n',
'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\n',
'your password will remain unchanged.\n'
].join('')
};
transporter.sendMail(mailOptions, function(err) {
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); }
res.redirect('/forgot');
});
}; };

View File

@ -14,6 +14,11 @@ var storySchema = new mongoose.Schema({
type: String, type: String,
unique: false unique: false
}, },
metaDescription: {
type: String,
default: '',
unique: false
},
description: { description: {
type: String, type: String,
unique: false unique: false

View File

@ -61,6 +61,7 @@
"passport-twitter": "^1.0.2", "passport-twitter": "^1.0.2",
"ramda": "^0.10.0", "ramda": "^0.10.0",
"request": "^2.53.0", "request": "^2.53.0",
"sanitize-html": "^1.6.1",
"sitemap": "^0.7.4", "sitemap": "^0.7.4",
"uglify-js": "^2.4.15", "uglify-js": "^2.4.15",
"validator": "^3.22.1", "validator": "^3.22.1",

View File

@ -821,6 +821,11 @@ iframe.iphone {
height: 50px; height: 50px;
} }
.url-preview {
max-width: 250px;
max-height: 250px;
}
//.media ~ .media .media-body-wrapper:nth-child(odd) { //.media ~ .media .media-body-wrapper:nth-child(odd) {
// background-color: #e5e5e5; // background-color: #e5e5e5;
//} //}

View File

@ -164,7 +164,7 @@ $(document).ready(function() {
headline: headline, headline: headline,
timePosted: Date.now(), timePosted: Date.now(),
description: description, description: description,
storyMetaDescription: storyMetaDescription,
rank: 1, rank: 1,
upVotes: [userDataForUpvote], upVotes: [userDataForUpvote],
author: { author: {
@ -173,7 +173,7 @@ $(document).ready(function() {
username: user.profile.username username: user.profile.username
}, },
comments: [], comments: [],
image: '' image: storyImage
} }
}) })
.fail(function (xhr, textStatus, errorThrown) { .fail(function (xhr, textStatus, errorThrown) {

View File

@ -40,7 +40,7 @@ block content
| Your usernames must be 20 characters or fewer. | Your usernames must be 20 characters or fewer.
.form-group .form-group
.col-sm-6.col-sm-offset-3 .col-sm-6.col-sm-offset-3
input.form-control(type='password', ng-model='password', name='password', id='password', placeholder='password', required, ng-minlength=5) input.form-control(type='password', ng-model='password', name='password', id='password', placeholder='password', required, ng-minlength=8)
.col-sm-6.col-sm-offset-3(ng-cloak, ng-show="signupForm.password.$error.minlength && !signupForm.password.$pristine") .col-sm-6.col-sm-offset-3(ng-cloak, ng-show="signupForm.password.$error.minlength && !signupForm.password.$pristine")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled

View File

@ -34,11 +34,13 @@
}) })
.done(function (data, textStatus, xhr) { .done(function (data, textStatus, xhr) {
if (data.alreadyPosted) { if (data.alreadyPosted) {
window.location = '/stories/' + data.storyURL; window.location = data.storyURL;
} else { } else {
window.location = '/stories/submit/url=' + window.location = '/stories/submit/new-story?url=' +
encodeURIComponent(data.storyURL) + encodeURIComponent(data.storyURL) +
'&title=' + encodeURIComponent(data.storyTitle); '&title=' + encodeURIComponent(data.storyTitle) +
'&image=' + encodeURIComponent(data.storyImage) +
'&description=' + encodeURIComponent(data.storyMetaDescription);
} }
}); });
} }

View File

@ -5,6 +5,7 @@
var comments = !{JSON.stringify(comments)}; var comments = !{JSON.stringify(comments)};
var upVotes = !{JSON.stringify(upVotes)}; var upVotes = !{JSON.stringify(upVotes)};
var user = !{JSON.stringify(user)}; var user = !{JSON.stringify(user)};
var image = !{JSON.stringify(image)};
.spacer .spacer
h3.row.col-xs-12 h3.row.col-xs-12
@ -25,7 +26,14 @@
a(href="#{link}") a(href="#{link}")
h3= title h3= title
h6 h6
.col-xs-12.negative-28 .col-xs-12.positive-15.hidden-element#image-display
.media
.media-left
img.url-preview.media-object(src="#{image}", alt="#{storyMetaDescription}")
.media-body
.col-xs-12.col-sm-12.col-md-6
h4= storyMetaDescription
.col-xs-12
h4= description h4= description
.negative-5 .negative-5
span Posted #{timeAgo} span Posted #{timeAgo}
@ -44,6 +52,9 @@
span.spacer.pull-left#textarea_feedback span.spacer.pull-left#textarea_feedback
script. script.
if (image) {
$('#image-display').removeClass('hidden-element')
}
$('#reply-to-main-post').on('click', function() { $('#reply-to-main-post').on('click', function() {
$('#initial-comment-submit').removeClass('hidden-element'); $('#initial-comment-submit').removeClass('hidden-element');
$(this).unbind('click'); $(this).unbind('click');

View File

@ -3,6 +3,8 @@
script. script.
var storyURL = !{JSON.stringify(storyURL)}; var storyURL = !{JSON.stringify(storyURL)};
var storyTitle = !{JSON.stringify(storyTitle)}; var storyTitle = !{JSON.stringify(storyTitle)};
var storyImage = !{JSON.stringify(storyImage)};
var storyMetaDescription = !{JSON.stringify(storyMetaDescription)};
form.form-horizontal.control-label-story-submission#story-submission-form form.form-horizontal.control-label-story-submission#story-submission-form
.col-xs-12 .col-xs-12
.form-group .form-group
@ -20,13 +22,29 @@
label.control-label.control-label-story-submission(for='name') Description label.control-label.control-label-story-submission(for='name') Description
.col-xs-12.col-md-11 .col-xs-12.col-md-11
input#description-box.form-control(name="comment-box", placeholder="Start off the discussion with a description of your post" maxlength='140') input#description-box.form-control(name="comment-box", placeholder="Start off the discussion with a description of your post" maxlength='140')
span.pull-left#textarea_feedback
.spacer
.form-group .form-group
button.btn.btn-big.btn-block.btn-primary#story-submit Submit .col-xs-12.col-md-offset-1
span.pull-left#textarea_feedback
.form-group
.col-xs-11.col-md-offset-1
.hidden-element#image-display
.media
.media-left
img.url-preview.media-object(src="#{storyImage}", alt="#{storyMetaDescription}")
.media-body
.col-xs-12
p= storyMetaDescription
.spacer
.row
.form-group
button.btn.btn-big.btn-block.btn-primary#story-submit Submit
script. script.
$('#story-url').val(storyURL).attr('disabled', 'disabled'); $('#story-url').val(storyURL).attr('disabled', 'disabled');
$('#story-title').val(storyTitle); $('#story-title').val(storyTitle);
if (storyImage) {
$('#image-display').removeClass('hidden-element');
}
var text_max = 140; var text_max = 140;
$('#textarea_feedback').html(text_max + ' characters remaining'); $('#textarea_feedback').html(text_max + ' characters remaining');
$('#description-box').keyup(function () { $('#description-box').keyup(function () {