feat: Use prettier-eslint to format code

This commit is contained in:
Bouncey
2019-02-18 19:32:49 +00:00
committed by mrugesh mohapatra
parent 1ba67c4e2b
commit b13e5fb41a
82 changed files with 1767 additions and 1017 deletions

View File

@ -115,10 +115,10 @@ export default function(UserIdent) {
return identity return identity
? Observable.of(identity.user()) ? Observable.of(identity.user())
: User.findOne$({ where: { email } }).flatMap(user => { : User.findOne$({ where: { email } }).flatMap(user => {
return user return user
? Observable.of(user) ? Observable.of(user)
: User.create$({ email }).toPromise(); : User.create$({ email }).toPromise();
}); });
}) })
.flatMap(user => { .flatMap(user => {
const createToken = observeQuery(AccessToken, 'create', { const createToken = observeQuery(AccessToken, 'create', {

View File

@ -2,11 +2,8 @@ import { Observable } from 'rx';
export default function(Block) { export default function(Block) {
Block.on('dataSourceAttached', () => { Block.on('dataSourceAttached', () => {
Block.findOne$ = Block.findOne$ = Observable.fromNodeCallback(Block.findOne, Block);
Observable.fromNodeCallback(Block.findOne, Block); Block.findById$ = Observable.fromNodeCallback(Block.findById, Block);
Block.findById$ = Block.find$ = Observable.fromNodeCallback(Block.find, Block);
Observable.fromNodeCallback(Block.findById, Block);
Block.find$ =
Observable.fromNodeCallback(Block.find, Block);
}); });
} }

View File

@ -1,10 +1,8 @@
import _ from 'lodash'; import _ from 'lodash';
export const alertTypes = _.keyBy([ export const alertTypes = _.keyBy(
'success', ['success', 'info', 'warning', 'danger'],
'info', _.identity
'warning', );
'danger'
], _.identity);
export const normalizeAlertType = alertType => alertTypes[alertType] || 'info'; export const normalizeAlertType = alertType => alertTypes[alertType] || 'info';

View File

@ -1,15 +1,9 @@
import emptyProtector from './empty-protector'; import emptyProtector from './empty-protector';
export function checkMapData( export function checkMapData({
{ entities: { challenge, block, superBlock },
entities: { result: { superBlocks }
challenge, }) {
block,
superBlock
},
result: { superBlocks }
}
) {
if ( if (
!challenge || !challenge ||
!block || !block ||
@ -17,9 +11,7 @@ export function checkMapData(
!superBlocks || !superBlocks ||
!superBlocks.length !superBlocks.length
) { ) {
throw new Error( throw new Error('entities not found, db may not be properly seeded');
'entities not found, db may not be properly seeded'
);
} }
} }
// getFirstChallenge( // getFirstChallenge(
@ -33,11 +25,8 @@ export function getFirstChallenge({
result: { superBlocks } result: { superBlocks }
}) { }) {
return challenge[ return challenge[
emptyProtector(block[ emptyProtector(block[emptyProtector(superBlock[superBlocks[0]]).blocks[0]])
emptyProtector(superBlock[ .challenges[0]
superBlocks[0]
]).blocks[0]
]).challenges[0]
]; ];
} }
@ -52,12 +41,10 @@ export function getFirstChallenge({
// }; // };
export function createNameIdMap({ challenge }) { export function createNameIdMap({ challenge }) {
return { return {
challengeIdToName: Object.keys(challenge) challengeIdToName: Object.keys(challenge).reduce((map, challengeName) => {
.reduce((map, challengeName) => { map[challenge[challengeName].id] = challenge[challengeName].dashedName;
map[challenge[challengeName].id] = return map;
challenge[challengeName].dashedName; }, {})
return map;
}, {})
}; };
} }
// addNameIdMap( // addNameIdMap(

View File

@ -3,14 +3,11 @@ import invariant from 'invariant';
import { Observable } from 'rx'; import { Observable } from 'rx';
import castToObservable from '../../server/utils/cast-to-observable'; import castToObservable from '../../server/utils/cast-to-observable';
// createFileStream( // createFileStream(
// files: [...PolyVinyl] // files: [...PolyVinyl]
// ) => Observable[...Observable[...PolyVinyl]] // ) => Observable[...Observable[...PolyVinyl]]
export function createFileStream(files = []) { export function createFileStream(files = []) {
return Observable.of( return Observable.of(Observable.from(files));
Observable.from(files)
);
} }
// Observable::pipe( // Observable::pipe(
@ -20,8 +17,8 @@ export function createFileStream(files = []) {
// ) => Observable[...Observable[...PolyVinyl]] // ) => Observable[...Observable[...PolyVinyl]]
export function pipe(project) { export function pipe(project) {
const source = this; const source = this;
return source.map( return source.map(files =>
files => files.flatMap(file => castToObservable(project(file))) files.flatMap(file => castToObservable(project(file)))
); );
} }
@ -44,24 +41,10 @@ export function pipe(project) {
// contents: String, // contents: String,
// history?: [...String], // history?: [...String],
// }) => PolyVinyl, throws // }) => PolyVinyl, throws
export function createPoly({ export function createPoly({ name, ext, contents, history, ...rest } = {}) {
name, invariant(typeof name === 'string', 'name must be a string but got %s', name);
ext,
contents,
history,
...rest
} = {}) {
invariant(
typeof name === 'string',
'name must be a string but got %s',
name
);
invariant( invariant(typeof ext === 'string', 'ext must be a string, but was %s', ext);
typeof ext === 'string',
'ext must be a string, but was %s',
ext
);
invariant( invariant(
typeof contents === 'string', typeof contents === 'string',
@ -71,7 +54,7 @@ export function createPoly({
return { return {
...rest, ...rest,
history: Array.isArray(history) ? history : [ name + ext ], history: Array.isArray(history) ? history : [name + ext],
name, name,
ext, ext,
path: name + '.' + ext, path: name + '.' + ext,
@ -83,11 +66,13 @@ export function createPoly({
// isPoly(poly: Any) => Boolean // isPoly(poly: Any) => Boolean
export function isPoly(poly) { export function isPoly(poly) {
return poly && return (
poly &&
typeof poly.contents === 'string' && typeof poly.contents === 'string' &&
typeof poly.name === 'string' && typeof poly.name === 'string' &&
typeof poly.ext === 'string' && typeof poly.ext === 'string' &&
Array.isArray(poly.history); Array.isArray(poly.history)
);
} }
// checkPoly(poly: Any) => Void, throws // checkPoly(poly: Any) => Void, throws
@ -125,7 +110,7 @@ export function setExt(ext, poly) {
path: poly.name + '.' + ext, path: poly.name + '.' + ext,
key: poly.name + ext key: poly.name + ext
}; };
newPoly.history = [ ...poly.history, newPoly.path ]; newPoly.history = [...poly.history, newPoly.path];
return newPoly; return newPoly;
} }
@ -138,7 +123,7 @@ export function setName(name, poly) {
path: name + '.' + poly.ext, path: name + '.' + poly.ext,
key: name + poly.ext key: name + poly.ext
}; };
newPoly.history = [ ...poly.history, newPoly.path ]; newPoly.history = [...poly.history, newPoly.path];
return newPoly; return newPoly;
} }
@ -177,10 +162,12 @@ export function appendToTail(tail, poly) {
// compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl // compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl
export function compileHeadTail(padding = '', poly) { export function compileHeadTail(padding = '', poly) {
return clearHeadTail(transformContents( return clearHeadTail(
() => [ poly.head, poly.contents, poly.tail ].join(padding), transformContents(
poly () => [poly.head, poly.contents, poly.tail].join(padding),
)); poly
)
);
} }
// transformContents( // transformContents(
@ -192,10 +179,7 @@ export function compileHeadTail(padding = '', poly) {
// already contains a source, this version will continue as // already contains a source, this version will continue as
// the source property // the source property
export function transformContents(wrap, poly) { export function transformContents(wrap, poly) {
const newPoly = setContent( const newPoly = setContent(wrap(poly.contents), poly);
wrap(poly.contents),
poly
);
// if no source exist, set the original contents as source // if no source exist, set the original contents as source
newPoly.source = poly.source || poly.contents; newPoly.source = poly.source || poly.contents;
return newPoly; return newPoly;
@ -207,10 +191,7 @@ export function transformContents(wrap, poly) {
// ) => PolyVinyl // ) => PolyVinyl
export function transformHeadTailAndContents(wrap, poly) { export function transformHeadTailAndContents(wrap, poly) {
return { return {
...transformContents( ...transformContents(wrap, poly),
wrap,
poly
),
head: wrap(poly.head), head: wrap(poly.head),
tail: wrap(poly.tail) tail: wrap(poly.tail)
}; };

View File

@ -3,8 +3,7 @@ export const themes = {
default: 'default' default: 'default'
}; };
export const invertTheme = currentTheme => ( export const invertTheme = currentTheme =>
!currentTheme || currentTheme === themes.default ? !currentTheme || currentTheme === themes.default
themes.night : ? themes.night
themes.default : themes.default;
);

View File

@ -16,7 +16,5 @@ module.exports = {
transform: { transform: {
'^.+\\.js$': 'babel-jest' '^.+\\.js$': 'babel-jest'
}, },
transformIgnorePatterns: [ transformIgnorePatterns: ['node_modules/(?!(gatsby)/)']
'node_modules/(?!(gatsby)/)'
]
}; };

View File

@ -350,9 +350,8 @@ function createShowCert(app) {
messages: [ messages: [
{ {
type: 'info', type: 'info',
message: `We could not find a user with the username "${ message:
username 'We could not find a user with the username "' + username + '"'
}"`
} }
] ]
}); });

View File

@ -321,7 +321,7 @@ export default async function bootChallenge(app, done) {
) { ) {
req.flash( req.flash(
'danger', 'danger',
'You haven\'t supplied the necessary URLs for us to inspect your work.' "You haven't supplied the necessary URLs for us to inspect your work."
); );
return res.sendStatus(403); return res.sendStatus(403);
} }

View File

@ -6,23 +6,13 @@ import dedent from 'dedent';
import { homeLocation } from '../../../config/env'; import { homeLocation } from '../../../config/env';
import nonprofits from '../utils/commit.json'; import nonprofits from '../utils/commit.json';
import { import { commitGoals, completeCommitment$ } from '../utils/commit';
commitGoals,
completeCommitment$
} from '../utils/commit';
import { import { unDasherize } from '../utils';
unDasherize
} from '../utils';
import { import { observeQuery, saveInstance } from '../utils/rx';
observeQuery,
saveInstance
} from '../utils/rx';
import { import { ifNoUserRedirectTo } from '../utils/middleware';
ifNoUserRedirectTo
} from '../utils/middleware';
const sendNonUserToSignIn = ifNoUserRedirectTo( const sendNonUserToSignIn = ifNoUserRedirectTo(
`${homeLocation}/signin`, `${homeLocation}/signin`,
@ -41,12 +31,12 @@ const debug = debugFactory('fcc:commit');
function findNonprofit(name) { function findNonprofit(name) {
let nonprofit; let nonprofit;
if (name) { if (name) {
nonprofit = _.find(nonprofits, (nonprofit) => { nonprofit = _.find(nonprofits, nonprofit => {
return name === nonprofit.name; return name === nonprofit.name;
}); });
} }
nonprofit = nonprofit || nonprofits[ _.random(0, nonprofits.length - 1) ]; nonprofit = nonprofit || nonprofits[_.random(0, nonprofits.length - 1)];
return nonprofit; return nonprofit;
} }
@ -55,33 +45,15 @@ export default function commit(app) {
const api = app.loopback.Router(); const api = app.loopback.Router();
const { Pledge } = app.models; const { Pledge } = app.models;
router.get( router.get('/commit', commitToNonprofit);
'/commit',
commitToNonprofit
);
router.get( router.get('/commit/pledge', sendNonUserToSignIn, pledge);
'/commit/pledge',
sendNonUserToSignIn,
pledge
);
router.get( router.get('/commit/directory', renderDirectory);
'/commit/directory',
renderDirectory
);
api.post( api.post('/commit/stop-commitment', sendNonUserToCommit, stopCommit);
'/commit/stop-commitment',
sendNonUserToCommit,
stopCommit
);
api.post( api.post('/commit/complete-goal', sendNonUserToCommit, completeCommitment);
'/commit/complete-goal',
sendNonUserToCommit,
completeCommitment
);
app.use(api); app.use(api);
app.use(router); app.use(router);
@ -101,33 +73,26 @@ export default function commit(app) {
} }
return Observable.just(); return Observable.just();
}) })
.subscribe( .subscribe(pledge => {
pledge => { if (pledge) {
if (pledge) { debug('found previous pledge');
debug('found previous pledge'); req.flash(
req.flash( 'info',
'info', dedent`
dedent`
Looks like you already have a pledge to ${pledge.displayName}. Looks like you already have a pledge to ${pledge.displayName}.
Clicking "Commit" here will replace your old commitment. If you Clicking "Commit" here will replace your old commitment. If you
do change your commitment, please remember to cancel your do change your commitment, please remember to cancel your
previous recurring donation directly with ${pledge.displayName}. previous recurring donation directly with ${pledge.displayName}.
` `
);
}
res.render(
'commit/',
{
title: 'Commit to a nonprofit. Commit to your goal.',
pledge,
...commitGoals,
...nonprofit
}
); );
}, }
next res.render('commit/', {
); title: 'Commit to a nonprofit. Commit to your goal.',
pledge,
...commitGoals,
...nonprofit
});
}, next);
} }
function pledge(req, res, next) { function pledge(req, res, next) {
@ -143,14 +108,12 @@ export default function commit(app) {
observeQuery(user, 'pledge') observeQuery(user, 'pledge')
.flatMap(oldPledge => { .flatMap(oldPledge => {
// create new pledge for user // create new pledge for user
const pledge = Pledge( const pledge = Pledge({
{ amount,
amount, goal,
goal, userId: user.id,
userId: user.id, ...nonprofit
...nonprofit });
}
);
if (oldPledge) { if (oldPledge) {
debug('user already has pledge, creating a new one'); debug('user already has pledge, creating a new one');
@ -159,28 +122,24 @@ export default function commit(app) {
oldPledge.formerUser = user.id; oldPledge.formerUser = user.id;
oldPledge.endDate = new Date(); oldPledge.endDate = new Date();
oldPledge.isOrphaned = true; oldPledge.isOrphaned = true;
return saveInstance(oldPledge) return saveInstance(oldPledge).flatMap(() => {
.flatMap(() => { return saveInstance(pledge);
return saveInstance(pledge); });
});
} }
return saveInstance(pledge); return saveInstance(pledge);
}) })
.subscribe( .subscribe(({ displayName, goal, amount }) => {
({ displayName, goal, amount }) => { req.flash(
req.flash( 'success',
'success', dedent`
dedent`
Congratulations, you have committed to giving Congratulations, you have committed to giving
${displayName} $${amount} each month until you have completed ${displayName} $${amount} each month until you have completed
your ${goal}. Please remember to cancel your pledge directly your ${goal}. Please remember to cancel your pledge directly
with ${displayName} once you finish. with ${displayName} once you finish.
` `
); );
res.redirect('/' + user.username); res.redirect('/' + user.username);
}, }, next);
next
);
} }
function renderDirectory(req, res) { function renderDirectory(req, res) {
@ -193,16 +152,12 @@ export default function commit(app) {
function completeCommitment(req, res, next) { function completeCommitment(req, res, next) {
const { user } = req; const { user } = req;
return completeCommitment$(user) return completeCommitment$(user).subscribe(msgOrPledge => {
.subscribe( if (typeof msgOrPledge === 'string') {
msgOrPledge => { return res.send(msgOrPledge);
if (typeof msgOrPledge === 'string') { }
return res.send(msgOrPledge); return res.send(true);
} }, next);
return res.send(true);
},
next
);
} }
function stopCommit(req, res, next) { function stopCommit(req, res, next) {
@ -220,23 +175,20 @@ export default function commit(app) {
pledge.dateEnded = new Date(); pledge.dateEnded = new Date();
return saveInstance(pledge); return saveInstance(pledge);
}) })
.subscribe( .subscribe(pledge => {
pledge => { let msg = dedent`
let msg = dedent`
You have successfully stopped your pledge. Please You have successfully stopped your pledge. Please
remember to cancel your recurring donation directly remember to cancel your recurring donation directly
with the nonprofit if you haven't already done so. with the nonprofit if you haven't already done so.
`; `;
if (!pledge) { if (!pledge) {
msg = dedent` msg = dedent`
It doesn't look like you had an active pledge, so It doesn't look like you had an active pledge, so
there's no pledge to stop. there's no pledge to stop.
`; `;
} }
req.flash('info', msg); req.flash('info', msg);
return res.redirect(`/${user.username}`); return res.redirect(`/${user.username}`);
}, }, next);
next
);
} }
} }

View File

@ -6,7 +6,6 @@ import keys from '../../../config/secrets';
const log = debug('fcc:boot:donate'); const log = debug('fcc:boot:donate');
export default function donateBoot(app, done) { export default function donateBoot(app, done) {
let stripe = false; let stripe = false;
const { User } = app.models; const { User } = app.models;
const api = app.loopback.Router(); const api = app.loopback.Router();
@ -25,7 +24,8 @@ export default function donateBoot(app, done) {
currency: 'usd', currency: 'usd',
id: `monthly-donation-${current}` id: `monthly-donation-${current}`
} }
}), {} }),
{}
); );
function connectToStripe() { function connectToStripe() {
@ -70,23 +70,26 @@ export default function donateBoot(app, done) {
return res.status(400).send({ error: 'Amount Required' }); return res.status(400).send({ error: 'Amount Required' });
} }
const { amount, token: {email, id} } = body; const {
amount,
token: { email, id }
} = body;
const fccUser = user ? const fccUser = user
Promise.resolve(user) : ? Promise.resolve(user)
new Promise((resolve, reject) => : new Promise((resolve, reject) =>
User.findOrCreate( User.findOrCreate(
{ where: { email }}, { where: { email } },
{ email }, { email },
(err, instance, isNew) => { (err, instance, isNew) => {
log('is new user instance: ', isNew); log('is new user instance: ', isNew);
if (err) { if (err) {
return reject(err); return reject(err);
} }
return resolve(instance); return resolve(instance);
} }
) )
); );
let donatingUser = {}; let donatingUser = {};
let donation = { let donation = {
@ -96,14 +99,13 @@ export default function donateBoot(app, done) {
startDate: new Date(Date.now()).toISOString() startDate: new Date(Date.now()).toISOString()
}; };
return fccUser.then( return fccUser
user => { .then(user => {
donatingUser = user; donatingUser = user;
return stripe.customers return stripe.customers.create({
.create({ email,
email, card: id
card: id });
});
}) })
.then(customer => { .then(customer => {
donation.customerId = customer.id; donation.customerId = customer.id;
@ -121,7 +123,9 @@ export default function donateBoot(app, done) {
return res.send(subscription); return res.send(subscription);
}) })
.then(() => { .then(() => {
donatingUser.createDonation(donation).toPromise() donatingUser
.createDonation(donation)
.toPromise()
.catch(err => { .catch(err => {
throw new Error(err); throw new Error(err);
}); });

View File

@ -15,7 +15,7 @@ module.exports = function mountLoopBackExplorer(app) {
app.once('started', function() { app.once('started', function() {
log( log(
'Run `npm install loopback-component-explorer` to enable ' + 'Run `npm install loopback-component-explorer` to enable ' +
'the LoopBack explorer' 'the LoopBack explorer'
); );
}); });
return; return;

View File

@ -32,9 +32,7 @@ function createShortLinkHandler(app) {
if (!article) { if (!article) {
return res.redirect('/news'); return res.redirect('/news');
} }
const { const { slugPart } = article;
slugPart
} = article;
const slug = `/news/${slugPart}`; const slug = `/news/${slugPart}`;
return res.redirect(slug); return res.redirect(slug);
} }

View File

@ -88,7 +88,7 @@ module.exports = function(app) {
.then(() => { .then(() => {
req.flash( req.flash(
'success', 'success',
'We\'ve successfully updated your email preferences.' "We've successfully updated your email preferences."
); );
return res.redirectWithFlash( return res.redirectWithFlash(
`${homeLocation}/unsubscribed/${unsubscribeId}` `${homeLocation}/unsubscribed/${unsubscribeId}`
@ -144,7 +144,7 @@ module.exports = function(app) {
.then(() => { .then(() => {
req.flash( req.flash(
'success', 'success',
'We\'ve successfully updated your email preferences. Thank you ' + "We've successfully updated your email preferences. Thank you " +
'for resubscribing.' 'for resubscribing.'
); );
return res.redirectWithFlash(homeLocation); return res.redirectWithFlash(homeLocation);
@ -175,7 +175,7 @@ module.exports = function(app) {
} }
pulls = pulls pulls = pulls
? Object.keys(JSON.parse(pulls)).length ? Object.keys(JSON.parse(pulls)).length
: 'Can\'t connect to github'; : "Can't connect to github";
return request( return request(
[ [
@ -193,7 +193,7 @@ module.exports = function(app) {
issues = issues =
pulls === parseInt(pulls, 10) && issues pulls === parseInt(pulls, 10) && issues
? Object.keys(JSON.parse(issues)).length - pulls ? Object.keys(JSON.parse(issues)).length - pulls
: 'Can\'t connect to GitHub'; : "Can't connect to GitHub";
return res.send({ return res.send({
issues: issues, issues: issues,
pulls: pulls pulls: pulls

View File

@ -1,6 +1,6 @@
export default function bootStatus(app) { export default function bootStatus(app) {
const api = app.loopback.Router(); const api = app.loopback.Router();
api.get('/status/ping', (req, res) => res.json({msg: 'pong'})); api.get('/status/ping', (req, res) => res.json({ msg: 'pong' }));
app.use(api); app.use(api);
} }

View File

@ -5,8 +5,6 @@ module.exports = function(app) {
app.use(router); app.use(router);
function showForum(req, res) { function showForum(req, res) {
res.redirect( res.redirect('http://forum.freecodecamp.org/');
'http://forum.freecodecamp.org/'
);
} }
}; };

View File

@ -219,8 +219,7 @@ function createPostReportUserProfile(app) {
if (!username || !report || report === '') { if (!username || !report || report === '') {
return res.json({ return res.json({
type: 'danger', type: 'danger',
message: message: 'Oops, something is not right please re-check your submission.'
'Oops, something is not right please re-check your submission.'
}); });
} }
return Email.send$( return Email.send$(

View File

@ -1,5 +1,7 @@
import passport from 'passport'; import passport from 'passport';
// eslint-disable-next-line
import { import {
// prettier ignore
PassportConfigurator PassportConfigurator
} from '@freecodecamp/loopback-component-passport'; } from '@freecodecamp/loopback-component-passport';
import url from 'url'; import url from 'url';
@ -132,9 +134,7 @@ export function setupPassport(app) {
} }
export const saveResponseAuthCookies = () => { export const saveResponseAuthCookies = () => {
return (req, res, next) => { return (req, res, next) => {
const user = req.user; const user = req.user;
if (!user) { if (!user) {
@ -157,7 +157,6 @@ export const saveResponseAuthCookies = () => {
}; };
export const loginRedirect = () => { export const loginRedirect = () => {
return (req, res) => { return (req, res) => {
const successRedirect = req => { const successRedirect = req => {
if (!!req && req.session && req.session.returnTo) { if (!!req && req.session && req.session.returnTo) {

View File

@ -11,11 +11,7 @@ const pathsOfNoReturn = [
'css' 'css'
]; ];
const pathsWhiteList = [ const pathsWhiteList = ['challenges', 'map', 'commit'];
'challenges',
'map',
'commit'
];
const pathsOfNoReturnRegex = new RegExp(pathsOfNoReturn.join('|'), 'i'); const pathsOfNoReturnRegex = new RegExp(pathsOfNoReturn.join('|'), 'i');
const whiteListRegex = new RegExp(pathsWhiteList.join('|'), 'i'); const whiteListRegex = new RegExp(pathsWhiteList.join('|'), 'i');
@ -33,9 +29,9 @@ export default function addReturnToUrl() {
) { ) {
return next(); return next();
} }
req.session.returnTo = req.originalUrl.includes('/map') ? req.session.returnTo = req.originalUrl.includes('/map')
'/' : ? '/'
req.originalUrl; : req.originalUrl;
return next(); return next();
}; };
} }

View File

@ -1,7 +1,8 @@
export default function constantHeaders() { export default function constantHeaders() {
return function(req, res, next) { return function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept' 'Origin, X-Requested-With, Content-Type, Accept'
); );
next(); next();

View File

@ -3,7 +3,7 @@ import helmet from 'helmet';
import { homeLocation } from '../../../config/env'; import { homeLocation } from '../../../config/env';
let trusted = [ let trusted = [
'\'self\'', "'self'",
'https://search.freecodecamp.org', 'https://search.freecodecamp.org',
homeLocation, homeLocation,
'https://' + process.env.AUTH0_DOMAIN 'https://' + process.env.AUTH0_DOMAIN
@ -31,8 +31,8 @@ export default function csp() {
'https://*.algolia.net' 'https://*.algolia.net'
]), ]),
scriptSrc: [ scriptSrc: [
'\'unsafe-eval\'', "'unsafe-eval'",
'\'unsafe-inline\'', "'unsafe-inline'",
'*.google-analytics.com', '*.google-analytics.com',
'*.gstatic.com', '*.gstatic.com',
'https://*.cloudflare.com', 'https://*.cloudflare.com',
@ -48,7 +48,7 @@ export default function csp() {
'*.ytimg.com' '*.ytimg.com'
].concat(trusted), ].concat(trusted),
styleSrc: [ styleSrc: [
'\'unsafe-inline\'', "'unsafe-inline'",
'*.gstatic.com', '*.gstatic.com',
'*.googleapis.com', '*.googleapis.com',
'*.bootstrapcdn.com', '*.bootstrapcdn.com',

View File

@ -1,15 +1,12 @@
import csurf from 'csurf'; import csurf from 'csurf';
export default function() { export default function() {
const protection = csurf( const protection = csurf({
{ cookie: {
cookie: { domain: process.env.COOKIE_DOMAIN || 'localhost'
domain: process.env.COOKIE_DOMAIN || 'localhost'
}
} }
); });
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$|^unauthenticated$|^internal$|^p$)/).test(path)) { if ((/(^api$|^unauthenticated$|^internal$|^p$)/).test(path)) {
return next(); return next();

View File

@ -14,7 +14,9 @@ export default function flashCheaters() {
if ( if (
ALLOWED_METHODS.indexOf(req.method) !== -1 && ALLOWED_METHODS.indexOf(req.method) !== -1 &&
EXCLUDED_PATHS.indexOf(req.path) === -1 && EXCLUDED_PATHS.indexOf(req.path) === -1 &&
req.user && req.url !== '/' && req.user.isCheater req.user &&
req.url !== '/' &&
req.user.isCheater
) { ) {
req.flash( req.flash(
'danger', 'danger',

View File

@ -20,11 +20,13 @@ export default function() {
return false; return false;
} }
const keys = Object.keys(value); const keys = Object.keys(value);
return !!keys.length && return (
!!keys.length &&
// every key is a file // every key is a file
keys.every(key => isObject(value[key])) && keys.every(key => isObject(value[key])) &&
// every file has contents // every file has contents
keys.map(key => value[key]).every(file => isPoly(file)); keys.map(key => value[key]).every(file => isPoly(file))
);
} }
}, },
customSanitizers: { customSanitizers: {
@ -32,16 +34,20 @@ export default function() {
trimTags(value) { trimTags(value) {
const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*'; const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
const tagOrComment = new RegExp( const tagOrComment = new RegExp(
'<(?:' '<(?:' +
// Comment body. // Comment body.
+ '!--(?:(?:-*[^->])*--+|-?)' '!--(?:(?:-*[^->])*--+|-?)' +
// Special "raw text" elements whose content should be elided. // Special "raw text" elements whose content should be elided.
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*' '|script\\b' +
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*' tagBody +
// Regular name '>[\\s\\S]*?</script\\s*' +
+ '|/?[a-z]' '|style\\b' +
+ tagBody tagBody +
+ ')>', '>[\\s\\S]*?</style\\s*' +
// Regular name
'|/?[a-z]' +
tagBody +
')>',
'gi' 'gi'
); );
let rawValue; let rawValue;

View File

@ -1,6 +1,5 @@
import { createActiveUsers } from '../utils/about.js'; import { createActiveUsers } from '../utils/about.js';
module.exports = function(About) { module.exports = function(About) {
const activeUsers = createActiveUsers(); const activeUsers = createActiveUsers();
let activeUsersForRendering = 0; let activeUsersForRendering = 0;
@ -14,17 +13,14 @@ module.exports = function(About) {
About.getActiveUsersForRendering = () => activeUsersForRendering; About.getActiveUsersForRendering = () => activeUsersForRendering;
About.remoteMethod( About.remoteMethod('getActiveUsers', {
'getActiveUsers', http: {
{ path: '/get-active-users',
http: { verb: 'get'
path: '/get-active-users', },
verb: 'get' returns: {
}, type: 'number',
returns: { arg: 'activeUsers'
type: 'number',
arg: 'activeUsers'
}
} }
); });
}; };

View File

@ -11,7 +11,6 @@ log.enabled = true;
// this is where server starts booting up // this is where server starts booting up
const app = require('./server'); const app = require('./server');
let timeoutHandler; let timeoutHandler;
let killTime = 15; let killTime = 15;

View File

@ -11,7 +11,6 @@ const fiveMinutes = 1000 * 60 * 5;
class NewsFeed { class NewsFeed {
constructor() { constructor() {
this.state = { this.state = {
readyState: false, readyState: false,
mediumFeed: [], mediumFeed: [],
@ -27,26 +26,23 @@ class NewsFeed {
const newState = stateUpdater(this.state); const newState = stateUpdater(this.state);
this.state = _.merge({}, this.state, newState); this.state = _.merge({}, this.state, newState);
return; return;
} };
refreshFeeds = () => { refreshFeeds = () => {
const currentFeed = this.state.combinedFeed.slice(0); const currentFeed = this.state.combinedFeed.slice(0);
log('grabbing feeds'); log('grabbing feeds');
return Promise.all([ return Promise.all([getMediumFeed(), getLybsynFeed()])
getMediumFeed(), .then(([mediumFeed, lybsynFeed]) =>
getLybsynFeed() this.setState(state => ({
]).then(
([mediumFeed, lybsynFeed]) => this.setState(
state => ({
...state, ...state,
mediumFeed, mediumFeed,
lybsynFeed lybsynFeed
}) }))
)) )
.then(() => { .then(() => {
log('crossing the streams'); log('crossing the streams');
const { mediumFeed, lybsynFeed} = this.state; const { mediumFeed, lybsynFeed } = this.state;
const combinedFeed = [ ...mediumFeed, ...lybsynFeed ].sort((a, b) => { const combinedFeed = [...mediumFeed, ...lybsynFeed].sort((a, b) => {
return compareDesc(a.isoDate, b.isoDate); return compareDesc(a.isoDate, b.isoDate);
}); });
this.setState(state => ({ this.setState(state => ({
@ -62,25 +58,24 @@ class NewsFeed {
combinedFeed: currentFeed combinedFeed: currentFeed
})); }));
}); });
} };
getFeed = () =>
getFeed = () => new Promise((resolve) => { new Promise(resolve => {
let notReadyCount = 0; let notReadyCount = 0;
function waitForReady() { function waitForReady() {
log('notReadyCount', notReadyCount); log('notReadyCount', notReadyCount);
notReadyCount++; notReadyCount++;
return this.state.readyState || notReadyCount === 5 ? return this.state.readyState || notReadyCount === 5
resolve(this.state.combinedFeed) : ? resolve(this.state.combinedFeed)
setTimeout(waitForReady, 100); : setTimeout(waitForReady, 100);
} }
log('are we ready?', this.state.readyState); log('are we ready?', this.state.readyState);
return this.state.readyState ? return this.state.readyState
resolve(this.state.combinedFeed) : ? resolve(this.state.combinedFeed)
setTimeout(waitForReady, 100); : setTimeout(waitForReady, 100);
}) });
} }
export default NewsFeed; export default NewsFeed;

View File

@ -9,7 +9,6 @@ function getExtract(str) {
return str.slice(0, str.indexOf('</p>') + 4); return str.slice(0, str.indexOf('</p>') + 4);
} }
function addResponsiveClass(str) { function addResponsiveClass(str) {
return str.replace(/<img/g, '<img class="img-responsive"'); return str.replace(/<img/g, '<img class="img-responsive"');
} }
@ -22,17 +21,15 @@ export function getMediumFeed() {
} }
const items = feed.items const items = feed.items
.map( .map(item =>
item => _.pick(item, ['title', 'link', 'isoDate', 'content:encoded']) _.pick(item, ['title', 'link', 'isoDate', 'content:encoded'])
)
.map(
(item) => ({
...item,
extract: getExtract(item['content:encoded'])
})
) )
.map(item => ({
...item,
extract: getExtract(item['content:encoded'])
}))
.map(item => _.omit(item, ['content:encoded'])) .map(item => _.omit(item, ['content:encoded']))
.map(item => ({ ...item, extract: addResponsiveClass(item.extract)})); .map(item => ({ ...item, extract: addResponsiveClass(item.extract) }));
resolve(items); resolve(items);
}); });
}); });

View File

@ -64,7 +64,7 @@ export function createActiveUsers() {
credentials['client_email'], credentials['client_email'],
null, null,
credentials['private_key'], credentials['private_key'],
[scope], [scope]
); );
const authorize = observeMethod(client, 'authorize'); const authorize = observeMethod(client, 'authorize');
const options = { const options = {
@ -89,7 +89,5 @@ export function createActiveUsers() {
.do(null, err => console.error(err)) .do(null, err => console.error(err))
// always send a number down // always send a number down
.catch(() => Observable.of(0)) .catch(() => Observable.of(0))
// cache for 2 seconds to prevent hitting our daily request limit
::timeCache(2, 'seconds'); ::timeCache(2, 'seconds');
} }

View File

@ -1,18 +1,18 @@
const githubRegex = (/github/i); const githubRegex = /github/i;
const providerHash = { const providerHash = {
facebook: ({ id }) => id, facebook: ({ id }) => id,
github: ({ username }) => username, github: ({ username }) => username,
twitter: ({ username }) => username, twitter: ({ username }) => username,
linkedin({ _json }) { linkedin({ _json }) {
return _json && _json.publicProfileUrl || null; return (_json && _json.publicProfileUrl) || null;
}, },
google: ({ id }) => id google: ({ id }) => id
}; };
export function getUsernameFromProvider(provider, profile) { export function getUsernameFromProvider(provider, profile) {
return typeof providerHash[provider] === 'function' ? return typeof providerHash[provider] === 'function'
providerHash[provider](profile) : ? providerHash[provider](profile)
null; : null;
} }
// createProfileAttributes(provider: String, profile: {}) => Object // createProfileAttributes(provider: String, profile: {}) => Object
@ -32,13 +32,7 @@ function createProfileAttributesFromGithub(profile) {
const { const {
profileUrl: githubProfile, profileUrl: githubProfile,
username, username,
_json: { _json: { avatar_url: picture, blog: website, location, bio, name } = {}
avatar_url: picture,
blog: website,
location,
bio,
name
} = {}
} = profile; } = profile;
return { return {
name, name,

View File

@ -20,36 +20,34 @@ export function completeCommitment$(user) {
isInfosecQaCert isInfosecQaCert
} = user; } = user;
return Observable.fromNodeCallback(user.pledge, user)() return Observable.fromNodeCallback(user.pledge, user)().flatMap(pledge => {
.flatMap(pledge => { if (!pledge) {
if (!pledge) { return Observable.just('No pledge found');
return Observable.just('No pledge found'); }
}
const { goal } = pledge; const { goal } = pledge;
if ( if (
(isFrontEndCert && goal === commitGoals.frontEndCert) || (isFrontEndCert && goal === commitGoals.frontEndCert) ||
(isBackEndCert && goal === commitGoals.backEndCert) || (isBackEndCert && goal === commitGoals.backEndCert) ||
(isFullStackCert && goal === commitGoals.fullStackCert) || (isFullStackCert && goal === commitGoals.fullStackCert) ||
(isRespWebDesignCert && goal === commitGoals.respWebDesignCert) || (isRespWebDesignCert && goal === commitGoals.respWebDesignCert) ||
(isFrontEndLibsCert && goal === commitGoals.frontEndLibsCert) || (isFrontEndLibsCert && goal === commitGoals.frontEndLibsCert) ||
(isJsAlgoDataStructCert && goal === commitGoals.jsAlgoDataStructCert) || (isJsAlgoDataStructCert && goal === commitGoals.jsAlgoDataStructCert) ||
(isDataVisCert && goal === commitGoals.dataVisCert) || (isDataVisCert && goal === commitGoals.dataVisCert) ||
(isApisMicroservicesCert && (isApisMicroservicesCert && goal === commitGoals.apisMicroservicesCert) ||
goal === commitGoals.apisMicroservicesCert) || (isInfosecQaCert && goal === commitGoals.infosecQaCert)
(isInfosecQaCert && goal === commitGoals.infosecQaCert) ) {
) { debug('marking goal complete');
debug('marking goal complete'); pledge.isCompleted = true;
pledge.isCompleted = true; pledge.dateEnded = new Date();
pledge.dateEnded = new Date(); pledge.formerUserId = pledge.userId;
pledge.formerUserId = pledge.userId; pledge.userId = null;
pledge.userId = null; return Observable.fromNodeCallback(pledge.save, pledge)();
return Observable.fromNodeCallback(pledge.save, pledge)(); }
} return Observable.just(dedent`
return Observable.just(dedent`
You have not yet reached your goal of completing the ${goal} You have not yet reached your goal of completing the ${goal}
Please retry when you have met the requirements. Please retry when you have met the requirements.
`); `);
}); });
} }

View File

@ -8,25 +8,20 @@ export function unwrapHandledError(err) {
return err[_handledError] || {}; return err[_handledError] || {};
} }
export function wrapHandledError(err, { export function wrapHandledError(
type, err,
message, { type, message, redirectTo, status = 200 }
redirectTo, ) {
status = 200
}) {
err[_handledError] = { type, message, redirectTo, status }; err[_handledError] = { type, message, redirectTo, status };
return err; return err;
} }
// for use with express-validator error formatter // for use with express-validator error formatter
export const createValidatorErrorFormatter = (type, redirectTo) => export const createValidatorErrorFormatter = (type, redirectTo) => ({ msg }) =>
({ msg }) => wrapHandledError( wrapHandledError(new Error(msg), {
new Error(msg), type,
{ message: msg,
type, redirectTo,
message: msg, // we default to 400 as these are malformed requests
redirectTo, status: 400
// we default to 400 as these are malformed requests });
status: 400
}
);

View File

@ -3,9 +3,16 @@ import moment from 'moment-timezone';
// day count between two epochs (inclusive) // day count between two epochs (inclusive)
export function dayCount([head, tail], timezone = 'UTC') { export function dayCount([head, tail], timezone = 'UTC') {
return Math.ceil( return Math.ceil(
moment(moment(head).tz(timezone).endOf('day')).diff( moment(
moment(tail).tz(timezone).startOf('day'), moment(head)
.tz(timezone)
.endOf('day')
).diff(
moment(tail)
.tz(timezone)
.startOf('day'),
'days', 'days',
true) true
)
); );
} }

View File

@ -1,8 +1,4 @@
export default [ export default ['auth', 'services', 'link'].reduce((throughs, route) => {
'auth',
'services',
'link'
].reduce((throughs, route) => {
throughs[route] = true; throughs[route] = true;
return throughs; return throughs;
}, {}); }, {});

View File

@ -32,64 +32,59 @@ const getFirstChallenge = _.once(_getFirstChallenge);
*/ */
export function _cachedMap({ Block, Challenge }) { export function _cachedMap({ Block, Challenge }) {
const challenges = Challenge.find$({ const challenges = Challenge.find$({
order: [ 'order ASC', 'suborder ASC' ], order: ['order ASC', 'suborder ASC'],
where: { isPrivate: false } where: { isPrivate: false }
}); });
const challengeMap = challenges const challengeMap = challenges.map(challenges =>
.map( challenges
challenges => challenges .map(challenge => challenge.toJSON())
.map(challenge => challenge.toJSON()) .reduce((hash, challenge) => {
.reduce((hash, challenge) => { hash[challenge.dashedName] = challenge;
hash[challenge.dashedName] = challenge; return hash;
return hash; }, {})
}, {}) );
);
const blocks = Block.find$({ const blocks = Block.find$({
order: [ 'superOrder ASC', 'order ASC' ], order: ['superOrder ASC', 'order ASC'],
where: { isPrivate: false } where: { isPrivate: false }
}); });
const blockMap = Observable.combineLatest( const blockMap = Observable.combineLatest(
blocks.map( blocks.map(blocks =>
blocks => blocks blocks.map(block => block.toJSON()).reduce((hash, block) => {
.map(block => block.toJSON()) hash[block.dashedName] = block;
.reduce((hash, block) => { return hash;
hash[block.dashedName] = block; }, {})
return hash;
}, {})
), ),
challenges challenges
) ).map(([blocksMap, challenges]) => {
.map(([ blocksMap, challenges ]) => { return challenges.reduce((blocksMap, challenge) => {
return challenges.reduce((blocksMap, challenge) => { if (blocksMap[challenge.block].challenges) {
if (blocksMap[challenge.block].challenges) { blocksMap[challenge.block].challenges.push(challenge.dashedName);
blocksMap[challenge.block].challenges.push(challenge.dashedName); } else {
} else { blocksMap[challenge.block] = {
blocksMap[challenge.block] = { ...blocksMap[challenge.block],
...blocksMap[challenge.block], challenges: [challenge.dashedName]
challenges: [ challenge.dashedName ] };
}; }
} return blocksMap;
return blocksMap; }, blocksMap);
}, blocksMap); });
}); const superBlockMap = blocks.map(blocks =>
const superBlockMap = blocks.map(blocks => blocks.reduce((map, block) => { blocks.reduce((map, block) => {
if ( if (map[block.superBlock] && map[block.superBlock].blocks) {
map[block.superBlock] && map[block.superBlock].blocks.push(block.dashedName);
map[block.superBlock].blocks } else {
) { map[block.superBlock] = {
map[block.superBlock].blocks.push(block.dashedName); title: _.startCase(block.superBlock),
} else { order: block.superOrder,
map[block.superBlock] = { name: nameify(_.startCase(block.superBlock)),
title: _.startCase(block.superBlock), dashedName: block.superBlock,
order: block.superOrder, blocks: [block.dashedName],
name: nameify(_.startCase(block.superBlock)), message: block.superBlockMessage
dashedName: block.superBlock, };
blocks: [block.dashedName], }
message: block.superBlockMessage return map;
}; }, {})
} );
return map;
}, {}));
const superBlocks = superBlockMap.map(superBlockMap => { const superBlocks = superBlockMap.map(superBlockMap => {
return Object.keys(superBlockMap) return Object.keys(superBlockMap)
.map(key => superBlockMap[key]) .map(key => superBlockMap[key])
@ -126,30 +121,25 @@ export function getChallengeById(map, id) {
return Observable.if( return Observable.if(
() => !id, () => !id,
map.map(getFirstChallenge), map.map(getFirstChallenge),
map.map(addNameIdMap) map.map(addNameIdMap).map(map => {
.map(map => { const {
const { entities: { challenge: challengeMap, challengeIdToName }
entities: { challenge: challengeMap, challengeIdToName } } = map;
} = map; let finalChallenge;
let finalChallenge; const dashedName = challengeIdToName[id];
const dashedName = challengeIdToName[id]; finalChallenge = challengeMap[dashedName];
finalChallenge = challengeMap[dashedName]; if (!finalChallenge) {
if (!finalChallenge) { finalChallenge = getFirstChallenge(map);
finalChallenge = getFirstChallenge(map); }
} return finalChallenge;
return finalChallenge; })
})
); );
} }
export function getChallengeInfo(map) { export function getChallengeInfo(map) {
return map.map(addNameIdMap) return map
.map(({ .map(addNameIdMap)
entities: { .map(({ entities: { challenge: challengeMap, challengeIdToName } }) => ({
challenge: challengeMap,
challengeIdToName
}
}) => ({
challengeMap, challengeMap,
challengeIdToName challengeIdToName
})); }));
@ -168,42 +158,37 @@ function loadComingSoonOrBetaChallenge({
// this is a hard search // this is a hard search
// falls back to soft search // falls back to soft search
export function getChallenge( export function getChallenge(challengeDashedName, blockDashedName, map) {
challengeDashedName, return map.flatMap(({ entities, result: { superBlocks } }) => {
blockDashedName, const superBlock = entities.superBlock;
map) { const block = entities.block[blockDashedName];
return map const challenge = entities.challenge[challengeDashedName];
.flatMap(({ entities, result: { superBlocks } }) => { return Observable.if(
const superBlock = entities.superBlock; () =>
const block = entities.block[blockDashedName]; !blockDashedName ||
const challenge = entities.challenge[challengeDashedName]; !block ||
return Observable.if( !challenge ||
() => ( !loadComingSoonOrBetaChallenge(challenge),
!blockDashedName || getChallengeByDashedName(challengeDashedName, map),
!block || Observable.just({ block, challenge })
!challenge || ).map(({ challenge, block }) => ({
!loadComingSoonOrBetaChallenge(challenge) redirect:
), challenge.block !== blockDashedName
getChallengeByDashedName(challengeDashedName, map), ? `/challenges/${block.dashedName}/${challenge.dashedName}`
Observable.just({ block, challenge }) : false,
) entities: {
.map(({ challenge, block }) => ({ superBlock,
redirect: challenge.block !== blockDashedName ? challenge: {
`/challenges/${block.dashedName}/${challenge.dashedName}` : [challenge.dashedName]: challenge
false, }
entities: { },
superBlock, result: {
challenge: { block: block.dashedName,
[challenge.dashedName]: challenge challenge: challenge.dashedName,
} superBlocks
}, }
result: { }));
block: block.dashedName, });
challenge: challenge.dashedName,
superBlocks
}
}));
});
} }
export function getBlockForChallenge(map, challenge) { export function getBlockForChallenge(map, challenge) {
@ -211,19 +196,21 @@ export function getBlockForChallenge(map, challenge) {
} }
export function getChallengeByDashedName(dashedName, map) { export function getChallengeByDashedName(dashedName, map) {
const challengeName = unDasherize(dashedName) const challengeName = unDasherize(dashedName).replace(challengesRegex, '');
.replace(challengesRegex, '');
const testChallengeName = new RegExp(challengeName, 'i'); const testChallengeName = new RegExp(challengeName, 'i');
return map return map
.map(({ entities }) => entities.challenge) .map(({ entities }) => entities.challenge)
.flatMap(challengeMap => { .flatMap(challengeMap => {
return Observable.from(Object.keys(challengeMap)) return Observable.from(Object.keys(challengeMap)).map(
.map(key => challengeMap[key]); key => challengeMap[key]
);
}) })
.filter(challenge => { .filter(challenge => {
return loadComingSoonOrBetaChallenge(challenge) && return (
testChallengeName.test(challenge.name); loadComingSoonOrBetaChallenge(challenge) &&
testChallengeName.test(challenge.name)
);
}) })
.last({ defaultValue: null }) .last({ defaultValue: null })
.flatMap(challengeOrNull => { .flatMap(challengeOrNull => {
@ -234,8 +221,9 @@ export function getChallengeByDashedName(dashedName, map) {
); );
}) })
.flatMap(challenge => { .flatMap(challenge => {
return getBlockForChallenge(map, challenge) return getBlockForChallenge(map, challenge).map(block => ({
.map(block => ({ challenge, block })); challenge,
block
}));
}); });
} }

View File

@ -57,9 +57,11 @@ export const userPropsForSession = [
export function normaliseUserFields(user) { export function normaliseUserFields(user) {
const about = user.bio && !user.about ? user.bio : user.about; const about = user.bio && !user.about ? user.bio : user.about;
const picture = user.picture || addPlaceholderImage(user.username); const picture = user.picture || addPlaceholderImage(user.username);
const twitter = user.twitter && isURL(user.twitter) ? const twitter =
user.twitter : user.twitter && isURL(user.twitter)
user.twitter && `https://www.twitter.com/${user.twitter.replace(/^@/, '')}`; ? user.twitter
: user.twitter &&
`https://www.twitter.com/${user.twitter.replace(/^@/, '')}`;
return { about, picture, twitter }; return { about, picture, twitter };
} }

View File

@ -49,7 +49,9 @@ export function timeCache(time, unit) {
// set new expire time in MS and create new subscription to source // set new expire time in MS and create new subscription to source
if (!expireCacheAt || expireCacheAt < Date.now()) { if (!expireCacheAt || expireCacheAt < Date.now()) {
// set expire in ms; // set expire in ms;
expireCacheAt = moment().add(time, unit).valueOf(); expireCacheAt = moment()
.add(time, unit)
.valueOf();
cache = new AsyncSubject(); cache = new AsyncSubject();
source.subscribe(cache); source.subscribe(cache);
} }

View File

@ -25,13 +25,7 @@ export function getHost() {
export function getServerFullURL() { export function getServerFullURL() {
if (!isDev) { if (!isDev) {
return getProtocol() return getProtocol() + '://' + getHost();
+ '://'
+ getHost();
} }
return getProtocol() return getProtocol() + '://' + getHost() + ':' + getPort();
+ '://'
+ getHost()
+ ':'
+ getPort();
} }

View File

@ -13,7 +13,6 @@ const hoursBetween = 24;
const hoursDay = 24; const hoursDay = 24;
export function prepUniqueDaysByHours(cals, tz = 'UTC') { export function prepUniqueDaysByHours(cals, tz = 'UTC') {
let prev = null; let prev = null;
// compose goes bottom to top (map > sortBy > transform) // compose goes bottom to top (map > sortBy > transform)
@ -25,27 +24,34 @@ export function prepUniqueDaysByHours(cals, tz = 'UTC') {
} else if ( } else if (
moment(cur) moment(cur)
.tz(tz) .tz(tz)
.diff(moment(prev).tz(tz).startOf('day'), 'hours') .diff(
>= hoursDay moment(prev)
.tz(tz)
.startOf('day'),
'hours'
) >= hoursDay
) { ) {
data.push(cur); data.push(cur);
prev = cur; prev = cur;
} }
}, []), }, []),
sortBy(e => e), sortBy(e => e),
map(ts => moment(ts).tz(tz).startOf('hours').valueOf()) map(ts =>
moment(ts)
.tz(tz)
.startOf('hours')
.valueOf()
)
)(cals); )(cals);
} }
export function calcCurrentStreak(cals, tz = 'UTC') { export function calcCurrentStreak(cals, tz = 'UTC') {
let prev = last(cals); let prev = last(cals);
if ( if (
moment() moment()
.tz(tz) .tz(tz)
.startOf('day') .startOf('day')
.diff(moment(prev).tz(tz), 'hours') .diff(moment(prev).tz(tz), 'hours') > hoursBetween
> hoursBetween
) { ) {
return 0; return 0;
} }
@ -56,8 +62,7 @@ export function calcCurrentStreak(cals, tz = 'UTC') {
moment(prev) moment(prev)
.tz(tz) .tz(tz)
.startOf('day') .startOf('day')
.diff(moment(cur).tz(tz), 'hours') .diff(moment(cur).tz(tz), 'hours') <= hoursBetween
<= hoursBetween
) { ) {
prev = cur; prev = cur;
currentStreak++; currentStreak++;
@ -72,20 +77,26 @@ export function calcCurrentStreak(cals, tz = 'UTC') {
} }
export function calcLongestStreak(cals, tz = 'UTC') { export function calcLongestStreak(cals, tz = 'UTC') {
let tail = cals[0]; let tail = cals[0];
const longest = cals.reduce((longest, head, index) => { const longest = cals.reduce(
const last = cals[index === 0 ? 0 : index - 1]; (longest, head, index) => {
// is streak broken const last = cals[index === 0 ? 0 : index - 1];
if (moment(head).tz(tz).startOf('day').diff(moment(last).tz(tz), 'hours') // is streak broken
> hoursBetween) { if (
tail = head; moment(head)
} .tz(tz)
if (dayCount(longest, tz) < dayCount([head, tail], tz)) { .startOf('day')
return [head, tail]; .diff(moment(last).tz(tz), 'hours') > hoursBetween
} ) {
return longest; tail = head;
}, [cals[0], cals[0]]); }
if (dayCount(longest, tz) < dayCount([head, tail], tz)) {
return [head, tail];
}
return longest;
},
[cals[0], cals[0]]
);
return dayCount(longest, tz); return dayCount(longest, tz);
} }

View File

@ -16,7 +16,5 @@ module.exports = {
transform: { transform: {
'^.+\\.js$': '<rootDir>/jest.transform.js' '^.+\\.js$': '<rootDir>/jest.transform.js'
}, },
transformIgnorePatterns: [ transformIgnorePatterns: ['node_modules/(?!(gatsby)/)']
'node_modules/(?!(gatsby)/)'
]
}; };

View File

@ -1,12 +1,10 @@
const babelOptions = { const babelOptions = {
presets: [ presets: ['@babel/preset-env', '@babel/react'],
'@babel/preset-env',
'@babel/react'
],
plugins: [ plugins: [
'@babel/plugin-proposal-function-bind', '@babel/plugin-proposal-function-bind',
[ [
'transform-imports', { 'transform-imports',
{
'@freecodecamp/react-bootstrap': { '@freecodecamp/react-bootstrap': {
transform: '@freecodecamp/react-bootstrap/lib/${member}', transform: '@freecodecamp/react-bootstrap/lib/${member}',
preventFullImport: true preventFullImport: true

View File

@ -29,7 +29,10 @@ exports.createNavigationNode = function createNavigationNode(node) {
const nodeDir = path.resolve(fileAbsolutePath).replace(indexMdRe, ''); const nodeDir = path.resolve(fileAbsolutePath).replace(indexMdRe, '');
const dashedName = nodeDir.split(path.sep).slice(-1)[0]; const dashedName = nodeDir.split(path.sep).slice(-1)[0];
const nodePath = nodeDir.split(pagesDir)[1].split(path.sep).join('/'); const nodePath = nodeDir
.split(pagesDir)[1]
.split(path.sep)
.join('/');
const parentPath = nodePath const parentPath = nodePath
.split('/') .split('/')
.slice(0, -1) .slice(0, -1)

View File

@ -33,15 +33,9 @@ function DonateCompletion({ processing, reset, success, error = null }) {
/> />
)} )}
{success && ( {success && (
<p> <p>Thank you for supporting the freeCodeCamp.org community.</p>
Thank you for supporting the freeCodeCamp.org community.
</p>
)}
{error && (
<p>
{error}
</p>
)} )}
{error && <p>{error}</p>}
</div> </div>
<p className='donation-completion-buttons'> <p className='donation-completion-buttons'>
{error && ( {error && (

View File

@ -20,10 +20,9 @@ import DonateText from './DonateText';
import '../Donation.css'; import '../Donation.css';
const mapStateToProps = createSelector( const mapStateToProps = createSelector(isDonationModalOpenSelector, show => ({
isDonationModalOpenSelector, show
show => ({ show }) }));
);
const mapDispatchToProps = dispatch => const mapDispatchToProps = dispatch =>
bindActionCreators( bindActionCreators(
@ -73,7 +72,9 @@ class DonateModal extends Component {
}; };
return ( return (
<div className='modal-close-btn-container'> <div className='modal-close-btn-container'>
<Button bsStyle='link' onClick={handleClick}>Close</Button> <Button bsStyle='link' onClick={handleClick}>
Close
</Button>
</div> </div>
); );
} }
@ -84,25 +85,25 @@ class DonateModal extends Component {
ga.modalview('/donation-modal'); ga.modalview('/donation-modal');
} }
return ( return (
<StripeProvider stripe={this.state.stripe}> <StripeProvider stripe={this.state.stripe}>
<Elements> <Elements>
<Modal bsSize='lg' className='donation-modal' show={show}> <Modal bsSize='lg' className='donation-modal' show={show}>
<Modal.Header className='fcc-modal'> <Modal.Header className='fcc-modal'>
<Modal.Title className='text-center'> <Modal.Title className='text-center'>
Support Our NonProfit Support Our NonProfit
</Modal.Title> </Modal.Title>
</Modal.Header> </Modal.Header>
<Modal.Body> <Modal.Body>
<DonateText /> <DonateText />
<DonateForm /> <DonateForm />
{this.renderMaybe()} {this.renderMaybe()}
</Modal.Body> </Modal.Body>
<Modal.Footer> <Modal.Footer>
<PoweredByStripe /> <PoweredByStripe />
</Modal.Footer> </Modal.Footer>
</Modal> </Modal>
</Elements> </Elements>
</StripeProvider> </StripeProvider>
); );
} }
} }

View File

@ -19,15 +19,14 @@ const DonateText = ({ activeDonations }) => {
return ( return (
<div className='text-center'> <div className='text-center'>
<p> <p>
freeCodeCamp.org is a tiny nonprofit that's helping millions of freeCodeCamp.org is a tiny nonprofit that's helping millions of people
people learn to code for free. learn to code for free.
</p> </p>
<p> <p>
Join <strong>{donationsLocale}</strong> supporters. Join <strong>{donationsLocale}</strong> supporters.
</p> </p>
<p> <p>
Your $5 / month donation will help keep tech education free and Your $5 / month donation will help keep tech education free and open.
open.
</p> </p>
</div> </div>
); );

View File

@ -77,9 +77,7 @@ class StripeCardForm extends Component {
return ( return (
<div className='donation-elements'> <div className='donation-elements'>
<FormGroup> <FormGroup>
<ControlLabel> <ControlLabel>Your Card Number:</ControlLabel>
Your Card Number:
</ControlLabel>
<CardNumberElement <CardNumberElement
className='form-control donate-input-element' className='form-control donate-input-element'
onChange={this.handleInputChange} onChange={this.handleInputChange}
@ -87,9 +85,7 @@ class StripeCardForm extends Component {
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<ControlLabel> <ControlLabel>Your Card Expiration Month:</ControlLabel>
Your Card Expiration Month:
</ControlLabel>
<CardExpiryElement <CardExpiryElement
className='form-control donate-input-element' className='form-control donate-input-element'
onChange={this.handleInputChange} onChange={this.handleInputChange}
@ -97,9 +93,7 @@ class StripeCardForm extends Component {
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<ControlLabel> <ControlLabel>Your Card CVC (3-digit security number):</ControlLabel>
Your Card CVC (3-digit security number):
</ControlLabel>
<CardCVCElement <CardCVCElement
className='form-control donate-input-element' className='form-control donate-input-element'
onChange={this.handleInputChange} onChange={this.handleInputChange}

View File

@ -96,12 +96,8 @@ function Footer() {
</Col> </Col>
<Col lg={3} sm={2} xs={12}> <Col lg={3} sm={2} xs={12}>
<ColHeader>Our Learning Resources</ColHeader> <ColHeader>Our Learning Resources</ColHeader>
<Link to='/learn'> <Link to='/learn'>Learn</Link>
Learn <Link to='/guide'>Guide</Link>
</Link>
<Link to='/guide'>
Guide
</Link>
<Link to='https://www.youtube.com/freecodecamp'>Youtube</Link> <Link to='https://www.youtube.com/freecodecamp'>Youtube</Link>
<Link to='https://podcast.freecodecamp.org'>Podcast</Link> <Link to='https://podcast.freecodecamp.org'>Podcast</Link>
<Link to='https://twitter.com/freecodecamp'>Twitter</Link> <Link to='https://twitter.com/freecodecamp'>Twitter</Link>

View File

@ -154,4 +154,7 @@ export class Block extends Component {
Block.displayName = 'Block'; Block.displayName = 'Block';
Block.propTypes = propTypes; Block.propTypes = propTypes;
export default connect(mapStateToProps, mapDispatchToProps)(Block); export default connect(
mapStateToProps,
mapDispatchToProps
)(Block);

View File

@ -33,7 +33,9 @@ export function DynamicForm({
errors, errors,
fields, fields,
handleSubmit, handleSubmit,
fields: { _meta: { allPristine } }, fields: {
_meta: { allPristine }
},
// HOC // HOC
buttonText, buttonText,

View File

@ -166,7 +166,7 @@ class DefaultLayout extends Component {
) : null} ) : null}
{children} {children}
</div> </div>
{showFooter && (<Footer />)} {showFooter && <Footer />}
</Fragment> </Fragment>
); );
} }

View File

@ -67,7 +67,9 @@ class NavPanel extends Component {
'caret ' + (isExpanded ? 'caretStyle expanded' : 'caretStyle') 'caret ' + (isExpanded ? 'caretStyle expanded' : 'caretStyle')
} }
/> />
<Link onClick={this.handleTitleClick} to={path}>{title}</Link> <Link onClick={this.handleTitleClick} to={path}>
{title}
</Link>
</div> </div>
); );
} }

View File

@ -8,32 +8,38 @@ import ReactGA from '../analytics/index.js';
import Spacer from '../components/helpers/Spacer'; import Spacer from '../components/helpers/Spacer';
const paypalMonthlyDonations = [ const paypalMonthlyDonations = [
{ eventLabel: 'paypal', {
eventLabel: 'paypal',
eventValue: 5, eventValue: 5,
defaultValueHash: 'KTAXU8B4MYAT8', defaultValueHash: 'KTAXU8B4MYAT8',
defaultValue: 'Donate $5 each month' defaultValue: 'Donate $5 each month'
}, },
{ eventLabel: 'paypal', {
eventLabel: 'paypal',
eventValue: 10, eventValue: 10,
defaultValueHash: 'BYEBQEHS5LHNC', defaultValueHash: 'BYEBQEHS5LHNC',
defaultValue: 'Donate $10 each month' defaultValue: 'Donate $10 each month'
}, },
{ eventLabel: 'paypal', {
eventLabel: 'paypal',
eventValue: 35, eventValue: 35,
defaultValueHash: '57ZPMKJ8G893Y', defaultValueHash: '57ZPMKJ8G893Y',
defaultValue: 'Donate $35 each month' defaultValue: 'Donate $35 each month'
}, },
{ eventLabel: 'paypal', {
eventLabel: 'paypal',
eventValue: 50, eventValue: 50,
defaultValueHash: '2ZVYTHK6Q7AFW', defaultValueHash: '2ZVYTHK6Q7AFW',
defaultValue: 'Donate $50 each month' defaultValue: 'Donate $50 each month'
}, },
{ eventLabel: 'paypal', {
eventLabel: 'paypal',
eventValue: 100, eventValue: 100,
defaultValueHash: 'C7PUT3LMJHKK2', defaultValueHash: 'C7PUT3LMJHKK2',
defaultValue: 'Donate $100 each month' defaultValue: 'Donate $100 each month'
}, },
{ eventLabel: 'paypal', {
eventLabel: 'paypal',
eventValue: 250, eventValue: 250,
defaultValueHash: '69JGTY4DHSTEN', defaultValueHash: '69JGTY4DHSTEN',
defaultValue: 'Donate $250 each month' defaultValue: 'Donate $250 each month'
@ -48,13 +54,19 @@ const paypalOneTimeDonation = {
}; };
class DonateOtherPage extends Component { class DonateOtherPage extends Component {
renderForm(item) { renderForm(item) {
return ( return (
<form <form
action='https://www.paypal.com/cgi-bin/webscr' action='https://www.paypal.com/cgi-bin/webscr'
method='post' method='post'
onSubmit={() => ReactGA.event({category: 'donation', action: 'click', label: item.eventLabel, value: item.eventValue})} onSubmit={() =>
ReactGA.event({
category: 'donation',
action: 'click',
label: item.eventLabel,
value: item.eventValue
})
}
target='_blank' target='_blank'
> >
<input defaultValue='_s-xclick' name='cmd' type='hidden' />{' '} <input defaultValue='_s-xclick' name='cmd' type='hidden' />{' '}
@ -74,122 +86,143 @@ class DonateOtherPage extends Component {
} }
render() { render() {
return ( return (
<Fragment> <Fragment>
<Helmet title='Other ways to donate | freeCodeCamp.org' /> <Helmet title='Other ways to donate | freeCodeCamp.org' />
<Spacer /> <Spacer />
<Grid className='container'> <Grid className='container'>
<Row> <Row>
<Col md={6} mdOffset={3} sm={10} smOffset={1} xs={12}> <Col md={6} mdOffset={3} sm={10} smOffset={1} xs={12}>
<h2 className='text-center'> <h2 className='text-center'>
Other ways you can support the freeCodeCamp.org nonprofit Other ways you can support the freeCodeCamp.org nonprofit
</h2> </h2>
<p> <p>
freeCodeCamp is a small donor-supported 501(c)(3) public charity. We freeCodeCamp is a small donor-supported 501(c)(3) public
are tax-exempt, so you can deduct donations you make to our charity. We are tax-exempt, so you can deduct donations you make
nonprofit from your taxes. You can{' '} to our nonprofit from your taxes. You can{' '}
<a href='https://s3.amazonaws.com/freecodecamp/Free+Code+Camp+Inc+IRS+Determination+Letter.pdf'> <a href='https://s3.amazonaws.com/freecodecamp/Free+Code+Camp+Inc+IRS+Determination+Letter.pdf'>
download our IRS Determination Letter here download our IRS Determination Letter here
</a>. </a>
</p> .
<hr /> </p>
<h3>Set up a monthly donation using PayPal</h3> <hr />
<p> <h3>Set up a monthly donation using PayPal</h3>
You can set up a monthly donation to freeCodeCamp by clicking one of <p>
the links below and following the instructions on PayPal. You can You can set up a monthly donation to freeCodeCamp by clicking
easily stop your donations at any time in the future. one of the links below and following the instructions on PayPal.
</p> You can easily stop your donations at any time in the future.
{paypalMonthlyDonations.map((item) => { </p>
return this.renderForm(item); {paypalMonthlyDonations.map(item => {
})} return this.renderForm(item);
<hr /> })}
<h3>Make a one-time donation using PayPal</h3> <hr />
<p> <h3>Make a one-time donation using PayPal</h3>
You can make a one-time monthly donation to freeCodeCamp for any <p>
amount of money by clicking one of the links below and following the You can make a one-time monthly donation to freeCodeCamp for any
instructions on PayPal: amount of money by clicking one of the links below and following
</p> the instructions on PayPal:
{this.renderForm(paypalOneTimeDonation)} </p>
<hr /> {this.renderForm(paypalOneTimeDonation)}
<h3>Get your employer to match your donation</h3> <hr />
<p> <h3>Get your employer to match your donation</h3>
Many freeCodeCamp supporters are able to get their employers to <p>
match their donations to freeCodeCamp. Our Tax-exempt number (EIN) Many freeCodeCamp supporters are able to get their employers to
is 82-0779546. If we can help you with setting this up, please email{' '} match their donations to freeCodeCamp. Our Tax-exempt number
<a href='mailto:team@freecodecamp.org'>team@freecodecamp.org</a>. (EIN) is 82-0779546. If we can help you with setting this up,
</p> please email{' '}
<hr /> <a href='mailto:team@freecodecamp.org'>team@freecodecamp.org</a>
<h3>Donate through a payroll deduction</h3> .
<p> </p>
In the US and Canada, some employers have a convenient way to give <hr />
to freeCodeCamp through a payroll deduction. Ask your employer if <h3>Donate through a payroll deduction</h3>
they can do this, and have them send any necessary paperwork to: <p>
</p> In the US and Canada, some employers have a convenient way to
<p> give to freeCodeCamp through a payroll deduction. Ask your
Free Code Camp, Inc.<br />7800 W Hefner Rd, PO BOX 721024<br />Oklahoma employer if they can do this, and have them send any necessary
City, Oklahoma 73172<br />United States paperwork to:
</p> </p>
<hr /> <p>
<h3>Donate cryptocurrency to freeCodeCamp</h3> Free Code Camp, Inc.
<p> <br />
Below are our wallets where we can receive cryptocurrency donations. 7800 W Hefner Rd, PO BOX 721024
</p> <br />
<h4>Make a one-time Bitcoin donation</h4> Oklahoma City, Oklahoma 73172
<p className='negative-15'> <br />
Our Bitcoin wallet is{' '} United States
<code>12skYi7aMCjDUdrVdoB3JjZ77ug8gxJfbL</code> </p>
</p> <hr />
<h4>Make a one-time Ethereum donation</h4> <h3>Donate cryptocurrency to freeCodeCamp</h3>
<p className='negative-15'> <p>
Our Ethereum wallet is{' '} Below are our wallets where we can receive cryptocurrency
<code>0x0ADbEf2471416BD8732cf0f3944294eE393CcAF5</code> donations.
</p> </p>
<h4>Make a one-time Litecoin donation</h4> <h4>Make a one-time Bitcoin donation</h4>
<p className='negative-15'> <p className='negative-15'>
Our Litecoin wallet is{' '} Our Bitcoin wallet is{' '}
<code>LKu8UG8Z1nbTxnq9Do96PsC3FwbNtycf3X</code> <code>12skYi7aMCjDUdrVdoB3JjZ77ug8gxJfbL</code>
</p> </p>
<h4>Make a one-time Bitcoin Cash donation</h4> <h4>Make a one-time Ethereum donation</h4>
<p className='negative-15'> <p className='negative-15'>
Our Bitcoin Cash wallet is{' '} Our Ethereum wallet is{' '}
<code>1EBxPEJWrGZWxe2UayyAsnd5VsRg5H9xfu</code> <code>0x0ADbEf2471416BD8732cf0f3944294eE393CcAF5</code>
</p> </p>
<hr /> <h4>Make a one-time Litecoin donation</h4>
<h3>Donate to freeCodeCamp by mailing us a check</h3> <p className='negative-15'>
<p>Our mailing address is:</p> Our Litecoin wallet is{' '}
<p> <code>LKu8UG8Z1nbTxnq9Do96PsC3FwbNtycf3X</code>
Free Code Camp, Inc.<br />7800 W Hefner Rd, PO BOX 721024<br />Oklahoma </p>
City, Oklahoma 73172<br />United States <h4>Make a one-time Bitcoin Cash donation</h4>
</p> <p className='negative-15'>
<hr /> Our Bitcoin Cash wallet is{' '}
<h3>Donate Stock to freeCodeCamp</h3> <code>1EBxPEJWrGZWxe2UayyAsnd5VsRg5H9xfu</code>
<p> </p>
If you want to donate stocks to freeCodeCamp, please email us at{' '} <hr />
<a href='mailto:team@freecodecamp.org'>team@freecodecamp.org</a>. <h3>Donate to freeCodeCamp by mailing us a check</h3>
</p> <p>Our mailing address is:</p>
<hr /> <p>
<h3>Legacy Gift</h3> Free Code Camp, Inc.
<p> <br />
You can help future generations of learners by listing freeCodeCamp 7800 W Hefner Rd, PO BOX 721024
in your will or living trust. If you're interested in doing this, <br />
email Quincy directly and we can discuss this:{' '} Oklahoma City, Oklahoma 73172
<a href='mailto:quincy@freecodecamp.org'>quincy@freecodecamp.org</a>. <br />
</p> United States
<hr /> </p>
<h3> <hr />
Thank you for supporting our nonprofit and the freeCodeCamp <h3>Donate Stock to freeCodeCamp</h3>
community. <p>
</h3> If you want to donate stocks to freeCodeCamp, please email us at{' '}
<Spacer /> <a href='mailto:team@freecodecamp.org'>team@freecodecamp.org</a>
<div className='text-center'> .
<Link to='/donate'>Or donate using a Credit or Debit Card.</Link> </p>
</div> <hr />
<Spacer /> <h3>Legacy Gift</h3>
</Col> <p>
</Row> You can help future generations of learners by listing
</Grid> freeCodeCamp in your will or living trust. If you're interested
</Fragment> in doing this, email Quincy directly and we can discuss this:{' '}
); <a href='mailto:quincy@freecodecamp.org'>
quincy@freecodecamp.org
</a>
.
</p>
<hr />
<h3>
Thank you for supporting our nonprofit and the freeCodeCamp
community.
</h3>
<Spacer />
<div className='text-center'>
<Link to='/donate'>
Or donate using a Credit or Debit Card.
</Link>
</div>
<Spacer />
</Col>
</Row>
</Grid>
</Fragment>
);
} }
} }

View File

@ -60,9 +60,7 @@ class DonatePage extends Component {
<Spacer /> <Spacer />
<Row> <Row>
<Col sm={8} smOffset={2} xs={12}> <Col sm={8} smOffset={2} xs={12}>
<h2 className='text-center'> <h2 className='text-center'>Become a Supporter</h2>
Become a Supporter
</h2>
<DonateText /> <DonateText />
</Col> </Col>
<Col sm={6} smOffset={3} xs={12}> <Col sm={6} smOffset={3} xs={12}>

View File

@ -85,9 +85,7 @@ export const query = graphql`
slug slug
} }
} }
allChallengeNode( allChallengeNode(sort: { fields: [superOrder, order, challengeOrder] }) {
sort: { fields: [superOrder, order, challengeOrder] }
) {
edges { edges {
node { node {
fields { fields {

View File

@ -16,7 +16,7 @@ function setTheme(currentTheme = defaultTheme, theme) {
html.classList.add(theme); html.classList.add(theme);
} }
function* updateLocalThemeSaga({ payload: {user, theme } }) { function* updateLocalThemeSaga({ payload: { user, theme } }) {
const currentTheme = store.get(themeKey) || defaultTheme; const currentTheme = store.get(themeKey) || defaultTheme;
if (user) { if (user) {
const { theme = defaultTheme } = user; const { theme = defaultTheme } = user;

View File

@ -6,10 +6,5 @@ import { sagas as challengeSagas } from '../templates/Challenges/redux';
import { sagas as settingsSagas } from './settings'; import { sagas as settingsSagas } from './settings';
export default function* rootSaga() { export default function* rootSaga() {
yield all([ yield all([...errorSagas, ...appSagas, ...challengeSagas, ...settingsSagas]);
...errorSagas,
...appSagas,
...challengeSagas,
...settingsSagas
]);
} }

View File

@ -170,7 +170,7 @@ export class BackEnd extends Component {
<LearnLayout> <LearnLayout>
<Grid> <Grid>
<Row> <Row>
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12} > <Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
<Spacer /> <Spacer />
<ChallengeTitle>{blockNameTitle}</ChallengeTitle> <ChallengeTitle>{blockNameTitle}</ChallengeTitle>
<ChallengeDescription <ChallengeDescription

View File

@ -10,8 +10,9 @@ const propTypes = {
}; };
function emptyInstruction(instructions) { function emptyInstruction(instructions) {
return (/^<section\s+id\s*=\s*("|')instructions\1\s*>\s*<\/section>$/) return (/^<section\s+id\s*=\s*("|')instructions\1\s*>\s*<\/section>$/).test(
.test(instructions); instructions
);
} }
function ChallengeDescription({ description, instructions, section }) { function ChallengeDescription({ description, instructions, section }) {

View File

@ -73,4 +73,7 @@ function ResetModal({ reset, close, isOpen }) {
ResetModal.displayName = 'ResetModal'; ResetModal.displayName = 'ResetModal';
ResetModal.propTypes = propTypes; ResetModal.propTypes = propTypes;
export default connect(mapStateToProps, mapDispatchToProps)(ResetModal); export default connect(
mapStateToProps,
mapDispatchToProps
)(ResetModal);

View File

@ -41,9 +41,11 @@ function ToolPanel({
}) { }) {
return ( return (
<Fragment> <Fragment>
<div className={`tool-panel-group ${ <div
className={`tool-panel-group ${
isMobile ? 'tool-panel-group-mobile' : '' isMobile ? 'tool-panel-group-mobile' : ''
}`}> }`}
>
<Button block={true} bsStyle='primary' onClick={executeChallenge}> <Button block={true} bsStyle='primary' onClick={executeChallenge}>
{isMobile ? 'Run' : 'Run the Tests'} {isMobile ? 'Run' : 'Run the Tests'}
</Button> </Button>
@ -92,7 +94,10 @@ function ToolPanel({
ToolPanel.displayName = 'ToolPanel'; ToolPanel.displayName = 'ToolPanel';
ToolPanel.propTypes = propTypes; ToolPanel.propTypes = propTypes;
export default connect(mapStateToProps, mapDispatchToProps)(ToolPanel); export default connect(
mapStateToProps,
mapDispatchToProps
)(ToolPanel);
/* /*
<Button <Button

View File

@ -89,7 +89,9 @@ export class ProjectForm extends Component {
} }
} }
handleSubmit(values) { handleSubmit(values) {
const { keysDown: { Control, Enter } } = this.state; const {
keysDown: { Control, Enter }
} = this.state;
if ((Control && Enter) || !Enter) { if ((Control && Enter) || !Enter) {
this.props.openModal('completion'); this.props.openModal('completion');
this.props.updateProjectForm(values); this.props.updateProjectForm(values);

View File

@ -55,35 +55,8 @@ export class ToolPanel extends Component {
ToolPanel.displayName = 'ProjectToolPanel'; ToolPanel.displayName = 'ProjectToolPanel';
ToolPanel.propTypes = propTypes; ToolPanel.propTypes = propTypes;
export default connect(mapStateToProps, mapDispatchToProps)(ToolPanel); export default connect(
mapStateToProps,
mapDispatchToProps
)(ToolPanel);
/**
*
* <Fragment>
<ProjectForm isFrontEnd={isFrontEnd} openModal={openCompletionModal} />
<ButtonSpacer />
{guideUrl && (
<Fragment>
<Button
block={true}
bsStyle='primary'
className='btn-primary-ghost btn-big'
href={guideUrl}
target='_blank'
>
Get a hint
</Button>
<ButtonSpacer />
</Fragment>
)}
<Button
block={true}
bsStyle='primary'
className='btn-primary-ghost btn-big'
onClick={openHelpModal}
>
Ask for help on the forum
</Button>
<ButtonSpacer />
</Fragment>
*/

View File

@ -1,4 +1,4 @@
import {cond, identity, stubTrue, conforms} from 'lodash'; import { cond, identity, stubTrue, conforms } from 'lodash';
const HTML$JSReg = /html|js/; const HTML$JSReg = /html|js/;

View File

@ -46,9 +46,7 @@ export const testJS$JSX = overSome(testJS, testJSX);
export const replaceNBSP = cond([ export const replaceNBSP = cond([
[ [
testHTML$JS$JSX, testHTML$JS$JSX,
partial(vinyl.transformContents, contents => partial(vinyl.transformContents, contents => contents.replace(NBSPReg, ' '))
contents.replace(NBSPReg, ' ')
)
], ],
[stubTrue, identity] [stubTrue, identity]
]); ]);

View File

@ -3,7 +3,10 @@ import { ofType } from 'redux-observable';
import { types, unlockCode } from './'; import { types, unlockCode } from './';
function codeLockEpic(action$) { function codeLockEpic(action$) {
return action$.pipe(ofType(types.updateFile), map(unlockCode)); return action$.pipe(
ofType(types.updateFile),
map(unlockCode)
);
} }
export default codeLockEpic; export default codeLockEpic;

View File

@ -7,16 +7,12 @@ function* fetchIdToNameMapSaga() {
try { try {
const { data } = yield call(getIdToNameMap); const { data } = yield call(getIdToNameMap);
yield put( yield put(fetchIdToNameMapComplete(data));
fetchIdToNameMapComplete(data)
);
} catch (e) { } catch (e) {
yield put(fetchIdToNameMapError(e)); yield put(fetchIdToNameMapError(e));
} }
} }
export function createIdToNameMapSaga(types) { export function createIdToNameMapSaga(types) {
return [ return [takeEvery(types.fetchIdToNameMap, fetchIdToNameMapSaga)];
takeEvery(types.fetchIdToNameMap, fetchIdToNameMapSaga)
];
} }

View File

@ -58,7 +58,6 @@ export function createFetchLink() {
} }
const link = await axios const link = await axios
.get({ url: href, crossDomain }) .get({ url: href, crossDomain })
.then(thing => { console.log(thing); return thing;})
.then(res => { .then(res => {
if (res.status !== 200) { if (res.status !== 200) {
throw new Error('Request error: ' + res.status); throw new Error('Request error: ' + res.status);
@ -67,7 +66,7 @@ export function createFetchLink() {
.then(({ data }) => data) .then(({ data }) => data)
.then(styles => `<style>${styles}</style>`) .then(styles => `<style>${styles}</style>`)
.catch(() => ''); .catch(() => '');
cache.set(href, link); cache.set(href, link);
return link; return link;
}; };
} }

View File

@ -13,7 +13,10 @@ const propTypes = {
}; };
function SuperBlockIntroductionPage({ data: { markdownRemark } }) { function SuperBlockIntroductionPage({ data: { markdownRemark } }) {
const { html, frontmatter: { superBlock } } = markdownRemark; const {
html,
frontmatter: { superBlock }
} = markdownRemark;
return ( return (
<Fragment> <Fragment>
<Helmet> <Helmet>

View File

@ -17,10 +17,11 @@ const arrToString = arr =>
exports.localeChallengesRootDir = getChallengesDirForLang(locale); exports.localeChallengesRootDir = getChallengesDirForLang(locale);
exports.replaceChallengeNode = exports.replaceChallengeNode = async function replaceChallengeNode(
async function replaceChallengeNode(fullFilePath) { fullFilePath
return prepareChallenge(await createChallenge(fullFilePath)); ) {
}; return prepareChallenge(await createChallenge(fullFilePath));
};
exports.buildChallenges = async function buildChallenges() { exports.buildChallenges = async function buildChallenges() {
const curriculum = await getChallengesForLang(locale); const curriculum = await getChallengesForLang(locale);

View File

@ -157,7 +157,7 @@ const stopWords = [
'in', 'in',
'into', 'into',
'it', 'it',
'it\'s', "it's",
'its', 'its',
'no', 'no',
'nor', 'nor',

View File

@ -17,7 +17,10 @@ function prototyper(str) {
if (prototypeRE.test(str)) { if (prototypeRE.test(str)) {
if (str.length > 9) { if (str.length > 9) {
return prototyper( return prototyper(
str.trim().split('prototype').join('-prototype-') str
.trim()
.split('prototype')
.join('-prototype-')
); );
} }
return str; return str;
@ -26,15 +29,13 @@ function prototyper(str) {
}) })
.join(' ') .join(' ')
.split(' '); .split(' ');
const noProto = formatted const noProto = formatted.filter(removeProto).filter(x => !!x);
.filter(removeProto)
.filter(x => !!x);
if (noProto.length === 2) { if (noProto.length === 2) {
const [ first, second ] = noProto; const [first, second] = noProto;
const secondLC = second.toLowerCase(); const secondLC = second.toLowerCase();
const finalSecond = preFormatted[secondLC] ? const finalSecond = preFormatted[secondLC]
preFormatted[secondLC] : ? preFormatted[secondLC]
secondLC; : secondLC;
return `${titleify(first)}.prototype.${finalSecond}`; return `${titleify(first)}.prototype.${finalSecond}`;
} }
if (noProto.length === 1) { if (noProto.length === 1) {
@ -43,7 +44,7 @@ function prototyper(str) {
.toLowerCase() .toLowerCase()
.split('.') .split('.')
.join('-') .join('-')
); );
} }
return titleify(str, true); return titleify(str, true);
} }
@ -62,9 +63,7 @@ function titleify(str, triedPrototyper) {
if (stopWords.some(x => x === word) && i !== 0) { if (stopWords.some(x => x === word) && i !== 0) {
return word; return word;
} }
return preFormatted[word] ? return preFormatted[word] ? preFormatted[word] : titleCase(word);
preFormatted[word] :
titleCase(word);
}) })
.join(' '); .join(' ');
} }

View File

@ -114,7 +114,7 @@ function superBlockInfoFromPath(filePath) {
} }
function superBlockInfoFromFullPath(fullFilePath) { function superBlockInfoFromFullPath(fullFilePath) {
const [,, maybeSuper] = fullFilePath.split(path.sep).reverse(); const [, , maybeSuper] = fullFilePath.split(path.sep).reverse();
return superBlockInfo(maybeSuper); return superBlockInfo(maybeSuper);
} }

View File

@ -1,13 +1,9 @@
const fs = require('fs-extra'); const fs = require('fs-extra');
const gulp = require('gulp'); const gulp = require('gulp');
const { const { locale } = require('../config/env.json');
locale
} = require('../config/env.json');
const { const { getChallengesForLang } = require('./getChallenges');
getChallengesForLang
} = require('./getChallenges');
function generateCurriculum(done) { function generateCurriculum(done) {
return getChallengesForLang(locale) return getChallengesForLang(locale)

View File

@ -8,9 +8,7 @@ const _ = require('lodash');
const createDebugger = require('debug'); const createDebugger = require('debug');
const utils = require('../server/utils'); const utils = require('../server/utils');
const getChallenges = require('./getChallenges'); const getChallenges = require('./getChallenges');
const { validateChallenge } = require( const { validateChallenge } = require('./schema/challengeSchema');
'./schema/challengeSchema'
);
const app = require('../server/server'); const app = require('../server/server');
const log = createDebugger('fcc:seed'); const log = createDebugger('fcc:seed');
@ -23,10 +21,14 @@ const nameify = utils.nameify;
const Observable = Rx.Observable; const Observable = Rx.Observable;
const Challenge = app.models.Challenge; const Challenge = app.models.Challenge;
const destroyChallenges = const destroyChallenges = Observable.fromNodeCallback(
Observable.fromNodeCallback(Challenge.destroyAll, Challenge); Challenge.destroyAll,
const createChallenges = Challenge
Observable.fromNodeCallback(Challenge.create, Challenge); );
const createChallenges = Observable.fromNodeCallback(
Challenge.create,
Challenge
);
const Block = app.models.Block; const Block = app.models.Block;
const destroyBlocks = Observable.fromNodeCallback(Block.destroyAll, Block); const destroyBlocks = Observable.fromNodeCallback(Block.destroyAll, Block);
@ -34,12 +36,11 @@ const createBlocks = Observable.fromNodeCallback(Block.create, Block);
const arrToString = arr => const arrToString = arr =>
Array.isArray(arr) ? arr.join('\n') : _.toString(arr); Array.isArray(arr) ? arr.join('\n') : _.toString(arr);
Observable.combineLatest( Observable.combineLatest(destroyChallenges(), destroyBlocks())
destroyChallenges(),
destroyBlocks()
)
.last() .last()
.flatMap(function() { return Observable.from(getChallenges()); }) .flatMap(function() {
return Observable.from(getChallenges());
})
.flatMap(function(challengeSpec) { .flatMap(function(challengeSpec) {
const order = challengeSpec.order; const order = challengeSpec.order;
const blockName = challengeSpec.name; const blockName = challengeSpec.name;
@ -80,23 +81,24 @@ Observable.combineLatest(
.map(block => { .map(block => {
log('successfully created %s block', block.name); log('successfully created %s block', block.name);
return challengeSpec.challenges return challengeSpec.challenges.map(function(challenge, index) {
.map(function(challenge, index) { challenge.name = nameify(challenge.title);
challenge.name = nameify(challenge.title);
challenge.dashedName = dasherize(challenge.name); challenge.dashedName = dasherize(challenge.name);
challenge.checksum = adler32.sum( challenge.checksum = adler32.sum(
Buffer( Buffer(
challenge.title + challenge.title +
JSON.stringify(challenge.description) + JSON.stringify(challenge.description) +
JSON.stringify(challenge.challengeSeed) + JSON.stringify(challenge.challengeSeed) +
JSON.stringify(challenge.tests) JSON.stringify(challenge.tests)
) )
); );
if (challenge.files) { if (challenge.files) {
challenge.files = _.reduce(challenge.files, (map, file) => { challenge.files = _.reduce(
challenge.files,
(map, file) => {
map[file.key] = { map[file.key] = {
...file, ...file,
head: arrToString(file.head), head: arrToString(file.head),
@ -104,46 +106,45 @@ Observable.combineLatest(
tail: arrToString(file.tail) tail: arrToString(file.tail)
}; };
return map; return map;
}, {}); },
} {}
challenge.fileName = fileName;
challenge.helpRoom = helpRoom;
challenge.order = order;
challenge.suborder = index + 1;
challenge.block = dasherize(blockName);
challenge.blockId = '' + block.id;
challenge.isBeta = challenge.isBeta || isBeta;
challenge.isComingSoon = challenge.isComingSoon || isComingSoon;
challenge.isLocked = challenge.isLocked || isLocked;
challenge.isPrivate = challenge.isPrivate || isPrivate;
challenge.time = challengeSpec.time;
challenge.superOrder = superOrder;
challenge.superBlock = superBlock
.split('-')
.map(function(word) {
return _.capitalize(word);
})
.join(' ');
challenge.required = (challenge.required || []).concat(required);
challenge.template = challenge.template || template;
return _.omit(
challenge,
[
'betaSolutions',
'betaTests',
'hints',
'MDNlinks',
'null',
'rawSolutions',
'react',
'reactRedux',
'redux',
'releasedOn',
'translations',
'type'
]
); );
}); }
challenge.fileName = fileName;
challenge.helpRoom = helpRoom;
challenge.order = order;
challenge.suborder = index + 1;
challenge.block = dasherize(blockName);
challenge.blockId = '' + block.id;
challenge.isBeta = challenge.isBeta || isBeta;
challenge.isComingSoon = challenge.isComingSoon || isComingSoon;
challenge.isLocked = challenge.isLocked || isLocked;
challenge.isPrivate = challenge.isPrivate || isPrivate;
challenge.time = challengeSpec.time;
challenge.superOrder = superOrder;
challenge.superBlock = superBlock
.split('-')
.map(function(word) {
return _.capitalize(word);
})
.join(' ');
challenge.required = (challenge.required || []).concat(required);
challenge.template = challenge.template || template;
return _.omit(challenge, [
'betaSolutions',
'betaTests',
'hints',
'MDNlinks',
'null',
'rawSolutions',
'react',
'reactRedux',
'redux',
'releasedOn',
'translations',
'type'
]);
});
}) })
.flatMap(challenges => { .flatMap(challenges => {
challenges.forEach(challenge => { challenges.forEach(challenge => {
@ -160,7 +161,9 @@ Observable.combineLatest(
function(challenges) { function(challenges) {
log('%s successfully saved', challenges[0].block); log('%s successfully saved', challenges[0].block);
}, },
function(err) { throw err; }, function(err) {
throw err;
},
function() { function() {
log('challenge seed completed'); log('challenge seed completed');
process.exit(0); process.exit(0);

View File

@ -55,11 +55,7 @@ async function translateChallenge(file) {
]; ];
return Promise.all(translatePromises).then( return Promise.all(translatePromises).then(
([title, description, instructions, ...tests]) => { ([title, description, instructions, ...tests]) => {
const { const { files = {}, solutions = [], ...challengeMeta } = challenge;
files = {},
solutions = [],
...challengeMeta
} = challenge;
const md = `--- const md = `---
${YAML.dump( ${YAML.dump(
Object.assign(challengeMeta, { Object.assign(challengeMeta, {

View File

@ -20,7 +20,7 @@ class ChallengeTitles {
The title ${title} is already assigned The title ${title} is already assigned
`); `);
} }
this.knownTitles = [ ...this.knownTitles, titleToCheck ]; this.knownTitles = [...this.knownTitles, titleToCheck];
} }
} }

View File

@ -17,7 +17,7 @@ class MongoIds {
The id for ${title} is already assigned The id for ${title} is already assigned
`); `);
} }
this.knownIds = [ ...this.knownIds, id ]; this.knownIds = [...this.knownIds, id];
} }
} }

883
package-lock.json generated
View File

@ -1155,6 +1155,12 @@
"json-schema-traverse": "^0.3.0" "json-schema-traverse": "^0.3.0"
} }
}, },
"ajv-keywords": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz",
"integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=",
"dev": true
},
"ansi-escapes": { "ansi-escapes": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
@ -1837,6 +1843,12 @@
"integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==", "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==",
"dev": true "dev": true
}, },
"boolify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/boolify/-/boolify-1.0.1.tgz",
"integrity": "sha1-tcCeF8rNET0Rt7s+04TMASmU2Gs=",
"dev": true
},
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -2256,6 +2268,12 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"common-tags": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
"integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==",
"dev": true
},
"compare-func": { "compare-func": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz",
@ -2801,6 +2819,12 @@
"path-type": "^3.0.0" "path-type": "^3.0.0"
} }
}, },
"dlv": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.2.tgz",
"integrity": "sha512-xxD4VSH67GbRvSGUrckvha94RD7hjgOH7rqGxiytLpkaeMvixOHFZTGFK6EkIm3T761OVHT8ABHmGkq9gXgu6Q==",
"dev": true
},
"doctrine": { "doctrine": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@ -5489,6 +5513,12 @@
"has": "^1.0.1" "has": "^1.0.1"
} }
}, },
"is-resolvable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz",
"integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
"dev": true
},
"is-ssh": { "is-ssh": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz", "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz",
@ -7097,6 +7127,18 @@
"integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=", "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=",
"dev": true "dev": true
}, },
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"lodash.merge": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz",
"integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==",
"dev": true
},
"lodash.set": { "lodash.set": {
"version": "4.3.2", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
@ -7128,6 +7170,12 @@
"lodash._reinterpolate": "~3.0.0" "lodash._reinterpolate": "~3.0.0"
} }
}, },
"lodash.unescape": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
"integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=",
"dev": true
},
"lodash.uniq": { "lodash.uniq": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@ -7143,6 +7191,58 @@
"chalk": "^2.0.1" "chalk": "^2.0.1"
} }
}, },
"loglevel": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
"integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=",
"dev": true
},
"loglevel-colored-level-prefix": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz",
"integrity": "sha1-akAhj9x64V/HbD0PPmdsRlOIYD4=",
"dev": true,
"requires": {
"chalk": "^1.1.3",
"loglevel": "^1.4.1"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
"dev": true
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
}
}
},
"loose-envify": { "loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -7214,6 +7314,24 @@
"ssri": "^6.0.0" "ssri": "^6.0.0"
} }
}, },
"make-plural": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz",
"integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true,
"optional": true
}
}
},
"makeerror": { "makeerror": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
@ -7322,6 +7440,41 @@
"integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==",
"dev": true "dev": true
}, },
"messageformat": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/messageformat/-/messageformat-1.1.1.tgz",
"integrity": "sha512-Q0uXcDtF5pEZsVSyhzDOGgZZK6ykN79VY9CwU3Nv0gsqx62BjdJW0MT+63UkHQ4exe3HE33ZlxR2/YwoJarRTg==",
"dev": true,
"requires": {
"glob": "~7.0.6",
"make-plural": "^4.1.1",
"messageformat-parser": "^1.1.0",
"nopt": "~3.0.6",
"reserved-words": "^0.1.2"
},
"dependencies": {
"glob": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz",
"integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.2",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
}
},
"messageformat-parser": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-1.1.0.tgz",
"integrity": "sha512-Hwem6G3MsKDLS1FtBRGIs8T50P1Q00r3srS6QJePCFbad9fq0nYxwf3rnU2BreApRGhmpKMV7oZI06Sy1c9TPA==",
"dev": true
},
"micromatch": { "micromatch": {
"version": "3.1.10", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@ -8433,6 +8586,569 @@
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
"dev": true "dev": true
}, },
"prettier": {
"version": "1.16.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz",
"integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==",
"dev": true
},
"prettier-eslint": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-8.8.2.tgz",
"integrity": "sha512-2UzApPuxi2yRoyMlXMazgR6UcH9DKJhNgCviIwY3ixZ9THWSSrUww5vkiZ3C48WvpFl1M1y/oU63deSy1puWEA==",
"dev": true,
"requires": {
"babel-runtime": "^6.26.0",
"common-tags": "^1.4.0",
"dlv": "^1.1.0",
"eslint": "^4.0.0",
"indent-string": "^3.2.0",
"lodash.merge": "^4.6.0",
"loglevel-colored-level-prefix": "^1.0.0",
"prettier": "^1.7.0",
"pretty-format": "^23.0.1",
"require-relative": "^0.8.7",
"typescript": "^2.5.1",
"typescript-eslint-parser": "^16.0.0",
"vue-eslint-parser": "^2.0.2"
},
"dependencies": {
"acorn-jsx": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
"integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
"dev": true,
"requires": {
"acorn": "^3.0.4"
},
"dependencies": {
"acorn": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
"integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
"dev": true
}
}
},
"chardet": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
"dev": true
},
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"eslint": {
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz",
"integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==",
"dev": true,
"requires": {
"ajv": "^5.3.0",
"babel-code-frame": "^6.22.0",
"chalk": "^2.1.0",
"concat-stream": "^1.6.0",
"cross-spawn": "^5.1.0",
"debug": "^3.1.0",
"doctrine": "^2.1.0",
"eslint-scope": "^3.7.1",
"eslint-visitor-keys": "^1.0.0",
"espree": "^3.5.4",
"esquery": "^1.0.0",
"esutils": "^2.0.2",
"file-entry-cache": "^2.0.0",
"functional-red-black-tree": "^1.0.1",
"glob": "^7.1.2",
"globals": "^11.0.1",
"ignore": "^3.3.3",
"imurmurhash": "^0.1.4",
"inquirer": "^3.0.6",
"is-resolvable": "^1.0.0",
"js-yaml": "^3.9.1",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.3.0",
"lodash": "^4.17.4",
"minimatch": "^3.0.2",
"mkdirp": "^0.5.1",
"natural-compare": "^1.4.0",
"optionator": "^0.8.2",
"path-is-inside": "^1.0.2",
"pluralize": "^7.0.0",
"progress": "^2.0.0",
"regexpp": "^1.0.1",
"require-uncached": "^1.0.3",
"semver": "^5.3.0",
"strip-ansi": "^4.0.0",
"strip-json-comments": "~2.0.1",
"table": "4.0.2",
"text-table": "~0.2.0"
}
},
"eslint-scope": {
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz",
"integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
"estraverse": "^4.1.1"
}
},
"espree": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
"integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==",
"dev": true,
"requires": {
"acorn": "^5.5.0",
"acorn-jsx": "^3.0.0"
}
},
"external-editor": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
"integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
"dev": true,
"requires": {
"chardet": "^0.4.0",
"iconv-lite": "^0.4.17",
"tmp": "^0.0.33"
}
},
"inquirer": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
"integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==",
"dev": true,
"requires": {
"ansi-escapes": "^3.0.0",
"chalk": "^2.0.0",
"cli-cursor": "^2.1.0",
"cli-width": "^2.0.0",
"external-editor": "^2.0.4",
"figures": "^2.0.0",
"lodash": "^4.3.0",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rx-lite": "^4.0.8",
"rx-lite-aggregates": "^4.0.8",
"string-width": "^2.1.0",
"strip-ansi": "^4.0.0",
"through": "^2.3.6"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
},
"regexpp": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz",
"integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==",
"dev": true
},
"slice-ansi": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
"integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0"
}
},
"table": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
"integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==",
"dev": true,
"requires": {
"ajv": "^5.2.3",
"ajv-keywords": "^2.1.0",
"chalk": "^2.1.0",
"lodash": "^4.17.4",
"slice-ansi": "1.0.0",
"string-width": "^2.1.1"
}
}
}
},
"prettier-eslint-cli": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/prettier-eslint-cli/-/prettier-eslint-cli-4.7.1.tgz",
"integrity": "sha512-hQbsGaEVz97oBBcKdsJ46khv0kOGkMyWrXzcFOXW6X8UuetZ/j0yDJkNJgUTVc6PVFbbzBXk+qgd5vos9qzXPQ==",
"dev": true,
"requires": {
"arrify": "^1.0.1",
"babel-runtime": "^6.23.0",
"boolify": "^1.0.0",
"camelcase-keys": "^4.1.0",
"chalk": "2.3.0",
"common-tags": "^1.4.0",
"eslint": "^4.5.0",
"find-up": "^2.1.0",
"get-stdin": "^5.0.1",
"glob": "^7.1.1",
"ignore": "^3.2.7",
"indent-string": "^3.1.0",
"lodash.memoize": "^4.1.2",
"loglevel-colored-level-prefix": "^1.0.0",
"messageformat": "^1.0.2",
"prettier-eslint": "^8.5.0",
"rxjs": "^5.3.0",
"yargs": "10.0.3"
},
"dependencies": {
"acorn-jsx": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
"integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
"dev": true,
"requires": {
"acorn": "^3.0.4"
},
"dependencies": {
"acorn": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
"integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
"dev": true
}
}
},
"chalk": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
"integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==",
"dev": true,
"requires": {
"ansi-styles": "^3.1.0",
"escape-string-regexp": "^1.0.5",
"supports-color": "^4.0.0"
}
},
"chardet": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
"dev": true
},
"cliui": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
"dev": true,
"requires": {
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wrap-ansi": "^2.0.0"
},
"dependencies": {
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "^1.0.0"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
}
}
},
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"eslint": {
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz",
"integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==",
"dev": true,
"requires": {
"ajv": "^5.3.0",
"babel-code-frame": "^6.22.0",
"chalk": "^2.1.0",
"concat-stream": "^1.6.0",
"cross-spawn": "^5.1.0",
"debug": "^3.1.0",
"doctrine": "^2.1.0",
"eslint-scope": "^3.7.1",
"eslint-visitor-keys": "^1.0.0",
"espree": "^3.5.4",
"esquery": "^1.0.0",
"esutils": "^2.0.2",
"file-entry-cache": "^2.0.0",
"functional-red-black-tree": "^1.0.1",
"glob": "^7.1.2",
"globals": "^11.0.1",
"ignore": "^3.3.3",
"imurmurhash": "^0.1.4",
"inquirer": "^3.0.6",
"is-resolvable": "^1.0.0",
"js-yaml": "^3.9.1",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.3.0",
"lodash": "^4.17.4",
"minimatch": "^3.0.2",
"mkdirp": "^0.5.1",
"natural-compare": "^1.4.0",
"optionator": "^0.8.2",
"path-is-inside": "^1.0.2",
"pluralize": "^7.0.0",
"progress": "^2.0.0",
"regexpp": "^1.0.1",
"require-uncached": "^1.0.3",
"semver": "^5.3.0",
"strip-ansi": "^4.0.0",
"strip-json-comments": "~2.0.1",
"table": "4.0.2",
"text-table": "~0.2.0"
}
},
"eslint-scope": {
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz",
"integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
"estraverse": "^4.1.1"
}
},
"espree": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
"integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==",
"dev": true,
"requires": {
"acorn": "^5.5.0",
"acorn-jsx": "^3.0.0"
}
},
"execa": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
"integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
"dev": true,
"requires": {
"cross-spawn": "^5.0.1",
"get-stream": "^3.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"strip-eof": "^1.0.0"
}
},
"external-editor": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
"integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
"dev": true,
"requires": {
"chardet": "^0.4.0",
"iconv-lite": "^0.4.17",
"tmp": "^0.0.33"
}
},
"find-up": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
"dev": true,
"requires": {
"locate-path": "^2.0.0"
}
},
"get-stdin": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",
"integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=",
"dev": true
},
"get-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true
},
"has-flag": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
"integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
"dev": true
},
"inquirer": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
"integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==",
"dev": true,
"requires": {
"ansi-escapes": "^3.0.0",
"chalk": "^2.0.0",
"cli-cursor": "^2.1.0",
"cli-width": "^2.0.0",
"external-editor": "^2.0.4",
"figures": "^2.0.0",
"lodash": "^4.3.0",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rx-lite": "^4.0.8",
"rx-lite-aggregates": "^4.0.8",
"string-width": "^2.1.0",
"strip-ansi": "^4.0.0",
"through": "^2.3.6"
}
},
"invert-kv": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
"dev": true
},
"lcid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
"integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
"dev": true,
"requires": {
"invert-kv": "^1.0.0"
}
},
"mem": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
"integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
"dev": true,
"requires": {
"mimic-fn": "^1.0.0"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
},
"os-locale": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
"integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
"dev": true,
"requires": {
"execa": "^0.7.0",
"lcid": "^1.0.0",
"mem": "^1.1.0"
}
},
"regexpp": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz",
"integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==",
"dev": true
},
"slice-ansi": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
"integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0"
}
},
"supports-color": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
"integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
"dev": true,
"requires": {
"has-flag": "^2.0.0"
}
},
"table": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
"integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==",
"dev": true,
"requires": {
"ajv": "^5.2.3",
"ajv-keywords": "^2.1.0",
"chalk": "^2.1.0",
"lodash": "^4.17.4",
"slice-ansi": "1.0.0",
"string-width": "^2.1.1"
}
},
"y18n": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
"dev": true
},
"yargs": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-10.0.3.tgz",
"integrity": "sha512-DqBpQ8NAUX4GyPP/ijDGHsJya4tYqLQrjPr95HNsr1YwL3+daCfvBwg7+gIC6IdJhR2kATh3hb61vjzMWEtjdw==",
"dev": true,
"requires": {
"cliui": "^3.2.0",
"decamelize": "^1.1.1",
"find-up": "^2.1.0",
"get-caller-file": "^1.0.1",
"os-locale": "^2.0.0",
"require-directory": "^2.1.1",
"require-main-filename": "^1.0.1",
"set-blocking": "^2.0.0",
"string-width": "^2.0.0",
"which-module": "^2.0.0",
"y18n": "^3.2.1",
"yargs-parser": "^8.0.0"
}
},
"yargs-parser": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz",
"integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==",
"dev": true,
"requires": {
"camelcase": "^4.1.0"
}
}
}
},
"pretty-format": { "pretty-format": {
"version": "23.6.0", "version": "23.6.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz",
@ -8965,6 +9681,51 @@
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
"dev": true "dev": true
}, },
"require-relative": {
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
"integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
"dev": true
},
"require-uncached": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
"integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
"dev": true,
"requires": {
"caller-path": "^0.1.0",
"resolve-from": "^1.0.0"
},
"dependencies": {
"caller-path": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
"integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
"dev": true,
"requires": {
"callsites": "^0.2.0"
}
},
"callsites": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
"integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
"dev": true
},
"resolve-from": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
"integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
"dev": true
}
}
},
"reserved-words": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz",
"integrity": "sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=",
"dev": true
},
"resolve": { "resolve": {
"version": "1.10.0", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
@ -9058,6 +9819,30 @@
"aproba": "^1.1.1" "aproba": "^1.1.1"
} }
}, },
"rx-lite": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
"integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
"dev": true
},
"rx-lite-aggregates": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz",
"integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=",
"dev": true,
"requires": {
"rx-lite": "*"
}
},
"rxjs": {
"version": "5.5.12",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz",
"integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==",
"dev": true,
"requires": {
"symbol-observable": "1.0.1"
}
},
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@ -9689,6 +10474,12 @@
"has-flag": "^3.0.0" "has-flag": "^3.0.0"
} }
}, },
"symbol-observable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
"integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=",
"dev": true
},
"symbol-tree": { "symbol-tree": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
@ -10109,6 +10900,30 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true "dev": true
}, },
"typescript": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
"dev": true
},
"typescript-eslint-parser": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-16.0.1.tgz",
"integrity": "sha512-IKawLTu4A2xN3aN/cPLxvZ0bhxZHILGDKTZWvWNJ3sLNhJ3PjfMEDQmR2VMpdRPrmWOadgWXRwjLBzSA8AGsaQ==",
"dev": true,
"requires": {
"lodash.unescape": "4.0.1",
"semver": "5.5.0"
},
"dependencies": {
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
"dev": true
}
}
},
"uglify-js": { "uglify-js": {
"version": "3.4.9", "version": "3.4.9",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
@ -10336,6 +11151,74 @@
"extsprintf": "^1.2.0" "extsprintf": "^1.2.0"
} }
}, },
"vue-eslint-parser": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz",
"integrity": "sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw==",
"dev": true,
"requires": {
"debug": "^3.1.0",
"eslint-scope": "^3.7.1",
"eslint-visitor-keys": "^1.0.0",
"espree": "^3.5.2",
"esquery": "^1.0.0",
"lodash": "^4.17.4"
},
"dependencies": {
"acorn-jsx": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
"integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
"dev": true,
"requires": {
"acorn": "^3.0.4"
},
"dependencies": {
"acorn": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
"integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
"dev": true
}
}
},
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"eslint-scope": {
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz",
"integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
"estraverse": "^4.1.1"
}
},
"espree": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
"integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==",
"dev": true,
"requires": {
"acorn": "^5.5.0",
"acorn-jsx": "^3.0.0"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
}
}
},
"w3c-hr-time": { "w3c-hr-time": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",

View File

@ -8,6 +8,7 @@
"develop:client": "cd ./client && npm run develop", "develop:client": "cd ./client && npm run develop",
"develop:server": "cd ./api-server && node development-entry.js", "develop:server": "cd ./api-server && node development-entry.js",
"ensure-env": "cross-env DEBUG=fcc:* node ./tools/scripts/ensure-env.js", "ensure-env": "cross-env DEBUG=fcc:* node ./tools/scripts/ensure-env.js",
"format": "prettier-eslint --trailing-comma none --semi --eslint-ignore --ignore curriculum/challenges --ignore guide --ignore mock-guide --write './**/*.js'",
"lint": "eslint .", "lint": "eslint .",
"seed": "npm-run-all -p seed:*", "seed": "npm-run-all -p seed:*",
"seed:auth-user": "cross-env DEBUG=fcc:* node ./tools/scripts/seed/seedAuthUser", "seed:auth-user": "cross-env DEBUG=fcc:* node ./tools/scripts/seed/seedAuthUser",
@ -39,6 +40,7 @@
"lodash": "^4.17.11", "lodash": "^4.17.11",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"ora": "^3.0.0", "ora": "^3.0.0",
"prettier-eslint-cli": "^4.7.1",
"readdirp-walk": "^1.6.0", "readdirp-walk": "^1.6.0",
"shortid": "^2.2.14", "shortid": "^2.2.14",
"slugg": "^1.2.1" "slugg": "^1.2.1"

View File

@ -36,9 +36,7 @@ describe('createRedirects', () => {
const { api, forum } = testLocations; const { api, forum } = testLocations;
expect(redirects.includes(`${api}/internal/:splat`)).toBe(true); expect(redirects.includes(`${api}/internal/:splat`)).toBe(true);
expect( expect(
redirects.includes( redirects.includes(`${forum}/t/free-code-camp-privacy-policy/19545 301`)
`${forum}/t/free-code-camp-privacy-policy/19545 301`
)
).toBe(true); ).toBe(true);
expect(redirects.includes(`${forum}`)).toBe(true); expect(redirects.includes(`${forum}`)).toBe(true);
}); });

View File

@ -22,80 +22,77 @@ function handleError(err, client) {
} }
} }
MongoClient.connect( MongoClient.connect(MONGOHQ_URL, { useNewUrlParser: true }, function(
MONGOHQ_URL, err,
{ useNewUrlParser: true }, client
function(err, client) { ) {
handleError(err, client);
log('Connected successfully to mongo');
const db = client.db('freecodecamp');
const user = db.collection('user');
user.deleteOne({ _id: ObjectId('5bd30e0f1caf6ac3ddddddb5') }, err => {
handleError(err, client); handleError(err, client);
log('Connected successfully to mongo'); try {
user.insertOne({
const db = client.db('freecodecamp'); _id: ObjectId('5bd30e0f1caf6ac3ddddddb5'),
const user = db.collection('user'); email: 'foo@bar.com',
emailVerified: true,
user.deleteOne({_id: ObjectId('5bd30e0f1caf6ac3ddddddb5') }, (err) => { progressTimestamps: [],
handleError(err, client); isBanned: false,
isCheater: false,
try { username: 'DevelopmentUser',
user.insertOne( about: '',
{ name: 'Development User',
_id: ObjectId('5bd30e0f1caf6ac3ddddddb5'), location: '',
email: 'foo@bar.com', picture: 'https://identicon.org/?t=dev&s=256',
emailVerified: true, acceptedPrivacyTerms: true,
progressTimestamps: [], sendQuincyEmail: false,
isBanned: false, currentChallengeId: '',
isCheater: false, isHonest: false,
username: 'DevelopmentUser', isFrontEndCert: false,
about: '', isDataVisCert: false,
name: 'Development User', isBackEndCert: false,
location: '', isFullStackCert: false,
picture: 'https://identicon.org/?t=dev&s=256', isRespWebDesignCert: false,
acceptedPrivacyTerms: true, is2018DataVisCert: false,
sendQuincyEmail: false, isFrontEndLibsCert: false,
currentChallengeId: '', isJsAlgoDataStructCert: false,
isHonest: false, isApisMicroservicesCert: false,
isFrontEndCert: false, isInfosecQaCert: false,
isDataVisCert: false, is2018FullStackCert: false,
isBackEndCert: false, completedChallenges: [],
isFullStackCert: false, portfolio: [],
isRespWebDesignCert: false, yearsTopContributor: [],
is2018DataVisCert: false, rand: 0.6126749173148205,
isFrontEndLibsCert: false, theme: 'default',
isJsAlgoDataStructCert: false, profileUI: {
isApisMicroservicesCert: false, isLocked: true,
isInfosecQaCert: false, showAbout: false,
is2018FullStackCert: false, showCerts: false,
completedChallenges: [], showDonation: false,
portfolio: [], showHeatMap: false,
yearsTopContributor: [], showLocation: false,
rand: 0.6126749173148205, showName: false,
theme: 'default', showPoints: false,
profileUI: { showPortfolio: false,
isLocked: true, showTimeLine: false
showAbout: false, },
showCerts: false, badges: {
showDonation: false, coreTeam: []
showHeatMap: false, },
showLocation: false, isDonating: false,
showName: false, emailAuthLinkTTL: null,
showPoints: false, emailVerifyTTL: null
showPortfolio: false, });
showTimeLine: false } catch (e) {
}, handleError(e, client);
badges: { } finally {
coreTeam: [] log('local auth user seed complete');
}, client.close();
isDonating: false, }
emailAuthLinkTTL: null, });
emailVerifyTTL: null });
}
);
} catch (e) {
handleError(e, client);
} finally {
log('local auth user seed complete');
client.close();
}
});
}
);

View File

@ -26,66 +26,61 @@ function handleError(err, client) {
} }
} }
MongoClient.connect( MongoClient.connect(MONGOHQ_URL, { useNewUrlParser: true }, function(
MONGOHQ_URL, err,
{ useNewUrlParser: true }, client
function(err, client) { ) {
handleError(err, client);
log('Connected successfully to mongo at %s', MONGOHQ_URL);
const db = client.db('freecodecamp');
const challengeCollection = db.collection('challenge');
challengeCollection.deleteMany({}, async err => {
handleError(err, client); handleError(err, client);
log('Connected successfully to mongo at %s', MONGOHQ_URL); log('deleted all the challenges');
const db = client.db('freecodecamp'); const curriculum = await getChallengesForLang(lang);
const challengeCollection = db.collection('challenge');
challengeCollection.deleteMany({}, async err => { const allChallenges = Object.keys(curriculum)
handleError(err, client); .map(key => curriculum[key].blocks)
.reduce((challengeArray, superBlock) => {
log('deleted all the challenges'); const challengesForBlock = Object.keys(superBlock).map(
key => superBlock[key].challenges
const curriculum = await getChallengesForLang(lang);
const allChallenges = Object.keys(curriculum)
.map(key => curriculum[key].blocks)
.reduce((challengeArray, superBlock) => {
const challengesForBlock = Object.keys(superBlock).map(
key => superBlock[key].challenges
);
return [...challengeArray, ...flatten(challengesForBlock)];
}, [])
.map(challenge => {
const currentId = challenge.id.slice(0);
challenge._id = ObjectID(currentId);
delete challenge.id;
return challenge;
});
try {
challengeCollection.insertMany(
allChallenges,
{ ordered: false },
err => {
handleError(err, client);
log('challenge seed complete');
client.close();
}
); );
} catch (e) { return [...challengeArray, ...flatten(challengesForBlock)];
handleError(e, client); }, [])
} finally { .map(challenge => {
log('generating path migration map'); const currentId = challenge.id.slice(0);
const pathMap = createPathMigrationMap(curriculum); challenge._id = ObjectID(currentId);
const outputDir = path.resolve( delete challenge.id;
__dirname, return challenge;
'../../../api-server/server/resources/pathMigration.json' });
);
fs.writeFile(outputDir, JSON.stringify(pathMap), err => { try {
if (err) { challengeCollection.insertMany(allChallenges, { ordered: false }, err => {
console.error('Oh noes!!'); handleError(err, client);
console.error(err); log('challenge seed complete');
} client.close();
log('path migration map generated'); });
}); } catch (e) {
} handleError(e, client);
}); } finally {
} log('generating path migration map');
); const pathMap = createPathMigrationMap(curriculum);
const outputDir = path.resolve(
__dirname,
'../../../api-server/server/resources/pathMigration.json'
);
fs.writeFile(outputDir, JSON.stringify(pathMap), err => {
if (err) {
console.error('Oh noes!!');
console.error(err);
}
log('path migration map generated');
});
}
});
});