2014-01-03 13:32:13 -05:00
GRAVATAR_URL = ' https://www.gravatar.com/ '
cache = { }
2014-06-30 22:16:26 -04:00
CocoModel = require ' ./CocoModel '
2014-11-28 20:49:41 -05:00
util = require ' core/utils '
2014-11-04 10:52:23 -05:00
ThangType = require ' ./ThangType '
2014-12-07 17:57:23 -05:00
Level = require ' ./Level '
2016-04-15 16:39:11 -04:00
utils = require ' core/utils '
2014-01-03 13:32:13 -05:00
module.exports = class User extends CocoModel
2014-06-30 22:16:26 -04:00
@className: ' User '
2014-04-22 14:11:08 -04:00
@schema: require ' schemas/models/user '
2014-06-30 22:16:26 -04:00
urlRoot: ' /db/user '
2014-07-10 04:30:23 -04:00
notyErrors: false
2014-01-03 13:32:13 -05:00
2014-09-01 12:11:10 -04:00
isAdmin: -> ' admin ' in @ get ( ' permissions ' , true )
2015-02-25 21:41:39 -05:00
isArtisan: -> ' artisan ' in @ get ( ' permissions ' , true )
2015-02-12 14:42:05 -05:00
isInGodMode: -> ' godmode ' in @ get ( ' permissions ' , true )
2014-08-23 14:07:52 -04:00
isAnonymous: -> @ get ( ' anonymous ' , true )
displayName: -> @ get ( ' name ' , true )
2015-11-30 16:59:22 -05:00
broadName: ->
2016-03-30 19:20:37 -04:00
return ' (deleted) ' if @ get ( ' deleted ' )
2016-01-22 13:58:02 -05:00
name = _ . filter ( [ @ get ( ' firstName ' ) , @ get ( ' lastName ' ) ] ) . join ( ' ' )
2015-11-30 16:59:22 -05:00
return name if name
2016-07-18 15:38:26 -04:00
name = @ get ( ' name ' )
return name if name
2016-05-11 17:39:26 -04:00
[ emailName , emailDomain ] = @ get ( ' email ' ) ? . split ( ' @ ' ) or [ ]
return emailName if emailName
2016-05-26 19:46:03 -04:00
return ' Anonymous '
2014-01-03 13:32:13 -05:00
2014-07-07 13:29:34 -04:00
getPhotoURL: (size=80, useJobProfilePhoto=false, useEmployerPageAvatar=false) ->
2014-04-10 20:54:28 -04:00
photoURL = if useJobProfilePhoto then @ get ( ' jobProfile ' ) ? . photoURL else null
photoURL || = @ get ( ' photoURL ' )
if photoURL
2014-06-30 22:16:26 -04:00
prefix = if photoURL . search ( /\?/ ) is - 1 then ' ? ' else ' & '
2014-04-09 19:46:44 -04:00
return " #{ photoURL } #{ prefix } s= #{ size } " if photoURL . search ( ' http ' ) isnt - 1 # legacy
return " /file/ #{ photoURL } #{ prefix } s= #{ size } "
2014-07-07 13:29:34 -04:00
return " /db/user/ #{ @ id } /avatar?s= #{ size } &employerPageAvatar= #{ useEmployerPageAvatar } "
2016-05-11 17:39:26 -04:00
getRequestVerificationEmailURL: ->
@ url ( ) + " /request-verify-email "
2014-01-03 13:32:13 -05:00
2014-08-07 16:03:00 -04:00
getSlugOrID: -> @ get ( ' slug ' ) or @ get ( ' _id ' )
2014-07-16 13:51:44 -04:00
set: ->
if arguments [ 0 ] is ' jobProfileApproved ' and @ get ( " jobProfileApproved " ) is false and not @ get ( " jobProfileApprovedDate " )
@ set " jobProfileApprovedDate " , ( new Date ( ) ) . toISOString ( )
super arguments . . .
2014-06-30 22:16:26 -04:00
2014-07-10 14:50:16 -04:00
@getUnconflictedName: (name, done) ->
2016-06-30 18:32:58 -04:00
# deprecate in favor of @checkNameConflicts, which uses Promises and returns the whole response
2016-05-03 17:47:24 -04:00
$ . ajax " /auth/name/ #{ encodeURIComponent ( name ) } " ,
2015-02-11 16:12:42 -05:00
cache: false
2016-06-30 18:32:58 -04:00
success: (data) -> done ( data . suggestedName )
@checkNameConflicts: (name) ->
new Promise (resolve, reject) ->
$ . ajax " /auth/name/ #{ encodeURIComponent ( name ) } " ,
cache: false
success: resolve
error: (jqxhr) -> reject ( jqxhr . responseJSON )
@checkEmailExists: (email) ->
new Promise (resolve, reject) ->
$ . ajax " /auth/email/ #{ encodeURIComponent ( email ) } " ,
cache: false
success: resolve
error: (jqxhr) -> reject ( jqxhr . responseJSON )
2014-07-10 14:50:16 -04:00
2014-04-21 19:15:23 -04:00
getEnabledEmails: ->
2014-08-23 14:07:52 -04:00
( emailName for emailName , emailDoc of @ get ( ' emails ' , true ) when emailDoc . enabled )
2014-06-30 22:16:26 -04:00
2014-04-21 19:15:23 -04:00
setEmailSubscription: (name, enabled) ->
newSubs = _ . clone ( @ get ( ' emails ' ) ) or { }
( newSubs [ name ] ? = { } ) . enabled = enabled
@ set ' emails ' , newSubs
2014-06-30 22:16:26 -04:00
2014-04-22 14:11:10 -04:00
isEmailSubscriptionEnabled: (name) -> ( @ get ( ' emails ' ) or { } ) [ name ] ? . enabled
2014-05-26 12:21:56 -04:00
2016-03-09 17:40:52 -05:00
isStudent: -> @ get ( ' role ' ) is ' student '
2016-03-04 15:05:07 -05:00
isTeacher: ->
2016-03-09 17:40:52 -05:00
return @ get ( ' role ' ) in [ ' teacher ' , ' technology coordinator ' , ' advisor ' , ' principal ' , ' superintendent ' , ' parent ' ]
2016-05-20 17:52:04 -04:00
justPlaysCourses: ->
# This heuristic could be better, but currently we don't add to me.get('courseInstances') for single-player anonymous intro courses, so they have to beat a level without choosing a hero.
return true if me . get ( ' role ' ) is ' student '
return me . get ( ' stats ' ) ? . gamesCompleted and not me . get ( ' heroConfig ' )
2016-04-15 13:58:52 -04:00
isSessionless: ->
# TODO: Fix old users who got mis-tagged as teachers
# TODO: Should this just be isTeacher, eventually?
2016-07-24 00:03:16 -04:00
Boolean ( ( utils . getQueryVariable ( ' dev ' , false ) or me . isTeacher ( ) ) and utils . getQueryVariable ( ' course ' , false ) )
2016-03-04 15:05:07 -05:00
2016-02-02 15:48:19 -05:00
setRole: (role, force=false) ->
oldRole = @ get ' role '
return if oldRole is role or ( oldRole and not force )
@ set ' role ' , role
@ patch ( )
application . tracker ? . updateRole ( )
return @ get ' role '
2014-05-26 12:21:56 -04:00
a = 5
2014-07-30 16:23:43 -04:00
b = 100
c = b
2014-05-26 12:21:56 -04:00
2014-07-30 16:23:43 -04:00
# y = a * ln(1/b * (x + c)) + 1
2014-05-26 12:21:56 -04:00
@levelFromExp: (xp) ->
2016-03-15 18:51:59 -04:00
if xp > 0 then Math . floor ( a * Math . log ( ( 1 / b ) * ( xp + c ) ) ) + 1 else 1
2014-05-26 12:21:56 -04:00
2014-07-30 16:23:43 -04:00
# x = b * e^((y-1)/a) - c
2014-05-26 12:21:56 -04:00
@expForLevel: (level) ->
2014-07-30 16:23:43 -04:00
if level > 1 then Math . ceil Math . exp ( ( level - 1 ) / a ) * b - c else 0
2014-05-26 12:21:56 -04:00
2014-11-11 01:07:55 -05:00
@tierFromLevel: (level) ->
# TODO: math
# For now, just eyeball it.
tiersByLevel [ Math . min ( level , tiersByLevel . length - 1 ) ]
@levelForTier: (tier) ->
# TODO: math
for tierThreshold , level in tiersByLevel
return level if tierThreshold >= tier
2014-05-26 12:21:56 -04:00
level: ->
2015-02-12 14:42:05 -05:00
totalPoint = @ get ( ' points ' )
totalPoint = totalPoint + 1000000 if me . isInGodMode ( )
User . levelFromExp ( totalPoint )
2014-09-26 05:28:54 -04:00
2014-11-11 01:07:55 -05:00
tier: ->
User . tierFromLevel @ level ( )
2014-09-26 05:28:54 -04:00
gems: ->
gemsEarned = @ get ( ' earned ' ) ? . gems ? 0
2015-02-12 14:42:05 -05:00
gemsEarned = gemsEarned + 100000 if me . isInGodMode ( )
2014-11-01 17:15:57 -04:00
gemsPurchased = @ get ( ' purchased ' ) ? . gems ? 0
gemsSpent = @ get ( ' spent ' ) ? 0
2015-01-07 15:25:31 -05:00
Math . floor gemsEarned + gemsPurchased - gemsSpent
2014-09-26 05:28:54 -04:00
2014-11-11 19:36:44 -05:00
heroes: ->
2016-06-24 17:57:21 -04:00
heroes = ( me . get ( ' purchased ' ) ? . heroes ? [ ] ) . concat ( [ ThangType . heroes . captain , ThangType . heroes . knight , ThangType . heroes . champion , ThangType . heroes . duelist ] )
2014-11-25 12:28:42 -05:00
#heroes = _.values ThangType.heroes if me.isAdmin()
2014-11-11 19:36:44 -05:00
heroes
2014-11-04 10:52:23 -05:00
items: -> ( me . get ( ' earned ' ) ? . items ? [ ] ) . concat ( me . get ( ' purchased ' ) ? . items ? [ ] ) . concat ( [ ThangType . items [ ' simple-boots ' ] ] )
2014-12-07 17:57:23 -05:00
levels: -> ( me . get ( ' earned ' ) ? . levels ? [ ] ) . concat ( me . get ( ' purchased ' ) ? . levels ? [ ] ) . concat ( Level . levels [ ' dungeons-of-kithgard ' ] )
2015-02-12 14:42:05 -05:00
ownsHero: (heroOriginal) -> me . isInGodMode ( ) || heroOriginal in @ heroes ( )
2014-11-01 17:15:57 -04:00
ownsItem: (itemOriginal) -> itemOriginal in @ items ( )
ownsLevel: (levelOriginal) -> levelOriginal in @ levels ( )
2014-10-23 19:36:59 -04:00
2014-11-24 13:51:20 -05:00
getHeroClasses: ->
idsToSlugs = _ . invert ThangType . heroes
myHeroSlugs = ( idsToSlugs [ id ] for id in @ heroes ( ) )
myHeroClasses = [ ]
myHeroClasses . push heroClass for heroClass , heroSlugs of ThangType . heroClasses when _ . intersection ( myHeroSlugs , heroSlugs ) . length
myHeroClasses
2014-12-07 22:38:24 -05:00
getAnnouncesActionAudioGroup: ->
return @ announcesActionAudioGroup if @ announcesActionAudioGroup
group = me . get ( ' testGroupNumber ' ) % 4
@announcesActionAudioGroup = switch group
when 0 then ' all-audio '
when 1 then ' no-audio '
when 2 then ' just-take-damage '
when 3 then ' without-take-damage '
@announcesActionAudioGroup = ' all-audio ' if me . isAdmin ( )
application . tracker . identify announcesActionAudioGroup: @ announcesActionAudioGroup unless me . isAdmin ( )
@ announcesActionAudioGroup
2016-02-02 19:56:08 -05:00
2016-03-15 18:51:59 -04:00
getCampaignAdsGroup: ->
return @ campaignAdsGroup if @ campaignAdsGroup
2016-03-21 11:07:22 -04:00
# group = me.get('testGroupNumber') % 2
# @campaignAdsGroup = switch group
# when 0 then 'no-ads'
# when 1 then 'leaderboard-ads'
@campaignAdsGroup = ' leaderboard-ads '
2016-03-15 18:51:59 -04:00
@campaignAdsGroup = ' no-ads ' if me . isAdmin ( )
application . tracker . identify campaignAdsGroup: @ campaignAdsGroup unless me . isAdmin ( )
@ campaignAdsGroup
2015-03-04 12:00:32 -05:00
# Signs and Portents was receiving updates after test started, and also had a big bug on March 4, so just look at test from March 5 on.
2015-03-10 12:45:21 -04:00
# ... and stopped working well until another update on March 10, so maybe March 11+...
2015-03-25 19:47:11 -04:00
# ... and another round, and then basically it just isn't completing well, so we pause the test until we can fix it.
2015-02-26 17:24:00 -05:00
getFourthLevelGroup: ->
2015-04-03 12:08:02 -04:00
return ' forgetful-gemsmith '
2015-02-26 17:24:00 -05:00
return @ fourthLevelGroup if @ fourthLevelGroup
2014-11-24 13:51:20 -05:00
group = me . get ( ' testGroupNumber ' ) % 8
2015-02-26 17:24:00 -05:00
@fourthLevelGroup = switch group
when 0 , 1 , 2 , 3 then ' signs-and-portents '
when 4 , 5 , 6 , 7 then ' forgetful-gemsmith '
@fourthLevelGroup = ' signs-and-portents ' if me . isAdmin ( )
application . tracker . identify fourthLevelGroup: @ fourthLevelGroup unless me . isAdmin ( )
@ fourthLevelGroup
2015-02-05 18:05:22 -05:00
2016-05-26 20:46:49 -04:00
getHintsGroup: ->
# A/B testing two styles of hints
return @ hintsGroup if @ hintsGroup
group = me . get ( ' testGroupNumber ' ) % 3
@hintsGroup = switch group
when 0 then ' no-hints '
when 1 then ' hints '
when 2 then ' hintsB '
@hintsGroup = ' hints ' if me . isAdmin ( )
application . tracker . identify hintsGroup: @ hintsGroup unless me . isAdmin ( )
@ hintsGroup
2014-12-18 02:55:11 -05:00
getVideoTutorialStylesIndex: (numVideos=0)->
# A/B Testing video tutorial styles
# Not a constant number of videos available (e.g. could be 0, 1, 3, or 4 currently)
return 0 unless numVideos > 0
return me . get ( ' testGroupNumber ' ) % numVideos
2014-12-09 02:43:52 -05:00
2015-08-21 14:10:06 -04:00
hasSubscription: ->
2014-12-05 20:19:44 -05:00
return false unless stripe = @ get ( ' stripe ' )
2015-03-13 18:19:20 -04:00
return true if stripe . sponsorID
2014-12-05 20:19:44 -05:00
return true if stripe . subscriptionID
return true if stripe . free is true
return true if _ . isString ( stripe . free ) and new Date ( ) < new Date ( stripe . free )
2015-08-21 14:10:06 -04:00
isPremium: ->
return true if me . isInGodMode ( )
return true if me . isAdmin ( )
return true if me . hasSubscription ( )
2014-12-05 20:19:44 -05:00
return false
2016-08-17 13:00:56 -04:00
2015-10-09 11:05:34 -04:00
isOnPremiumServer: ->
2016-08-17 13:00:56 -04:00
return true if me . get ( ' country ' ) in [ ' brazil ' ]
return true if me . get ( ' country ' ) in [ ' china ' ] and me . isPremium ( )
return false
isOnFreeOnlyServer: ->
return true if me . get ( ' country ' ) in [ ' china ' ] and not me . isPremium ( )
return false
2016-05-11 17:39:26 -04:00
sendVerificationCode: (code) ->
$ . ajax ( {
method: ' POST '
url: " /db/user/ #{ @ id } /verify/ #{ code } "
success: (attributes) =>
this . set attributes
@ trigger ' email-verify-success '
error: =>
@ trigger ' email-verify-error '
} )
2016-03-09 17:39:40 -05:00
2016-05-09 18:16:54 -04:00
isEnrolled: -> @ prepaidStatus ( ) is ' enrolled '
prepaidStatus: -> # 'not-enrolled', 'enrolled', 'expired'
coursePrepaid = @ get ( ' coursePrepaid ' )
return ' not-enrolled ' unless coursePrepaid
return ' enrolled ' unless coursePrepaid . endDate
return if coursePrepaid . endDate > new Date ( ) . toISOString ( ) then ' enrolled ' else ' expired '
2016-03-09 17:39:40 -05:00
# Function meant for "me"
2016-03-03 17:22:50 -05:00
spy: (user, options={}) ->
user = user . id or user # User instance, user ID, email or username
options.url = ' /auth/spy '
options.type = ' POST '
options . data ? = { }
options.data.user = user
@ fetch ( options )
stopSpying: (options={}) ->
options.url = ' /auth/stop-spying '
options.type = ' POST '
@ fetch ( options )
2015-10-09 11:05:34 -04:00
2016-03-09 17:39:40 -05:00
logout: (options={}) ->
options.type = ' POST '
options.url = ' /auth/logout '
FB ? . logout ? ( )
options . success ? = ->
location = _ . result ( currentView , ' logoutRedirectURL ' )
if location
window . location = location
else
window . location . reload ( )
@ fetch ( options )
2016-06-30 18:32:58 -04:00
2016-07-13 19:50:03 -04:00
signupWithPassword: (name, email, password, options={}) ->
2016-06-30 18:32:58 -04:00
options.url = _ . result ( @ , ' url ' ) + ' /signup-with-password '
options.type = ' POST '
options . data ? = { }
2016-07-13 19:50:03 -04:00
_ . extend ( options . data , { name , email , password } )
2016-06-30 18:32:58 -04:00
jqxhr = @ fetch ( options )
jqxhr . then ->
window . tracker ? . trackEvent ' Finished Signup ' , category: " Signup " , label: ' CodeCombat '
return jqxhr
2016-07-18 14:41:18 -04:00
signupWithFacebook: (name, email, facebookID, options={}) ->
2016-06-30 18:32:58 -04:00
options.url = _ . result ( @ , ' url ' ) + ' /signup-with-facebook '
options.type = ' POST '
options . data ? = { }
2016-07-18 14:41:18 -04:00
_ . extend ( options . data , { name , email , facebookID , facebookAccessToken: application . facebookHandler . token ( ) } )
2016-06-30 18:32:58 -04:00
jqxhr = @ fetch ( options )
jqxhr . then ->
window . tracker ? . trackEvent ' Facebook Login ' , category: " Signup " , label: ' Facebook '
window . tracker ? . trackEvent ' Finished Signup ' , category: " Signup " , label: ' Facebook '
return jqxhr
2016-07-18 14:41:18 -04:00
signupWithGPlus: (name, email, gplusID, options={}) ->
2016-06-30 18:32:58 -04:00
options.url = _ . result ( @ , ' url ' ) + ' /signup-with-gplus '
options.type = ' POST '
options . data ? = { }
2016-07-18 14:41:18 -04:00
_ . extend ( options . data , { name , email , gplusID , gplusAccessToken: application . gplusHandler . token ( ) } )
2016-06-30 18:32:58 -04:00
jqxhr = @ fetch ( options )
jqxhr . then ->
window . tracker ? . trackEvent ' Google Login ' , category: " Signup " , label: ' GPlus '
window . tracker ? . trackEvent ' Finished Signup ' , category: " Signup " , label: ' GPlus '
return jqxhr
2016-03-09 17:39:40 -05:00
2016-02-25 18:24:16 -05:00
fetchGPlusUser: (gplusID, options={}) ->
options . data ? = { }
options.data.gplusID = gplusID
options.data.gplusAccessToken = application . gplusHandler . token ( )
@ fetch ( options )
loginGPlusUser: (gplusID, options={}) ->
options.url = ' /auth/login-gplus '
options.type = ' POST '
options . data ? = { }
options.data.gplusID = gplusID
options.data.gplusAccessToken = application . gplusHandler . token ( )
@ fetch ( options )
fetchFacebookUser: (facebookID, options={}) ->
options . data ? = { }
options.data.facebookID = facebookID
options.data.facebookAccessToken = application . facebookHandler . token ( )
@ fetch ( options )
loginFacebookUser: (facebookID, options={}) ->
options.url = ' /auth/login-facebook '
options.type = ' POST '
options . data ? = { }
options.data.facebookID = facebookID
options.data.facebookAccessToken = application . facebookHandler . token ( )
@ fetch ( options )
2016-05-09 18:16:54 -04:00
2016-06-08 16:45:25 -04:00
loginPasswordUser: (usernameOrEmail, password, options={}) ->
options.url = ' /auth/login '
options.type = ' POST '
options . data ? = { }
_ . extend ( options . data , { username: usernameOrEmail , password } )
@ fetch ( options )
2016-05-09 18:16:54 -04:00
makeCoursePrepaid: ->
coursePrepaid = @ get ( ' coursePrepaid ' )
return null unless coursePrepaid
Prepaid = require ' models/Prepaid '
return new Prepaid ( coursePrepaid )
2016-02-25 18:24:16 -05:00
2016-05-16 17:33:20 -04:00
becomeStudent: (options={}) ->
options.url = ' /db/user/-/become-student '
options.type = ' PUT '
@ fetch ( options )
remainTeacher: (options={}) ->
options.url = ' /db/user/-/remain-teacher '
options.type = ' PUT '
@ fetch ( options )
2016-07-25 19:09:24 -04:00
destudent: (options={}) ->
options.url = _ . result ( @ , ' url ' ) + ' /destudent '
options.type = ' POST '
@ fetch ( options )
deteacher: (options={}) ->
options.url = _ . result ( @ , ' url ' ) + ' /deteacher '
options.type = ' POST '
@ fetch ( options )
2016-05-16 17:33:20 -04:00
2014-11-28 15:11:51 -05:00
tiersByLevel = [ - 1 , 0 , 0.05 , 0.14 , 0.18 , 0.32 , 0.41 , 0.5 , 0.64 , 0.82 , 0.91 , 1.04 , 1.22 , 1.35 , 1.48 , 1.65 , 1.78 , 1.96 , 2.1 , 2.24 , 2.38 , 2.55 , 2.69 , 2.86 , 3.03 , 3.16 , 3.29 , 3.42 , 3.58 , 3.74 , 3.89 , 4.04 , 4.19 , 4.32 , 4.47 , 4.64 , 4.79 , 4.96 ,
5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 10 , 10.5 , 11 , 11.5 , 12 , 12.5 , 13 , 13.5 , 14 , 14.5 , 15
]