| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  | /** | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Any ref to fixCompletedChallengesItem should be removed post | 
					
						
							|  |  |  |  * a db migration to fix all completedChallenges | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  | import { Observable } from 'rx'; | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  | import uuid from 'uuid/v4'; | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  | import moment from 'moment'; | 
					
						
							| 
									
										
										
										
											2015-12-28 12:41:37 -08:00
										 |  |  | import dedent from 'dedent'; | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  | import debugFactory from 'debug'; | 
					
						
							| 
									
										
										
										
											2016-05-12 15:48:34 -07:00
										 |  |  | import { isEmail } from 'validator'; | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  | import _ from 'lodash'; | 
					
						
							| 
									
										
										
										
											2018-08-03 01:28:49 +05:30
										 |  |  | import generate from 'nanoid/generate'; | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  | import { apiLocation } from '../../../config/env'; | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  | import { | 
					
						
							|  |  |  |   fixCompletedChallengeItem, | 
					
						
							|  |  |  |   getEncodedEmail, | 
					
						
							|  |  |  |   getWaitMessage, | 
					
						
							|  |  |  |   renderEmailChangeEmail, | 
					
						
							|  |  |  |   renderSignUpEmail, | 
					
						
							|  |  |  |   renderSignInEmail | 
					
						
							|  |  |  | } from '../utils'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 11:39:07 -07:00
										 |  |  | import { blacklistedUsernames } from '../../server/utils/constants.js'; | 
					
						
							|  |  |  | import { wrapHandledError } from '../../server/utils/create-handled-error.js'; | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  | import { saveUser, observeMethod } from '../../server/utils/rx.js'; | 
					
						
							|  |  |  | import { getEmailSender } from '../../server/utils/url-utils'; | 
					
						
							| 
									
										
										
										
											2018-02-19 20:32:14 +00:00
										 |  |  | import { | 
					
						
							|  |  |  |   normaliseUserFields, | 
					
						
							|  |  |  |   getProgress, | 
					
						
							|  |  |  |   publicUserProps | 
					
						
							|  |  |  | } from '../../server/utils/publicUserProps'; | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  | import { | 
					
						
							|  |  |  |   setAccessTokenToResponse, | 
					
						
							|  |  |  |   removeCookies | 
					
						
							|  |  |  | } from '../../server/utils/getSetAccessToken'; | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  | const log = debugFactory('fcc:models:user'); | 
					
						
							| 
									
										
										
										
											2015-08-15 12:19:36 -07:00
										 |  |  | const BROWNIEPOINTS_TIMEOUT = [1, 'hour']; | 
					
						
							| 
									
										
										
										
											2018-08-03 01:28:49 +05:30
										 |  |  | const nanoidCharSet = | 
					
						
							|  |  |  |   '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  | const createEmailError = redirectTo => | 
					
						
							|  |  |  |   wrapHandledError(new Error('email format is invalid'), { | 
					
						
							| 
									
										
										
										
											2017-12-27 10:52:13 -08:00
										 |  |  |     type: 'info', | 
					
						
							|  |  |  |     message: 'Please check to make sure the email is a valid email address.', | 
					
						
							|  |  |  |     redirectTo | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-07-13 11:39:07 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | function destroyAll(id, Model) { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   return Observable.fromNodeCallback(Model.destroyAll, Model)({ userId: id }); | 
					
						
							| 
									
										
										
										
											2017-07-13 11:39:07 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-10 00:02:40 +01:00
										 |  |  | function ensureLowerCaseString(maybeString) { | 
					
						
							|  |  |  |   return (maybeString && maybeString.toLowerCase()) || ''; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  | function buildCompletedChallengesUpdate(completedChallenges, project) { | 
					
						
							| 
									
										
										
										
											2018-03-05 14:15:30 +00:00
										 |  |  |   const key = Object.keys(project)[0]; | 
					
						
							|  |  |  |   const solutions = project[key]; | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |   const solutionKeys = Object.keys(solutions); | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |   const currentCompletedChallenges = [ | 
					
						
							|  |  |  |     ...completedChallenges.map(fixCompletedChallengeItem) | 
					
						
							|  |  |  |   ]; | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   const currentCompletedProjects = currentCompletedChallenges.filter(({ id }) => | 
					
						
							|  |  |  |     solutionKeys.includes(id) | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |   const now = Date.now(); | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |   const update = solutionKeys.reduce((update, currentId) => { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     const indexOfCurrentId = _.findIndex(update, ({ id }) => id === currentId); | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |     const isCurrentlyCompleted = indexOfCurrentId !== -1; | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |     if (isCurrentlyCompleted) { | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |       update[indexOfCurrentId] = { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         ..._.find(update, ({ id }) => id === currentId), | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |         solution: solutions[currentId] | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |     if (!isCurrentlyCompleted) { | 
					
						
							|  |  |  |       return [ | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |         ...update, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |         { | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |           id: currentId, | 
					
						
							| 
									
										
										
										
											2018-03-05 14:15:30 +00:00
										 |  |  |           solution: solutions[currentId], | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |           challengeType: 3, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |           completedDate: now | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |       ]; | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |     } | 
					
						
							|  |  |  |     return update; | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |   }, currentCompletedProjects); | 
					
						
							|  |  |  |   const updatedExisting = _.uniqBy( | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     [...update, ...currentCompletedChallenges], | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |     'id' | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |   return { | 
					
						
							|  |  |  |     updated: updatedExisting, | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     isNewCompletionCount: updatedExisting.length - completedChallenges.length | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function isTheSame(val1, val2) { | 
					
						
							|  |  |  |   return val1 === val2; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  | function getAboutProfile({ | 
					
						
							|  |  |  |   username, | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |   githubProfile: github, | 
					
						
							|  |  |  |   progressTimestamps = [], | 
					
						
							|  |  |  |   bio | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  | }) { | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     username, | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |     github, | 
					
						
							|  |  |  |     browniePoints: progressTimestamps.length, | 
					
						
							|  |  |  |     bio | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function nextTick(fn) { | 
					
						
							|  |  |  |   return process.nextTick(fn); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2015-06-11 19:11:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  | const getRandomNumber = () => Math.random(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function populateRequiredFields(user) { | 
					
						
							|  |  |  |   user.username = user.username.trim().toLowerCase(); | 
					
						
							|  |  |  |   user.email = | 
					
						
							|  |  |  |     typeof user.email === 'string' | 
					
						
							|  |  |  |       ? user.email.trim().toLowerCase() | 
					
						
							|  |  |  |       : user.email; | 
					
						
							| 
									
										
										
										
											2017-12-27 11:16:53 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  |   if (!user.progressTimestamps) { | 
					
						
							|  |  |  |     user.progressTimestamps = []; | 
					
						
							| 
									
										
										
										
											2016-10-26 13:22:33 +00:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-12-27 11:16:53 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  |   if (user.progressTimestamps.length === 0) { | 
					
						
							|  |  |  |     user.progressTimestamps.push(Date.now()); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-12-27 11:16:53 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  |   if (!user.externalId) { | 
					
						
							|  |  |  |     user.externalId = uuid(); | 
					
						
							| 
									
										
										
										
											2017-12-27 11:16:53 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  |   if (!user.unsubscribeId) { | 
					
						
							|  |  |  |     user.unsubscribeId = generate(nanoidCharSet, 20); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return; | 
					
						
							| 
									
										
										
										
											2017-12-27 11:16:53 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  | export default function(User) { | 
					
						
							| 
									
										
										
										
											2015-06-12 11:38:00 -07:00
										 |  |  |   // set salt factor for passwords
 | 
					
						
							|  |  |  |   User.settings.saltWorkFactor = 5; | 
					
						
							| 
									
										
										
										
											2015-08-15 12:19:36 -07:00
										 |  |  |   // set user.rand to random number
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   User.definition.rawProperties.rand.default = getRandomNumber; | 
					
						
							|  |  |  |   User.definition.properties.rand.default = getRandomNumber; | 
					
						
							| 
									
										
										
										
											2016-04-21 20:35:19 -07:00
										 |  |  |   // increase user accessToken ttl to 900 days
 | 
					
						
							|  |  |  |   User.settings.ttl = 900 * 24 * 60 * 60 * 1000; | 
					
						
							| 
									
										
										
										
											2015-06-12 13:54:38 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-12 16:23:20 -07:00
										 |  |  |   // username should not be in blacklist
 | 
					
						
							|  |  |  |   User.validatesExclusionOf('username', { | 
					
						
							| 
									
										
										
										
											2016-04-15 23:48:02 +08:00
										 |  |  |     in: blacklistedUsernames, | 
					
						
							| 
									
										
										
										
											2015-06-12 16:23:20 -07:00
										 |  |  |     message: 'is taken' | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // username should be unique
 | 
					
						
							|  |  |  |   User.validatesUniquenessOf('username'); | 
					
						
							| 
									
										
										
										
											2015-08-17 23:57:38 -07:00
										 |  |  |   User.settings.emailVerificationRequired = false; | 
					
						
							| 
									
										
										
										
											2015-06-12 13:54:38 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |   User.on('dataSourceAttached', () => { | 
					
						
							|  |  |  |     User.findOne$ = Observable.fromNodeCallback(User.findOne, User); | 
					
						
							|  |  |  |     User.count$ = Observable.fromNodeCallback(User.count, User); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     User.create$ = Observable.fromNodeCallback(User.create.bind(User)); | 
					
						
							| 
									
										
										
										
											2017-04-24 00:37:10 +05:30
										 |  |  |     User.prototype.createAccessToken$ = Observable.fromNodeCallback( | 
					
						
							|  |  |  |       User.prototype.createAccessToken | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2016-02-09 14:33:25 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-28 20:37:10 -08:00
										 |  |  |   User.observe('before save', function(ctx) { | 
					
						
							|  |  |  |     const beforeCreate = Observable.of(ctx) | 
					
						
							|  |  |  |       .filter(({ isNewInstance }) => isNewInstance) | 
					
						
							|  |  |  |       // User.create
 | 
					
						
							|  |  |  |       .map(({ instance }) => instance) | 
					
						
							|  |  |  |       .flatMap(user => { | 
					
						
							|  |  |  |         // note(berks): we now require all new users to supply an email
 | 
					
						
							|  |  |  |         // this was not always the case
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         if (typeof user.email !== 'string' || !isEmail(user.email)) { | 
					
						
							| 
									
										
										
										
											2017-12-28 20:37:10 -08:00
										 |  |  |           throw createEmailError(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // assign random username to new users
 | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |         user.username = 'fcc' + uuid(); | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  |         populateRequiredFields(user); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         return Observable.fromPromise(User.doesExist(null, user.email)).do( | 
					
						
							|  |  |  |           exists => { | 
					
						
							| 
									
										
										
										
											2017-12-28 20:37:10 -08:00
										 |  |  |             if (exists) { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |               throw wrapHandledError(new Error('user already exists'), { | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  |                 redirectTo: `${apiLocation}/signin`, | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |                 message: dedent`
 | 
					
						
							| 
									
										
										
										
											2017-12-28 20:37:10 -08:00
										 |  |  |         The ${user.email} email address is already associated with an account. | 
					
						
							|  |  |  |         Try signing in with it here instead. | 
					
						
							|  |  |  |                   `
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |               }); | 
					
						
							| 
									
										
										
										
											2017-12-28 20:37:10 -08:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |           } | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2017-07-13 11:39:07 -07:00
										 |  |  |       }) | 
					
						
							| 
									
										
										
										
											2017-12-28 20:37:10 -08:00
										 |  |  |       .ignoreElements(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const updateOrSave = Observable.of(ctx) | 
					
						
							|  |  |  |       // not new
 | 
					
						
							|  |  |  |       .filter(({ isNewInstance }) => !isNewInstance) | 
					
						
							|  |  |  |       .map(({ instance }) => instance) | 
					
						
							|  |  |  |       // is update or save user
 | 
					
						
							|  |  |  |       .filter(Boolean) | 
					
						
							|  |  |  |       .do(user => { | 
					
						
							| 
									
										
										
										
											2018-11-16 04:29:02 -07:00
										 |  |  |         // Some old accounts will not have emails associated with them
 | 
					
						
							| 
									
										
										
										
											2017-12-28 20:37:10 -08:00
										 |  |  |         // we verify only if the email field is populated
 | 
					
						
							|  |  |  |         if (user.email && !isEmail(user.email)) { | 
					
						
							|  |  |  |           throw createEmailError(); | 
					
						
							| 
									
										
										
										
											2017-07-13 11:39:07 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  |         populateRequiredFields(user); | 
					
						
							| 
									
										
										
										
											2017-12-28 20:37:10 -08:00
										 |  |  |       }) | 
					
						
							|  |  |  |       .ignoreElements(); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     return Observable.merge(beforeCreate, updateOrSave).toPromise(); | 
					
						
							| 
									
										
										
										
											2015-08-05 18:51:15 -07:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 11:39:07 -07:00
										 |  |  |   // remove lingering user identities before deleting user
 | 
					
						
							|  |  |  |   User.observe('before delete', function(ctx, next) { | 
					
						
							|  |  |  |     const UserIdentity = User.app.models.UserIdentity; | 
					
						
							|  |  |  |     const UserCredential = User.app.models.UserCredential; | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |     log('removing user', ctx.where); | 
					
						
							| 
									
										
										
										
											2017-07-13 11:39:07 -07:00
										 |  |  |     var id = ctx.where && ctx.where.id ? ctx.where.id : null; | 
					
						
							|  |  |  |     if (!id) { | 
					
						
							|  |  |  |       return next(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return Observable.combineLatest( | 
					
						
							|  |  |  |       destroyAll(id, UserIdentity), | 
					
						
							|  |  |  |       destroyAll(id, UserCredential), | 
					
						
							|  |  |  |       function(identData, credData) { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |           identData: identData, | 
					
						
							|  |  |  |           credData: credData | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     ).subscribe( | 
					
						
							|  |  |  |       function(data) { | 
					
						
							|  |  |  |         log('deleted', data); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       function(err) { | 
					
						
							|  |  |  |         log('error deleting user %s stuff', id, err); | 
					
						
							|  |  |  |         next(err); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       function() { | 
					
						
							|  |  |  |         log('user stuff deleted for user %s', id); | 
					
						
							|  |  |  |         next(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2017-07-13 11:39:07 -07:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |   log('setting up user hooks'); | 
					
						
							| 
									
										
										
										
											2017-12-29 10:49:49 -08:00
										 |  |  |   // overwrite lb confirm
 | 
					
						
							|  |  |  |   User.confirm = function(uid, token, redirectTo) { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     return this.findById(uid).then(user => { | 
					
						
							|  |  |  |       if (!user) { | 
					
						
							|  |  |  |         throw wrapHandledError(new Error(`User not found: ${uid}`), { | 
					
						
							|  |  |  |           // standard oops
 | 
					
						
							|  |  |  |           type: 'info', | 
					
						
							|  |  |  |           redirectTo | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (user.verificationToken !== token) { | 
					
						
							|  |  |  |         throw wrapHandledError(new Error(`Invalid token: ${token}`), { | 
					
						
							|  |  |  |           type: 'info', | 
					
						
							|  |  |  |           message: dedent`
 | 
					
						
							| 
									
										
										
										
											2017-12-29 10:49:49 -08:00
										 |  |  |                 Looks like you have clicked an invalid link. | 
					
						
							|  |  |  |                 Please sign in and request a fresh one. | 
					
						
							|  |  |  |               `,
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |           redirectTo | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return new Promise((resolve, reject) => | 
					
						
							|  |  |  |         user.updateAttributes( | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             email: user.newEmail, | 
					
						
							|  |  |  |             emailVerified: true, | 
					
						
							|  |  |  |             emailVerifyTTL: null, | 
					
						
							|  |  |  |             newEmail: null, | 
					
						
							|  |  |  |             verificationToken: null | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           err => { | 
					
						
							|  |  |  |             if (err) { | 
					
						
							|  |  |  |               return reject(err); | 
					
						
							| 
									
										
										
										
											2017-12-29 10:49:49 -08:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |             return resolve(); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2017-12-29 10:49:49 -08:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2015-08-25 22:27:01 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |   User.prototype.loginByRequest = function loginByRequest(req, res) { | 
					
						
							|  |  |  |     const { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       query: { emailChange } | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |     } = req; | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     const createToken = this.createAccessToken$().do(accessToken => { | 
					
						
							|  |  |  |       if (accessToken && accessToken.id) { | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  |         setAccessTokenToResponse({ accessToken }, req, res); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |     let data = { | 
					
						
							| 
									
										
										
										
											2017-12-29 11:28:42 -08:00
										 |  |  |       emailVerified: true, | 
					
						
							|  |  |  |       emailAuthLinkTTL: null, | 
					
						
							|  |  |  |       emailVerifyTTL: null | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |     }; | 
					
						
							|  |  |  |     if (emailChange && this.newEmail) { | 
					
						
							|  |  |  |       data = { | 
					
						
							|  |  |  |         ...data, | 
					
						
							|  |  |  |         email: this.newEmail, | 
					
						
							|  |  |  |         newEmail: null | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     const updateUser = new Promise((resolve, reject) => | 
					
						
							|  |  |  |       this.updateAttributes(data, err => { | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           return reject(err); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return resolve(); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2017-12-29 11:28:42 -08:00
										 |  |  |     return Observable.combineLatest( | 
					
						
							|  |  |  |       createToken, | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       Observable.fromPromise(updateUser), | 
					
						
							| 
									
										
										
										
											2017-12-29 11:28:42 -08:00
										 |  |  |       req.logIn(this), | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       accessToken => accessToken | 
					
						
							| 
									
										
										
										
											2017-12-29 11:28:42 -08:00
										 |  |  |     ); | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2015-06-16 00:27:32 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   User.afterRemote('logout', function({ req, res }, result, next) { | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  |     removeCookies(req, res); | 
					
						
							| 
									
										
										
										
											2015-06-11 19:11:07 -04:00
										 |  |  |     next(); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-28 13:52:28 -08:00
										 |  |  |   User.doesExist = function doesExist(username, email) { | 
					
						
							| 
									
										
										
										
											2016-05-12 15:48:34 -07:00
										 |  |  |     if (!username && (!email || !isEmail(email))) { | 
					
						
							| 
									
										
										
										
											2015-12-28 13:52:28 -08:00
										 |  |  |       return Promise.resolve(false); | 
					
						
							| 
									
										
										
										
											2015-06-11 19:11:07 -04:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |     log('checking existence'); | 
					
						
							| 
									
										
										
										
											2015-06-11 16:46:31 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // check to see if username is on blacklist
 | 
					
						
							|  |  |  |     if (username && blacklistedUsernames.indexOf(username) !== -1) { | 
					
						
							| 
									
										
										
										
											2015-12-28 13:52:28 -08:00
										 |  |  |       return Promise.resolve(true); | 
					
						
							| 
									
										
										
										
											2015-06-11 16:46:31 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-11 19:11:07 -04:00
										 |  |  |     var where = {}; | 
					
						
							|  |  |  |     if (username) { | 
					
						
							|  |  |  |       where.username = username.toLowerCase(); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       where.email = email ? email.toLowerCase() : email; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |     log('where', where); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     return User.count(where).then(count => count > 0); | 
					
						
							| 
									
										
										
										
											2015-06-11 19:11:07 -04:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   User.remoteMethod('doesExist', { | 
					
						
							|  |  |  |     description: 'checks whether a user exists using email or username', | 
					
						
							|  |  |  |     accepts: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'username', | 
					
						
							|  |  |  |         type: 'string' | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'email', | 
					
						
							|  |  |  |         type: 'string' | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     returns: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'exists', | 
					
						
							|  |  |  |         type: 'boolean' | 
					
						
							| 
									
										
										
										
											2015-06-11 19:11:07 -04:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     ], | 
					
						
							|  |  |  |     http: { | 
					
						
							|  |  |  |       path: '/exists', | 
					
						
							|  |  |  |       verb: 'get' | 
					
						
							| 
									
										
										
										
											2015-06-11 19:11:07 -04:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2015-07-29 11:32:16 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   User.about = function about(username, cb) { | 
					
						
							|  |  |  |     if (!username) { | 
					
						
							|  |  |  |       // Zalgo!!
 | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |       return nextTick(() => { | 
					
						
							| 
									
										
										
										
											2018-06-25 20:20:40 +01:00
										 |  |  |         cb(null, {}); | 
					
						
							| 
									
										
										
										
											2015-07-29 11:32:16 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |     return User.findOne({ where: { username } }, (err, user) => { | 
					
						
							|  |  |  |       if (err) { | 
					
						
							|  |  |  |         return cb(err); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (!user || user.username !== username) { | 
					
						
							| 
									
										
										
										
											2018-06-25 20:20:40 +01:00
										 |  |  |         return cb(null, {}); | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |       const aboutUser = getAboutProfile(user); | 
					
						
							|  |  |  |       return cb(null, aboutUser); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2015-07-29 11:32:16 -07:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   User.remoteMethod('about', { | 
					
						
							|  |  |  |     description: 'get public info about user', | 
					
						
							|  |  |  |     accepts: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'username', | 
					
						
							|  |  |  |         type: 'string' | 
					
						
							| 
									
										
										
										
											2015-07-29 11:32:16 -07:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     ], | 
					
						
							|  |  |  |     returns: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'about', | 
					
						
							|  |  |  |         type: 'object' | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     http: { | 
					
						
							|  |  |  |       path: '/about', | 
					
						
							|  |  |  |       verb: 'get' | 
					
						
							| 
									
										
										
										
											2015-07-29 11:32:16 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-29 09:59:27 -08:00
										 |  |  |   User.prototype.createAuthToken = function createAuthToken({ ttl } = {}) { | 
					
						
							|  |  |  |     return Observable.fromNodeCallback( | 
					
						
							|  |  |  |       this.authTokens.create.bind(this.authTokens) | 
					
						
							|  |  |  |     )({ ttl }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-07 22:35:06 +01:00
										 |  |  |   User.prototype.createDonation = function createDonation(donation = {}) { | 
					
						
							|  |  |  |     return Observable.fromNodeCallback( | 
					
						
							|  |  |  |       this.donations.create.bind(this.donations) | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     )(donation).do(() => | 
					
						
							|  |  |  |       this.updateAttributes({ | 
					
						
							|  |  |  |         isDonating: true, | 
					
						
							|  |  |  |         donationEmails: [...(this.donationEmails || []), donation.email] | 
					
						
							| 
									
										
										
										
											2018-06-07 22:35:06 +01:00
										 |  |  |       }) | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2018-06-07 22:35:06 +01:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |   function requestCompletedChallenges() { | 
					
						
							|  |  |  |     return this.getCompletedChallenges$(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   User.prototype.requestCompletedChallenges = requestCompletedChallenges; | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  |   function requestAuthEmail(isSignUp, newEmail) { | 
					
						
							|  |  |  |     return Observable.defer(() => { | 
					
						
							|  |  |  |       const messageOrNull = getWaitMessage(this.emailAuthLinkTTL); | 
					
						
							|  |  |  |       if (messageOrNull) { | 
					
						
							|  |  |  |         throw wrapHandledError(new Error('request is throttled'), { | 
					
						
							|  |  |  |           type: 'info', | 
					
						
							|  |  |  |           message: messageOrNull | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // create a temporary access token with ttl for 15 minutes
 | 
					
						
							|  |  |  |       return this.createAuthToken({ ttl: 15 * 60 * 1000 }); | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |       .flatMap(token => { | 
					
						
							|  |  |  |         let renderAuthEmail = renderSignInEmail; | 
					
						
							|  |  |  |         let subject = 'Your sign in link for freeCodeCamp.org'; | 
					
						
							|  |  |  |         if (isSignUp) { | 
					
						
							|  |  |  |           renderAuthEmail = renderSignUpEmail; | 
					
						
							|  |  |  |           subject = 'Your sign in link for your new freeCodeCamp.org account'; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (newEmail) { | 
					
						
							|  |  |  |           renderAuthEmail = renderEmailChangeEmail; | 
					
						
							|  |  |  |           subject = dedent`
 | 
					
						
							|  |  |  |             Please confirm your updated email address for freeCodeCamp.org | 
					
						
							|  |  |  |           `;
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const { id: loginToken, created: emailAuthLinkTTL } = token; | 
					
						
							|  |  |  |         const loginEmail = getEncodedEmail(newEmail ? newEmail : null); | 
					
						
							|  |  |  |         const host = apiLocation; | 
					
						
							|  |  |  |         const mailOptions = { | 
					
						
							|  |  |  |           type: 'email', | 
					
						
							|  |  |  |           to: newEmail ? newEmail : this.email, | 
					
						
							|  |  |  |           from: getEmailSender(), | 
					
						
							|  |  |  |           subject, | 
					
						
							|  |  |  |           text: renderAuthEmail({ | 
					
						
							|  |  |  |             host, | 
					
						
							|  |  |  |             loginEmail, | 
					
						
							|  |  |  |             loginToken, | 
					
						
							|  |  |  |             emailChange: !!newEmail | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         const userUpdate = new Promise((resolve, reject) => | 
					
						
							|  |  |  |           this.updateAttributes({ emailAuthLinkTTL }, err => { | 
					
						
							|  |  |  |             if (err) { | 
					
						
							|  |  |  |               return reject(err); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return resolve(); | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return Observable.forkJoin( | 
					
						
							|  |  |  |           User.email.send$(mailOptions), | 
					
						
							|  |  |  |           Observable.fromPromise(userUpdate) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       .map( | 
					
						
							|  |  |  |         () => | 
					
						
							|  |  |  |           'Check your email and click the link we sent you to confirm' + | 
					
						
							|  |  |  |           ' your new email address.' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   User.prototype.requestAuthEmail = requestAuthEmail; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-10 00:02:40 +01:00
										 |  |  |   function requestUpdateEmail(requestedEmail) { | 
					
						
							|  |  |  |     const newEmail = ensureLowerCaseString(requestedEmail); | 
					
						
							|  |  |  |     const currentEmail = ensureLowerCaseString(this.email); | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  |     const isOwnEmail = isTheSame(newEmail, currentEmail); | 
					
						
							| 
									
										
										
										
											2019-09-10 00:02:40 +01:00
										 |  |  |     const isResendUpdateToSameEmail = isTheSame( | 
					
						
							|  |  |  |       newEmail, | 
					
						
							|  |  |  |       ensureLowerCaseString(this.newEmail) | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  |     const isLinkSentWithinLimit = getWaitMessage(this.emailVerifyTTL); | 
					
						
							|  |  |  |     const isVerifiedEmail = this.emailVerified; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (isOwnEmail && isVerifiedEmail) { | 
					
						
							|  |  |  |       // email is already associated and verified with this account
 | 
					
						
							|  |  |  |       throw wrapHandledError(new Error('email is already verified'), { | 
					
						
							|  |  |  |         type: 'info', | 
					
						
							|  |  |  |         message: `
 | 
					
						
							|  |  |  |             ${newEmail} is already associated with this account. | 
					
						
							|  |  |  |             You can update a new email address instead.`
 | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (isResendUpdateToSameEmail && isLinkSentWithinLimit) { | 
					
						
							|  |  |  |       // trying to update with the same newEmail and
 | 
					
						
							|  |  |  |       // confirmation email is still valid
 | 
					
						
							|  |  |  |       throw wrapHandledError(new Error(), { | 
					
						
							|  |  |  |         type: 'info', | 
					
						
							|  |  |  |         message: dedent`
 | 
					
						
							|  |  |  |           We have already sent an email confirmation request to ${newEmail}. | 
					
						
							|  |  |  |           ${isLinkSentWithinLimit}`
 | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!isEmail('' + newEmail)) { | 
					
						
							|  |  |  |       throw createEmailError(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // newEmail is not associated with this user, and
 | 
					
						
							|  |  |  |     // this attempt to change email is the first or
 | 
					
						
							|  |  |  |     // previous attempts have expired
 | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       !isOwnEmail || | 
					
						
							|  |  |  |       (isOwnEmail && !isVerifiedEmail) || | 
					
						
							|  |  |  |       (isResendUpdateToSameEmail && !isLinkSentWithinLimit) | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       const updateConfig = { | 
					
						
							|  |  |  |         newEmail, | 
					
						
							|  |  |  |         emailVerified: false, | 
					
						
							|  |  |  |         emailVerifyTTL: new Date() | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // defer prevents the promise from firing prematurely (before subscribe)
 | 
					
						
							|  |  |  |       return Observable.defer(() => User.doesExist(null, newEmail)) | 
					
						
							|  |  |  |         .do(exists => { | 
					
						
							|  |  |  |           if (exists && !isOwnEmail) { | 
					
						
							|  |  |  |             // newEmail is not associated with this account,
 | 
					
						
							|  |  |  |             // but is associated with different account
 | 
					
						
							|  |  |  |             throw wrapHandledError(new Error('email already in use'), { | 
					
						
							|  |  |  |               type: 'info', | 
					
						
							|  |  |  |               message: `${newEmail} is already associated with another account.` | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         .flatMap(() => { | 
					
						
							|  |  |  |           const updatePromise = new Promise((resolve, reject) => | 
					
						
							|  |  |  |             this.updateAttributes(updateConfig, err => { | 
					
						
							|  |  |  |               if (err) { | 
					
						
							|  |  |  |                 return reject(err); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               return resolve(); | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |           return Observable.forkJoin( | 
					
						
							|  |  |  |             Observable.fromPromise(updatePromise), | 
					
						
							|  |  |  |             this.requestAuthEmail(false, newEmail), | 
					
						
							|  |  |  |             (_, message) => message | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       return 'Something unexpected happened while updating your email.'; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-09-10 00:02:40 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   User.prototype.requestUpdateEmail = requestUpdateEmail; | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   User.prototype.requestUpdateFlags = async function requestUpdateFlags( | 
					
						
							|  |  |  |     values | 
					
						
							|  |  |  |   ) { | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |     const flagsToCheck = Object.keys(values); | 
					
						
							|  |  |  |     const valuesToCheck = _.pick({ ...this }, flagsToCheck); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     const flagsToUpdate = flagsToCheck.filter( | 
					
						
							|  |  |  |       flag => !isTheSame(values[flag], valuesToCheck[flag]) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     if (!flagsToUpdate.length) { | 
					
						
							|  |  |  |       return Observable.of( | 
					
						
							|  |  |  |         dedent`
 | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |         No property in | 
					
						
							|  |  |  |         ${JSON.stringify(flagsToCheck, null, 2)} | 
					
						
							|  |  |  |         will introduce a change in this user. | 
					
						
							|  |  |  |         `
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       ).map(() => dedent`Your settings have not been updated.`); | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     const userUpdateData = flagsToUpdate.reduce((data, currentFlag) => { | 
					
						
							|  |  |  |       data[currentFlag] = values[currentFlag]; | 
					
						
							|  |  |  |       return data; | 
					
						
							|  |  |  |     }, {}); | 
					
						
							|  |  |  |     log(userUpdateData); | 
					
						
							|  |  |  |     const userUpdate = new Promise((resolve, reject) => | 
					
						
							|  |  |  |       this.updateAttributes(userUpdateData, err => { | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           return reject(err); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return resolve(); | 
					
						
							| 
									
										
										
										
											2016-07-19 16:36:34 -07:00
										 |  |  |       }) | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     ); | 
					
						
							|  |  |  |     return Observable.fromPromise(userUpdate).map( | 
					
						
							|  |  |  |       () => dedent`
 | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |         We have successfully updated your account. | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       `
 | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2016-04-22 02:17:59 +05:30
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   User.prototype.updateMyPortfolio = function updateMyPortfolio( | 
					
						
							|  |  |  |     portfolioItem, | 
					
						
							|  |  |  |     deleteRequest | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     const currentPortfolio = this.portfolio.slice(0); | 
					
						
							|  |  |  |     const pIndex = _.findIndex( | 
					
						
							|  |  |  |       currentPortfolio, | 
					
						
							|  |  |  |       p => p.id === portfolioItem.id | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     let updatedPortfolio = []; | 
					
						
							|  |  |  |     if (deleteRequest) { | 
					
						
							|  |  |  |       updatedPortfolio = currentPortfolio.filter( | 
					
						
							|  |  |  |         p => p.id !== portfolioItem.id | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     } else if (pIndex === -1) { | 
					
						
							|  |  |  |       updatedPortfolio = currentPortfolio.concat([portfolioItem]); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       updatedPortfolio = [...currentPortfolio]; | 
					
						
							|  |  |  |       updatedPortfolio[pIndex] = { ...portfolioItem }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const userUpdate = new Promise((resolve, reject) => | 
					
						
							|  |  |  |       this.updateAttribute('portfolio', updatedPortfolio, err => { | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           return reject(err); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return resolve(); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return Observable.fromPromise(userUpdate).map( | 
					
						
							|  |  |  |       () => dedent`
 | 
					
						
							| 
									
										
										
										
											2018-03-09 13:39:44 -06:00
										 |  |  |           Your portfolio has been updated. | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         `
 | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   User.prototype.updateMyProjects = function updateMyProjects(project) { | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |     const updateData = { $set: {} }; | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |     return this.getCompletedChallenges$() | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |       .flatMap(() => { | 
					
						
							|  |  |  |         const { | 
					
						
							|  |  |  |           updated, | 
					
						
							|  |  |  |           isNewCompletionCount | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         } = buildCompletedChallengesUpdate(this.completedChallenges, project); | 
					
						
							| 
									
										
										
										
											2018-05-24 14:59:46 +01:00
										 |  |  |         updateData.$set.completedChallenges = updated; | 
					
						
							|  |  |  |         if (isNewCompletionCount) { | 
					
						
							|  |  |  |           let points = []; | 
					
						
							|  |  |  |           // give points a length of isNewCompletionCount
 | 
					
						
							|  |  |  |           points[isNewCompletionCount - 1] = true; | 
					
						
							|  |  |  |           updateData.$push = {}; | 
					
						
							|  |  |  |           updateData.$push.progressTimestamps = { | 
					
						
							|  |  |  |             $each: points.map(() => Date.now()) | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         const updatePromise = new Promise((resolve, reject) => | 
					
						
							|  |  |  |           this.updateAttributes(updateData, err => { | 
					
						
							|  |  |  |             if (err) { | 
					
						
							|  |  |  |               return reject(err); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return resolve(); | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return Observable.fromPromise(updatePromise); | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |       }) | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       .map( | 
					
						
							|  |  |  |         () => dedent`
 | 
					
						
							| 
									
										
										
										
											2018-03-09 13:39:44 -06:00
										 |  |  |         Your projects have been updated. | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       `
 | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-20 04:07:41 +01:00
										 |  |  |   User.prototype.updateMyProfileUI = function updateMyProfileUI(profileUI) { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     const newProfileUI = { | 
					
						
							|  |  |  |       ...this.profileUI, | 
					
						
							|  |  |  |       ...profileUI | 
					
						
							| 
									
										
										
										
											2018-05-20 04:07:41 +01:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     const profileUIUpdate = new Promise((resolve, reject) => | 
					
						
							|  |  |  |       this.updateAttribute('profileUI', newProfileUI, err => { | 
					
						
							|  |  |  |         if (err) { | 
					
						
							|  |  |  |           return reject(err); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return resolve(); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return Observable.fromPromise(profileUIUpdate).map( | 
					
						
							|  |  |  |       () => dedent`
 | 
					
						
							| 
									
										
										
										
											2018-05-20 04:07:41 +01:00
										 |  |  |         Your privacy settings have been updated. | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       `
 | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2018-05-20 04:07:41 +01:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |   User.prototype.updateMyUsername = function updateMyUsername(newUsername) { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     return Observable.defer(() => { | 
					
						
							| 
									
										
										
										
											2019-12-13 00:52:36 +05:30
										 |  |  |       const isOwnUsername = isTheSame(newUsername, this.username); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       if (isOwnUsername) { | 
					
						
							|  |  |  |         return Observable.of(dedent`
 | 
					
						
							| 
									
										
										
										
											2018-03-09 13:39:44 -06:00
										 |  |  |           ${newUsername} is already associated with this account. | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |           `);
 | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-12-13 00:52:36 +05:30
										 |  |  |       return Observable.fromPromise(User.doesExist(newUsername)); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     }).flatMap(boolOrMessage => { | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |       if (typeof boolOrMessage === 'string') { | 
					
						
							|  |  |  |         return Observable.of(boolOrMessage); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (boolOrMessage) { | 
					
						
							|  |  |  |         return Observable.of(dedent`
 | 
					
						
							| 
									
										
										
										
											2018-03-09 13:39:44 -06:00
										 |  |  |         ${newUsername} is already associated with a different account. | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |         `);
 | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-13 00:52:36 +05:30
										 |  |  |       const usernameUpdate = new Promise((resolve, reject) => | 
					
						
							|  |  |  |         this.updateAttribute('username', newUsername, err => { | 
					
						
							|  |  |  |           if (err) { | 
					
						
							|  |  |  |             return reject(err); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2019-12-13 00:52:36 +05:30
										 |  |  |           return resolve(); | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |       return Observable.fromPromise(usernameUpdate).map( | 
					
						
							|  |  |  |         () => dedent`
 | 
					
						
							| 
									
										
										
										
											2018-03-09 13:39:44 -06:00
										 |  |  |         Your username has been updated successfully. | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         `
 | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |     }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-20 15:19:39 +01:00
										 |  |  |   function prepUserForPublish(user, profileUI) { | 
					
						
							|  |  |  |     const { | 
					
						
							|  |  |  |       about, | 
					
						
							|  |  |  |       calendar, | 
					
						
							|  |  |  |       completedChallenges, | 
					
						
							|  |  |  |       isDonating, | 
					
						
							|  |  |  |       location, | 
					
						
							|  |  |  |       name, | 
					
						
							|  |  |  |       points, | 
					
						
							|  |  |  |       portfolio, | 
					
						
							|  |  |  |       streak, | 
					
						
							| 
									
										
										
										
											2018-08-02 18:39:51 +03:00
										 |  |  |       username, | 
					
						
							|  |  |  |       yearsTopContributor | 
					
						
							| 
									
										
										
										
											2018-06-20 15:19:39 +01:00
										 |  |  |     } = user; | 
					
						
							|  |  |  |     const { | 
					
						
							|  |  |  |       isLocked = true, | 
					
						
							|  |  |  |       showAbout = false, | 
					
						
							|  |  |  |       showCerts = false, | 
					
						
							|  |  |  |       showDonation = false, | 
					
						
							|  |  |  |       showHeatMap = false, | 
					
						
							|  |  |  |       showLocation = false, | 
					
						
							|  |  |  |       showName = false, | 
					
						
							|  |  |  |       showPoints = false, | 
					
						
							|  |  |  |       showPortfolio = false, | 
					
						
							|  |  |  |       showTimeLine = false | 
					
						
							|  |  |  |     } = profileUI; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (isLocked) { | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         isLocked, | 
					
						
							| 
									
										
										
										
											2018-08-15 15:30:27 +05:30
										 |  |  |         profileUI, | 
					
						
							| 
									
										
										
										
											2018-06-20 15:19:39 +01:00
										 |  |  |         username | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       ...user, | 
					
						
							|  |  |  |       about: showAbout ? about : '', | 
					
						
							|  |  |  |       calendar: showHeatMap ? calendar : {}, | 
					
						
							| 
									
										
										
										
											2019-12-20 07:49:02 -06:00
										 |  |  |       completedChallenges: (function() { | 
					
						
							|  |  |  |         if (showTimeLine) { | 
					
						
							|  |  |  |           return showCerts | 
					
						
							|  |  |  |             ? completedChallenges | 
					
						
							|  |  |  |             : completedChallenges.filter( | 
					
						
							|  |  |  |                 ({ challengeType }) => challengeType !== 7 | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           return []; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       })(), | 
					
						
							| 
									
										
										
										
											2018-06-20 15:19:39 +01:00
										 |  |  |       isDonating: showDonation ? isDonating : null, | 
					
						
							|  |  |  |       location: showLocation ? location : '', | 
					
						
							|  |  |  |       name: showName ? name : '', | 
					
						
							|  |  |  |       points: showPoints ? points : null, | 
					
						
							|  |  |  |       portfolio: showPortfolio ? portfolio : [], | 
					
						
							| 
									
										
										
										
											2018-08-02 18:39:51 +03:00
										 |  |  |       streak: showHeatMap ? streak : {}, | 
					
						
							|  |  |  |       yearsTopContributor: yearsTopContributor | 
					
						
							| 
									
										
										
										
											2018-06-20 15:19:39 +01:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-19 20:32:14 +00:00
										 |  |  |   User.getPublicProfile = function getPublicProfile(username, cb) { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     return User.findOne$({ where: { username } }) | 
					
						
							| 
									
										
										
										
											2018-02-19 20:32:14 +00:00
										 |  |  |       .flatMap(user => { | 
					
						
							|  |  |  |         if (!user) { | 
					
						
							|  |  |  |           return Observable.of({}); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-06-20 15:19:39 +01:00
										 |  |  |         const { | 
					
						
							|  |  |  |           completedChallenges, | 
					
						
							|  |  |  |           progressTimestamps, | 
					
						
							|  |  |  |           timezone, | 
					
						
							|  |  |  |           profileUI | 
					
						
							|  |  |  |         } = user; | 
					
						
							|  |  |  |         const allUser = { | 
					
						
							|  |  |  |           ..._.pick(user, publicUserProps), | 
					
						
							|  |  |  |           isGithub: !!user.githubProfile, | 
					
						
							| 
									
										
										
										
											2019-10-25 23:44:20 +02:00
										 |  |  |           isLinkedIn: !!user.linkedin, | 
					
						
							| 
									
										
										
										
											2018-06-20 15:19:39 +01:00
										 |  |  |           isTwitter: !!user.twitter, | 
					
						
							|  |  |  |           isWebsite: !!user.website, | 
					
						
							|  |  |  |           points: progressTimestamps.length, | 
					
						
							|  |  |  |           completedChallenges, | 
					
						
							|  |  |  |           ...getProgress(progressTimestamps, timezone), | 
					
						
							|  |  |  |           ...normaliseUserFields(user) | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const publicUser = prepUserForPublish(allUser, profileUI); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-19 20:32:14 +00:00
										 |  |  |         return Observable.of({ | 
					
						
							|  |  |  |           entities: { | 
					
						
							|  |  |  |             user: { | 
					
						
							|  |  |  |               [user.username]: { | 
					
						
							| 
									
										
										
										
											2018-06-20 15:19:39 +01:00
										 |  |  |                 ...publicUser | 
					
						
							| 
									
										
										
										
											2018-02-19 20:32:14 +00:00
										 |  |  |               } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           result: user.username | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }) | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |       .subscribe(user => cb(null, user), cb); | 
					
						
							| 
									
										
										
										
											2018-02-19 20:32:14 +00:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   User.remoteMethod('getPublicProfile', { | 
					
						
							|  |  |  |     accepts: { | 
					
						
							|  |  |  |       arg: 'username', | 
					
						
							|  |  |  |       type: 'string', | 
					
						
							|  |  |  |       required: true | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     returns: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'user', | 
					
						
							|  |  |  |         type: 'object', | 
					
						
							|  |  |  |         root: true | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     http: { | 
					
						
							|  |  |  |       path: '/get-public-profile', | 
					
						
							|  |  |  |       verb: 'GET' | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   User.giveBrowniePoints = function giveBrowniePoints( | 
					
						
							|  |  |  |     receiver, | 
					
						
							|  |  |  |     giver, | 
					
						
							|  |  |  |     data = {}, | 
					
						
							|  |  |  |     dev = false, | 
					
						
							|  |  |  |     cb | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     const findUser = observeMethod(User, 'findOne'); | 
					
						
							|  |  |  |     if (!receiver) { | 
					
						
							|  |  |  |       return nextTick(() => { | 
					
						
							|  |  |  |         cb(new TypeError(`receiver should be a string but got ${receiver}`)); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!giver) { | 
					
						
							|  |  |  |       return nextTick(() => { | 
					
						
							|  |  |  |         cb(new TypeError(`giver should be a string but got ${giver}`)); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     let temp = moment(); | 
					
						
							|  |  |  |     const browniePoints = temp.subtract | 
					
						
							|  |  |  |       .apply(temp, BROWNIEPOINTS_TIMEOUT) | 
					
						
							|  |  |  |       .valueOf(); | 
					
						
							|  |  |  |     const user$ = findUser({ where: { username: receiver } }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       user$ | 
					
						
							|  |  |  |         .tapOnNext(user => { | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |           if (!user) { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |             throw new Error(`could not find receiver for ${receiver}`); | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |           } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         .flatMap(({ progressTimestamps = [] }) => { | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |           return Observable.from(progressTimestamps); | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |         }) | 
					
						
							|  |  |  |         // filter out non objects
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         .filter(timestamp => !!timestamp || typeof timestamp === 'object') | 
					
						
							| 
									
										
										
										
											2018-11-16 04:29:02 -07:00
										 |  |  |         // filter out timestamps older than one hour
 | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |         .filter(({ timestamp = 0 }) => { | 
					
						
							|  |  |  |           return timestamp >= browniePoints; | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |         // filter out brownie points given by giver
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         .filter(browniePoint => { | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |           return browniePoint.giver === giver; | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |         // no results means this is the first brownie point given by giver
 | 
					
						
							|  |  |  |         // so return -1 to indicate receiver should receive point
 | 
					
						
							| 
									
										
										
										
											2015-10-15 00:33:45 -07:00
										 |  |  |         .first({ defaultValue: -1 }) | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         .flatMap(browniePointsFromGiver => { | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |           if (browniePointsFromGiver === -1) { | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |             return user$.flatMap(user => { | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |               user.progressTimestamps.push({ | 
					
						
							|  |  |  |                 giver, | 
					
						
							|  |  |  |                 timestamp: Date.now(), | 
					
						
							|  |  |  |                 ...data | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |               }); | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |               return saveUser(user); | 
					
						
							|  |  |  |             }); | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |           } | 
					
						
							|  |  |  |           return Observable.throw( | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |             new Error(`${giver} already gave ${receiver} points`) | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |           ); | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         .subscribe( | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |           user => { | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |             return cb( | 
					
						
							|  |  |  |               null, | 
					
						
							|  |  |  |               getAboutProfile(user), | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |               dev ? { giver, receiver, data } : null | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |             ); | 
					
						
							|  |  |  |           }, | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |           e => cb(e, null, dev ? { giver, receiver, data } : null), | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |           () => { | 
					
						
							| 
									
										
										
										
											2018-06-12 16:50:35 +01:00
										 |  |  |             log('brownie points assigned completed'); | 
					
						
							| 
									
										
										
										
											2015-07-29 15:00:24 -07:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |         ) | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2016-05-12 18:52:03 -07:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   User.remoteMethod('giveBrowniePoints', { | 
					
						
							|  |  |  |     description: 'Give this user brownie points', | 
					
						
							|  |  |  |     accepts: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'receiver', | 
					
						
							|  |  |  |         type: 'string', | 
					
						
							|  |  |  |         required: true | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'giver', | 
					
						
							|  |  |  |         type: 'string', | 
					
						
							|  |  |  |         required: true | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'data', | 
					
						
							|  |  |  |         type: 'object' | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'debug', | 
					
						
							|  |  |  |         type: 'boolean' | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     returns: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'about', | 
					
						
							|  |  |  |         type: 'object' | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'debug', | 
					
						
							|  |  |  |         type: 'object' | 
					
						
							| 
									
										
										
										
											2016-05-12 18:52:03 -07:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     ], | 
					
						
							|  |  |  |     http: { | 
					
						
							|  |  |  |       path: '/give-brownie-points', | 
					
						
							|  |  |  |       verb: 'POST' | 
					
						
							| 
									
										
										
										
											2016-05-12 18:52:03 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-05-12 18:52:03 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |   User.prototype.getPoints$ = function getPoints$() { | 
					
						
							| 
									
										
										
										
											2019-10-18 01:17:37 +01:00
										 |  |  |     if ( | 
					
						
							|  |  |  |       Array.isArray(this.progressTimestamps) && | 
					
						
							|  |  |  |       this.progressTimestamps.length | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       return Observable.of(this.progressTimestamps); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |     const id = this.getId(); | 
					
						
							|  |  |  |     const filter = { | 
					
						
							|  |  |  |       where: { id }, | 
					
						
							|  |  |  |       fields: { progressTimestamps: true } | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     return this.constructor.findOne$(filter).map(user => { | 
					
						
							|  |  |  |       this.progressTimestamps = user.progressTimestamps; | 
					
						
							|  |  |  |       return user.progressTimestamps; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |   User.prototype.getCompletedChallenges$ = function getCompletedChallenges$() { | 
					
						
							| 
									
										
										
										
											2019-10-18 01:17:37 +01:00
										 |  |  |     if ( | 
					
						
							|  |  |  |       Array.isArray(this.completedChallenges) && | 
					
						
							|  |  |  |       this.completedChallenges.length | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       return Observable.of(this.completedChallenges); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |     const id = this.getId(); | 
					
						
							|  |  |  |     const filter = { | 
					
						
							|  |  |  |       where: { id }, | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |       fields: { completedChallenges: true } | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2018-11-12 16:58:34 +00:00
										 |  |  |     return this.constructor.findOne$(filter).map(user => { | 
					
						
							|  |  |  |       this.completedChallenges = user.completedChallenges; | 
					
						
							|  |  |  |       return user.completedChallenges; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2016-04-06 21:08:19 -07:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2018-01-09 17:11:07 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-12 11:09:09 -08:00
										 |  |  |   User.getMessages = messages => Promise.resolve(messages); | 
					
						
							| 
									
										
										
										
											2018-01-09 17:11:07 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   User.remoteMethod('getMessages', { | 
					
						
							|  |  |  |     http: { | 
					
						
							|  |  |  |       verb: 'get', | 
					
						
							|  |  |  |       path: '/get-messages' | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     accepts: { | 
					
						
							|  |  |  |       arg: 'messages', | 
					
						
							|  |  |  |       type: 'object', | 
					
						
							| 
									
										
										
										
											2018-01-12 11:09:09 -08:00
										 |  |  |       http: ctx => ctx.req.flash() | 
					
						
							| 
									
										
										
										
											2018-01-09 17:11:07 -08:00
										 |  |  |     }, | 
					
						
							|  |  |  |     returns: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         arg: 'messages', | 
					
						
							|  |  |  |         type: 'object', | 
					
						
							|  |  |  |         root: true | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2019-03-04 21:03:46 +00:00
										 |  |  | } |