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
@ -99,9 +99,25 @@ exports.postEmailSignup = function(req, res, next) {
if (errors) {
req.flash('errors', errors);
return res.redirect('/email-signup');
debug(errors);
}
var possibleUserData = req.body;
if (possibleUserData.password.length < 8) {
req.flash('errors', {
msg: 'Your password is too short'
});
return 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,
@ -111,18 +127,30 @@ exports.postEmailSignup = function(req, res, next) {
}
});
User.findOne({ email: req.body.email }, function(err, existingUser) {
if (err) { return next(err); }
User.findOne({ email: req.body.email }, function(err, existingEmail) {
if (err) {
return next(err);
}
if (existingUser) {
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');
@ -152,6 +180,7 @@ exports.postEmailSignup = function(req, res, next) {
if (err) { return err; }
});
});
});
};
/**
@ -169,7 +198,7 @@ exports.getAccount = function(req, res) {
* Angular API Call
*/
exports.getAccountAngular = function(req, res) {
exports.getAccountAngular = function(req, res) {
res.json({
user: req.user
});
@ -292,6 +321,8 @@ exports.updateProgress = function(req, res) {
*/
exports.postUpdateProfile = function(req, res, next) {
// What does this do?
User.findById(req.user.id, function(err, user) {
if (err) return next(err);
var errors = req.validationErrors();
@ -322,7 +353,6 @@ exports.postUpdateProfile = function(req, res, next) {
});
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() || '';

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
.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 () {