2015-06-25 22:03:46 +00:00
|
|
|
var Rx = require('rx'),
|
|
|
|
nodemailer = require('nodemailer'),
|
|
|
|
assign = require('object.assign'),
|
2015-06-01 23:23:53 +00:00
|
|
|
sanitizeHtml = require('sanitize-html'),
|
|
|
|
moment = require('moment'),
|
|
|
|
mongodb = require('mongodb'),
|
2015-06-09 20:57:23 +00:00
|
|
|
// debug = require('debug')('freecc:cntr:story'),
|
2015-06-04 00:14:45 +00:00
|
|
|
utils = require('../utils'),
|
2015-06-25 22:03:46 +00:00
|
|
|
observeMethod = require('../utils/rx').observeMethod,
|
|
|
|
saveUser = require('../utils/rx').saveUser,
|
|
|
|
saveInstance = require('../utils/rx').saveInstance,
|
2015-06-01 23:23:53 +00:00
|
|
|
MongoClient = mongodb.MongoClient,
|
2015-06-03 23:19:23 +00:00
|
|
|
secrets = require('../../config/secrets');
|
|
|
|
|
2015-06-25 22:03:46 +00:00
|
|
|
var foundationDate = 1413298800000;
|
|
|
|
var time48Hours = 172800000;
|
|
|
|
|
|
|
|
var unDasherize = utils.unDasherize;
|
|
|
|
var dasherize = utils.dasherize;
|
|
|
|
var getURLTitle = utils.getURLTitle;
|
|
|
|
|
|
|
|
var transporter = nodemailer.createTransport({
|
|
|
|
service: 'Mandrill',
|
|
|
|
auth: {
|
|
|
|
user: secrets.mandrill.user,
|
|
|
|
pass: secrets.mandrill.password
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
function sendMailWhillyNilly(mailOptions) {
|
|
|
|
transporter.sendMail(mailOptions, function(err) {
|
|
|
|
if (err) {
|
|
|
|
console.log('err sending mail whilly nilly', err);
|
|
|
|
console.log('logging err but not carring');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function hotRank(timeValue, rank) {
|
|
|
|
/*
|
|
|
|
* Hotness ranking algorithm: http://amix.dk/blog/post/19588
|
|
|
|
* tMS = postedOnDate - foundationTime;
|
|
|
|
* Ranking...
|
|
|
|
* f(ts, 1, rank) = log(10)z + (ts)/45000;
|
|
|
|
*/
|
|
|
|
var z = Math.log(rank) / Math.log(10);
|
|
|
|
var hotness = z + (timeValue / time48Hours);
|
|
|
|
return hotness;
|
|
|
|
}
|
|
|
|
|
|
|
|
function sortByRank(a, b) {
|
|
|
|
return hotRank(b.timePosted - foundationDate, b.rank) -
|
|
|
|
hotRank(a.timePosted - foundationDate, a.rank);
|
|
|
|
}
|
|
|
|
|
|
|
|
function cleanData(data, opts) {
|
|
|
|
var options = assign(
|
|
|
|
{},
|
|
|
|
{
|
|
|
|
allowedTags: [],
|
|
|
|
allowedAttributes: []
|
|
|
|
},
|
|
|
|
opts || {}
|
|
|
|
);
|
|
|
|
return sanitizeHtml(data, options).replace(/";/g, '"');
|
|
|
|
}
|
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
module.exports = function(app) {
|
2015-06-03 23:31:42 +00:00
|
|
|
var router = app.loopback.Router();
|
2015-06-03 23:19:23 +00:00
|
|
|
var User = app.models.User;
|
2015-06-25 22:03:46 +00:00
|
|
|
var findUserById = observeMethod(User, 'findById');
|
|
|
|
var findOneUser = observeMethod(User, 'findOne');
|
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
var Story = app.models.Story;
|
2015-06-25 22:03:46 +00:00
|
|
|
var findStory = observeMethod(Story, 'find');
|
|
|
|
var findOneStory = observeMethod(Story, 'findOne');
|
|
|
|
var findStoryById = observeMethod(Story, 'findById');
|
|
|
|
var countStories = observeMethod(Story, 'count');
|
|
|
|
|
2015-06-24 14:15:39 +00:00
|
|
|
var Comment = app.models.Comment;
|
2015-06-25 22:03:46 +00:00
|
|
|
var findCommentById = observeMethod(Comment, 'findById');
|
2015-06-03 23:19:23 +00:00
|
|
|
|
|
|
|
router.get('/stories/hotStories', hotJSON);
|
|
|
|
router.get('/stories/comments/:id', comments);
|
|
|
|
router.post('/stories/comment/', commentSubmit);
|
|
|
|
router.post('/stories/comment/:id/comment', commentOnCommentSubmit);
|
|
|
|
router.put('/stories/comment/:id/edit', commentEdit);
|
|
|
|
router.get('/stories/submit', submitNew);
|
|
|
|
router.get('/stories/submit/new-story', preSubmit);
|
|
|
|
router.post('/stories/preliminary', newStory);
|
|
|
|
router.post('/stories/', storySubmission);
|
2015-06-05 13:07:46 +00:00
|
|
|
router.get('/stories/', hot);
|
2015-06-03 23:19:23 +00:00
|
|
|
router.post('/stories/search', getStories);
|
2015-06-05 13:07:46 +00:00
|
|
|
router.get('/stories/:storyName', returnIndividualStory);
|
2015-06-03 23:19:23 +00:00
|
|
|
router.post('/stories/upvote/', upvote);
|
|
|
|
|
2015-06-03 23:31:42 +00:00
|
|
|
app.use(router);
|
|
|
|
|
2015-06-09 18:54:45 +00:00
|
|
|
function hotJSON(req, res, next) {
|
2015-06-25 22:03:46 +00:00
|
|
|
var query = {
|
2015-06-23 23:28:16 +00:00
|
|
|
order: 'timePosted DESC',
|
|
|
|
limit: 1000
|
2015-06-25 22:03:46 +00:00
|
|
|
};
|
|
|
|
findStory(query).subscribe(
|
|
|
|
function(stories) {
|
|
|
|
var sliceVal = stories.length >= 100 ? 100 : stories.length;
|
|
|
|
var data = stories.sort(sortByRank).slice(0, sliceVal);
|
|
|
|
res.json(data);
|
|
|
|
},
|
|
|
|
next
|
|
|
|
);
|
2015-06-03 23:19:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function hot(req, res) {
|
|
|
|
return res.render('stories/index', {
|
|
|
|
title: 'Hot stories currently trending on Camper News',
|
|
|
|
page: 'hot'
|
|
|
|
});
|
|
|
|
}
|
2015-03-07 08:42:22 +00:00
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
function submitNew(req, res) {
|
|
|
|
return res.render('stories/index', {
|
|
|
|
title: 'Submit a new story to Camper News',
|
|
|
|
page: 'submit'
|
|
|
|
});
|
|
|
|
}
|
2015-03-03 10:50:16 +00:00
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
function preSubmit(req, res) {
|
|
|
|
var data = req.query;
|
2015-06-25 22:03:46 +00:00
|
|
|
var cleanedData = cleanData(data.url);
|
2015-03-03 10:50:16 +00:00
|
|
|
|
2015-06-25 22:03:46 +00:00
|
|
|
if (data.url.replace(/&/g, '&') !== cleanedData) {
|
2015-03-22 11:20:08 +00:00
|
|
|
req.flash('errors', {
|
2015-06-03 23:19:23 +00:00
|
|
|
msg: 'The data for this post is malformed'
|
|
|
|
});
|
|
|
|
return res.render('stories/index', {
|
|
|
|
page: 'stories/submit'
|
2015-03-22 11:20:08 +00:00
|
|
|
});
|
|
|
|
}
|
2015-03-03 10:50:16 +00:00
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
var title = data.title || '';
|
|
|
|
var image = data.image || '';
|
|
|
|
var description = data.description || '';
|
|
|
|
return res.render('stories/index', {
|
|
|
|
title: 'Confirm your Camper News story submission',
|
|
|
|
page: 'storySubmission',
|
|
|
|
storyURL: data.url,
|
|
|
|
storyTitle: title,
|
|
|
|
storyImage: image,
|
|
|
|
storyMetaDescription: description
|
|
|
|
});
|
|
|
|
}
|
2015-03-03 10:50:16 +00:00
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
function returnIndividualStory(req, res, next) {
|
|
|
|
var dashedName = req.params.storyName;
|
2015-06-25 22:03:46 +00:00
|
|
|
var storyName = unDasherize(dashedName);
|
2015-06-03 23:19:23 +00:00
|
|
|
|
2015-06-25 22:03:46 +00:00
|
|
|
findOneStory({ where: { storyLink: storyName } }).subscribe(
|
|
|
|
function(story) {
|
|
|
|
if (!story) {
|
|
|
|
req.flash('errors', {
|
|
|
|
msg: "404: We couldn't find a story with that name. " +
|
|
|
|
'Please double check the name.'
|
|
|
|
});
|
2015-06-03 23:19:23 +00:00
|
|
|
|
2015-06-25 22:03:46 +00:00
|
|
|
var dashedNameFull = story.storyLink.toLowerCase()
|
|
|
|
.replace(/\s+/g, ' ')
|
|
|
|
.replace(/\s/g, '-');
|
2015-03-03 13:03:33 +00:00
|
|
|
|
2015-06-25 22:03:46 +00:00
|
|
|
if (dashedNameFull !== dashedName) {
|
|
|
|
return res.redirect('../stories/' + dashedNameFull);
|
|
|
|
}
|
|
|
|
return res.redirect('/stories/');
|
|
|
|
}
|
2015-06-03 23:19:23 +00:00
|
|
|
|
2015-06-26 06:00:49 +00:00
|
|
|
var username = req.user ? req.user.username : '';
|
2015-06-25 22:03:46 +00:00
|
|
|
// true if any of votes are made by user
|
|
|
|
var userVoted = story.upVotes.some(function(upvote) {
|
2015-06-26 06:00:49 +00:00
|
|
|
return upvote.upVotedByUsername === username;
|
2015-06-03 23:19:23 +00:00
|
|
|
});
|
|
|
|
|
2015-06-25 22:03:46 +00:00
|
|
|
res.render('stories/index', {
|
|
|
|
title: story.headline,
|
|
|
|
link: story.link,
|
|
|
|
originalStoryLink: dashedName,
|
|
|
|
originalStoryAuthorEmail: story.author.email || '',
|
|
|
|
author: story.author,
|
|
|
|
description: story.description,
|
|
|
|
rank: story.upVotes.length,
|
|
|
|
upVotes: story.upVotes,
|
|
|
|
comments: story.comments,
|
|
|
|
id: story.id,
|
|
|
|
timeAgo: moment(story.timePosted).fromNow(),
|
|
|
|
image: story.image,
|
|
|
|
page: 'show',
|
|
|
|
storyMetaDescription: story.metaDescription,
|
|
|
|
hasUserVoted: userVoted
|
2015-06-03 23:19:23 +00:00
|
|
|
});
|
2015-06-25 22:03:46 +00:00
|
|
|
},
|
|
|
|
next
|
|
|
|
);
|
2015-06-03 23:19:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getStories(req, res, next) {
|
|
|
|
MongoClient.connect(secrets.db, function(err, database) {
|
2015-03-22 11:20:08 +00:00
|
|
|
if (err) {
|
2015-03-25 17:28:04 +00:00
|
|
|
return next(err);
|
2015-03-22 11:20:08 +00:00
|
|
|
}
|
2015-06-03 23:19:23 +00:00
|
|
|
database.collection('stories').find({
|
|
|
|
'$text': {
|
2015-06-09 20:57:23 +00:00
|
|
|
'$search': req.body.data ? req.body.data.searchValue : ''
|
2015-06-03 23:19:23 +00:00
|
|
|
}
|
|
|
|
}, {
|
|
|
|
headline: 1,
|
|
|
|
timePosted: 1,
|
|
|
|
link: 1,
|
|
|
|
description: 1,
|
|
|
|
rank: 1,
|
|
|
|
upVotes: 1,
|
|
|
|
author: 1,
|
|
|
|
comments: 1,
|
|
|
|
image: 1,
|
|
|
|
storyLink: 1,
|
|
|
|
metaDescription: 1,
|
|
|
|
textScore: {
|
|
|
|
$meta: 'textScore'
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
sort: {
|
|
|
|
textScore: {
|
|
|
|
$meta: 'textScore'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}).toArray(function(err, items) {
|
2015-05-09 23:23:45 +00:00
|
|
|
if (err) {
|
|
|
|
return next(err);
|
|
|
|
}
|
2015-06-03 23:19:23 +00:00
|
|
|
if (items !== null && items.length !== 0) {
|
|
|
|
return res.json(items);
|
|
|
|
}
|
|
|
|
return res.sendStatus(404);
|
2015-05-09 23:23:45 +00:00
|
|
|
});
|
2015-03-03 13:03:33 +00:00
|
|
|
});
|
2015-03-22 11:20:08 +00:00
|
|
|
}
|
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
function upvote(req, res, next) {
|
2015-06-25 22:03:46 +00:00
|
|
|
var id = req.body.data.id;
|
|
|
|
var savedStory = findStoryById(id)
|
|
|
|
.flatMap(function(story) {
|
|
|
|
story.rank += 1;
|
|
|
|
story.upVotes.push({
|
|
|
|
upVotedBy: req.user.id,
|
|
|
|
upVotedByUsername: req.user.username
|
|
|
|
});
|
|
|
|
return saveInstance(story);
|
|
|
|
})
|
|
|
|
.shareReplay();
|
|
|
|
|
|
|
|
savedStory.flatMap(function(story) {
|
|
|
|
// find story author
|
|
|
|
return findUserById(story.author.userId);
|
|
|
|
})
|
|
|
|
.flatMap(function(user) {
|
|
|
|
// if user deletes account then this will not exist
|
|
|
|
if (user) {
|
|
|
|
user.progressTimestamps.push(Date.now());
|
2015-06-03 23:19:23 +00:00
|
|
|
}
|
2015-06-25 22:03:46 +00:00
|
|
|
return saveUser(user);
|
|
|
|
})
|
|
|
|
.flatMap(function() {
|
|
|
|
req.user.progressTimestamps.push(Date.now());
|
|
|
|
return saveUser(req.user);
|
|
|
|
})
|
|
|
|
.flatMap(savedStory)
|
|
|
|
.subscribe(
|
|
|
|
function(story) {
|
|
|
|
return res.send(story);
|
|
|
|
},
|
|
|
|
next
|
2015-06-03 23:19:23 +00:00
|
|
|
);
|
2015-03-22 11:20:08 +00:00
|
|
|
}
|
2015-06-03 23:19:23 +00:00
|
|
|
|
|
|
|
function comments(req, res, next) {
|
2015-06-25 22:03:46 +00:00
|
|
|
var id = req.params.id;
|
|
|
|
findCommentById(id).subscribe(
|
|
|
|
function(comment) {
|
|
|
|
res.send(comment);
|
|
|
|
},
|
|
|
|
next
|
|
|
|
);
|
2015-03-22 11:20:08 +00:00
|
|
|
}
|
2015-06-03 23:19:23 +00:00
|
|
|
|
|
|
|
function newStory(req, res, next) {
|
|
|
|
if (!req.user) {
|
|
|
|
return next(new Error('Must be logged in'));
|
2015-03-17 08:44:52 +00:00
|
|
|
}
|
2015-06-03 23:19:23 +00:00
|
|
|
var url = req.body.data.url;
|
2015-06-25 22:03:46 +00:00
|
|
|
var cleanURL = cleanData(url);
|
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
if (cleanURL !== url) {
|
2015-03-22 11:20:08 +00:00
|
|
|
req.flash('errors', {
|
2015-06-03 23:19:23 +00:00
|
|
|
msg: "The URL you submitted doesn't appear valid"
|
2015-03-22 11:20:08 +00:00
|
|
|
});
|
|
|
|
return res.json({
|
|
|
|
alreadyPosted: true,
|
2015-06-03 23:19:23 +00:00
|
|
|
storyURL: '/stories/submit'
|
2015-03-22 11:20:08 +00:00
|
|
|
});
|
2015-06-03 23:19:23 +00:00
|
|
|
|
2015-03-09 09:30:37 +00:00
|
|
|
}
|
2015-06-03 23:19:23 +00:00
|
|
|
if (url.search(/^https?:\/\//g) === -1) {
|
|
|
|
url = 'http://' + url;
|
2015-03-07 08:42:22 +00:00
|
|
|
}
|
2015-06-25 22:03:46 +00:00
|
|
|
|
|
|
|
findStory({ where: { link: url } })
|
|
|
|
.map(function(stories) {
|
|
|
|
if (stories.length) {
|
|
|
|
return {
|
2015-06-03 23:19:23 +00:00
|
|
|
alreadyPosted: true,
|
2015-06-25 22:03:46 +00:00
|
|
|
storyURL: '/stories/' + stories.pop().storyLink
|
|
|
|
};
|
2015-06-03 23:19:23 +00:00
|
|
|
}
|
2015-06-25 22:03:46 +00:00
|
|
|
return {
|
2015-06-03 23:19:23 +00:00
|
|
|
alreadyPosted: false,
|
2015-06-25 22:03:46 +00:00
|
|
|
storyURL: url
|
|
|
|
};
|
|
|
|
})
|
|
|
|
.flatMap(function(data) {
|
|
|
|
if (data.alreadyPosted) {
|
|
|
|
return Rx.Observable.just(data);
|
|
|
|
}
|
|
|
|
return Rx.Observable.fromNodeCallback(getURLTitle)(data.storyURL)
|
|
|
|
.map(function(story) {
|
|
|
|
return {
|
|
|
|
alreadyPosted: false,
|
|
|
|
storyURL: data.storyURL,
|
|
|
|
storyTitle: story.title,
|
|
|
|
storyImage: story.image,
|
|
|
|
storyMetaDescription: story.description
|
|
|
|
};
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.subscribe(
|
|
|
|
function(story) {
|
|
|
|
if (story.alreadyPosted) {
|
|
|
|
req.flash('errors', {
|
|
|
|
msg: "Someone's already posted that link. Here's the discussion."
|
|
|
|
});
|
|
|
|
}
|
|
|
|
res.json(story);
|
|
|
|
},
|
|
|
|
next
|
|
|
|
);
|
2015-03-22 11:20:08 +00:00
|
|
|
}
|
2015-06-01 23:23:53 +00:00
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
function storySubmission(req, res, next) {
|
|
|
|
var data = req.body.data;
|
|
|
|
if (!req.user) {
|
|
|
|
return next(new Error('Not authorized'));
|
2015-04-20 04:28:55 +00:00
|
|
|
}
|
2015-06-03 23:19:23 +00:00
|
|
|
var storyLink = data.headline
|
|
|
|
.replace(/[^a-z0-9\s]/gi, '')
|
|
|
|
.replace(/\s+/g, ' ')
|
|
|
|
.toLowerCase()
|
|
|
|
.trim();
|
2015-03-22 11:20:08 +00:00
|
|
|
|
2015-04-20 04:28:55 +00:00
|
|
|
var link = data.link;
|
2015-06-03 23:19:23 +00:00
|
|
|
|
2015-04-20 04:28:55 +00:00
|
|
|
if (link.search(/^https?:\/\//g) === -1) {
|
|
|
|
link = 'http://' + link;
|
|
|
|
}
|
2015-06-03 23:19:23 +00:00
|
|
|
|
2015-06-25 22:03:46 +00:00
|
|
|
var query = {
|
2015-06-23 23:28:16 +00:00
|
|
|
storyLink: {
|
|
|
|
like: ('^' + storyLink + '(?: [0-9]+)?$'),
|
|
|
|
options: 'i'
|
|
|
|
}
|
2015-06-25 22:03:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
var savedStory = countStories(query)
|
|
|
|
.flatMap(function(storyCount) {
|
|
|
|
// if duplicate storyLink add unique number
|
|
|
|
storyLink = (storyCount === 0) ?
|
|
|
|
storyLink :
|
|
|
|
storyLink + ' ' + storyCount;
|
|
|
|
|
|
|
|
var link = data.link;
|
|
|
|
if (link.search(/^https?:\/\//g) === -1) {
|
|
|
|
link = 'http://' + link;
|
2015-05-09 22:53:01 +00:00
|
|
|
}
|
2015-06-25 22:03:46 +00:00
|
|
|
var newStory = new Story({
|
|
|
|
headline: cleanData(data.headline),
|
|
|
|
timePosted: Date.now(),
|
|
|
|
link: link,
|
|
|
|
description: cleanData(data.description),
|
|
|
|
rank: 1,
|
|
|
|
upVotes: [({
|
|
|
|
upVotedBy: req.user.id,
|
|
|
|
upVotedByUsername: req.user.username
|
|
|
|
})],
|
|
|
|
author: {
|
|
|
|
picture: req.user.picture,
|
|
|
|
userId: req.user.id,
|
|
|
|
username: req.user.username,
|
|
|
|
email: req.user.email
|
|
|
|
},
|
|
|
|
comments: [],
|
|
|
|
image: data.image,
|
|
|
|
storyLink: storyLink,
|
|
|
|
metaDescription: data.storyMetaDescription,
|
|
|
|
originalStoryAuthorEmail: req.user.email
|
2015-06-03 23:19:23 +00:00
|
|
|
});
|
2015-06-25 22:03:46 +00:00
|
|
|
return saveInstance(newStory);
|
2015-05-09 22:53:01 +00:00
|
|
|
});
|
2015-06-25 22:03:46 +00:00
|
|
|
|
|
|
|
req.user.progressTimestamps.push(Date.now());
|
|
|
|
return saveUser(req.user)
|
|
|
|
.flatMap(savedStory)
|
|
|
|
.subscribe(
|
|
|
|
function(story) {
|
|
|
|
res.json({
|
|
|
|
storyLink: dasherize(story.storyLink)
|
|
|
|
});
|
|
|
|
},
|
|
|
|
next
|
|
|
|
);
|
2015-06-01 22:30:43 +00:00
|
|
|
}
|
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
function commentSubmit(req, res, next) {
|
|
|
|
var data = req.body.data;
|
|
|
|
if (!req.user) {
|
|
|
|
return next(new Error('Not authorized'));
|
|
|
|
}
|
2015-06-25 22:03:46 +00:00
|
|
|
var sanitizedBody = cleanData(data.body);
|
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
if (data.body !== sanitizedBody) {
|
|
|
|
req.flash('errors', {
|
|
|
|
msg: 'HTML is not allowed'
|
|
|
|
});
|
|
|
|
return res.send(true);
|
|
|
|
}
|
|
|
|
var comment = new Comment({
|
|
|
|
associatedPost: data.associatedPost,
|
|
|
|
originalStoryLink: data.originalStoryLink,
|
|
|
|
originalStoryAuthorEmail: data.originalStoryAuthorEmail,
|
|
|
|
body: sanitizedBody,
|
|
|
|
rank: 0,
|
|
|
|
upvotes: 0,
|
|
|
|
author: {
|
2015-06-04 19:54:41 +00:00
|
|
|
picture: req.user.picture,
|
2015-06-04 20:20:42 +00:00
|
|
|
userId: req.user.id,
|
2015-06-04 19:54:41 +00:00
|
|
|
username: req.user.username,
|
2015-06-03 23:19:23 +00:00
|
|
|
email: req.user.email
|
|
|
|
},
|
|
|
|
comments: [],
|
|
|
|
topLevel: true,
|
|
|
|
commentOn: Date.now()
|
2015-04-20 04:28:55 +00:00
|
|
|
});
|
2015-06-03 23:19:23 +00:00
|
|
|
|
2015-06-25 22:03:46 +00:00
|
|
|
commentSave(comment, findStoryById).subscribe(
|
|
|
|
function() {},
|
|
|
|
next,
|
|
|
|
function() {
|
|
|
|
res.send(true);
|
|
|
|
}
|
|
|
|
);
|
2015-06-01 22:30:43 +00:00
|
|
|
}
|
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
function commentOnCommentSubmit(req, res, next) {
|
|
|
|
var data = req.body.data;
|
|
|
|
if (!req.user) {
|
2015-04-20 02:22:11 +00:00
|
|
|
return next(new Error('Not authorized'));
|
|
|
|
}
|
|
|
|
|
2015-06-25 22:03:46 +00:00
|
|
|
var sanitizedBody = cleanData(data.body);
|
2015-06-01 22:30:43 +00:00
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
if (data.body !== sanitizedBody) {
|
2015-04-20 02:22:11 +00:00
|
|
|
req.flash('errors', {
|
|
|
|
msg: 'HTML is not allowed'
|
2015-04-18 06:46:16 +00:00
|
|
|
});
|
2015-04-20 02:22:11 +00:00
|
|
|
return res.send(true);
|
2015-04-18 06:46:16 +00:00
|
|
|
}
|
2015-04-20 02:22:11 +00:00
|
|
|
|
2015-06-03 23:19:23 +00:00
|
|
|
var comment = new Comment({
|
|
|
|
associatedPost: data.associatedPost,
|
|
|
|
body: sanitizedBody,
|
|
|
|
rank: 0,
|
|
|
|
upvotes: 0,
|
|
|
|
originalStoryLink: data.originalStoryLink,
|
|
|
|
originalStoryAuthorEmail: data.originalStoryAuthorEmail,
|
|
|
|
author: {
|
2015-06-04 19:54:41 +00:00
|
|
|
picture: req.user.picture,
|
2015-06-04 20:20:42 +00:00
|
|
|
userId: req.user.id,
|
2015-06-04 19:54:41 +00:00
|
|
|
username: req.user.username,
|
2015-06-03 23:19:23 +00:00
|
|
|
email: req.user.email
|
|
|
|
},
|
|
|
|
comments: [],
|
|
|
|
topLevel: false,
|
|
|
|
commentOn: Date.now()
|
|
|
|
});
|
2015-06-25 22:03:46 +00:00
|
|
|
commentSave(comment, findCommentById).subscribe(
|
|
|
|
function() {},
|
|
|
|
next,
|
|
|
|
function() {
|
|
|
|
res.send(true);
|
|
|
|
}
|
|
|
|
);
|
2015-06-03 23:19:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function commentEdit(req, res, next) {
|
2015-06-25 22:03:46 +00:00
|
|
|
findCommentById(req.params.id)
|
|
|
|
.doOnNext(function(comment) {
|
|
|
|
if (!req.user && comment.author.userId !== req.user.id) {
|
|
|
|
throw new Error('Not authorized');
|
2015-03-05 21:08:40 +00:00
|
|
|
}
|
2015-06-25 22:03:46 +00:00
|
|
|
})
|
|
|
|
.flatMap(function(comment) {
|
|
|
|
var sanitizedBody = cleanData(req.body.body);
|
|
|
|
if (req.body.body !== sanitizedBody) {
|
|
|
|
req.flash('errors', {
|
|
|
|
msg: 'HTML is not allowed'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
comment.body = sanitizedBody;
|
|
|
|
comment.commentOn = Date.now();
|
|
|
|
return saveInstance(comment);
|
|
|
|
})
|
|
|
|
.subscribe(
|
|
|
|
function() {
|
|
|
|
res.send(true);
|
|
|
|
},
|
|
|
|
next
|
|
|
|
);
|
2015-06-03 23:19:23 +00:00
|
|
|
}
|
|
|
|
|
2015-06-25 22:03:46 +00:00
|
|
|
function commentSave(comment, findContextById) {
|
|
|
|
return saveInstance(comment)
|
|
|
|
.flatMap(function(comment) {
|
2015-06-03 23:19:23 +00:00
|
|
|
// Based on the context retrieve the parent
|
|
|
|
// object of the comment (Story/Comment)
|
2015-06-25 22:03:46 +00:00
|
|
|
return findContextById(comment.associatedPost);
|
|
|
|
})
|
|
|
|
.flatMap(function(associatedContext) {
|
|
|
|
if (associatedContext) {
|
|
|
|
associatedContext.comments.push(comment.id);
|
|
|
|
}
|
|
|
|
// NOTE(berks): saveInstance is safe
|
|
|
|
// it will automatically call onNext with null and onCompleted if
|
|
|
|
// argument is falsey or has no method save
|
|
|
|
return saveInstance(associatedContext);
|
|
|
|
})
|
|
|
|
.flatMap(function(associatedContext) {
|
|
|
|
// Find the author of the parent object
|
|
|
|
// if no username
|
|
|
|
var username = associatedContext && associatedContext.author ?
|
|
|
|
associatedContext.author.username :
|
|
|
|
null;
|
|
|
|
|
|
|
|
var query = { where: { username: username } };
|
|
|
|
return findOneUser(query);
|
|
|
|
})
|
|
|
|
// if no user is found we don't want to hit the doOnNext
|
|
|
|
// filter here will call onCompleted without running through the following
|
|
|
|
// steps
|
|
|
|
.filter(function(user) {
|
|
|
|
return !!user;
|
|
|
|
})
|
|
|
|
// if this is called user is guarenteed to exits
|
|
|
|
// this is a side effect, hence we use do/tap observable methods
|
|
|
|
.doOnNext(function(user) {
|
|
|
|
// If the emails of both authors differ,
|
|
|
|
// only then proceed with email notification
|
|
|
|
if (
|
|
|
|
comment.author &&
|
|
|
|
comment.author.email &&
|
|
|
|
user.email &&
|
|
|
|
(comment.author.email !== user.email)
|
|
|
|
) {
|
|
|
|
sendMailWhillyNilly({
|
|
|
|
to: user.email,
|
|
|
|
from: 'Team@freecodecamp.com',
|
|
|
|
subject: comment.author.username +
|
|
|
|
' replied to your post on Camper News',
|
|
|
|
text: [
|
|
|
|
'Just a quick heads-up: ',
|
|
|
|
comment.author.username,
|
|
|
|
' replied to you on Camper News.',
|
|
|
|
'You can keep this conversation going.',
|
|
|
|
'Just head back to the discussion here: ',
|
|
|
|
'http://freecodecamp.com/stories/',
|
|
|
|
comment.originalStoryLink,
|
|
|
|
'- the Free Code Camp Volunteer Team'
|
|
|
|
].join('\n')
|
2015-06-03 23:19:23 +00:00
|
|
|
});
|
2015-06-25 22:03:46 +00:00
|
|
|
}
|
|
|
|
});
|
2015-06-03 23:19:23 +00:00
|
|
|
}
|
|
|
|
};
|