From 5eb90ed8c87b02f0773361c3d048fc146ca2cef8 Mon Sep 17 00:00:00 2001 From: Stuart Taylor Date: Mon, 14 May 2018 08:34:51 +0100 Subject: [PATCH] feat(challenge-redirect): Make '/challenge' and '/map' redirect to learn (#17144) * feat(challenge-redirect): Make '/challenge' and '/map' redirect to learn * fix(linting): Generate pathMigrationMap on 'prelint-js' * fix(script): fix "only once" script * fix(lock): Fix lock file --- .gitignore | 1 + gulpfile.js | 14 ++++++-- package-lock.json | 16 ++++++--- package.json | 4 +-- seed/createPathMigrationMap.js | 60 ++++++++++++++++++++++++++++++++++ server/boot/challenge.js | 26 +++++++++++++-- server/boot/react.js | 3 -- 7 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 seed/createPathMigrationMap.js diff --git a/.gitignore b/.gitignore index 857f24aa5d..0c969a31df 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ seed/unpacked server/rev-manifest.json server/manifests/* !server/manifests/README.md +server/resources/pathMigration.json public/js/main* public/js/commonFramework* diff --git a/gulpfile.js b/gulpfile.js index ef69840475..df0356d215 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -38,7 +38,9 @@ const Rx = require('rx'), // rev rev = require('gulp-rev'), - revDel = require('rev-del'); + revDel = require('rev-del'), + + { createPathMigrationMap } = require('./seed/createPathMigrationMap'); Rx.config.longStackSupport = true; const sync = browserSync.create('fcc-sync-server'); @@ -395,6 +397,10 @@ gulp.task('build-manifest', buildDependents, function() { .pipe(gulp.dest('server/')); }); +gulp.task('generate-migration-map', done => { + createPathMigrationMap().then(done); +}); + gulp.task('build', [ 'less', 'js', @@ -402,7 +408,8 @@ gulp.task('build', [ 'pack-frame-runner', 'move-webpack-manifest', 'clean-webpack-manifest', - 'build-manifest' + 'build-manifest', + 'generate-migration-map' ]); const watchDependents = [ @@ -424,5 +431,6 @@ gulp.task('default', [ 'serve', 'watch', 'dev-server', - 'pack-frame-runner' + 'pack-frame-runner', + 'generate-migration-map' ]); diff --git a/package-lock.json b/package-lock.json index 6575427aee..a7fe759ca3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11364,17 +11364,23 @@ } }, "loopback-component-passport": { - "version": "git+https://github.com/freeCodeCamp/loopback-component-passport.git#e158f6bbd631e00e0194515ae25b9971c58a1121", + "version": "git+https://github.com/freeCodeCamp/loopback-component-passport.git#6097e69ec12ecfb306c8c83aa6af66f7e25fbb95", "requires": { "passport": "0.4.0", "strong-globalize": "2.10.0", - "underscore": "1.8.3" + "underscore": "1.9.0", + "uuid": "3.2.1" }, "dependencies": { "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.0.tgz", + "integrity": "sha512-4IV1DSSxC1QK48j9ONFK1MoIAKKkbE8i7u55w2R6IqBqbT7A/iG7aZBCR2Bi8piF0Uz+i/MG1aeqLwl/5vqF+A==" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" } } }, diff --git a/package.json b/package.json index 9408ec43d2..3f0c743c25 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "lint-utils": "jsonlint-cli server/utils/*.json", "lint-js": "eslint --ext=.js,.jsx gulpfile.js server/ common/ config/ client/", "lint-json": "npm run lint-server && npm run lint-challenges && npm run lint-resources && npm run lint-utils", - "only-once": "npm run create-rev && echo '/****/' && echo 'Seeding Database' && echo '/****/' && node seed && echo '/****/' && echo 'Seeding Completed' && echo '/****/'", - "prelint-js": "npm run create-rev", + "only-once": "npm run prelint-js && echo '/****/' && echo 'Seeding Database' && echo '/****/' && node seed && echo '/****/' && echo 'Seeding Completed' && echo '/****/'", + "prelint-js": "npm run create-rev && gulp generate-migration-map", "pretest": "npm run create-rev && npm run lint", "prestart-production": "gulp build -p", "seed": "node seed", diff --git a/seed/createPathMigrationMap.js b/seed/createPathMigrationMap.js new file mode 100644 index 0000000000..b904063093 --- /dev/null +++ b/seed/createPathMigrationMap.js @@ -0,0 +1,60 @@ +require('babel-register'); + +const fs = require('fs'); +const path = require('path'); +const { Observable } = require('rx'); +const getChallenges = require('./getChallenges'); +const { dasherize } = require('../server/utils'); + +let pathMap = {}; + +function createPathMigrationMap() { + return new Promise(resolve => { + Observable.of(getChallenges()) +.map(blocks => { + blocks.forEach(block => { + const {name: blockName, superBlock, challenges } = block; + if (!(dasherize(superBlock) in pathMap)) { + pathMap[dasherize(superBlock)] = {}; + } + if (!(dasherize(blockName) in pathMap[superBlock])) { + pathMap[dasherize(superBlock)][dasherize(blockName)] = challenges + .map(({ title, challengeType }) => ({ + dashedName: dasherize(title), + challengeType + })); + } + }); +}) + .subscribe(() => {}, console.error, () => { + const migMap = Object.keys(pathMap) + .filter(key => !key.includes('certificate')) + .map(superBlock => { + return Object.keys(pathMap[superBlock]) + .map(block => { + return pathMap[superBlock][block] + .reduce((map, {dashedName, challengeType}) => ({ + ...map, + [dashedName]: challengeType === 7 ? + `/${superBlock}/${block}` : + `/${superBlock}/${block}/${dashedName}` + }), {}); + }).reduce((acc, current) => ({ + ...acc, + ...current + }), {}); + }).reduce((acc, current) => ({ + ...acc, + ...current + }), {}); + fs.writeFileSync( + path.resolve(__dirname, '../server/resources/pathMigration.json'), + JSON.stringify(migMap, null, 2) + ); + resolve(); + }); + }); +} + + + exports.createPathMigrationMap = createPathMigrationMap; diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 57ec2410fa..536af52bcf 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -5,9 +5,14 @@ import dedent from 'dedent'; import { ifNoUserSend } from '../utils/middleware'; import { getChallengeById, cachedMap } from '../utils/map'; +import { dasherize } from '../utils'; + +import pathMigrations from '../resources/pathMigration.json'; const log = debug('fcc:boot:challenges'); +const learnURL = 'https://learn.freecodecamp.org'; + function buildUserUpdate( user, challengeId, @@ -122,6 +127,12 @@ export default function(app) { redirectToCurrentChallenge ); + router.get('/challenges', redirectToLearn); + + router.get('/challenges/*', redirectToLearn); + + router.get('/map', redirectToLearn); + app.use(api); app.use('/:lang', router); @@ -348,7 +359,7 @@ export default function(app) { const challengeId = user && user.currentChallengeId; return getChallengeById(map, challengeId) .map(challenge => { - const { block, dashedName } = challenge; + const { block, dashedName, superBlock } = challenge; if (!dashedName || !block) { // this should normally not be hit if database is properly seeded throw new Error(dedent` @@ -358,11 +369,20 @@ export default function(app) { db may not be properly seeded. `); } - return `/challenges/${block}/${dashedName}`; + return `${learnURL}/${dasherize(superBlock)}/${block}/${dashedName}`; }) .subscribe( - redirect => res.redirect(redirect || '/'), + redirect => res._oldRedirect(redirect || learnURL), next ); } + + function redirectToLearn(req, res) { + const maybeChallenge = _.last(req.path.split('/')); + if (maybeChallenge in pathMigrations) { + const redirectPath = pathMigrations[maybeChallenge]; + return res.status(302)._oldRedirect(`${learnURL}${redirectPath}`); + } + return res.status(302)._oldRedirect(learnURL); + } } diff --git a/server/boot/react.js b/server/boot/react.js index b5e2a3f422..c8d6f84cad 100644 --- a/server/boot/react.js +++ b/server/boot/react.js @@ -17,9 +17,6 @@ const isDev = process.env.NODE_ENV !== 'production'; // add routes here as they slowly get reactified // remove their individual controllers const routes = [ - '/challenges', - '/challenges/*', - '/map', '/settings', '/settings/*', '/portfolio/:username'