| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  | /** | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Any ref to fixCompletedChallengesItem should be removed post | 
					
						
							|  |  |  |  * a db migration to fix all completedChallenges | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  | import { ifNoUserSend } from '../utils/middleware'; | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  | import { getChallengeById, cachedMap } from '../utils/map'; | 
					
						
							| 
									
										
										
										
											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-05-14 08:34:51 +01:00
										 |  |  | const learnURL = 'https://learn.freecodecamp.org'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  | const jsProjects = [ | 
					
						
							|  |  |  | 'aaa48de84e1ecc7c742e1124', | 
					
						
							|  |  |  | 'a7f4d8f2483413a6ce226cac', | 
					
						
							|  |  |  | '56533eb9ac21ba0edf2244e2', | 
					
						
							|  |  |  | 'aff0395860f5d3034dc0bfc9', | 
					
						
							|  |  |  | 'aa2e6f85cab2ab736c9a9b24' | 
					
						
							|  |  |  | ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  | function buildUserUpdate( | 
					
						
							|  |  |  |   user, | 
					
						
							|  |  |  |   challengeId, | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |   _completedChallenge, | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |   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]) | 
					
						
							|  |  |  |         .map(file => _.pick( | 
					
						
							|  |  |  |           file, | 
					
						
							|  |  |  |           [ | 
					
						
							|  |  |  |             'contents', | 
					
						
							|  |  |  |             'key', | 
					
						
							|  |  |  |             'index', | 
					
						
							|  |  |  |             'name', | 
					
						
							|  |  |  |             'path', | 
					
						
							|  |  |  |             'ext' | 
					
						
							|  |  |  |           ] | 
					
						
							|  |  |  |         )) | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } 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-01-09 20:08:01 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |   log('user update data', updateData); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  | export default function(app) { | 
					
						
							| 
									
										
										
										
											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(); | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |   const map = cachedMap(app.models); | 
					
						
							| 
									
										
										
										
											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
 | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |   api.post( | 
					
						
							| 
									
										
										
										
											2016-06-08 11:11:13 -07:00
										 |  |  |     '/completed-challenge', | 
					
						
							| 
									
										
										
										
											2015-06-22 16:43:31 -07:00
										 |  |  |     send200toNonUser, | 
					
						
							|  |  |  |     completedChallenge | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |   api.post( | 
					
						
							| 
									
										
										
										
											2016-06-08 11:26:33 -07:00
										 |  |  |     '/challenge-completed', | 
					
						
							|  |  |  |     send200toNonUser, | 
					
						
							|  |  |  |     completedChallenge | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |   api.post( | 
					
						
							| 
									
										
										
										
											2016-06-08 11:11:13 -07:00
										 |  |  |     '/project-completed', | 
					
						
							|  |  |  |     send200toNonUser, | 
					
						
							|  |  |  |     projectCompleted | 
					
						
							| 
									
										
										
										
											2015-06-22 16:43:31 -07:00
										 |  |  |   ); | 
					
						
							| 
									
										
										
										
											2015-06-02 19:02:54 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  |   api.post( | 
					
						
							|  |  |  |     '/backend-challenge-completed', | 
					
						
							|  |  |  |     send200toNonUser, | 
					
						
							|  |  |  |     backendChallengeCompleted | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |   router.get( | 
					
						
							|  |  |  |     '/challenges/current-challenge', | 
					
						
							|  |  |  |     redirectToCurrentChallenge | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-05-15 14:56:26 +01:00
										 |  |  |     return user.getCompletedChallenges$() | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |       .flatMap(() => { | 
					
						
							|  |  |  |         const completedDate = Date.now(); | 
					
						
							|  |  |  |         const { | 
					
						
							|  |  |  |           id, | 
					
						
							|  |  |  |           files | 
					
						
							|  |  |  |         } = req.body; | 
					
						
							| 
									
										
										
										
											2016-02-14 17:10:26 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 16:41:03 -07:00
										 |  |  |         const { | 
					
						
							|  |  |  |           alreadyCompleted, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |           updateData | 
					
						
							| 
									
										
										
										
											2016-08-11 16:41:03 -07:00
										 |  |  |         } = buildUserUpdate( | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |           user, | 
					
						
							|  |  |  |           id, | 
					
						
							| 
									
										
										
										
											2016-08-11 16:41:03 -07:00
										 |  |  |           { id, files, completedDate } | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |         ); | 
					
						
							| 
									
										
										
										
											2016-02-14 17:10:26 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |         const points = alreadyCompleted ? user.points : user.points + 1; | 
					
						
							| 
									
										
										
										
											2016-01-14 15:15:44 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |         return user.update$(updateData) | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |           .doOnNext(() => user.manualReload()) | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |           .doOnNext(({ count }) => log('%s documents updated', count)) | 
					
						
							|  |  |  |           .map(() => { | 
					
						
							|  |  |  |             if (type === 'json') { | 
					
						
							|  |  |  |               return res.json({ | 
					
						
							|  |  |  |                 points, | 
					
						
							| 
									
										
										
										
											2016-08-11 16:41:03 -07:00
										 |  |  |                 alreadyCompleted, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |                 completedDate | 
					
						
							| 
									
										
										
										
											2016-01-14 15:15:44 -08:00
										 |  |  |               }); | 
					
						
							| 
									
										
										
										
											2016-06-01 15:52:08 -07:00
										 |  |  |             } | 
					
						
							|  |  |  |             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); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (errors) { | 
					
						
							|  |  |  |       if (type === 'json') { | 
					
						
							|  |  |  |         return res.status(403).send({ errors }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       log('errors', errors); | 
					
						
							|  |  |  |       return res.sendStatus(403); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |     return req.user.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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 16:41:03 -07:00
										 |  |  |         const { | 
					
						
							|  |  |  |           alreadyCompleted, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |           updateData | 
					
						
							| 
									
										
										
										
											2016-08-11 16:41:03 -07:00
										 |  |  |         } = buildUserUpdate( | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |           req.user, | 
					
						
							|  |  |  |           id, | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |           { id, solution, completedDate, files }, | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |           timezone | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const user = req.user; | 
					
						
							|  |  |  |         const points = alreadyCompleted ? user.points : user.points + 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return user.update$(updateData) | 
					
						
							|  |  |  |           .doOnNext(({ count }) => log('%s documents updated', count)) | 
					
						
							|  |  |  |           .map(() => { | 
					
						
							|  |  |  |             if (type === 'json') { | 
					
						
							|  |  |  |               return res.json({ | 
					
						
							|  |  |  |                 points, | 
					
						
							| 
									
										
										
										
											2016-08-11 16:41:03 -07:00
										 |  |  |                 alreadyCompleted, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |                 completedDate | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |               }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return res.sendStatus(200); | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       .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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-10 22:10:06 -08:00
										 |  |  |     const completedChallenge = _.pick( | 
					
						
							|  |  |  |       body, | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |       [ '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
 | 
					
						
							|  |  |  |       ( | 
					
						
							|  |  |  |         completedChallenge.challengeType === 4 && | 
					
						
							|  |  |  |         !completedChallenge.githubLink | 
					
						
							|  |  |  |       ) | 
					
						
							|  |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2018-01-12 14:16:33 -08:00
										 |  |  |       req.flash( | 
					
						
							|  |  |  |         'danger', | 
					
						
							|  |  |  |         'You haven\'t supplied the necessary URLs for us to inspect your work.' | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-19 22:30:01 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |     return user.getCompletedChallenges$() | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  |       .flatMap(() => { | 
					
						
							|  |  |  |         const { | 
					
						
							|  |  |  |           alreadyCompleted, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |           updateData | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  |         } = buildUserUpdate(user, completedChallenge.id, completedChallenge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return user.update$(updateData) | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |           .doOnNext(() => user.manualReload()) | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  |           .doOnNext(({ count }) => log('%s documents updated', count)) | 
					
						
							|  |  |  |           .doOnNext(() => { | 
					
						
							|  |  |  |             if (type === 'json') { | 
					
						
							|  |  |  |               return res.send({ | 
					
						
							|  |  |  |                 alreadyCompleted, | 
					
						
							|  |  |  |                 points: alreadyCompleted ? user.points : user.points + 1, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |                 completedDate: completedChallenge.completedDate | 
					
						
							| 
									
										
										
										
											2017-01-26 21:07:22 -08:00
										 |  |  |               }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return res.status(200).send(true); | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       .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; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const completedChallenge = _.pick( | 
					
						
							|  |  |  |       body, | 
					
						
							|  |  |  |       [ 'id', 'solution' ] | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     completedChallenge.completedDate = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |     return user.getCompletedChallenges$() | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |       .flatMap(() => { | 
					
						
							|  |  |  |         const { | 
					
						
							|  |  |  |           alreadyCompleted, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |           updateData | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |         } = buildUserUpdate(user, completedChallenge.id, completedChallenge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return user.update$(updateData) | 
					
						
							|  |  |  |           .doOnNext(({ count }) => log('%s documents updated', count)) | 
					
						
							|  |  |  |           .doOnNext(() => { | 
					
						
							|  |  |  |             if (type === 'json') { | 
					
						
							|  |  |  |               return res.send({ | 
					
						
							|  |  |  |                 alreadyCompleted, | 
					
						
							| 
									
										
										
										
											2016-08-11 16:41:03 -07:00
										 |  |  |                 points: alreadyCompleted ? user.points : user.points + 1, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |                 completedDate: completedChallenge.completedDate | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |               }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             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
										 |  |  | 
 | 
					
						
							|  |  |  |   function redirectToCurrentChallenge(req, res, next) { | 
					
						
							|  |  |  |     const { user } = req; | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |     const challengeId = user && user.currentChallengeId; | 
					
						
							|  |  |  |     return getChallengeById(map, challengeId) | 
					
						
							|  |  |  |       .map(challenge => { | 
					
						
							| 
									
										
										
										
											2018-05-14 08:34:51 +01:00
										 |  |  |         const { block, dashedName, superBlock } = challenge; | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |         if (!dashedName || !block) { | 
					
						
							| 
									
										
										
										
											2016-08-05 14:49:23 -07:00
										 |  |  |           // this should normally not be hit if database is properly seeded
 | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |           throw new Error(dedent`
 | 
					
						
							| 
									
										
										
										
											2017-10-18 00:23:14 +08:00
										 |  |  |             Attempted to find '${dashedName}' | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |             from '${ challengeId || 'no challenge id found'}' | 
					
						
							| 
									
										
										
										
											2016-08-05 14:49:23 -07:00
										 |  |  |             but came up empty. | 
					
						
							|  |  |  |             db may not be properly seeded. | 
					
						
							| 
									
										
										
										
											2017-08-03 20:45:36 -07:00
										 |  |  |           `);
 | 
					
						
							| 
									
										
										
										
											2016-08-05 14:49:23 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-05-14 08:34:51 +01:00
										 |  |  |         return `${learnURL}/${dasherize(superBlock)}/${block}/${dashedName}`; | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |       }) | 
					
						
							|  |  |  |       .subscribe( | 
					
						
							| 
									
										
										
										
											2018-05-15 06:12:05 +01:00
										 |  |  |         redirect => res.redirect(redirect || learnURL), | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  |         next | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											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
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-08-04 10:49:37 -07:00
										 |  |  | } |