| 
									
										
										
										
											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'; | 
					
						
							| 
									
										
										
										
											2015-08-09 22:14:31 -07:00
										 |  |  | import _ from 'lodash'; | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  | import debug from 'debug'; | 
					
						
							| 
									
										
										
										
											2016-01-09 20:08:01 -08:00
										 |  |  | import accepts from 'accepts'; | 
					
						
							| 
									
										
										
										
											2016-08-05 14:49:23 -07:00
										 |  |  | import dedent from 'dedent'; | 
					
						
							| 
									
										
										
										
											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'; | 
					
						
							|  |  |  | 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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  | ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  | 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 => | 
					
						
							|  |  |  |           _.pick(file, ['contents', 'key', 'index', 'name', 'path', 'ext']) | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     completedChallenge = _.omit(_completedChallenge, ['files']); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |   const oldChallenge = _.find( | 
					
						
							|  |  |  |     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 = { | 
					
						
							|  |  |  |     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
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  | function buildChallengeUrl(challenge) { | 
					
						
							|  |  |  |   const { superBlock, block, dashedName } = challenge; | 
					
						
							|  |  |  |   return `/learn/${dasherize(superBlock)}/${dasherize(block)}/${dashedName}`; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getFirstChallenge(Challenge) { | 
					
						
							|  |  |  |   return new Promise(resolve => { | 
					
						
							|  |  |  |     Challenge.find( | 
					
						
							|  |  |  |       { where: { challengeOrder: 0, superOrder: 1, order: 0 } }, | 
					
						
							|  |  |  |       (err, challenge) => { | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           console.log(err); | 
					
						
							|  |  |  |           return resolve('/learn'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return resolve(buildChallengeUrl(challenge)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function createChallengeUrlResolver(app) { | 
					
						
							|  |  |  |   const { Challenge } = app.models; | 
					
						
							|  |  |  |   const cache = new Map(); | 
					
						
							|  |  |  |   const firstChallenge = await getFirstChallenge(Challenge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return function resolveChallengeUrl(id) { | 
					
						
							|  |  |  |     return new Promise(resolve => { | 
					
						
							|  |  |  |       if (cache.has(id)) { | 
					
						
							|  |  |  |         return resolve(cache.get(id)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return Challenge.findById(id, (err, challenge) => { | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           console.log(err); | 
					
						
							|  |  |  |           return firstChallenge; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const challengeUrl = buildChallengeUrl(challenge); | 
					
						
							|  |  |  |         cache.set(id, challengeUrl); | 
					
						
							|  |  |  |         return resolve(challengeUrl); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default async function bootChallenge(app, done) { | 
					
						
							| 
									
										
										
										
											2015-08-09 22:14:31 -07:00
										 |  |  |   const send200toNonUser = ifNoUserSend(true); | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |   const api = app.loopback.Router(); | 
					
						
							|  |  |  |   const router = app.loopback.Router(); | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   const challengeUrlResolver = await createChallengeUrlResolver(app); | 
					
						
							| 
									
										
										
										
											2015-06-22 16:43:31 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |   api.post( | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |     '/modern-challenge-completed', | 
					
						
							|  |  |  |     send200toNonUser, | 
					
						
							|  |  |  |     modernChallengeCompleted | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 11:26:33 -07:00
										 |  |  |   // deprecate endpoint
 | 
					
						
							|  |  |  |   // remove once new endpoint is live
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   api.post('/completed-challenge', send200toNonUser, completedChallenge); | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   api.post('/challenge-completed', send200toNonUser, completedChallenge); | 
					
						
							| 
									
										
										
										
											2016-06-08 11:26:33 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 11:11:13 -07:00
										 |  |  |   // deprecate endpoint
 | 
					
						
							|  |  |  |   // remove once new endpoint is live
 | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |   api.post( | 
					
						
							| 
									
										
										
										
											2015-06-22 16:43:31 -07:00
										 |  |  |     '/completed-zipline-or-basejump', | 
					
						
							|  |  |  |     send200toNonUser, | 
					
						
							| 
									
										
										
										
											2016-06-08 11:11:13 -07:00
										 |  |  |     projectCompleted | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   api.post('/project-completed', send200toNonUser, projectCompleted); | 
					
						
							| 
									
										
										
										
											2015-06-02 19:02:54 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  |   api.post( | 
					
						
							|  |  |  |     '/backend-challenge-completed', | 
					
						
							|  |  |  |     send200toNonUser, | 
					
						
							|  |  |  |     backendChallengeCompleted | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   router.get('/challenges/current-challenge', redirectToCurrentChallenge); | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-14 08:34:51 +01:00
										 |  |  |   router.get('/challenges', redirectToLearn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   router.get('/challenges/*', redirectToLearn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   router.get('/map', redirectToLearn); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |   app.use(api); | 
					
						
							| 
									
										
										
										
											2018-05-23 21:10:56 +01:00
										 |  |  |   app.use('/external', api); | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |   app.use('/internal', api); | 
					
						
							| 
									
										
										
										
											2018-05-15 06:12:05 +01:00
										 |  |  |   app.use(router); | 
					
						
							| 
									
										
										
										
											2015-06-03 16:31:42 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |   function modernChallengeCompleted(req, res, next) { | 
					
						
							|  |  |  |     const type = accepts(req).type('html', 'json', 'text'); | 
					
						
							|  |  |  |     req.checkBody('id', 'id must be an ObjectId').isMongoId(); | 
					
						
							| 
									
										
										
										
											2016-02-14 17:10:26 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |     const errors = req.validationErrors(true); | 
					
						
							|  |  |  |     if (errors) { | 
					
						
							|  |  |  |       if (type === 'json') { | 
					
						
							|  |  |  |         return res.status(403).send({ errors }); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-02-14 17:10:26 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |       log('errors', errors); | 
					
						
							|  |  |  |       return res.sendStatus(403); | 
					
						
							| 
									
										
										
										
											2016-01-15 01:44:18 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-02-14 17:10:26 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |     const user = req.user; | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |     return user | 
					
						
							|  |  |  |       .getCompletedChallenges$() | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |       .flatMap(() => { | 
					
						
							|  |  |  |         const completedDate = Date.now(); | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |         const { id, files } = req.body; | 
					
						
							| 
									
										
										
										
											2016-02-14 17:10:26 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |         const { alreadyCompleted, updateData } = buildUserUpdate(user, id, { | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |           id, | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |           files, | 
					
						
							|  |  |  |           completedDate | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2016-02-14 17:10:26 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |         const points = alreadyCompleted ? user.points : user.points + 1; | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         const updatePromise = new Promise((resolve, reject) => | 
					
						
							|  |  |  |           user.updateAttributes(updateData, err => { | 
					
						
							|  |  |  |             if (err) { | 
					
						
							|  |  |  |               return reject(err); | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |             return resolve(); | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return Observable.fromPromise(updatePromise).map(() => { | 
					
						
							|  |  |  |           if (type === 'json') { | 
					
						
							|  |  |  |             return res.json({ | 
					
						
							|  |  |  |               points, | 
					
						
							|  |  |  |               alreadyCompleted, | 
					
						
							|  |  |  |               completedDate | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           return res.sendStatus(200); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2016-01-14 15:15:44 -08:00
										 |  |  |       }) | 
					
						
							|  |  |  |       .subscribe(() => {}, next); | 
					
						
							| 
									
										
										
										
											2015-05-16 00:39:43 -04:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-02 19:02:54 -07:00
										 |  |  |   function completedChallenge(req, res, next) { | 
					
						
							| 
									
										
										
										
											2016-03-30 23:58:38 -04:00
										 |  |  |     req.checkBody('id', 'id must be an ObjectId').isMongoId(); | 
					
						
							| 
									
										
										
										
											2016-01-09 20:08:01 -08:00
										 |  |  |     const type = accepts(req).type('html', 'json', 'text'); | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  |     const errors = req.validationErrors(true); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     const { user } = req; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  |     if (errors) { | 
					
						
							|  |  |  |       if (type === 'json') { | 
					
						
							|  |  |  |         return res.status(403).send({ errors }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       log('errors', errors); | 
					
						
							|  |  |  |       return res.sendStatus(403); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     return user | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |       .getCompletedChallenges$() | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |       .flatMap(() => { | 
					
						
							|  |  |  |         const completedDate = Date.now(); | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |         const { id, solution, timezone, files } = req.body; | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |         const { alreadyCompleted, updateData } = buildUserUpdate( | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |           user, | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |           id, | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |           { id, solution, completedDate, files }, | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |           timezone | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const points = alreadyCompleted ? user.points : user.points + 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         const updatePromise = new Promise((resolve, reject) => | 
					
						
							|  |  |  |           user.updateAttributes(updateData, err => { | 
					
						
							|  |  |  |             if (err) { | 
					
						
							|  |  |  |               return reject(err); | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |             return resolve(); | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return Observable.fromPromise(updatePromise).map(() => { | 
					
						
							|  |  |  |           if (type === 'json') { | 
					
						
							|  |  |  |             return res.json({ | 
					
						
							|  |  |  |               points, | 
					
						
							|  |  |  |               alreadyCompleted, | 
					
						
							|  |  |  |               completedDate | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           return res.sendStatus(200); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |       }) | 
					
						
							|  |  |  |       .subscribe(() => {}, next); | 
					
						
							| 
									
										
										
										
											2015-05-20 21:50:31 -04:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-05-19 22:31:01 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 11:11:13 -07:00
										 |  |  |   function projectCompleted(req, res, next) { | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  |     const type = accepts(req).type('html', 'json', 'text'); | 
					
						
							|  |  |  |     req.checkBody('id', 'id must be an ObjectId').isMongoId(); | 
					
						
							| 
									
										
										
										
											2016-06-08 11:11:13 -07:00
										 |  |  |     req.checkBody('challengeType', 'must be a number').isNumber(); | 
					
						
							|  |  |  |     req.checkBody('solution', 'solution must be a URL').isURL(); | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const errors = req.validationErrors(true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (errors) { | 
					
						
							|  |  |  |       if (type === 'json') { | 
					
						
							|  |  |  |         return res.status(403).send({ errors }); | 
					
						
							| 
									
										
										
										
											2016-01-19 22:30:01 -08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  |       log('errors', errors); | 
					
						
							|  |  |  |       return res.sendStatus(403); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-06-02 19:02:54 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  |     const { user, body = {} } = req; | 
					
						
							| 
									
										
										
										
											2015-06-20 19:52:37 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |     const completedChallenge = _.pick(body, [ | 
					
						
							|  |  |  |       'id', | 
					
						
							|  |  |  |       'solution', | 
					
						
							|  |  |  |       'githubLink', | 
					
						
							|  |  |  |       'challengeType', | 
					
						
							|  |  |  |       'files' | 
					
						
							|  |  |  |     ]); | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  |     completedChallenge.completedDate = Date.now(); | 
					
						
							| 
									
										
										
										
											2015-06-20 19:52:37 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-19 22:30:01 -08:00
										 |  |  |     if ( | 
					
						
							|  |  |  |       !completedChallenge.solution || | 
					
						
							|  |  |  |       // only basejumps require github links
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |       (completedChallenge.challengeType === 4 && !completedChallenge.githubLink) | 
					
						
							| 
									
										
										
										
											2016-01-19 22:30:01 -08:00
										 |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2018-01-12 14:16:33 -08:00
										 |  |  |       req.flash( | 
					
						
							|  |  |  |         'danger', | 
					
						
							| 
									
										
										
										
											2019-02-04 11:42:31 +00:00
										 |  |  |         'You haven\'t supplied the necessary URLs for us to inspect your work.' | 
					
						
							| 
									
										
										
										
											2018-01-12 14:16:33 -08:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2015-06-02 19:02:54 -07:00
										 |  |  |       return res.sendStatus(403); | 
					
						
							| 
									
										
										
										
											2015-05-19 22:31:01 -04:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-05-21 00:17:44 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |     return user | 
					
						
							|  |  |  |       .getCompletedChallenges$() | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  |       .flatMap(() => { | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |         const { alreadyCompleted, updateData } = buildUserUpdate( | 
					
						
							|  |  |  |           user, | 
					
						
							|  |  |  |           completedChallenge.id, | 
					
						
							|  |  |  |           completedChallenge | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         const updatePromise = new Promise((resolve, reject) => | 
					
						
							|  |  |  |           user.updateAttributes(updateData, err => { | 
					
						
							|  |  |  |             if (err) { | 
					
						
							|  |  |  |               return reject(err); | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |             return resolve(); | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return Observable.fromPromise(updatePromise).doOnNext(() => { | 
					
						
							|  |  |  |           if (type === 'json') { | 
					
						
							|  |  |  |             return res.send({ | 
					
						
							|  |  |  |               alreadyCompleted, | 
					
						
							|  |  |  |               points: alreadyCompleted ? user.points : user.points + 1, | 
					
						
							|  |  |  |               completedDate: completedChallenge.completedDate | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           return res.status(200).send(true); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  |       }) | 
					
						
							|  |  |  |       .subscribe(() => {}, next); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function backendChallengeCompleted(req, res, next) { | 
					
						
							|  |  |  |     const type = accepts(req).type('html', 'json', 'text'); | 
					
						
							|  |  |  |     req.checkBody('id', 'id must be an ObjectId').isMongoId(); | 
					
						
							|  |  |  |     req.checkBody('solution', 'solution must be a URL').isURL(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const errors = req.validationErrors(true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (errors) { | 
					
						
							|  |  |  |       if (type === 'json') { | 
					
						
							|  |  |  |         return res.status(403).send({ errors }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       log('errors', errors); | 
					
						
							|  |  |  |       return res.sendStatus(403); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const { user, body = {} } = req; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |     const completedChallenge = _.pick(body, ['id', 'solution']); | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  |     completedChallenge.completedDate = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |     return user | 
					
						
							|  |  |  |       .getCompletedChallenges$() | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |       .flatMap(() => { | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |         const { alreadyCompleted, updateData } = buildUserUpdate( | 
					
						
							|  |  |  |           user, | 
					
						
							|  |  |  |           completedChallenge.id, | 
					
						
							|  |  |  |           completedChallenge | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         const updatePromise = new Promise((resolve, reject) => | 
					
						
							|  |  |  |           user.updateAttributes(updateData, err => { | 
					
						
							|  |  |  |             if (err) { | 
					
						
							|  |  |  |               return reject(err); | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |             return resolve(); | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return Observable.fromPromise(updatePromise).doOnNext(() => { | 
					
						
							|  |  |  |           if (type === 'json') { | 
					
						
							|  |  |  |             return res.send({ | 
					
						
							|  |  |  |               alreadyCompleted, | 
					
						
							|  |  |  |               points: alreadyCompleted ? user.points : user.points + 1, | 
					
						
							|  |  |  |               completedDate: completedChallenge.completedDate | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           return res.status(200).send(true); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  |       }) | 
					
						
							| 
									
										
										
										
											2016-01-19 22:30:01 -08:00
										 |  |  |       .subscribe(() => {}, next); | 
					
						
							| 
									
										
										
										
											2015-05-19 22:31:01 -04:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   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) { | 
					
						
							|  |  |  |       return res.redirect(learnURL); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |     const challengeId = user && user.currentChallengeId; | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |     log(req.user.username); | 
					
						
							|  |  |  |     log(challengeId); | 
					
						
							|  |  |  |     const challengeUrl = await challengeUrlResolver(challengeId).catch(next); | 
					
						
							|  |  |  |     log(challengeUrl); | 
					
						
							|  |  |  |     if (challengeUrl === '/learn') { | 
					
						
							|  |  |  |       // this should normally not be hit if database is properly seeded
 | 
					
						
							|  |  |  |       throw new Error(dedent`
 | 
					
						
							|  |  |  |         Attempted to find the url for ${challengeId}' | 
					
						
							|  |  |  |         but came up empty. | 
					
						
							|  |  |  |         db may not be properly seeded. | 
					
						
							|  |  |  |       `);
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return res.redirect(`${homeLocation}${challengeUrl}`); | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-05-14 08:34:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   function redirectToLearn(req, res) { | 
					
						
							|  |  |  |     const maybeChallenge = _.last(req.path.split('/')); | 
					
						
							|  |  |  |     if (maybeChallenge in pathMigrations) { | 
					
						
							|  |  |  |       const redirectPath = pathMigrations[maybeChallenge]; | 
					
						
							| 
									
										
										
										
											2018-05-15 06:12:05 +01:00
										 |  |  |       return res.status(302).redirect(`${learnURL}${redirectPath}`); | 
					
						
							| 
									
										
										
										
											2018-05-14 08:34:51 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-05-15 06:12:05 +01:00
										 |  |  |     return res.status(302).redirect(learnURL); | 
					
						
							| 
									
										
										
										
											2018-05-14 08:34:51 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   done(); | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  | } |