feat(article-views): Initial view tracking
This commit is contained in:
committed by
mrugesh mohapatra
parent
c63aecdf79
commit
9cdb0ec7a2
@ -11,7 +11,8 @@
|
|||||||
"type": [
|
"type": [
|
||||||
"object"
|
"object"
|
||||||
],
|
],
|
||||||
"required": true
|
"required": true,
|
||||||
|
"default": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"validations": [],
|
"validations": [],
|
||||||
|
@ -7,7 +7,7 @@ import { Image } from 'react-bootstrap';
|
|||||||
|
|
||||||
import Author from './components/Author';
|
import Author from './components/Author';
|
||||||
import { Loader } from '../../../common/app/helperComponents';
|
import { Loader } from '../../../common/app/helperComponents';
|
||||||
import { getArticleById } from '../../utils/ajax';
|
import { getArticleById, postPopularityEvent } from '../../utils/ajax';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
history: PropTypes.shape({
|
history: PropTypes.shape({
|
||||||
@ -83,6 +83,11 @@ class ShowArticle extends Component {
|
|||||||
}
|
}
|
||||||
if (article) {
|
if (article) {
|
||||||
const [, shortId] = slug.split('--');
|
const [, shortId] = slug.split('--');
|
||||||
|
postPopularityEvent({
|
||||||
|
event: 'view',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
shortId
|
||||||
|
});
|
||||||
/* eslint-disable react/no-did-mount-set-state */
|
/* eslint-disable react/no-did-mount-set-state */
|
||||||
return this.setState(
|
return this.setState(
|
||||||
{
|
{
|
||||||
|
@ -21,3 +21,7 @@ export function getFeaturedList(skip = 0) {
|
|||||||
})}`
|
})}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function postPopularityEvent(event) {
|
||||||
|
return axios.post('/p', event);
|
||||||
|
}
|
||||||
|
@ -1,9 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { renderToString } from 'react-dom/server';
|
import { renderToString } from 'react-dom/server';
|
||||||
import { StaticRouter } from 'react-router-dom';
|
import { StaticRouter } from 'react-router-dom';
|
||||||
|
import { has } from 'lodash';
|
||||||
|
import debug from 'debug';
|
||||||
|
|
||||||
import NewsApp from '../../news/NewsApp';
|
import NewsApp from '../../news/NewsApp';
|
||||||
|
|
||||||
|
const routerLog = debug('fcc:boot:news:router');
|
||||||
|
const apiLog = debug('fcc:boot:news:api');
|
||||||
|
|
||||||
|
export default function newsBoot(app) {
|
||||||
|
const router = app.loopback.Router();
|
||||||
|
const api = app.loopback.Router();
|
||||||
|
|
||||||
|
router.get('/n', (req, res) => res.redirect('/news'));
|
||||||
|
router.get('/n/:shortId', createReferralHandler(app));
|
||||||
|
|
||||||
|
router.get('/news', serveNewsApp);
|
||||||
|
router.get('/news/*', serveNewsApp);
|
||||||
|
|
||||||
|
api.post('/p', createPopularityHandler(app));
|
||||||
|
|
||||||
|
app.use(api);
|
||||||
|
app.use(router);
|
||||||
|
}
|
||||||
|
|
||||||
function serveNewsApp(req, res) {
|
function serveNewsApp(req, res) {
|
||||||
const context = {};
|
const context = {};
|
||||||
const markup = renderToString(
|
const markup = renderToString(
|
||||||
@ -12,20 +33,23 @@ function serveNewsApp(req, res) {
|
|||||||
</StaticRouter>
|
</StaticRouter>
|
||||||
);
|
);
|
||||||
if (context.url) {
|
if (context.url) {
|
||||||
|
routerLog('redirect found in `renderToString`');
|
||||||
// 'client-side' routing hit on a redirect
|
// 'client-side' routing hit on a redirect
|
||||||
return res.redirect(context.url);
|
return res.redirect(context.url);
|
||||||
}
|
}
|
||||||
|
routerLog('news markup sending');
|
||||||
return res.render('layout-news', { title: 'News | freeCodeCamp', markup });
|
return res.render('layout-news', { title: 'News | freeCodeCamp', markup });
|
||||||
}
|
}
|
||||||
|
|
||||||
function createReferralHandler(app) {
|
function createReferralHandler(app) {
|
||||||
|
const { Article } = app.models;
|
||||||
|
|
||||||
return function referralHandler(req, res, next) {
|
return function referralHandler(req, res, next) {
|
||||||
const { Article } = app.models;
|
|
||||||
const { shortId } = req.params;
|
const { shortId } = req.params;
|
||||||
if (!shortId) {
|
if (!shortId) {
|
||||||
return res.redirect('/news');
|
return res.redirect('/news');
|
||||||
}
|
}
|
||||||
console.log(shortId);
|
routerLog('shortId', shortId);
|
||||||
return Article.findOne(
|
return Article.findOne(
|
||||||
{
|
{
|
||||||
where: {
|
where: {
|
||||||
@ -51,14 +75,77 @@ function createReferralHandler(app) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function newsBoot(app) {
|
function createPopularityHandler(app) {
|
||||||
const router = app.loopback.Router();
|
const { Article, Popularity } = app.models;
|
||||||
|
|
||||||
router.get('/n', (req, res) => res.redirect('/news'));
|
return function handlePopularityStats(req, res, next) {
|
||||||
router.get('/n/:shortId', createReferralHandler(app));
|
const { body, user } = req;
|
||||||
|
if (
|
||||||
router.get('/news', serveNewsApp);
|
!has(body, 'event') ||
|
||||||
router.get('/news/*', serveNewsApp);
|
!has(body, 'timestamp') ||
|
||||||
|
!has(body, 'shortId')
|
||||||
app.use(router);
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
res.sendStatus(200);
|
||||||
|
const { shortId } = body;
|
||||||
|
apiLog('shortId', shortId);
|
||||||
|
const populartiyUpdate = {
|
||||||
|
...body,
|
||||||
|
byAuthenticatedUser: !!user
|
||||||
|
};
|
||||||
|
Popularity.findOne({ where: { articleId: shortId } }, (err, popularity) => {
|
||||||
|
if (err) {
|
||||||
|
apiLog(err);
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
if (popularity) {
|
||||||
|
return popularity.updateAttribute(
|
||||||
|
'events',
|
||||||
|
[populartiyUpdate, ...popularity.events],
|
||||||
|
err => {
|
||||||
|
if (err) {
|
||||||
|
apiLog(err);
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
return apiLog('poplarity updated');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Popularity.create(
|
||||||
|
{
|
||||||
|
events: [populartiyUpdate],
|
||||||
|
articleId: shortId
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
if (err) {
|
||||||
|
apiLog(err);
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
return apiLog('poulartiy created');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return body.event === 'view'
|
||||||
|
? Article.findOne({ where: { shortId } }, (err, article) => {
|
||||||
|
if (err) {
|
||||||
|
apiLog(err);
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
return article.updateAttributes(
|
||||||
|
{ viewCount: article.viewCount + 1 },
|
||||||
|
err => {
|
||||||
|
if (err) {
|
||||||
|
apiLog(err);
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
return apiLog('article views updated');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ export default function() {
|
|||||||
return function csrf(req, res, next) {
|
return function csrf(req, res, next) {
|
||||||
|
|
||||||
const path = req.path.split('/')[1];
|
const path = req.path.split('/')[1];
|
||||||
if (/(api|external)/.test(path)) {
|
if (/(api|external|^p$)/.test(path)) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
return protection(req, res, next);
|
return protection(req, res, next);
|
||||||
|
Reference in New Issue
Block a user