| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  | /** | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Any ref to fixCompletedChallengesItem should be removed post | 
					
						
							|  |  |  |  * a db migration to fix all completedChallenges | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  | import { Observable } from 'rx'; | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  | import { isEmpty, pick, omit, find, uniqBy, last } from 'lodash'; | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  | import debug from 'debug'; | 
					
						
							| 
									
										
										
										
											2016-08-05 14:49:23 -07:00
										 |  |  | import dedent from 'dedent'; | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  | import { ObjectID } from 'mongodb'; | 
					
						
							|  |  |  | import isNumeric from 'validator/lib/isNumeric'; | 
					
						
							|  |  |  | import isURL from 'validator/lib/isURL'; | 
					
						
							| 
									
										
										
										
											2015-11-09 17:27:56 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-04 11:42:31 +00:00
										 |  |  | import { homeLocation } from '../../../config/env'; | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  | import { ifNoUserSend } from '../utils/middleware'; | 
					
						
							| 
									
										
										
										
											2018-05-14 08:34:51 +01:00
										 |  |  | import { dasherize } from '../utils'; | 
					
						
							| 
									
										
										
										
											2019-02-23 23:40:10 +00:00
										 |  |  | import _pathMigrations from '../resources/pathMigration.json'; | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  | import { fixCompletedChallengeItem } from '../../common/utils'; | 
					
						
							| 
									
										
										
										
											2015-08-09 22:14:31 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  | const log = debug('fcc:boot:challenges'); | 
					
						
							| 
									
										
										
										
											2015-08-09 22:14:31 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  | export default async function bootChallenge(app, done) { | 
					
						
							|  |  |  |   const send200toNonUser = ifNoUserSend(true); | 
					
						
							|  |  |  |   const api = app.loopback.Router(); | 
					
						
							|  |  |  |   const router = app.loopback.Router(); | 
					
						
							| 
									
										
										
										
											2019-02-23 23:40:10 +00:00
										 |  |  |   const redirectToLearn = createRedirectToLearn(_pathMigrations); | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   const challengeUrlResolver = await createChallengeUrlResolver(app); | 
					
						
							|  |  |  |   const redirectToCurrentChallenge = createRedirectToCurrentChallenge( | 
					
						
							|  |  |  |     challengeUrlResolver | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   api.post( | 
					
						
							|  |  |  |     '/modern-challenge-completed', | 
					
						
							|  |  |  |     send200toNonUser, | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |     isValidChallengeCompletion, | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |     modernChallengeCompleted | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   api.post( | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |     '/project-completed', | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |     send200toNonUser, | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |     isValidChallengeCompletion, | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |     projectCompleted | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   api.post( | 
					
						
							|  |  |  |     '/backend-challenge-completed', | 
					
						
							|  |  |  |     send200toNonUser, | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |     isValidChallengeCompletion, | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |     backendChallengeCompleted | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   router.get('/challenges/current-challenge', redirectToCurrentChallenge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   router.get('/challenges', redirectToLearn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   router.get('/challenges/*', redirectToLearn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   router.get('/map', redirectToLearn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   app.use(api); | 
					
						
							|  |  |  |   app.use('/internal', api); | 
					
						
							|  |  |  |   app.use(router); | 
					
						
							|  |  |  |   done(); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  | const learnURL = `${homeLocation}/learn`; | 
					
						
							| 
									
										
										
										
											2018-05-14 08:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  | const jsProjects = [ | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   'aaa48de84e1ecc7c742e1124', | 
					
						
							|  |  |  |   'a7f4d8f2483413a6ce226cac', | 
					
						
							|  |  |  |   '56533eb9ac21ba0edf2244e2', | 
					
						
							|  |  |  |   'aff0395860f5d3034dc0bfc9', | 
					
						
							|  |  |  |   'aa2e6f85cab2ab736c9a9b24' | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  | ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 21:43:51 +00:00
										 |  |  | export function buildUserUpdate( | 
					
						
							|  |  |  |   user, | 
					
						
							|  |  |  |   challengeId, | 
					
						
							|  |  |  |   _completedChallenge, | 
					
						
							|  |  |  |   timezone | 
					
						
							|  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |   const { files } = _completedChallenge; | 
					
						
							|  |  |  |   let completedChallenge = {}; | 
					
						
							|  |  |  |   if (jsProjects.includes(challengeId)) { | 
					
						
							|  |  |  |     completedChallenge = { | 
					
						
							|  |  |  |       ..._completedChallenge, | 
					
						
							|  |  |  |       files: Object.keys(files) | 
					
						
							|  |  |  |         .map(key => files[key]) | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |         .map(file => | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |           pick(file, ['contents', 'key', 'index', 'name', 'path', 'ext']) | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } else { | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |     completedChallenge = omit(_completedChallenge, ['files']); | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |   let finalChallenge; | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |   const updateData = {}; | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |   const { timezone: userTimezone, completedChallenges = [] } = user; | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   const oldChallenge = find( | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |     completedChallenges, | 
					
						
							|  |  |  |     ({ id }) => challengeId === id | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |   const alreadyCompleted = !!oldChallenge; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (alreadyCompleted) { | 
					
						
							| 
									
										
										
										
											2017-09-08 00:16:02 -07:00
										 |  |  |     finalChallenge = { | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |       ...completedChallenge, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |       completedDate: oldChallenge.completedDate | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2016-02-10 12:01:00 -08:00
										 |  |  |   } else { | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |     updateData.$push = { | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |       ...updateData.$push, | 
					
						
							|  |  |  |       progressTimestamps: Date.now() | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2017-09-04 03:41:55 -07:00
										 |  |  |     finalChallenge = { | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |       ...completedChallenge | 
					
						
							| 
									
										
										
										
											2017-09-04 03:41:55 -07:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2015-06-20 19:52:37 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-10-01 21:44:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |   updateData.$set = { | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |     completedChallenges: uniqBy( | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |       [finalChallenge, ...completedChallenges.map(fixCompletedChallengeItem)], | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |       'id' | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if ( | 
					
						
							| 
									
										
										
										
											2016-02-10 10:05:51 -08:00
										 |  |  |     timezone && | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |     timezone !== 'UTC' && | 
					
						
							|  |  |  |     (!userTimezone || userTimezone === 'UTC') | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     updateData.$set = { | 
					
						
							|  |  |  |       ...updateData.$set, | 
					
						
							|  |  |  |       timezone: userTimezone | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-08-11 16:41:03 -07:00
										 |  |  |   return { | 
					
						
							|  |  |  |     alreadyCompleted, | 
					
						
							|  |  |  |     updateData, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |     completedDate: finalChallenge.completedDate | 
					
						
							| 
									
										
										
										
											2016-08-11 16:41:03 -07:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2015-06-20 19:52:37 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  | export function buildChallengeUrl(challenge) { | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   const { superBlock, block, dashedName } = challenge; | 
					
						
							|  |  |  |   return `/learn/${dasherize(superBlock)}/${dasherize(block)}/${dashedName}`; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  | export function getFirstChallenge(Challenge) { | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   return new Promise(resolve => { | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |     Challenge.findOne( | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |       { where: { challengeOrder: 0, superOrder: 1, order: 0 } }, | 
					
						
							|  |  |  |       (err, challenge) => { | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |         if (err || isEmpty(challenge)) { | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |           return resolve('/learn'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return resolve(buildChallengeUrl(challenge)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  | export async function createChallengeUrlResolver( | 
					
						
							|  |  |  |   app, | 
					
						
							|  |  |  |   { _getFirstChallenge = getFirstChallenge } = {} | 
					
						
							|  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   const { Challenge } = app.models; | 
					
						
							|  |  |  |   const cache = new Map(); | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   const firstChallenge = await _getFirstChallenge(Challenge); | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   return function resolveChallengeUrl(id) { | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |     if (isEmpty(id)) { | 
					
						
							|  |  |  |       return Promise.resolve(firstChallenge); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |     return new Promise(resolve => { | 
					
						
							|  |  |  |       if (cache.has(id)) { | 
					
						
							|  |  |  |         return resolve(cache.get(id)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return Challenge.findById(id, (err, challenge) => { | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |         if (err || isEmpty(challenge)) { | 
					
						
							|  |  |  |           return resolve(firstChallenge); | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |         } | 
					
						
							|  |  |  |         const challengeUrl = buildChallengeUrl(challenge); | 
					
						
							|  |  |  |         cache.set(id, challengeUrl); | 
					
						
							|  |  |  |         return resolve(challengeUrl); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  | export function isValidChallengeCompletion(req, res, next) { | 
					
						
							|  |  |  |   const { | 
					
						
							|  |  |  |     body: { id, challengeType, solution } | 
					
						
							|  |  |  |   } = req; | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |   if (!ObjectID.isValid(id)) { | 
					
						
							|  |  |  |     log('isObjectId', id, ObjectID.isValid(id)); | 
					
						
							|  |  |  |     return res.sendStatus(403); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-02-24 10:12:51 +00:00
										 |  |  |   if ('challengeType' in req.body && !isNumeric(String(challengeType))) { | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |     log('challengeType', challengeType, isNumeric(challengeType)); | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |     return res.sendStatus(403); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |   if ('solution' in req.body && !isURL(solution)) { | 
					
						
							|  |  |  |     log('isObjectId', id, ObjectID.isValid(id)); | 
					
						
							|  |  |  |     return res.sendStatus(403); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return next(); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  | export function modernChallengeCompleted(req, res, next) { | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   const user = req.user; | 
					
						
							|  |  |  |   return user | 
					
						
							|  |  |  |     .getCompletedChallenges$() | 
					
						
							|  |  |  |     .flatMap(() => { | 
					
						
							|  |  |  |       const completedDate = Date.now(); | 
					
						
							|  |  |  |       const { id, files } = req.body; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const { alreadyCompleted, updateData } = buildUserUpdate(user, id, { | 
					
						
							|  |  |  |         id, | 
					
						
							|  |  |  |         files, | 
					
						
							|  |  |  |         completedDate | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2016-06-08 11:26:33 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |       const points = alreadyCompleted ? user.points : user.points + 1; | 
					
						
							|  |  |  |       const updatePromise = new Promise((resolve, reject) => | 
					
						
							|  |  |  |         user.updateAttributes(updateData, err => { | 
					
						
							|  |  |  |           if (err) { | 
					
						
							|  |  |  |             return reject(err); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           return resolve(); | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return Observable.fromPromise(updatePromise).map(() => { | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |         return res.json({ | 
					
						
							|  |  |  |           points, | 
					
						
							|  |  |  |           alreadyCompleted, | 
					
						
							|  |  |  |           completedDate | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |     .subscribe(() => {}, next); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2015-06-03 16:31:42 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  | function projectCompleted(req, res, next) { | 
					
						
							|  |  |  |   const { user, body = {} } = req; | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   const completedChallenge = pick(body, [ | 
					
						
							|  |  |  |     'id', | 
					
						
							|  |  |  |     'solution', | 
					
						
							|  |  |  |     'githubLink', | 
					
						
							|  |  |  |     'challengeType', | 
					
						
							|  |  |  |     'files' | 
					
						
							|  |  |  |   ]); | 
					
						
							|  |  |  |   completedChallenge.completedDate = Date.now(); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   if ( | 
					
						
							|  |  |  |     !completedChallenge.solution || | 
					
						
							|  |  |  |     // only basejumps require github links
 | 
					
						
							|  |  |  |     (completedChallenge.challengeType === 4 && !completedChallenge.githubLink) | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     req.flash( | 
					
						
							|  |  |  |       'danger', | 
					
						
							|  |  |  |       "You haven't supplied the necessary URLs for us to inspect your work." | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return res.sendStatus(403); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   return user | 
					
						
							|  |  |  |     .getCompletedChallenges$() | 
					
						
							|  |  |  |     .flatMap(() => { | 
					
						
							|  |  |  |       const { alreadyCompleted, updateData } = buildUserUpdate( | 
					
						
							|  |  |  |         user, | 
					
						
							|  |  |  |         completedChallenge.id, | 
					
						
							|  |  |  |         completedChallenge | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |       const updatePromise = new Promise((resolve, reject) => | 
					
						
							|  |  |  |         user.updateAttributes(updateData, err => { | 
					
						
							|  |  |  |           if (err) { | 
					
						
							|  |  |  |             return reject(err); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |           return resolve(); | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return Observable.fromPromise(updatePromise).doOnNext(() => { | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |         return res.send({ | 
					
						
							|  |  |  |           alreadyCompleted, | 
					
						
							|  |  |  |           points: alreadyCompleted ? user.points : user.points + 1, | 
					
						
							|  |  |  |           completedDate: completedChallenge.completedDate | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |     .subscribe(() => {}, next); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  | function backendChallengeCompleted(req, res, next) { | 
					
						
							|  |  |  |   const { user, body = {} } = req; | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   const completedChallenge = pick(body, ['id', 'solution']); | 
					
						
							|  |  |  |   completedChallenge.completedDate = Date.now(); | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   return user | 
					
						
							|  |  |  |     .getCompletedChallenges$() | 
					
						
							|  |  |  |     .flatMap(() => { | 
					
						
							|  |  |  |       const { alreadyCompleted, updateData } = buildUserUpdate( | 
					
						
							|  |  |  |         user, | 
					
						
							|  |  |  |         completedChallenge.id, | 
					
						
							|  |  |  |         completedChallenge | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |       const updatePromise = new Promise((resolve, reject) => | 
					
						
							|  |  |  |         user.updateAttributes(updateData, err => { | 
					
						
							|  |  |  |           if (err) { | 
					
						
							|  |  |  |             return reject(err); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |           return resolve(); | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return Observable.fromPromise(updatePromise).doOnNext(() => { | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |         return res.send({ | 
					
						
							|  |  |  |           alreadyCompleted, | 
					
						
							|  |  |  |           points: alreadyCompleted ? user.points : user.points + 1, | 
					
						
							|  |  |  |           completedDate: completedChallenge.completedDate | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |     .subscribe(() => {}, next); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  | export function createRedirectToCurrentChallenge( | 
					
						
							|  |  |  |   challengeUrlResolver, | 
					
						
							|  |  |  |   { _homeLocation = homeLocation, _learnUrl = learnURL } = {} | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  |   return async function redirectToCurrentChallenge(req, res, next) { | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |     const { user } = req; | 
					
						
							| 
									
										
										
										
											2018-11-20 10:51:48 +00:00
										 |  |  |     if (!user) { | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |       return res.redirect(_learnUrl); | 
					
						
							| 
									
										
										
										
											2018-11-20 10:51:48 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |     const challengeId = user && user.currentChallengeId; | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |     const challengeUrl = await challengeUrlResolver(challengeId).catch(next); | 
					
						
							|  |  |  |     if (challengeUrl === '/learn') { | 
					
						
							|  |  |  |       // this should normally not be hit if database is properly seeded
 | 
					
						
							|  |  |  |       throw new Error(dedent`
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |         Attempted to find the url for ${challengeId || 'Unknown ID'}' | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |         but came up empty. | 
					
						
							|  |  |  |         db may not be properly seeded. | 
					
						
							|  |  |  |       `);
 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |     return res.redirect(`${_homeLocation}${challengeUrl}`); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-05-14 08:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 23:40:10 +00:00
										 |  |  | export function createRedirectToLearn( | 
					
						
							|  |  |  |   pathMigrations, | 
					
						
							|  |  |  |   base = homeLocation, | 
					
						
							|  |  |  |   learn = learnURL | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  |   return function redirectToLearn(req, res) { | 
					
						
							|  |  |  |     const maybeChallenge = last(req.path.split('/')); | 
					
						
							|  |  |  |     if (maybeChallenge in pathMigrations) { | 
					
						
							|  |  |  |       const redirectPath = pathMigrations[maybeChallenge]; | 
					
						
							|  |  |  |       return res.status(302).redirect(`${base}${redirectPath}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return res.status(302).redirect(learn); | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  | } |