From 513d55b23da41a232166a4bc4484f1b840cacb87 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Wed, 6 Apr 2016 23:06:19 -0700 Subject: [PATCH] Add caching to news Deprecate story creation --- server/boot/story.js | 399 ++++--------------------------------------- 1 file changed, 32 insertions(+), 367 deletions(-) diff --git a/server/boot/story.js b/server/boot/story.js index 4d1ca77660..6849812874 100755 --- a/server/boot/story.js +++ b/server/boot/story.js @@ -1,27 +1,11 @@ -var Rx = require('rx'), - assign = require('object.assign'), - sanitizeHtml = require('sanitize-html'), - moment = require('moment'), - debug = require('debug')('fcc:cntr:story'), - utils = require('../utils'), - observeMethod = require('../utils/rx').observeMethod, - saveUser = require('../utils/rx').saveUser, - saveInstance = require('../utils/rx').saveInstance, - validator = require('validator'); +import moment from 'moment'; -import { - ifNoUser401, - ifNoUserRedirectTo -} from '../utils/middleware'; +import { unDasherize } from '../utils'; +import { observeMethod } from '../utils/rx'; const foundationDate = 1413298800000; const time48Hours = 172800000; -const unDasherize = utils.unDasherize; -const dasherize = utils.dasherize; -const getURLTitle = utils.getURLTitle; -const sendNonUserToNews = ifNoUserRedirectTo('/news'); - function hotRank(timeValue, rank) { /* * Hotness ranking algorithm: http://amix.dk/blog/post/19588 @@ -39,81 +23,54 @@ function sortByRank(a, b) { hotRank(a.timePosted - foundationDate, a.rank); } -function cleanData(data, opts) { - var options = assign( - {}, - { - allowedTags: [], - allowedAttributes: [] - }, - opts || {} - ); - return sanitizeHtml(data, options).replace(/";/g, '"'); -} - module.exports = function(app) { - var router = app.loopback.Router(); - var User = app.models.User; - var findUserById = observeMethod(User, 'findById'); + const router = app.loopback.Router(); + const Story = app.models.Story; + const findStory = observeMethod(Story, 'find'); + const findOneStory = observeMethod(Story, 'findOne'); + const query = { + order: 'timePosted DESC', + limit: 1000 + }; + const storiesData$ = findStory(query) + .map(stories => { + const sliceVal = stories.length >= 100 ? 100 : stories.length; + return stories.sort(sortByRank).slice(0, sliceVal); + }) + .shareReplay(); - var Story = app.models.Story; - var findStory = observeMethod(Story, 'find'); - var findOneStory = observeMethod(Story, 'findOne'); - var findStoryById = observeMethod(Story, 'findById'); - var countStories = observeMethod(Story, 'count'); - - router.post('/news/userstories', userStories); + const redirectToNews = (req, res) => res.redirect('/news'); + const deprecated = (req, res) => res.sendStatus(410); + router.post('/news/userstories', deprecated); router.get('/news/hot', hotJSON); router.get('/news/feed', RSSFeed); router.get('/stories/hotStories', hotJSON); - router.get( - '/stories/submit', - sendNonUserToNews, - submitNew - ); - router.get( - '/stories/submit/new-story', - sendNonUserToNews, - preSubmit - ); - router.post('/stories/preliminary', ifNoUser401, newStory); - router.post('/stories/', ifNoUser401, storySubmission); - router.post('/stories/search', getStories); + router.get('/stories/submit', redirectToNews); + router.get('/stories/submit/new-story', redirectToNews); + router.post('/stories/preliminary', deprecated); + router.post('/stories/', deprecated); + router.post('/stories/search', deprecated); router.get('/news/:storyName', returnIndividualStory); - router.post('/stories/upvote/', ifNoUser401, upvote); - router.get('/stories/:storyName', redirectToNews); + router.post('/stories/upvote/', deprecated); + router.get('/stories/:storyName', replaceStoryWithNews); app.use(router); - function redirectToNews(req, res) { + function replaceStoryWithNews(req, res) { var url = req.originalUrl.replace(/^\/stories/, '/news'); return res.redirect(url); } function hotJSON(req, res, next) { - var query = { - order: 'timePosted DESC', - limit: 1000 - }; - findStory(query).subscribe( - function(stories) { - var sliceVal = stories.length >= 100 ? 100 : stories.length; - var data = stories.sort(sortByRank).slice(0, sliceVal); - res.json(data); - }, + storiesData$.subscribe( + stories => res.json(stories), next ); } function RSSFeed(req, res, next) { - var query = { - order: 'timePosted DESC', - limit: 1000 - }; - findStory(query).subscribe( - function(stories) { - var sliceVal = stories.length >= 100 ? 100 : stories.length; - var data = stories.sort(sortByRank).slice(0, sliceVal); + storiesData$.subscribe( + data => { res.set('Content-Type', 'text/xml'); res.render('feed', { title: 'FreeCodeCamp Camper News RSS Feed', @@ -126,51 +83,6 @@ module.exports = function(app) { ); } - function submitNew(req, res) { - if (!req.user.isGithubCool) { - req.flash('errors', { - msg: 'You must link GitHub with your account before you can post' + - ' on Camper News.' - }); - return res.redirect('/news'); - } - - return res.render('stories/index', { - title: 'Submit a new story to Camper News', - page: 'submit' - }); - } - - function preSubmit(req, res) { - var data = req.query; - if (typeof data.url !== 'string') { - req.flash('errors', { msg: 'No URL supplied with story' }); - return res.redirect('/news'); - } - var cleanedData = cleanData(data.url); - - if (data.url.replace(/&/g, '&') !== cleanedData) { - req.flash('errors', { - msg: 'The data for this post is malformed' - }); - return res.render('stories/index', { - page: 'stories/submit' - }); - } - - 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 - }); - } - function returnIndividualStory(req, res, next) { var dashedName = req.params.storyName; var storyName = unDasherize(dashedName); @@ -217,251 +129,4 @@ module.exports = function(app) { next ); } - - function userStories({ body: { search = '' } = {} }, res, next) { - if (!search || typeof search !== 'string') { - return res.sendStatus(404); - } - - return app.dataSources.db.connector - .collection('story') - .find({ - 'author.username': search.toLowerCase().replace('$', '') - }) - .toArray(function(err, items) { - if (err) { - return next(err); - } - if (items && items.length !== 0) { - return res.json(items.sort(sortByRank)); - } - return res.sendStatus(404); - }); - } - - function getStories({ body: { search = '' } = {} }, res, next) { - if (!search || typeof search !== 'string') { - return res.sendStatus(404); - } - - const query = { - '$text': { - // protect against NoSQL injection - '$search': search.replace('$', '') - } - }; - - const fields = { - headline: 1, - timePosted: 1, - link: 1, - description: 1, - rank: 1, - upVotes: 1, - author: 1, - image: 1, - storyLink: 1, - metaDescription: 1, - textScore: { - $meta: 'textScore' - } - }; - - const options = { - sort: { - textScore: { - $meta: 'textScore' - } - } - }; - - return app.dataSources.db.connector - .collection('story') - .find(query, fields, options) - .toArray(function(err, items) { - if (err) { - return next(err); - } - if (items && items.length !== 0) { - return res.json(items); - } - return res.sendStatus(404); - }); - } - - function upvote(req, res, next) { - const { id } = req.body; - var story$ = findStoryById(id).shareReplay(); - - story$.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({ - timestamp: Date.now() - }); - } - return saveUser(user); - }) - .flatMap(function() { - return story$; - }) - .flatMap(function(story) { - debug('upvoting'); - story.rank += 1; - story.upVotes.push({ - upVotedBy: req.user.id, - upVotedByUsername: req.user.username - }); - return saveInstance(story); - }) - .subscribe( - function(story) { - return res.send(story); - }, - next - ); - } - - function newStory(req, res, next) { - if (!req.user.isGithubCool) { - req.flash('errors', { - msg: 'You must authenticate with GitHub to post to Camper News' - }); - return res.redirect('/news'); - } - var url = req.body.data.url; - - if (!validator.isURL('' + 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; - } - - return findStory({ where: { link: url } }) - .map(function(stories) { - if (stories.length) { - return { - alreadyPosted: true, - storyURL: '/stories/' + stories.pop().storyLink - }; - } - return { - alreadyPosted: false, - 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 - ); - } - - function storySubmission(req, res, next) { - if (req.user.isBanned) { - return res.json({ - isBanned: true - }); - } - var data = req.body.data; - - var storyLink = data.headline - .replace(/[^a-z0-9\s]/gi, '') - .replace(/\s+/g, ' ') - .toLowerCase() - .trim(); - - var link = data.link; - - if (link.search(/^https?:\/\//g) === -1) { - link = 'http://' + link; - } - - var query = { - storyLink: { - like: ('^' + storyLink + '(?: [0-9]+)?$'), - options: 'i' - } - }; - - 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; - } - 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 - }, - image: data.image, - storyLink: storyLink, - metaDescription: data.storyMetaDescription - }); - return saveInstance(newStory); - }); - - req.user.progressTimestamps.push({ - timestamp: Date.now() - }); - return saveUser(req.user) - .flatMap(savedStory) - .subscribe( - function(story) { - res.json({ - storyLink: dasherize(story.storyLink) - }); - }, - next - ); - } };