freeCodeCamp/api-server/server/boot/news.js

260 lines
6.6 KiB
JavaScript
Raw Normal View History

2018-11-29 12:12:15 +00:00
import { has, pick, isEmpty } from 'lodash';
import debug from 'debug';
2018-11-29 12:12:15 +00:00
import { reportError } from '../middlewares/error-reporter';
2018-07-31 15:58:04 +00:00
2018-11-29 12:12:15 +00:00
const log = debug('fcc:boot:news');
export default function newsBoot(app) {
2018-11-29 12:12:15 +00:00
const api = app.loopback.Router();
api.get('/n/:shortId', createShortLinkHandler(app));
api.post('/p', createPopularityHandler(app));
app.use('/internal', api);
}
function createShortLinkHandler(app) {
const { Article } = app.models;
const referralHandler = createReferralHandler(app);
return function shortLinkHandler(req, res, next) {
const { query, user } = req;
const { shortId } = req.params;
// We manually report the error here as it should not affect this request
referralHandler(query, shortId, !!user).catch(err => reportError(err));
if (!shortId) {
return res.sendStatus(400);
}
return Article.findOne(
{
where: {
or: [{ shortId }, { slugPart: shortId }]
}
},
(err, article) => {
if (err) {
next(err);
}
if (!article) {
return res.status(404).send('Could not find article by shortId');
}
const {
slugPart,
shortId,
author: { username }
} = article;
const slug = `/news/${username}/${slugPart}--${shortId}`;
const articleData = {
...pick(article, [
'author',
'renderableContent',
'firstPublishedDate',
'viewCount',
'title',
'featureImage',
'slugPart',
'shortId',
'meta'
]),
slug
};
return res.json(articleData);
}
);
};
}
2018-11-29 12:12:15 +00:00
function createPopularityHandler(app) {
const { Article, Popularity } = app.models;
2018-11-29 12:12:15 +00:00
function findArticleByShortId(shortId) {
return new Promise((resolve, reject) =>
Article.findOne({ where: { shortId } }, (err, article) => {
if (err) {
log('Error returned from Article.findOne(shortId)');
return reject(err);
}
log('article found');
return resolve(article);
})
);
}
2018-11-29 12:12:15 +00:00
function findPopularityByShortId(shortId) {
return new Promise((resolve, reject) =>
Popularity.findOne(
{ where: { articleId: shortId } },
(err, popularity) => {
if (err) {
log('Error returned from Popularity.findOne(shortId)');
return reject(err);
}
log('popularity found');
return resolve(popularity);
}
)
);
}
2018-11-29 12:12:15 +00:00
function createPopularity(popularityUpdate, shortId) {
return new Promise((resolve, reject) =>
Popularity.create(
{
events: [popularityUpdate],
articleId: shortId
},
err => {
if (err) {
return reject(err);
}
log('poulartiy created');
return resolve();
}
)
);
}
function updatePopularity(popularity, popularityUpdate) {
return new Promise((resolve, reject) =>
popularity.updateAttribute(
'events',
[popularityUpdate, ...popularity.events],
err => {
if (err) {
log('Error returned from popularity.updateAttribute()');
return reject(err);
}
log('poplarity updated');
return resolve();
}
)
);
}
function incrementArticleViews(article) {
return new Promise((resolve, reject) =>
article.updateAttributes({ $inc: { viewCount: 1 } }, err => {
if (err) {
log(err);
return reject(err);
}
log('article views updated');
return resolve();
})
);
}
return async function handlePopularityStats(req, res, next) {
const { body, user } = req;
if (
!has(body, 'event') ||
!has(body, 'timestamp') ||
!has(body, 'shortId')
) {
console.warn('Popularity event recieved from client is malformed');
console.log(JSON.stringify(body, null, 2));
// sending 200 because the client shouldn't care for this
return res.sendStatus(200);
}
const { shortId } = body;
log('shortId', shortId);
const articlePromise = findArticleByShortId(shortId);
const popularityPromise = findPopularityByShortId(shortId);
const [article, popularity] = await Promise.all([
articlePromise,
popularityPromise
]).catch(err => {
log('find catch');
return next(err);
});
if (!article || isEmpty(article)) {
log('No article found to handle the populartity update');
// sending 200 because the client shouldn't care for this
return res.sendStatus(200);
}
const populartiyUpdate = {
...body,
byAuthenticatedUser: !!user
};
const populartiyUpdateOrCreatePromise = isEmpty(popularity)
? createPopularity(populartiyUpdate, shortId)
: updatePopularity(popularity, populartiyUpdate);
const maybeUpdateArticlePromise =
body.event === 'view' ? incrementArticleViews(article) : null;
return Promise.all([
populartiyUpdateOrCreatePromise,
maybeUpdateArticlePromise
])
.then(() => res.sendStatus(200))
.catch(err => {
log('updates catch');
return next(err);
});
};
}
2018-11-29 12:12:15 +00:00
function createReferralHandler(app) {
const { Popularity } = app.models;
return function referralHandler(query, shortId, byAuthenticatedUser) {
if (!query.refsource) {
return Promise.resolve();
}
const eventUpdate = {
event: `referral - ${query.refsource}`,
timestamp: new Date(Date.now()),
byAuthenticatedUser
};
return new Promise((resolve, reject) =>
Popularity.findOne(
{ where: { articleId: shortId } },
(err, popularity) => {
if (err) {
console.error(
'Failed finding a `Popularity` in a referral handler',
err
);
return reject(err);
}
if (popularity) {
return popularity.updateAttribute(
'events',
[eventUpdate, ...popularity.events],
err => {
if (err) {
return reject(err);
}
log('populartiy updated');
return resolve();
}
);
}
return Popularity.create(
{
events: [eventUpdate],
articleId: shortId
},
err => {
if (err) {
return reject(err);
}
log('poulartiy created');
return resolve();
}
);
}
)
);
};
}