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(
'/stories/submit/:newStory',
'/stories/submit/new-story',
storyController.preSubmit
);

View File

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

View File

@ -8,9 +8,9 @@ var R = require('ramda'),
mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient,
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
* 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);
story.exec(function(err, stories) {
if (err) {
throw err;
res.send(500);
return next(err);
}
var foundationDate = 1413298800000;
var sliceVal = stories.length >= 100 ? 100 : stories.length;
var rankedStories = stories;
return res.json(rankedStories.map(function(elem) {
return res.json(stories.map(function(elem) {
return elem;
}).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);
}).slice(0, sliceVal));
@ -49,48 +48,61 @@ exports.recentJSON = function(req, res, next) {
var story = Story.find({}).sort({'timePosted': -1}).limit(100);
story.exec(function(err, stories) {
if (err) {
throw err;
res.status(500);
return next(err);
}
res.json(stories);
});
};
exports.hot = function(req, res, next) {
exports.hot = function(req, res) {
res.render('stories/index', {
page: 'hot'
});
};
exports.submitNew = function(req,res, next) {
exports.submitNew = function(req, res) {
res.render('stories/index', {
page: 'submit'
});
};
exports.search = function(req, res, next) {
exports.search = function(req, res) {
res.render('stories/index', {
page: 'search'
});
};
exports.recent = function(req, res, next) {
exports.recent = function(req, res) {
res.render('stories/index', {
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 url = data[0];
var title = data[1];
res.render('stories/index', {
var title = data.title || '';
var image = data.image || '';
var description = data.description || '';
return res.render('stories/index', {
page: 'storySubmission',
storyURL: url,
storyTitle: title
storyURL: data.url,
storyTitle: title,
storyImage: image,
storyMetaDescription: description
});
};
@ -132,15 +144,15 @@ exports.returnIndividualStory = function(req, res, next) {
user: req.user,
timeAgo: moment(story.timePosted).fromNow(),
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) {
var db = database;
db.collection('stories').find({
database.collection('stories').find({
"$text": {
"$search": req.body.data.searchValue
}
@ -155,6 +167,7 @@ exports.getStories = function(req, res, next) {
comments: 1,
image: 1,
storyLink: 1,
metaDescription: 1,
textScore: {
$meta: "textScore"
}
@ -177,7 +190,8 @@ exports.upvote = function(req, res, next) {
var data = req.body.data;
Story.find({'_id': data.id}, function(err, story) {
if (err) {
throw err;
res.status(500);
return next(err);
}
story = story.pop();
story.rank++;
@ -197,59 +211,68 @@ exports.comments = function(req, res, next) {
var data = req.params.id;
Comment.find({'_id': data}, function(err, comment) {
if (err) {
throw err;
res.status(500);
return next(err);
}
comment = comment.pop();
return res.send(comment);
});
};
exports.newStory = function(req, res, next) {
exports.newStory = function(req, res) {
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) {
url = 'http://' + url;
}
debug('In pre submit with a url', url);
Story.find({'link': url}, function(err, story) {
debug('Attempting to find a story');
if (err) {
debug('oops');
return res.status(500);
}
if (story.length) {
debug('Found a story already, here\'s the return from find', story);
req.flash('errors', {
msg: "Someone's already posted that link. Here's the discussion."
});
debug('Redirecting the user with', story[0].storyLink);
return res.json({
alreadyPosted: true,
storyURL: story.pop().storyLink
storyURL: '/stories/' + story.pop().storyLink
});
}
resources.getURLTitle(url, processResponse);
});
function processResponse(err, storyTitle) {
function processResponse(err, story) {
if (err) {
res.json({
alreadyPosted: false,
storyURL: url,
storyTitle: ''
storyTitle: '',
storyImage: '',
storyMetaDescription: ''
});
} else {
storyTitle = storyTitle ? storyTitle : '';
res.json({
alreadyPosted: false,
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 storyLink = data.headline
.replace(/\'/g, '')
@ -263,19 +286,20 @@ exports.storySubmission = function(req, res, next) {
link = 'http://' + link;
}
var story = new Story({
headline: data.headline,
headline: sanitizeHtml(data.headline),
timePosted: Date.now(),
link: link,
description: data.description,
description: sanitizeHtml(data.description),
rank: 1,
upVotes: data.upVotes,
author: data.author,
comments: [],
image: data.image,
storyLink: storyLink
storyLink: storyLink,
metaDescription: data.storyMetaDescription
});
story.save(function(err, data) {
story.save(function(err) {
if (err) {
return res.status(500);
}
@ -285,12 +309,22 @@ exports.storySubmission = function(req, res, next) {
});
};
exports.commentSubmit = function(req, res, next) {
debug('comment submit fired');
exports.commentSubmit = function(req, res) {
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({
associatedPost: data.associatedPost,
body: data.body,
body: sanitizedBody,
rank: 0,
upvotes: 0,
author: data.author,
@ -301,13 +335,22 @@ exports.commentSubmit = function(req, res, next) {
commentSave(comment, Story, res);
};
exports.commentOnCommentSubmit = function(req, res, next) {
debug('comment on comment submit');
var idToFind = req.params.id;
exports.commentOnCommentSubmit = function(req, res) {
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({
associatedPost: data.associatedPost,
body: data.body,
body: sanitizedBody,
rank: 0,
upvotes: 0,
author: data.author,
@ -331,7 +374,7 @@ function commentSave(comment, Context, res) {
associatedStory = associatedStory.pop();
if (associatedStory) {
associatedStory.comments.push(data._id);
associatedStory.save(function (err, data) {
associatedStory.save(function (err) {
if (err) {
res.status(500);
}

View File

@ -7,10 +7,10 @@ var _ = require('lodash'),
secrets = require('../config/secrets'),
moment = require('moment'),
Challenge = require('./../models/Challenge'),
debug = require('debug')('freecc:cntr:challenges')
debug = require('debug')('freecc:cntr:challenges'),
resources = require('./resources');
//TODO(Berks): Refactor to use module.exports = {} pattern.
/**
* GET /signin
@ -18,10 +18,10 @@ var _ = require('lodash'),
*/
exports.getSignin = function(req, res) {
if (req.user) return res.redirect('/');
res.render('account/signin', {
title: 'Free Code Camp Login'
});
if (req.user) return res.redirect('/');
res.render('account/signin', {
title: 'Free Code Camp Login'
});
};
/**
@ -30,28 +30,28 @@ exports.getSignin = function(req, res) {
*/
exports.postSignin = function(req, res, next) {
req.assert('email', 'Email is not valid').isEmail();
req.assert('password', 'Password cannot be blank').notEmpty();
req.assert('email', 'Email is not valid').isEmail();
req.assert('password', 'Password cannot be blank').notEmpty();
var errors = req.validationErrors();
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
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');
if (errors) {
req.flash('errors', errors);
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);
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);
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) {
req.logout();
res.redirect('/');
req.logout();
res.redirect('/');
};
/**
@ -70,10 +70,10 @@ exports.signout = function(req, res) {
*/
exports.getEmailSignin = function(req, res) {
if (req.user) return res.redirect('/');
res.render('account/email-signin', {
title: 'Sign in to your Free Code Camp Account'
});
if (req.user) return res.redirect('/');
res.render('account/email-signin', {
title: 'Sign in to your Free Code Camp Account'
});
};
/**
@ -82,10 +82,10 @@ exports.getEmailSignin = function(req, res) {
*/
exports.getEmailSignup = function(req, res) {
if (req.user) return res.redirect('/');
res.render('account/email-signup', {
title: 'Create Your Free Code Camp Account'
});
if (req.user) return res.redirect('/');
res.render('account/email-signup', {
title: 'Create Your Free Code Camp Account'
});
};
/**
@ -94,64 +94,93 @@ exports.getEmailSignup = function(req, res) {
*/
exports.postEmailSignup = function(req, res, next) {
var errors = req.validationErrors();
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
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'
if (errors) {
req.flash('errors', errors);
return res.redirect('/email-signup');
}
});
User.findOne({ email: req.body.email }, function(err, existingUser) {
if (err) { return next(err); }
var possibleUserData = req.body;
if (existingUser) {
req.flash('errors', {
msg: 'Account with that email address already exists.'
});
return res.redirect('/email-signup');
if (possibleUserData.password.length < 8) {
req.flash('errors', {
msg: 'Your password is too short'
});
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');
});
if (possibleUserData.username.length < 5 || possibleUserData.length > 20) {
req.flash('errors', {
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',
auth: {
user: secrets.mandrill.user,
pass: secrets.mandrill.password
}
User.findOne({ email: req.body.email }, function(err, existingEmail) {
if (err) {
return next(err);
}
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) {
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
*/
exports.getAccountAngular = function(req, res) {
exports.getAccountAngular = function(req, res) {
res.json({
user: req.user
user: req.user
});
};
@ -180,13 +209,13 @@ exports.getAccount = function(req, res) {
*/
exports.checkUniqueUsername = function(req, res) {
User.count({'profile.username': req.params.username.toLowerCase()}, function (err, data) {
if (data == 1) {
return res.send(true);
} else {
return res.send(false);
}
});
User.count({'profile.username': req.params.username.toLowerCase()}, function (err, data) {
if (data == 1) {
return res.send(true);
} else {
return res.send(false);
}
});
};
/**
@ -207,13 +236,13 @@ exports.checkExistingUsername = function(req, res) {
*/
exports.checkUniqueEmail = function(req, res) {
User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) {
if (data == 1) {
return res.send(true);
} else {
return res.send(false);
}
});
User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) {
if (data == 1) {
return res.send(true);
} else {
return res.send(false);
}
});
};
@ -223,44 +252,44 @@ exports.checkUniqueEmail = function(req, res) {
*/
exports.returnUser = function(req, res, next) {
User.find({'profile.username': req.params.username.toLowerCase()}, function(err, user) {
if (err) { debug('Username err: ', err); next(err); }
if (user[0]) {
var user = user[0];
Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) {
res.render('account/show', {
title: 'Camper: ',
username: user.profile.username,
name: user.profile.name,
location: user.profile.location,
githubProfile: user.profile.githubProfile,
linkedinProfile: user.profile.linkedinProfile,
codepenProfile: user.profile.codepenProfile,
twitterHandle: user.profile.twitterHandle,
bio: user.profile.bio,
picture: user.profile.picture,
points: user.points,
website1Link: user.portfolio.website1Link,
website1Title: user.portfolio.website1Title,
website1Image: user.portfolio.website1Image,
website2Link: user.portfolio.website2Link,
website2Title: user.portfolio.website2Title,
website2Image: user.portfolio.website2Image,
website3Link: user.portfolio.website3Link,
website3Title: user.portfolio.website3Title,
website3Image: user.portfolio.website3Image,
challenges: c,
ch: user.challengesHash,
moment: moment
});
});
} else {
req.flash('errors', {
msg: "404: We couldn't find a page with that url. Please double check the link."
});
return res.redirect('/');
}
});
User.find({'profile.username': req.params.username.toLowerCase()}, function(err, user) {
if (err) { debug('Username err: ', err); next(err); }
if (user[0]) {
var user = user[0];
Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) {
res.render('account/show', {
title: 'Camper: ',
username: user.profile.username,
name: user.profile.name,
location: user.profile.location,
githubProfile: user.profile.githubProfile,
linkedinProfile: user.profile.linkedinProfile,
codepenProfile: user.profile.codepenProfile,
twitterHandle: user.profile.twitterHandle,
bio: user.profile.bio,
picture: user.profile.picture,
points: user.points,
website1Link: user.portfolio.website1Link,
website1Title: user.portfolio.website1Title,
website1Image: user.portfolio.website1Image,
website2Link: user.portfolio.website2Link,
website2Title: user.portfolio.website2Title,
website2Image: user.portfolio.website2Image,
website3Link: user.portfolio.website3Link,
website3Title: user.portfolio.website3Title,
website3Image: user.portfolio.website3Image,
challenges: c,
ch: user.challengesHash,
moment: moment
});
});
} else {
req.flash('errors', {
msg: "404: We couldn't find a page with that url. Please double check the link."
});
return res.redirect('/');
}
});
};
@ -292,69 +321,70 @@ exports.updateProgress = function(req, res) {
*/
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) {
if (err) {
return next(err);
}
var user = req.user;
if (existingEmail && existingEmail.email != user.email) {
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);
// What does this do?
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');
}
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.save(function (err) {
User.findOne({ email: req.body.email }, function(err, existingEmail) {
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);
var user = req.user;
if (existingEmail && existingEmail.email != user.email) {
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) {
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('password', 'Password must be at least 4 characters long').len(4);
req.assert('confirmPassword', 'Passwords do not match')
.equals(req.body.password);
var errors = req.validationErrors();
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
return res.redirect('/account');
}
if (errors) {
req.flash('errors', errors);
return res.redirect('/account');
}
User.findById(req.user.id, function(err, user) {
if (err) { return next(err); }
User.findById(req.user.id, function(err, user) {
if (err) { return next(err); }
user.password = req.body.password;
user.password = req.body.password;
user.save(function(err) {
if (err) { return next(err); }
user.save(function(err) {
if (err) { return next(err); }
req.flash('success', { msg: 'Password has been changed.' });
res.redirect('/account');
req.flash('success', { msg: 'Password has been changed.' });
res.redirect('/account');
});
});
});
};
/**
@ -394,12 +424,12 @@ 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); }
req.logout();
req.flash('info', { msg: 'Your account has been deleted.' });
res.redirect('/');
});
User.remove({ _id: req.user.id }, function(err) {
if (err) { return next(err); }
req.logout();
req.flash('info', { msg: 'Your account has been deleted.' });
res.redirect('/');
});
};
/**
@ -408,22 +438,22 @@ exports.postDeleteAccount = function(req, res, next) {
*/
exports.getOauthUnlink = function(req, res, next) {
var provider = req.params.provider;
User.findById(req.user.id, function(err, user) {
if (err) { return next(err); }
var provider = req.params.provider;
User.findById(req.user.id, function(err, user) {
if (err) { return next(err); }
user[provider] = undefined;
user.tokens =
_.reject(user.tokens, function(token) {
return token.kind === provider;
});
user[provider] = undefined;
user.tokens =
_.reject(user.tokens, function(token) {
return token.kind === provider;
});
user.save(function(err) {
if (err) { return next(err); }
req.flash('info', { msg: provider + ' account has been unlinked.' });
res.redirect('/account');
user.save(function(err) {
if (err) { return next(err); }
req.flash('info', { msg: provider + ' account has been unlinked.' });
res.redirect('/account');
});
});
});
};
/**
@ -432,25 +462,25 @@ exports.getOauthUnlink = function(req, res, next) {
*/
exports.getReset = function(req, res) {
if (req.isAuthenticated()) {
return res.redirect('/');
}
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.'
if (req.isAuthenticated()) {
return res.redirect('/');
}
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('/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) {
var errors = req.validationErrors();
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
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();
});
if (errors) {
req.flash('errors', errors);
return res.redirect('back');
}
], function(err) {
if (err) { return next(err); }
res.redirect('/');
});
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); }
res.redirect('/');
});
};
/**
@ -533,12 +563,12 @@ exports.postReset = function(req, res, next) {
*/
exports.getForgot = function(req, res) {
if (req.isAuthenticated()) {
return res.redirect('/');
}
res.render('account/forgot', {
title: 'Forgot Password'
});
if (req.isAuthenticated()) {
return res.redirect('/');
}
res.render('account/forgot', {
title: 'Forgot Password'
});
};
/**
@ -547,80 +577,80 @@ exports.getForgot = function(req, res) {
*/
exports.postForgot = function(req, res, next) {
var errors = req.validationErrors();
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
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');
});
if (errors) {
req.flash('errors', errors);
return res.redirect('/forgot');
}
], function(err) {
if (err) { return next(err); }
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); }
res.redirect('/forgot');
});
};

View File

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

View File

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

View File

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

View File

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

View File

@ -40,7 +40,7 @@ block content
| Your usernames must be 20 characters or fewer.
.form-group
.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")
alert(type='danger')
span.ion-close-circled

View File

@ -34,11 +34,13 @@
})
.done(function (data, textStatus, xhr) {
if (data.alreadyPosted) {
window.location = '/stories/' + data.storyURL;
window.location = data.storyURL;
} else {
window.location = '/stories/submit/url=' +
window.location = '/stories/submit/new-story?url=' +
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 upVotes = !{JSON.stringify(upVotes)};
var user = !{JSON.stringify(user)};
var image = !{JSON.stringify(image)};
.spacer
h3.row.col-xs-12
@ -25,7 +26,14 @@
a(href="#{link}")
h3= title
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
.negative-5
span Posted #{timeAgo}
@ -44,6 +52,9 @@
span.spacer.pull-left#textarea_feedback
script.
if (image) {
$('#image-display').removeClass('hidden-element')
}
$('#reply-to-main-post').on('click', function() {
$('#initial-comment-submit').removeClass('hidden-element');
$(this).unbind('click');

View File

@ -3,6 +3,8 @@
script.
var storyURL = !{JSON.stringify(storyURL)};
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
.col-xs-12
.form-group
@ -20,13 +22,29 @@
label.control-label.control-label-story-submission(for='name') Description
.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')
span.pull-left#textarea_feedback
.spacer
.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.
$('#story-url').val(storyURL).attr('disabled', 'disabled');
$('#story-title').val(storyTitle);
if (storyImage) {
$('#image-display').removeClass('hidden-element');
}
var text_max = 140;
$('#textarea_feedback').html(text_max + ' characters remaining');
$('#description-box').keyup(function () {