| 
									
										
										
										
											2016-08-01 16:54:33 -07:00
										 |  |  | import _ from 'lodash'; | 
					
						
							|  |  |  | import { Observable } from 'rx'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-25 20:16:08 +02:00
										 |  |  | import { unDasherize, nameify } from '../../../utils/slugs'; | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  | import { | 
					
						
							|  |  |  |   addNameIdMap as _addNameIdToMap, | 
					
						
							|  |  |  |   checkMapData, | 
					
						
							|  |  |  |   getFirstChallenge as _getFirstChallenge | 
					
						
							|  |  |  | } from '../../common/utils/map.js'; | 
					
						
							| 
									
										
										
										
											2016-08-01 16:54:33 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-19 01:19:40 +05:30
										 |  |  | const isDev = process.env.FREECODECAMP_NODE_ENV !== 'production'; | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  | const isBeta = !!process.env.BETA; | 
					
						
							|  |  |  | const challengesRegex = /^(bonfire|waypoint|zipline|basejump|checkpoint)/i; | 
					
						
							|  |  |  | const addNameIdMap = _.once(_addNameIdToMap); | 
					
						
							|  |  |  | const getFirstChallenge = _.once(_getFirstChallenge); | 
					
						
							| 
									
										
										
										
											2016-08-01 16:54:33 -07:00
										 |  |  | /* | 
					
						
							|  |  |  |  * interface ChallengeMap { | 
					
						
							| 
									
										
										
										
											2017-07-31 20:04:01 -07:00
										 |  |  |  *   result: { | 
					
						
							|  |  |  |  *     superBlocks: [ ...superBlockDashedName: String ] | 
					
						
							| 
									
										
										
										
											2019-02-19 01:59:12 +03:00
										 |  |  |  *    }, | 
					
						
							| 
									
										
										
										
											2016-08-01 16:54:33 -07:00
										 |  |  |  *   entities: { | 
					
						
							|  |  |  |  *     superBlock: { | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |  *       [ ...superBlockDashedName ]: SuperBlock | 
					
						
							| 
									
										
										
										
											2016-08-01 16:54:33 -07:00
										 |  |  |  *     }, | 
					
						
							|  |  |  |  *     block: { | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |  *       [ ...blockDashedNameg ]: Block, | 
					
						
							| 
									
										
										
										
											2016-08-01 16:54:33 -07:00
										 |  |  |  *     challenge: { | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |  *       [ ...challengeDashedNameg ]: Challenge | 
					
						
							| 
									
										
										
										
											2016-08-01 16:54:33 -07:00
										 |  |  |  *     } | 
					
						
							|  |  |  |  *   } | 
					
						
							|  |  |  |  * } | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  | export function _cachedMap({ Block, Challenge }) { | 
					
						
							|  |  |  |   const challenges = Challenge.find$({ | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |     order: ['order ASC', 'suborder ASC'], | 
					
						
							| 
									
										
										
										
											2018-02-21 09:24:52 +00:00
										 |  |  |     where: { isPrivate: false } | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |   const challengeMap = challenges.map(challenges => | 
					
						
							|  |  |  |     challenges | 
					
						
							|  |  |  |       .map(challenge => challenge.toJSON()) | 
					
						
							|  |  |  |       .reduce((hash, challenge) => { | 
					
						
							|  |  |  |         hash[challenge.dashedName] = challenge; | 
					
						
							|  |  |  |         return hash; | 
					
						
							|  |  |  |       }, {}) | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2018-02-21 09:24:52 +00:00
										 |  |  |   const blocks = Block.find$({ | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |     order: ['superOrder ASC', 'order ASC'], | 
					
						
							| 
									
										
										
										
											2018-02-21 09:24:52 +00:00
										 |  |  |     where: { isPrivate: false } | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |   const blockMap = Observable.combineLatest( | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |     blocks.map(blocks => | 
					
						
							| 
									
										
										
										
											2019-02-19 01:59:12 +03:00
										 |  |  |       blocks | 
					
						
							|  |  |  |         .map(block => block.toJSON()) | 
					
						
							|  |  |  |         .reduce((hash, block) => { | 
					
						
							|  |  |  |           hash[block.dashedName] = block; | 
					
						
							|  |  |  |           return hash; | 
					
						
							|  |  |  |         }, {}) | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |     ), | 
					
						
							|  |  |  |     challenges | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |   ).map(([blocksMap, challenges]) => { | 
					
						
							|  |  |  |     return challenges.reduce((blocksMap, challenge) => { | 
					
						
							|  |  |  |       if (blocksMap[challenge.block].challenges) { | 
					
						
							|  |  |  |         blocksMap[challenge.block].challenges.push(challenge.dashedName); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         blocksMap[challenge.block] = { | 
					
						
							|  |  |  |           ...blocksMap[challenge.block], | 
					
						
							|  |  |  |           challenges: [challenge.dashedName] | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return blocksMap; | 
					
						
							|  |  |  |     }, blocksMap); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   const superBlockMap = blocks.map(blocks => | 
					
						
							|  |  |  |     blocks.reduce((map, block) => { | 
					
						
							|  |  |  |       if (map[block.superBlock] && map[block.superBlock].blocks) { | 
					
						
							|  |  |  |         map[block.superBlock].blocks.push(block.dashedName); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         map[block.superBlock] = { | 
					
						
							|  |  |  |           title: _.startCase(block.superBlock), | 
					
						
							|  |  |  |           order: block.superOrder, | 
					
						
							|  |  |  |           name: nameify(_.startCase(block.superBlock)), | 
					
						
							|  |  |  |           dashedName: block.superBlock, | 
					
						
							|  |  |  |           blocks: [block.dashedName], | 
					
						
							|  |  |  |           message: block.superBlockMessage | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return map; | 
					
						
							|  |  |  |     }, {}) | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |   const superBlocks = superBlockMap.map(superBlockMap => { | 
					
						
							|  |  |  |     return Object.keys(superBlockMap) | 
					
						
							|  |  |  |       .map(key => superBlockMap[key]) | 
					
						
							|  |  |  |       .map(({ dashedName }) => dashedName); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   return Observable.combineLatest( | 
					
						
							|  |  |  |     superBlockMap, | 
					
						
							|  |  |  |     blockMap, | 
					
						
							|  |  |  |     challengeMap, | 
					
						
							|  |  |  |     superBlocks, | 
					
						
							|  |  |  |     (superBlock, block, challenge, superBlocks) => ({ | 
					
						
							|  |  |  |       entities: { | 
					
						
							|  |  |  |         superBlock, | 
					
						
							|  |  |  |         block, | 
					
						
							|  |  |  |         challenge | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       result: { | 
					
						
							|  |  |  |         superBlocks | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-08-01 16:54:33 -07:00
										 |  |  |     }) | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |   ) | 
					
						
							|  |  |  |     .do(checkMapData) | 
					
						
							| 
									
										
										
										
											2016-08-01 16:54:33 -07:00
										 |  |  |     .shareReplay(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  | export const cachedMap = _.once(_cachedMap); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // type ObjectId: String;
 | 
					
						
							|  |  |  | // getChallengeById(
 | 
					
						
							|  |  |  | //   map: Observable[map],
 | 
					
						
							|  |  |  | //   id: ObjectId
 | 
					
						
							|  |  |  | // ) => Observable[Challenge] | Void;
 | 
					
						
							|  |  |  | export function getChallengeById(map, id) { | 
					
						
							|  |  |  |   return Observable.if( | 
					
						
							|  |  |  |     () => !id, | 
					
						
							|  |  |  |     map.map(getFirstChallenge), | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |     map.map(addNameIdMap).map(map => { | 
					
						
							|  |  |  |       const { | 
					
						
							|  |  |  |         entities: { challenge: challengeMap, challengeIdToName } | 
					
						
							|  |  |  |       } = map; | 
					
						
							|  |  |  |       let finalChallenge; | 
					
						
							|  |  |  |       const dashedName = challengeIdToName[id]; | 
					
						
							|  |  |  |       finalChallenge = challengeMap[dashedName]; | 
					
						
							|  |  |  |       if (!finalChallenge) { | 
					
						
							|  |  |  |         finalChallenge = getFirstChallenge(map); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return finalChallenge; | 
					
						
							|  |  |  |     }) | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |   ); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function getChallengeInfo(map) { | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |   return map | 
					
						
							|  |  |  |     .map(addNameIdMap) | 
					
						
							|  |  |  |     .map(({ entities: { challenge: challengeMap, challengeIdToName } }) => ({ | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |       challengeMap, | 
					
						
							|  |  |  |       challengeIdToName | 
					
						
							|  |  |  |     })); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // if challenge is not isComingSoon or isBeta => load
 | 
					
						
							|  |  |  | // if challenge is ComingSoon we are in beta||dev => load
 | 
					
						
							|  |  |  | // if challenge is beta and we are in beta||dev => load
 | 
					
						
							|  |  |  | // else hide
 | 
					
						
							|  |  |  | function loadComingSoonOrBetaChallenge({ | 
					
						
							|  |  |  |   isComingSoon, | 
					
						
							|  |  |  |   isBeta: challengeIsBeta | 
					
						
							|  |  |  | }) { | 
					
						
							|  |  |  |   return !(isComingSoon || challengeIsBeta) || isDev || isBeta; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // this is a hard search
 | 
					
						
							|  |  |  | // falls back to soft search
 | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  | export function getChallenge(challengeDashedName, blockDashedName, map) { | 
					
						
							|  |  |  |   return map.flatMap(({ entities, result: { superBlocks } }) => { | 
					
						
							|  |  |  |     const superBlock = entities.superBlock; | 
					
						
							|  |  |  |     const block = entities.block[blockDashedName]; | 
					
						
							|  |  |  |     const challenge = entities.challenge[challengeDashedName]; | 
					
						
							|  |  |  |     return Observable.if( | 
					
						
							|  |  |  |       () => | 
					
						
							|  |  |  |         !blockDashedName || | 
					
						
							|  |  |  |         !block || | 
					
						
							|  |  |  |         !challenge || | 
					
						
							|  |  |  |         !loadComingSoonOrBetaChallenge(challenge), | 
					
						
							|  |  |  |       getChallengeByDashedName(challengeDashedName, map), | 
					
						
							|  |  |  |       Observable.just({ block, challenge }) | 
					
						
							|  |  |  |     ).map(({ challenge, block }) => ({ | 
					
						
							|  |  |  |       redirect: | 
					
						
							|  |  |  |         challenge.block !== blockDashedName | 
					
						
							|  |  |  |           ? `/challenges/${block.dashedName}/${challenge.dashedName}` | 
					
						
							|  |  |  |           : false, | 
					
						
							|  |  |  |       entities: { | 
					
						
							|  |  |  |         superBlock, | 
					
						
							|  |  |  |         challenge: { | 
					
						
							|  |  |  |           [challenge.dashedName]: challenge | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       result: { | 
					
						
							|  |  |  |         block: block.dashedName, | 
					
						
							|  |  |  |         challenge: challenge.dashedName, | 
					
						
							|  |  |  |         superBlocks | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     })); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function getBlockForChallenge(map, challenge) { | 
					
						
							|  |  |  |   return map.map(({ entities: { block } }) => block[challenge.block]); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function getChallengeByDashedName(dashedName, map) { | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |   const challengeName = unDasherize(dashedName).replace(challengesRegex, ''); | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |   const testChallengeName = new RegExp(challengeName, 'i'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return map | 
					
						
							|  |  |  |     .map(({ entities }) => entities.challenge) | 
					
						
							|  |  |  |     .flatMap(challengeMap => { | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |       return Observable.from(Object.keys(challengeMap)).map( | 
					
						
							|  |  |  |         key => challengeMap[key] | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |     }) | 
					
						
							|  |  |  |     .filter(challenge => { | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |       return ( | 
					
						
							|  |  |  |         loadComingSoonOrBetaChallenge(challenge) && | 
					
						
							|  |  |  |         testChallengeName.test(challenge.name) | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |     }) | 
					
						
							|  |  |  |     .last({ defaultValue: null }) | 
					
						
							|  |  |  |     .flatMap(challengeOrNull => { | 
					
						
							|  |  |  |       return Observable.if( | 
					
						
							|  |  |  |         () => !!challengeOrNull, | 
					
						
							|  |  |  |         Observable.just(challengeOrNull), | 
					
						
							|  |  |  |         map.map(getFirstChallenge) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |     .flatMap(challenge => { | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |       return getBlockForChallenge(map, challenge).map(block => ({ | 
					
						
							|  |  |  |         challenge, | 
					
						
							|  |  |  |         block | 
					
						
							|  |  |  |       })); | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | } |