| 
									
										
										
										
											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'; | 
					
						
							| 
									
										
										
										
											2020-08-21 18:55:28 +02:00
										 |  |  | import { isEmpty, pick, omit, find, uniqBy } 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'; | 
					
						
							| 
									
										
										
										
											2019-09-25 20:16:08 +02:00
										 |  |  | import { dasherize } from '../../../utils/slugs'; | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  | import { fixCompletedChallengeItem } from '../../common/utils'; | 
					
						
							| 
									
										
										
										
											2020-06-23 10:01:21 +02:00
										 |  |  | import { getChallenges } from '../utils/get-curriculum'; | 
					
						
							| 
									
										
										
										
											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(); | 
					
						
							| 
									
										
										
										
											2020-06-23 10:01:21 +02:00
										 |  |  |   const challengeUrlResolver = await createChallengeUrlResolver( | 
					
						
							|  |  |  |     await getChallenges() | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   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); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   app.use(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}`; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-23 10:01:21 +02:00
										 |  |  | // this is only called once during boot, so it can be slow.
 | 
					
						
							|  |  |  | export function getFirstChallenge(allChallenges) { | 
					
						
							|  |  |  |   const first = allChallenges.find( | 
					
						
							|  |  |  |     ({ challengeOrder, superOrder, order }) => | 
					
						
							|  |  |  |       challengeOrder === 0 && superOrder === 1 && order === 0 | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return first ? buildChallengeUrl(first) : '/learn'; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getChallengeById(allChallenges, targetId) { | 
					
						
							|  |  |  |   return allChallenges.find(({ id }) => id === targetId); | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  | export async function createChallengeUrlResolver( | 
					
						
							| 
									
										
										
										
											2020-06-23 10:01:21 +02:00
										 |  |  |   allChallenges, | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   { _getFirstChallenge = getFirstChallenge } = {} | 
					
						
							|  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   const cache = new Map(); | 
					
						
							| 
									
										
										
										
											2020-06-23 10:01:21 +02:00
										 |  |  |   const firstChallenge = _getFirstChallenge(allChallenges); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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); | 
					
						
							| 
									
										
										
										
											2020-06-23 10:01:21 +02:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       return new Promise(resolve => { | 
					
						
							|  |  |  |         if (cache.has(id)) { | 
					
						
							|  |  |  |           resolve(cache.get(id)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const challenge = getChallengeById(allChallenges, id); | 
					
						
							|  |  |  |         if (isEmpty(challenge)) { | 
					
						
							|  |  |  |           resolve(firstChallenge); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           const challengeUrl = buildChallengeUrl(challenge); | 
					
						
							|  |  |  |           cache.set(id, challengeUrl); | 
					
						
							|  |  |  |           resolve(challengeUrl); | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-06-23 10:01:21 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-10-28 06:18:13 +00:00
										 |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-10-24 17:30:23 +05:30
										 |  |  |   const isValidChallengeCompletionErrorMsg = { | 
					
						
							|  |  |  |     type: 'error', | 
					
						
							|  |  |  |     message: 'That does not appear to be a valid challenge submission.' | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |   if (!ObjectID.isValid(id)) { | 
					
						
							|  |  |  |     log('isObjectId', id, ObjectID.isValid(id)); | 
					
						
							| 
									
										
										
										
											2019-10-24 17:30:23 +05:30
										 |  |  |     return res.status(403).json(isValidChallengeCompletionErrorMsg); | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											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-10-24 17:30:23 +05:30
										 |  |  |     return res.status(403).json(isValidChallengeCompletionErrorMsg); | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |   if ('solution' in req.body && !isURL(solution)) { | 
					
						
							|  |  |  |     log('isObjectId', id, ObjectID.isValid(id)); | 
					
						
							| 
									
										
										
										
											2019-10-24 17:30:23 +05:30
										 |  |  |     return res.status(403).json(isValidChallengeCompletionErrorMsg); | 
					
						
							| 
									
										
										
										
											2019-02-23 16:12:50 +00:00
										 |  |  |   } | 
					
						
							|  |  |  |   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-12-20 08:00:33 -06:00
										 |  |  |   if (!completedChallenge.solution) { | 
					
						
							| 
									
										
										
										
											2019-10-24 17:30:23 +05:30
										 |  |  |     return res.status(403).json({ | 
					
						
							|  |  |  |       type: 'error', | 
					
						
							|  |  |  |       message: | 
					
						
							|  |  |  |         'You have not provided the valid links for us to inspect your work.' | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-02-22 11:49:12 +00:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											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}`); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } |