diff --git a/api-server/server/boot/news.js b/api-server/server/boot/news.js index b7dc2e88b9..6ca80df1bd 100644 --- a/api-server/server/boot/news.js +++ b/api-server/server/boot/news.js @@ -1,34 +1,28 @@ -import { has, pick, isEmpty } from 'lodash'; import debug from 'debug'; -import { reportError } from '../middlewares/error-reporter'; const log = debug('fcc:boot:news'); export default function newsBoot(app) { - const api = app.loopback.Router(); + const router = app.loopback.Router(); - api.get('/n/:shortId', createShortLinkHandler(app)); - - api.post('/p', createPopularityHandler(app)); - - app.use('/internal', api); + router.get('/n', (req, res) => res.redirect('/news')); + router.get('/n/:shortId', createShortLinkHandler(app)); } function createShortLinkHandler(app) { const { Article } = app.models; - const referralHandler = createReferralHandler(app); - return function shortLinkHandler(req, res, next) { - const { query, user } = req; + const { query } = 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)); + log(req.origin); + log(query.refsource); if (!shortId) { - return res.sendStatus(400); + return res.redirect('/news'); } + log('shortId', shortId); return Article.findOne( { where: { @@ -40,220 +34,14 @@ function createShortLinkHandler(app) { next(err); } if (!article) { - return res.status(404).send('Could not find article by shortId'); + return res.redirect('/news'); } const { - slugPart, - shortId, - author: { username } + slugPart } = 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); + const slug = `/news/${slugPart}`; + return res.redirect(slug); } ); }; } - -function createPopularityHandler(app) { - const { Article, Popularity } = app.models; - - 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); - }) - ); - } - - 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); - } - ) - ); - } - - 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); - }); - }; -} - -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(); - } - ); - } - ) - ); - }; -} diff --git a/api-server/server/middlewares/add-return-to.js b/api-server/server/middlewares/add-return-to.js index 73f3b6a694..00935140f9 100644 --- a/api-server/server/middlewares/add-return-to.js +++ b/api-server/server/middlewares/add-return-to.js @@ -12,10 +12,8 @@ const pathsOfNoReturn = [ ]; const pathsWhiteList = [ - 'news', 'challenges', 'map', - 'news', 'commit' ]; diff --git a/client/gatsby-config.js b/client/gatsby-config.js index a0c262e5c1..9e353ca49e 100644 --- a/client/gatsby-config.js +++ b/client/gatsby-config.js @@ -46,12 +46,6 @@ module.exports = { curriculumPath: localeChallengesRootDir } }, - { - resolve: 'fcc-source-news', - options: { - maximumStaticRenderCount: 100 - } - }, { resolve: 'gatsby-source-filesystem', options: { diff --git a/client/gatsby-node.js b/client/gatsby-node.js index a898d5f5fe..13fe801eee 100644 --- a/client/gatsby-node.js +++ b/client/gatsby-node.js @@ -8,10 +8,8 @@ const { createChallengePages, createBlockIntroPages, createSuperBlockIntroPages, - createGuideArticlePages, - createNewsArticle + createGuideArticlePages } = require('./utils/gatsby'); -const { createArticleSlug } = require('./utils/news'); const createByIdentityMap = { guideMarkdown: createGuideArticlePages, @@ -37,15 +35,7 @@ exports.onCreateNode = function onCreateNode({ node, actions, getNode }) { createNodeField({ node, name: 'slug', value: slug }); } } - if (node.internal.type === 'NewsArticleNode') { - const { - author: { username }, - slugPart, - shortId - } = node; - const slug = createArticleSlug({ username, shortId, slugPart }); - createNodeField({ node, name: 'slug', value: slug }); - } + }; exports.createPages = function createPages({ graphql, actions }) { @@ -97,19 +87,6 @@ exports.createPages = function createPages({ graphql, actions }) { } } } - allNewsArticleNode( - sort: { fields: firstPublishedDate, order: DESC } - ) { - edges { - node { - id - shortId - fields { - slug - } - } - } - } } `).then(result => { if (result.errors) { @@ -150,11 +127,6 @@ exports.createPages = function createPages({ graphql, actions }) { return null; }); - // Create news article pages - result.data.allNewsArticleNode.edges.forEach( - createNewsArticle(createPage) - ); - return null; }) ); diff --git a/client/plugins/fcc-source-news/create-news-node.js b/client/plugins/fcc-source-news/create-news-node.js deleted file mode 100644 index c360e8017c..0000000000 --- a/client/plugins/fcc-source-news/create-news-node.js +++ /dev/null @@ -1,26 +0,0 @@ -const crypto = require('crypto'); - -function createNewsNode(article) { - const contentDigest = crypto - .createHash('md5') - .update(JSON.stringify(article)) - .digest('hex'); - - const internal = { - contentDigest, - type: 'NewsArticleNode' - }; - - return JSON.parse( - JSON.stringify({ - ...article, - id: article._id + ' >>>>>>> ' + internal.type, - children: [], - parent: null, - internal, - sourceInstanceName: 'article' - }) - ); -} - -exports.createNewsNode = createNewsNode; diff --git a/client/plugins/fcc-source-news/gatsby-node.js b/client/plugins/fcc-source-news/gatsby-node.js deleted file mode 100644 index 961414727c..0000000000 --- a/client/plugins/fcc-source-news/gatsby-node.js +++ /dev/null @@ -1,55 +0,0 @@ -const path = require('path'); -require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') }); -const { MongoClient } = require('mongodb'); - -const { createNewsNode } = require('./create-news-node'); -const { db } = require('../../../config/secrets'); - -exports.sourceNodes = function sourceChallengesSourceNodes( - { actions, reporter }, - pluginOptions -) { - function handleError(err, client, reject) { - if (err) { - if (client) { - client.close(); - } - reject(err); - reporter.panic(err); - } - } - const { maximumStaticRenderCount = 100 } = pluginOptions; - const { createNode } = actions; - - return new Promise((resolve, reject) => - MongoClient.connect( - db, - { useNewUrlParser: true }, - async function(err, client) { - handleError(err, client, reject); - - reporter.info('fcc-source-news connected successfully to mongo'); - const db = client.db('freecodecamp'); - const articleCollection = db.collection('article'); - - articleCollection - .aggregate([ - { $match: { featured: true } }, - { $sort: { firstPublishedDate: -1 } }, - { $limit: maximumStaticRenderCount } - ]) - .toArray((err, articles) => { - handleError(err, client, reject); - - articles - .map(article => createNewsNode(article)) - .map(node => createNode(node)); - - client.close(); - reporter.info('fcc-source-news mongo connection closed'); - return resolve(); - }); - } - ) - ); -}; diff --git a/client/plugins/fcc-source-news/package-lock.json b/client/plugins/fcc-source-news/package-lock.json deleted file mode 100644 index 1e506ea7c0..0000000000 --- a/client/plugins/fcc-source-news/package-lock.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "name": "fcc-source-news", - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "bson": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.0.tgz", - "integrity": "sha512-9Aeai9TacfNtWXOYarkFJRW2CWo+dRon+fuLZYJmvLV3+MiUp0bEI6IAZfXEIg7/Pl/7IWlLaDnhzTsD81etQA==" - }, - "memory-pager": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.1.0.tgz", - "integrity": "sha512-Mf9OHV/Y7h6YWDxTzX/b4ZZ4oh9NSXblQL8dtPCOomOtZciEHxePR78+uHFLLlsk01A6jVHhHsQZZ/WcIPpnzg==", - "optional": true - }, - "mongodb": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.1.9.tgz", - "integrity": "sha512-f+Og32wK/ovzVlC1S6Ft7yjVTvNsAOs6pBpDrPd2/3wPO9ijNsQrTNntuECjOSxGZpPVl0aRqgHzF1e9e+KvnQ==", - "requires": { - "mongodb-core": "3.1.8", - "safe-buffer": "^5.1.2" - } - }, - "mongodb-core": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.1.8.tgz", - "integrity": "sha512-reWCqIRNehyuLaqaz5JMOmh3Xd8JIjNX34o8mnewXLK2Fyt/Ky6BZbU+X0OPzy8qbX+JZrOtnuay7ASCieTYZw==", - "requires": { - "bson": "^1.1.0", - "require_optional": "^1.0.1", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - } - }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "^2.0.0", - "semver": "^5.1.0" - } - }, - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "saslprep": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.2.tgz", - "integrity": "sha512-4cDsYuAjXssUSjxHKRe4DTZC0agDwsCqcMqtJAQPzC74nJ7LfAJflAtC1Zed5hMzEQKj82d3tuzqdGNRsLJ4Gw==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" - }, - "sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", - "optional": true, - "requires": { - "memory-pager": "^1.0.2" - } - } - } -} diff --git a/client/plugins/fcc-source-news/package.json b/client/plugins/fcc-source-news/package.json deleted file mode 100644 index 78ebc005d2..0000000000 --- a/client/plugins/fcc-source-news/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "fcc-source-news", - "dependencies": { - "mongodb": "^3.1.9" - } -} diff --git a/client/src/__mocks__/news-article.js b/client/src/__mocks__/news-article.js deleted file mode 100644 index 4ca619f060..0000000000 --- a/client/src/__mocks__/news-article.js +++ /dev/null @@ -1,16 +0,0 @@ -export const slugWithId = '/news/quincy/an-article-title--abcDEF123'; -export const slugWithIdAndQuery = - '/news/quincy/an-article-title--abcDEF123?some=query'; -export const slugWithIdAndHash = - '/news/quincy/an-article-title--abcDEF123#top-of-page'; -export const slugWithIdAndTrailingSlash = - '/news/quincy/an-article-title--abcDEF123/'; - -export const mockId = 'abcDEF123'; -export const slugWithoutId = '/news/quincy/an-article-title'; - -export const mockArguments = { - username: 'quincy', - slugPart: 'an-article-title', - shortId: 'abcDEF123' -}; diff --git a/client/src/__tests__/integration/news-slug.test.js b/client/src/__tests__/integration/news-slug.test.js deleted file mode 100644 index f79357dbdc..0000000000 --- a/client/src/__tests__/integration/news-slug.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/* global describe it expect */ -import faker from 'faker'; -import { kebabCase } from 'lodash'; -import shortid from 'shortid'; - -import { createArticleSlug } from '../../../utils/news'; -import { getShortIdFromSlug } from '../../utils'; - -shortid.characters( - '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$+' -); -const shortId = () => shortid.generate(); - -describe('news-slug integration', () => { - it('returns the correct id from a generated slug', () => { - expect.assertions(100); - - const generatedArguments = Array(100) - .fill(null) - .map(() => ({ - username: faker.internet.userName(), - slugPart: kebabCase(faker.lorem.sentence()).toLowerCase(), - shortId: shortId() - })); - - return generatedArguments.forEach(arg => { - const { shortId } = arg; - const generatedSlug = createArticleSlug(arg); - const extractedId = getShortIdFromSlug(generatedSlug); - return expect(extractedId).toEqual(shortId); - }); - }); -}); diff --git a/client/src/client-only-routes/ShowDynamicNewsOrFourOhFour.js b/client/src/client-only-routes/ShowDynamicNewsOrFourOhFour.js deleted file mode 100644 index bb62d5b783..0000000000 --- a/client/src/client-only-routes/ShowDynamicNewsOrFourOhFour.js +++ /dev/null @@ -1,89 +0,0 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import { isNull, pick, isEmpty } from 'lodash'; - -import Layout from '../components/layouts/Default'; -import Loader from '../components/helpers/Loader'; - -import { getShortIdFromSlug } from '../utils'; -import { createArticleSlug } from '../../utils/news'; -import { - resolveShortId, - resolveShortIdFetchStateSelector, - dynamicArticleByIdMapSelector -} from '../templates/News/redux'; -import { createFlashMessage } from '../components/Flash/redux'; -import ShowArticle from '../templates/News/ShowArticle'; - -const mapStateToProps = () => (state, { articleSlug = '' }) => { - const shortId = getShortIdFromSlug(articleSlug); - const articleMap = dynamicArticleByIdMapSelector(state); - const article = articleMap[shortId] || null; - const fetchState = resolveShortIdFetchStateSelector(state); - return { article, fetchState, shortId }; -}; -const mapDispatchToProps = dispatch => - bindActionCreators({ createFlashMessage, resolveShortId }, dispatch); - -class DynamicNewsArticle extends Component { - componentDidMount() { - const { shortId, article, resolveShortId } = this.props; - if (isNull(article)) { - return resolveShortId(shortId); - } - return null; - } - - getArticleAsGatsbyProps(article) { - const { - author: { username }, - slugPart, - shortId, - meta: { readTime } - } = article; - - return { - data: { - newsArticleNode: { - ...pick(article, [ - 'title', - 'renderableContent', - 'youtube', - 'author', - 'firstPublishedDate', - 'shortId', - 'featureImage' - ]), - fields: { slug: createArticleSlug({ username, slugPart, shortId }) }, - meta: { readTime } - } - } - }; - } - - render() { - const { - fetchState: { pending }, - article - } = this.props; - if (pending) { - return ( - -
- -
-
- ); - } - return isEmpty(article) ? null : ( - - ); - } -} -DynamicNewsArticle.displayName = 'DynamicNewsArticle'; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(DynamicNewsArticle); diff --git a/client/src/components/Header/index.js b/client/src/components/Header/index.js index c6e1b8b46d..f45d9787b1 100644 --- a/client/src/components/Header/index.js +++ b/client/src/components/Header/index.js @@ -22,7 +22,7 @@ function Header({ disableSettings }) {
  • diff --git a/client/src/components/RedirectNews.js b/client/src/components/RedirectNews.js deleted file mode 100644 index a4c2a7178d..0000000000 --- a/client/src/components/RedirectNews.js +++ /dev/null @@ -1,3 +0,0 @@ -import createRedirect from './createRedirect'; - -export default createRedirect('/news'); diff --git a/client/src/pages/404.js b/client/src/pages/404.js index 4743023e15..e6c64971f1 100644 --- a/client/src/pages/404.js +++ b/client/src/pages/404.js @@ -2,18 +2,14 @@ import React from 'react'; import { Router } from '@reach/router'; import NotFoundPage from '../components/FourOhFour'; -import RedirectNews from '../components/RedirectNews'; /* eslint-disable max-len */ import ShowProfileOrFourOhFour from '../client-only-routes/ShowProfileOrFourOhFour'; -import ShowDynamicNewsOrFourOhFour from '../client-only-routes/ShowDynamicNewsOrFourOhFour'; /* eslint-enable max-len */ function FourOhFourPage() { return ( - - ); diff --git a/client/src/pages/n.js b/client/src/pages/n.js deleted file mode 100644 index 091967ec4a..0000000000 --- a/client/src/pages/n.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { Router } from '@reach/router'; - -import NewsReferalLinkHandler from '../templates/News/NewsReferalLinkHandler'; -import RedirectNews from '../components/RedirectNews'; - -export default function NewsReferalLinkRouter() { - return ( - - - - - - ); -} diff --git a/client/src/pages/news.js b/client/src/pages/news.js deleted file mode 100644 index bb4d064310..0000000000 --- a/client/src/pages/news.js +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Grid } from '@freecodecamp/react-bootstrap'; -import { graphql } from 'gatsby'; - -import Layout from '../components/layouts/Default'; - -import FullWidthRow from '../components/helpers/FullWidthRow'; -import Featured from '../templates/News/Featured'; - -const propTypes = { - data: PropTypes.shape({ - allNewsArticleNode: PropTypes.shape({ - edges: PropTypes.arrayOf( - PropTypes.shape({ - title: PropTypes.string, - shortId: PropTypes.string, - slugPart: PropTypes.string, - featureImage: PropTypes.shape({ - src: PropTypes.string, - alt: PropTypes.string, - caption: PropTypes.string - }), - meta: PropTypes.shape({ - readTime: PropTypes.number - }), - author: PropTypes.shape({ - name: PropTypes.string, - avatar: PropTypes.string, - twitter: PropTypes.string, - username: PropTypes.string, - bio: PropTypes.string - }), - viewCount: PropTypes.number, - firstPublishedDate: PropTypes.string - }) - ) - }) - }) -}; - -export default function NewsIndexPage(props) { - const { - allNewsArticleNode: { edges } - } = props.data; - const articles = edges.map(({ node }) => node); - return ( - - - -

    News - freeCodeCamp.org

    -
    - - - -
    -
    - ); -} - -export const query = graphql` - { - allNewsArticleNode(sort: { fields: firstPublishedDate, order: DESC }) { - edges { - node { - title - shortId - slugPart - featureImage { - src - alt - caption - } - meta { - readTime - } - author { - name - avatar - twitter - bio - username - } - viewCount - firstPublishedDate - fields { - slug - } - } - } - } - } -`; - -NewsIndexPage.displayName = 'NewsIndexPage'; -NewsIndexPage.propTypes = propTypes; diff --git a/client/src/redux/rootReducer.js b/client/src/redux/rootReducer.js index df875b795a..8541b82759 100644 --- a/client/src/redux/rootReducer.js +++ b/client/src/redux/rootReducer.js @@ -15,7 +15,6 @@ import { reducer as challenge, ns as challengeNameSpace } from '../templates/Challenges/redux'; -import { reducer as news, ns as newsNameSpace } from '../templates/News/redux'; export default combineReducers({ [appNameSpace]: app, @@ -23,6 +22,5 @@ export default combineReducers({ [curriculumMapNameSpace]: curriculumMap, [flashNameSpace]: flash, form: formReducer, - [newsNameSpace]: news, [settingsNameSpace]: settings }); diff --git a/client/src/redux/rootSaga.js b/client/src/redux/rootSaga.js index 4d053e9b30..d6d07b335c 100644 --- a/client/src/redux/rootSaga.js +++ b/client/src/redux/rootSaga.js @@ -3,7 +3,6 @@ import { all } from 'redux-saga/effects'; import errorSagas from './error-saga'; import { sagas as appSagas } from './'; import { sagas as challengeSagas } from '../templates/Challenges/redux'; -import { sagas as newsSagas } from '../templates/News/redux'; import { sagas as settingsSagas } from './settings'; export default function* rootSaga() { @@ -11,7 +10,6 @@ export default function* rootSaga() { ...errorSagas, ...appSagas, ...challengeSagas, - ...newsSagas, ...settingsSagas ]); } diff --git a/client/src/templates/News/Featured/Featured.js b/client/src/templates/News/Featured/Featured.js deleted file mode 100644 index c905c02125..0000000000 --- a/client/src/templates/News/Featured/Featured.js +++ /dev/null @@ -1,62 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { navigate } from 'gatsby'; -import { Image } from '@freecodecamp/react-bootstrap'; - -import Spacer from '../../../components/helpers/Spacer'; -import BannerWide from '../components/BannerWide'; -import ArticleMeta from '../components/ArticleMeta'; - -import './featured.css'; - -const propTypes = { - featuredList: PropTypes.arrayOf(PropTypes.object) -}; - -class Featured extends Component { - createHandleArticleClick(slug) { - return e => { - e.preventDefault(); - return navigate(slug); - }; - } - - renderFeatured(articles) { - return articles.map(article => { - const { featureImage, shortId, title, fields: {slug} } = article; - return ( -
  • - -

    {title}

    - {featureImage && featureImage.src ? ( - - ) : ( - - )} - -
    - -
  • - ); - }); - } - - render() { - const { featuredList } = this.props; - return ( - - ); - } -} - -Featured.displayName = 'Featured'; -Featured.propTypes = propTypes; - -export default Featured; diff --git a/client/src/templates/News/Featured/featured.css b/client/src/templates/News/Featured/featured.css deleted file mode 100644 index 9b00e36b29..0000000000 --- a/client/src/templates/News/Featured/featured.css +++ /dev/null @@ -1,33 +0,0 @@ -.featured-list { - list-style: none; - padding-left: 0; - margin-top: 40px; -} - -.featured-list-item { - padding-bottom: 20px; -} - -.featured-list-item .title { - color: #333; - padding-bottom: 20px; -} - -.featured-list-item a { - padding-top: 5px; -} - -.featured-list-image { - margin: 0 auto; -} - -.featured-list-item a:hover, -.featured-list-item a:focus { - text-decoration: none; - text-decoration-line: none; - text-decoration-color: transparaent; -} -.featured-list-item a:hover > .meta-wrapper, -.featured-list-item a:focus > .meta-wrapper { - color: #006400; -} diff --git a/client/src/templates/News/Featured/index.js b/client/src/templates/News/Featured/index.js deleted file mode 100644 index b9c878bbea..0000000000 --- a/client/src/templates/News/Featured/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Featured'; diff --git a/client/src/templates/News/NewsApp.js b/client/src/templates/News/NewsApp.js deleted file mode 100644 index 2d8e1345d7..0000000000 --- a/client/src/templates/News/NewsApp.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { Grid } from 'react-bootstrap'; -import Helmet from 'react-helmet'; - -import { SlimWidthRow } from '../common/app/helperComponents'; -import Nav from './components/Nav'; -import { routes } from './routes'; - -const propTypes = {}; -/* eslint-disable max-len */ -const styles = ` -.app-layout p, -.app-layout li, -.app-layout a, -.app-layout span { - font-size: 21.5px; -} -.app-layout hr { - background-image: linear-gradient(to right, rgba(0, 100, 0, 0), rgba(0, 100, 0, 0.75), rgba(0, 100, 0, 0)); -} - -.app-layout p { - paddin-top: 8px; -} - - .app-layout h1, .app-layout h2, .app-layout h3, .app-layout h4, .app-layout h5, .app-layout h6 - { - padding-top: 35px; - padding-bottom: 0px; - } - -.app-layout h1 { - font-size: 42px; -} - -.app-layout h2 { - font-size: 34px; -} - -.app-layout h3 { - font-size: 32px; -} -`; -/* eslint-enable max-len */ -function NewsApp() { - return ( -
    - - - -
    - ); -} - -NewsApp.displayName = 'NewsApp'; -NewsApp.propTypes = propTypes; - -export default NewsApp; diff --git a/client/src/templates/News/NewsReferalLinkHandler/index.js b/client/src/templates/News/NewsReferalLinkHandler/index.js deleted file mode 100644 index 166b6b614a..0000000000 --- a/client/src/templates/News/NewsReferalLinkHandler/index.js +++ /dev/null @@ -1,61 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { navigate } from 'gatsby'; -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; - -import Layout from '../../../components/layouts/Default'; -import { resolveShortId, dynamicArticleSelector } from '../redux'; -import { Loader } from '../../../components/helpers'; - -const propTypes = { - redirect: PropTypes.string, - resolveShortId: PropTypes.func.isRequired, - shortId: PropTypes.string.isRequired -}; - -const mapStateToProps = () => (state, props) => { - const article = dynamicArticleSelector(state, props); - return { - redirect: article.redirect - }; -}; - -const mapDispatchToProps = dispatch => - bindActionCreators({ resolveShortId }, dispatch); - -class NewsReferalLinkHandler extends Component { - componentDidMount() { - const { shortId, redirect, resolveShortId } = this.props; - if (!redirect) { - return resolveShortId(shortId); - } - return navigate(redirect); - } - - componentDidUpdate() { - const { redirect } = this.props; - if (redirect) { - return navigate(redirect); - } - return null; - } - - render() { - return ( - -
    - -
    -
    - ); - } -} - -NewsReferalLinkHandler.displayName = 'NewsReferalLinkHandler'; -NewsReferalLinkHandler.propTypes = propTypes; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(NewsReferalLinkHandler); diff --git a/client/src/templates/News/ShowArticle/components/Author.js b/client/src/templates/News/ShowArticle/components/Author.js deleted file mode 100644 index 2f29db4673..0000000000 --- a/client/src/templates/News/ShowArticle/components/Author.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Helmet from 'react-helmet'; - -import ArticleMeta from '../../components/ArticleMeta'; - -const propTypes = { - article: PropTypes.shape({ - author: PropTypes.objectOf(PropTypes.string) - }) -}; - -const styles = ` - .author-block { - display: flex; - align-items: center; - justify-content: center; - margin-top: 15px; - } - - .author-block img { - border-radius: 20%; - } - - .author-bio { - display: flex; - flex-direction: column; - margin-left: 30px; - } - - .author-bio span { - font-size: 16px; - } - - .author-block { - text-decoration: none; - } -`; - -function Author({ article }) { - const { - author: { avatar } - } = article; - return ( -
    - - - - -
    - -
    -
    - ); -} - -Author.displayName = 'Author'; -Author.propTypes = propTypes; - -export default Author; diff --git a/client/src/templates/News/ShowArticle/index.js b/client/src/templates/News/ShowArticle/index.js deleted file mode 100644 index b31d7871d8..0000000000 --- a/client/src/templates/News/ShowArticle/index.js +++ /dev/null @@ -1,187 +0,0 @@ -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import Helmet from 'react-helmet'; -import { graphql } from 'gatsby'; -import Youtube from 'react-youtube'; -import { Image, Grid } from '@freecodecamp/react-bootstrap'; - -import Layout from '../../../components/layouts/Default'; -import Author from './components/Author'; -import FullWidthRow from '../../../components/helpers/FullWidthRow'; -import Spacer from '../../../components/helpers/Spacer'; -import { postPopularityEvent } from '../../../utils/ajax'; -import { newsArticleNodePropTypes } from './proptypes'; - -import './show-article.css'; - -const propTypes = { - data: PropTypes.shape({ - newsArticleNode: newsArticleNodePropTypes - }) -}; - -const youtubeOpts = { - playerVars: { - // https://developers.google.com/youtube/player_parameters - autoplay: 0 - } -}; - -class ShowArticle extends Component { - constructor(props) { - super(props); - - this.state = { - fetchState: { - pending: false, - complete: false, - errored: false, - error: null - }, - currentArticle: {}, - requiredArticle: '' - }; - } - - componentDidMount() { - window.scrollTo(0, 0); - - const { shortId } = this.props.data.newsArticleNode; - return postPopularityEvent({ - event: 'view', - timestamp: Date.now(), - shortId - }); - } - - youtubeReady(event) { - event.target.pauseVideo(); - } - - render() { - const { - data: { - newsArticleNode: { - title, - renderableContent, - youtubeId, - featureImage, - fields: { slug } - }, - newsArticleNode - } - } = this.props; - - // RegEx finds the first paragraph and groups the content - const description = renderableContent.join('').match(/

    (.*?)<\/p>/)[1]; - const pageTitle = `${title} | freeCodeCamp.org`; - return ( - - - {pageTitle} - - - - - - - - -

    - - - - -

    {title}

    -
    - {featureImage ? ( - -
    -
    - {featureImage.alt} - {featureImage.caption ? ( -
    - ) : null} -
    -
    -
    - ) : null} - - -
    - - {youtubeId ? ( - -
    - -
    - -
    - ) : null} - -
    - - - ); - } -} - -ShowArticle.displayName = 'ShowArticle'; -ShowArticle.propTypes = propTypes; - -export default ShowArticle; - -export const query = graphql` - fragment newsArticleContent on NewsArticleNode { - title - renderableContent - featureImage { - src - alt - caption - } - fields { - slug - } - author { - name - avatar - twitter - bio - username - } - meta { - readTime - } - firstPublishedDate - shortId - } - - query NewsArticleById($id: String!) { - newsArticleNode(id: { eq: $id }) { - ...newsArticleContent - } - } -`; diff --git a/client/src/templates/News/ShowArticle/proptypes.js b/client/src/templates/News/ShowArticle/proptypes.js deleted file mode 100644 index 1d968664ff..0000000000 --- a/client/src/templates/News/ShowArticle/proptypes.js +++ /dev/null @@ -1,26 +0,0 @@ -import PropTypes from 'prop-types'; - -export const newsArticleNodePropTypes = PropTypes.shape({ - title: PropTypes.string, - renderableContent: PropTypes.arrayOf(PropTypes.string), - featureImage: PropTypes.shape({ - src: PropTypes.string, - alt: PropTypes.string, - caption: PropTypes.string - }), - fields: PropTypes.shape({ - slug: PropTypes.string - }), - author: PropTypes.shape({ - name: PropTypes.string, - avatar: PropTypes.string, - twitter: PropTypes.string, - bio: PropTypes.string, - username: PropTypes.string - }), - meta: PropTypes.shape({ - readTime: PropTypes.number - }), - firstPublishedDate: PropTypes.string, - shortId: PropTypes.string -}); diff --git a/client/src/templates/News/ShowArticle/show-article.css b/client/src/templates/News/ShowArticle/show-article.css deleted file mode 100644 index 75761a65cb..0000000000 --- a/client/src/templates/News/ShowArticle/show-article.css +++ /dev/null @@ -1,34 +0,0 @@ -.show-article figure { - display: flex; - flex-direction: column; - align-items: center; -} - -.show-article figcaption > * { - font-size: 16px; -} - -.show-article figcaption { - padding-top: 5px; -} - -.show-article a { - text-decoration: underline; -} - -.feature-image-wrapper { - padding-top: 32px; -} -.youtube-wrapper { - position: relative; - padding-bottom: 56.25%; /* 16:9 */ - padding-top: 25px; - height: 0; - overflow: hidden; -} -.youtube-wrapper iframe { - position: absolute; - left: 0; - width: 100%; - height: 95%; -} diff --git a/client/src/templates/News/components/ArticleMeta.js b/client/src/templates/News/components/ArticleMeta.js deleted file mode 100644 index d016c41ecf..0000000000 --- a/client/src/templates/News/components/ArticleMeta.js +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Helmet from 'react-helmet'; -import differenceInMinutes from 'date-fns/difference_in_minutes'; -import differenceInHours from 'date-fns/difference_in_hours'; -import differenceInDays from 'date-fns/difference_in_calendar_days'; -import format from 'date-fns/format'; -import FontAwesomeIcon from '@fortawesome/react-fontawesome'; -import { faCalendarAlt, faClock } from '@fortawesome/free-regular-svg-icons'; -import { faFreeCodeCamp } from '@fortawesome/free-brands-svg-icons'; - -const propTypes = { - article: PropTypes.object -}; - -const styles = ` - -.meta-wrapper { - padding-top: 10px; -} - -.meta-wrapper span, -.meta-wrapper a { - font-size: 16px; -} - -.meta-item { - margin-right: 20px; -} - -`; - -function pluralise(singular, count) { - return `${singular}${count === 1 ? '' : 's'}`; -} - -function getTimeString(pubDate) { - const now = new Date(Date.now()); - const minuteDiff = differenceInMinutes(now, pubDate); - - if (minuteDiff < 60) { - return `${minuteDiff} ${pluralise('minute', minuteDiff)} ago`; - } - const hourDiff = differenceInHours(now, pubDate); - if (hourDiff < 24) { - return `${hourDiff} ${pluralise('hour', hourDiff)} ago`; - } - const dayDiff = differenceInDays(now, pubDate); - if (dayDiff < 8) { - return `${dayDiff} ${pluralise('day', dayDiff)} ago`; - } - - if (dayDiff < 365) { - return format(pubDate, 'MMM D'); - } - - return format(pubDate, 'MMM D YYYY'); -} - -function ArticleMeta({ - article: { viewCount, author, meta, firstPublishedDate } -}) { - return ( -
    - - - -
    - By {author.name} - - {' '} - {getTimeString(firstPublishedDate)} - - - {`${meta.readTime} minute read`} - - {viewCount >= 100 && ( - - {`${viewCount} views`} - - )} -
    -
    - ); -} - -ArticleMeta.displayName = 'ArticleMeta'; -ArticleMeta.propTypes = propTypes; - -export default ArticleMeta; diff --git a/client/src/templates/News/components/BannerWide.js b/client/src/templates/News/components/BannerWide.js deleted file mode 100644 index 6dd9e1b168..0000000000 --- a/client/src/templates/News/components/BannerWide.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; - -const propTypes = {}; - -function BannerWide() { - return ( - - - - ); -} - -BannerWide.displayName = 'BannerWide'; -BannerWide.propTypes = propTypes; - -export default BannerWide; diff --git a/client/src/templates/News/redux/index.js b/client/src/templates/News/redux/index.js deleted file mode 100644 index 1de714d14c..0000000000 --- a/client/src/templates/News/redux/index.js +++ /dev/null @@ -1,67 +0,0 @@ -import { createAction, handleActions } from 'redux-actions'; - -import { createTypes } from '../../../../utils/stateManagement'; -import { createAsyncTypes } from '../../../utils/createTypes'; -import { defaultFetchState } from '../../../redux'; -import { createShortIdSaga } from './shortId-saga'; - -export const ns = 'news'; -const initialState = { - resolveShortIdFetchState: { ...defaultFetchState }, - dynamicArticleByIdMap: {} -}; - -export const types = createTypes([...createAsyncTypes('resolveShortId')], ns); - -export const sagas = [...createShortIdSaga(types)]; - -export const resolveShortId = createAction(types.resolveShortId); -export const resolveShortIdComplete = createAction( - types.resolveShortIdComplete, - article => { - const { slug } = article; - article.redirect = slug; - return article; - } -); -export const resolveShortIdError = createAction(types.resolveShortIdError); - -export const resolveShortIdFetchStateSelector = state => - state[ns].resolveShortIdFetchState; -export const dynamicArticleByIdMapSelector = state => - state[ns].dynamicArticleByIdMap; -export const dynamicArticleSelector = (state, { shortId }) => { - const map = dynamicArticleByIdMapSelector(state); - return map[shortId] || {}; -}; - -export const reducer = handleActions( - { - [types.resolveShortId]: state => ({ - ...state, - resolveShortIdFetchState: { ...defaultFetchState } - }), - [types.resolveShortIdComplete]: (state, { payload }) => ({ - ...state, - resolveShortIdFetchState: { - ...defaultFetchState, - pending: false, - complete: true - }, - dynamicArticleByIdMap: { - ...state.dynamicArticleByIdMap, - [payload.shortId]: payload - } - }), - [types.resolveShortIdError]: (state, { payload: error }) => ({ - ...state, - resolveShortIdFetchState: { - ...defaultFetchState, - pending: false, - errored: true, - error - } - }) - }, - initialState -); diff --git a/client/src/templates/News/redux/shortId-saga.js b/client/src/templates/News/redux/shortId-saga.js deleted file mode 100644 index fe293f3068..0000000000 --- a/client/src/templates/News/redux/shortId-saga.js +++ /dev/null @@ -1,27 +0,0 @@ -import { call, put, takeEvery } from 'redux-saga/effects'; - -import { getArticleById } from '../../../utils/ajax'; -import { resolveShortIdComplete, resolveShortIdError } from './'; -import { handleAPIError, wrapHandledError } from '../../../utils/handled-error'; - -function* fetchArticleByIdSaga({ payload }) { - try { - const { data } = yield call(getArticleById, payload); - yield put(resolveShortIdComplete(data)); - } catch (e) { - const { response: { status } = {} } = e; - if (typeof status !== 'undefined') { - const handledError = wrapHandledError( - e, - handleAPIError(e, { redirectTo: '/news' }) - ); - yield put(resolveShortIdError(handledError)); - return; - } - yield put(resolveShortIdError(e)); - } -} - -export function createShortIdSaga(types) { - return [takeEvery(types.resolveShortId, fetchArticleByIdSaga)]; -} diff --git a/client/src/utils/index.js b/client/src/utils/index.js index 84b6546c43..d9d2a7fa53 100644 --- a/client/src/utils/index.js +++ b/client/src/utils/index.js @@ -1,22 +1,6 @@ -import { findIndex } from 'lodash'; - // These regex are not for validation, it is purely to see // if we are looking at something like what we want to validate // before we try to validate export const maybeEmailRE = /.*@.*\.\w\w/; export const maybeUrlRE = /https?:\/\/.*\..*/; export const hasProtocolRE = /^http/; - -export const getShortIdFromSlug = (slug = '') => { - const slugToArray = [...slug]; - const endOfUseableSlug = findIndex( - slugToArray, - char => char === '?' || char === '#' - ); - let operableSlug = slug.slice(0); - if (endOfUseableSlug !== -1) { - operableSlug = slug.slice(0, endOfUseableSlug); - } - const [, maybeShortId = ''] = operableSlug.split('--'); - return maybeShortId.replace(/\/*$/, ''); -}; diff --git a/client/src/utils/utils.test.js b/client/src/utils/utils.test.js index a63e70a792..0769858d1f 100644 --- a/client/src/utils/utils.test.js +++ b/client/src/utils/utils.test.js @@ -1,45 +1,9 @@ /* global describe it expect */ -import { - slugWithId, - slugWithIdAndHash, - slugWithIdAndQuery, - slugWithIdAndTrailingSlash, - slugWithoutId, - mockId -} from '../__mocks__/news-article'; - -import { getShortIdFromSlug } from './'; describe('client/src utilities', () => { - describe('getShortIdFromSlug', () => { - const emptyString = ''; - it('returns a string', () => { - expect(typeof getShortIdFromSlug()).toEqual('string'); - }); - - it('extracts the shortId when one is present', () => { - const result = getShortIdFromSlug(slugWithId); - expect(result).toEqual(mockId); - }); - - it('still returns a string when no id is found', () => { - const result = getShortIdFromSlug(slugWithoutId); - expect(result).toEqual(emptyString); - }); - - it('can handle query', () => { - const result = getShortIdFromSlug(slugWithIdAndQuery); - expect(result).toEqual(mockId); - }); - - it('can handle hash', () => { - const result = getShortIdFromSlug(slugWithIdAndHash); - expect(result).toEqual(mockId); - }); - - it('can handle trails slashes', () => { - const result = getShortIdFromSlug(slugWithIdAndTrailingSlash); - expect(result).toEqual(mockId); + describe('No tests for utils', () => { + it('No tests for utils', () => { + expect(true); }); }); }); diff --git a/client/utils/gatsby/index.js b/client/utils/gatsby/index.js index 60854efda6..410a359403 100644 --- a/client/utils/gatsby/index.js +++ b/client/utils/gatsby/index.js @@ -1,9 +1,7 @@ const challengePageCreators = require('./challengePageCreator'); const guidePageCreators = require('./guidePageCreator'); -const newsPageCreators = require('./newsPageCreator'); module.exports = { ...challengePageCreators, - ...guidePageCreators, - ...newsPageCreators + ...guidePageCreators }; diff --git a/client/utils/gatsby/newsPageCreator.js b/client/utils/gatsby/newsPageCreator.js deleted file mode 100644 index 1b8e15ac08..0000000000 --- a/client/utils/gatsby/newsPageCreator.js +++ /dev/null @@ -1,22 +0,0 @@ -const path = require('path'); - -const newsArticle = path.resolve( - __dirname, '../../src/templates/News/ShowArticle/index.js' -); - -exports.createNewsArticle = createPage => ({ - node: { - fields: { slug }, - shortId, - id - } -}) => - createPage({ - path: slug, - component: newsArticle, - context: { - slug, - shortId, - id - } - }); diff --git a/client/utils/news.js b/client/utils/news.js deleted file mode 100644 index 1187f78e0f..0000000000 --- a/client/utils/news.js +++ /dev/null @@ -1,18 +0,0 @@ -exports.createArticleSlug = ({ - username = '', - slugPart = '', - shortId = '' -} = {}) => { - if (!username || !slugPart || !shortId) { - throw new Error(` - createArtcileSlug: One or more properties were missing, all are required - - { - username: ${username}, - slugPart: ${slugPart}, - shortId: ${shortId} - } -`); - } - return `/news/${username}/${slugPart.concat('--', shortId)}`; -}; diff --git a/client/utils/news.test.js b/client/utils/news.test.js deleted file mode 100644 index 1d131fc91c..0000000000 --- a/client/utils/news.test.js +++ /dev/null @@ -1,32 +0,0 @@ -/* global describe it expect */ -import { mockArguments, slugWithId} from '../src/__mocks__/news-article'; -import { createArticleSlug } from './news'; - -describe('news utils', () => { - describe('createArticleSlug', () => { - - it('returns a string', () => { - expect(typeof createArticleSlug(mockArguments)).toEqual('string'); - }); - - it('throws when values are missing', () => { - expect.assertions(3); - /* eslint-disable no-undefined */ - expect(() => - createArticleSlug({ ...mockArguments, shortId: undefined }) - ).toThrow(); - expect(() => - createArticleSlug({ ...mockArguments, slugPart: undefined }) - ).toThrow(); - expect(() => - createArticleSlug({ ...mockArguments, username: undefined }) - ).toThrow(); - }); - - it('creates a slug in the expected format', () => { - const result = createArticleSlug(mockArguments); - - expect(result).toEqual(slugWithId); - }); - }); -}); diff --git a/package.json b/package.json index 9a3dd2ace5..d1913e90f8 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "lint": "echo 'Warning: TODO - Define Linting with fixing.'", "seed": "npm-run-all -p seed:*", "seed:challenges": "cross-env DEBUG=fcc:* node ./tools/scripts/seed/seedChallenges", - "seed:news": "cross-env DEBUG=fcc:* node ./tools/scripts/seed/seedNewsArticles", "seed:auth-user": "cross-env DEBUG=fcc:* node ./tools/scripts/seed/seedAuthUser", "start-develop": "node ./tools/scripts/start-develop.js", "pretest": "npm-run-all -s test:lint", diff --git a/tools/scripts/seed/seedNewsArticles.js b/tools/scripts/seed/seedNewsArticles.js deleted file mode 100644 index 8d8760ae38..0000000000 --- a/tools/scripts/seed/seedNewsArticles.js +++ /dev/null @@ -1,126 +0,0 @@ -const path = require('path'); -require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') }); -const MongoClient = require('mongodb').MongoClient; -const faker = require('faker'); -const shortid = require('shortid'); -const slugg = require('slugg'); -const { homeLocation } = require('../../../config/env.json'); -const debug = require('debug'); - -const log = debug('fcc:tools:seedNewsArticles'); - -shortid.characters( - '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$+' -); - -const shortId = () => shortid.generate(); - -const { MONGOHQ_URL, NODE_ENV: env } = process.env; - -function handleError(err, client) { - if (err) { - console.error('Oh noes!!'); - console.error(err); - try { - client.close(); - } catch (e) { - // no-op - } finally { - /* eslint-disable-next-line no-process-exit */ - process.exit(1); - } - } -} - -if (env !== 'production') { - MongoClient.connect( - MONGOHQ_URL, - { useNewUrlParser: true }, - async function(err, client) { - handleError(err, client); - - log('Connected successfully to mongo'); - const db = client.db('freecodecamp'); - const articleCollection = db.collection('article'); - - const articles = stubArticles(200); - - await articleCollection - .deleteMany({}) - .catch(err => handleError(err, client)); - return articleCollection - .insertMany(articles) - .then(({ insertedCount }) => { - log('inserted %d new articles', insertedCount); - client.close(); - }) - .catch(err => handleError(err, client)); - } - ); -} - -function stubArticles(numberOfArticles = 1) { - return new Array(numberOfArticles).fill('').map(() => generateArticle()); -} - -const sixMonths = 15780000000; - -function generateArticle() { - const now = Date.now(); - const id = shortId(); - const title = faker.lorem.sentence(); - const paragraphs = faker.random.number(10) || 1; - const arrayToLoopOver = new Array(paragraphs).fill(''); - const fakeDate = faker.date.between(new Date(now - sixMonths), new Date(now)); - const fakeDateMs = new Date(fakeDate).getTime(); - return { - shortId: id, - slugPart: slugg(title), - title, - author: { - name: faker.name.findName(), - avatar: faker.internet.avatar(), - twitter: 'https://twitter.com/camperbot', - bio: faker.lorem.sentence(), - username: faker.internet.userName() - }, - featureImage: { - src: 'https://picsum.photos/2000/1300?random', - alt: faker.lorem.sentence(), - caption: paragraphs >= 5 ? faker.lorem.sentence() : '' - }, - meta: { - readTime: paragraphs, - refLink: `${homeLocation}/n/${id}` - }, - draft: 'this needs to be fixed', - renderableContent: arrayToLoopOver.map( - () => `

    ${faker.lorem.paragraph()}

    ` - ), - published: true, - featured: Math.random() < 0.6, - underReview: false, - viewCount: faker.random.number(90000), - firstPublishedDate: fakeDate, - createdDate: fakeDate, - lastEditedDate: fakeDate, - history: [ - { - event: 'created', - timestamp: fakeDateMs - }, - { - event: 'edited', - timestamp: fakeDateMs - }, - { - event: 'publish', - timestamp: fakeDateMs - }, - { - event: 'featured', - timestamp: fakeDateMs - } - ] - }; -}