GRAVATAR_URL = 'https://www.gravatar.com/' cache = {} CocoModel = require './CocoModel' util = require 'core/utils' ThangType = require './ThangType' Level = require './Level' utils = require 'core/utils' module.exports = class User extends CocoModel @className: 'User' @schema: require 'schemas/models/user' urlRoot: '/db/user' notyErrors: false isAdmin: -> 'admin' in @get('permissions', true) isArtisan: -> 'artisan' in @get('permissions', true) isInGodMode: -> 'godmode' in @get('permissions', true) isAnonymous: -> @get('anonymous', true) displayName: -> @get('name', true) broadName: -> return '(deleted)' if @get('deleted') name = _.filter([@get('firstName'), @get('lastName')]).join(' ') return name if name name = @get('name') return name if name [emailName, emailDomain] = @get('email')?.split('@') or [] return emailName if emailName return 'Anonymous' getPhotoURL: (size=80, useJobProfilePhoto=false, useEmployerPageAvatar=false) -> photoURL = if useJobProfilePhoto then @get('jobProfile')?.photoURL else null photoURL ||= @get('photoURL') if photoURL prefix = if photoURL.search(/\?/) is -1 then '?' else '&' return "#{photoURL}#{prefix}s=#{size}" if photoURL.search('http') isnt -1 # legacy return "/file/#{photoURL}#{prefix}s=#{size}" return "/db/user/#{@id}/avatar?s=#{size}&employerPageAvatar=#{useEmployerPageAvatar}" getRequestVerificationEmailURL: -> @url() + "/request-verify-email" getSlugOrID: -> @get('slug') or @get('_id') set: -> if arguments[0] is 'jobProfileApproved' and @get("jobProfileApproved") is false and not @get("jobProfileApprovedDate") @set "jobProfileApprovedDate", (new Date()).toISOString() super arguments... @getUnconflictedName: (name, done) -> # deprecate in favor of @checkNameConflicts, which uses Promises and returns the whole response $.ajax "/auth/name/#{encodeURIComponent(name)}", cache: false 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) getEnabledEmails: -> (emailName for emailName, emailDoc of @get('emails', true) when emailDoc.enabled) setEmailSubscription: (name, enabled) -> newSubs = _.clone(@get('emails')) or {} (newSubs[name] ?= {}).enabled = enabled @set 'emails', newSubs isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled isStudent: -> @get('role') is 'student' isTeacher: -> return @get('role') in ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent'] 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') isSessionless: -> # TODO: Fix old users who got mis-tagged as teachers # TODO: Should this just be isTeacher, eventually? Boolean((utils.getQueryVariable('dev', false) or me.isTeacher()) and utils.getQueryVariable('course', false)) 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' a = 5 b = 100 c = b # y = a * ln(1/b * (x + c)) + 1 @levelFromExp: (xp) -> if xp > 0 then Math.floor(a * Math.log((1 / b) * (xp + c))) + 1 else 1 # x = b * e^((y-1)/a) - c @expForLevel: (level) -> if level > 1 then Math.ceil Math.exp((level - 1)/ a) * b - c else 0 @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 level: -> totalPoint = @get('points') totalPoint = totalPoint + 1000000 if me.isInGodMode() User.levelFromExp(totalPoint) tier: -> User.tierFromLevel @level() gems: -> gemsEarned = @get('earned')?.gems ? 0 gemsEarned = gemsEarned + 100000 if me.isInGodMode() gemsPurchased = @get('purchased')?.gems ? 0 gemsSpent = @get('spent') ? 0 Math.floor gemsEarned + gemsPurchased - gemsSpent heroes: -> heroes = (me.get('purchased')?.heroes ? []).concat([ThangType.heroes.captain, ThangType.heroes.knight, ThangType.heroes.champion, ThangType.heroes.duelist]) #heroes = _.values ThangType.heroes if me.isAdmin() heroes items: -> (me.get('earned')?.items ? []).concat(me.get('purchased')?.items ? []).concat([ThangType.items['simple-boots']]) levels: -> (me.get('earned')?.levels ? []).concat(me.get('purchased')?.levels ? []).concat(Level.levels['dungeons-of-kithgard']) ownsHero: (heroOriginal) -> me.isInGodMode() || heroOriginal in @heroes() ownsItem: (itemOriginal) -> itemOriginal in @items() ownsLevel: (levelOriginal) -> levelOriginal in @levels() 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 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 getCampaignAdsGroup: -> return @campaignAdsGroup if @campaignAdsGroup # group = me.get('testGroupNumber') % 2 # @campaignAdsGroup = switch group # when 0 then 'no-ads' # when 1 then 'leaderboard-ads' @campaignAdsGroup = 'leaderboard-ads' @campaignAdsGroup = 'no-ads' if me.isAdmin() application.tracker.identify campaignAdsGroup: @campaignAdsGroup unless me.isAdmin() @campaignAdsGroup # 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. # ... and stopped working well until another update on March 10, so maybe March 11+... # ... and another round, and then basically it just isn't completing well, so we pause the test until we can fix it. getFourthLevelGroup: -> return 'forgetful-gemsmith' return @fourthLevelGroup if @fourthLevelGroup group = me.get('testGroupNumber') % 8 @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 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 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 hasSubscription: -> return false unless stripe = @get('stripe') return true if stripe.sponsorID 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) isPremium: -> return true if me.isInGodMode() return true if me.isAdmin() return true if me.hasSubscription() return false isOnPremiumServer: -> me.get('country') in ['china', 'brazil'] 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' }) 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' # Function meant for "me" 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) 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) signupWithPassword: (name, email, password, options={}) -> options.url = _.result(@, 'url') + '/signup-with-password' options.type = 'POST' options.data ?= {} _.extend(options.data, {name, email, password}) jqxhr = @fetch(options) jqxhr.then -> window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'CodeCombat' return jqxhr signupWithFacebook: (name, email, facebookID, options={}) -> options.url = _.result(@, 'url') + '/signup-with-facebook' options.type = 'POST' options.data ?= {} _.extend(options.data, {name, email, facebookID, facebookAccessToken: application.facebookHandler.token()}) 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 signupWithGPlus: (name, email, gplusID, options={}) -> options.url = _.result(@, 'url') + '/signup-with-gplus' options.type = 'POST' options.data ?= {} _.extend(options.data, {name, email, gplusID, gplusAccessToken: application.gplusHandler.token()}) 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 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) loginPasswordUser: (usernameOrEmail, password, options={}) -> options.url = '/auth/login' options.type = 'POST' options.data ?= {} _.extend(options.data, { username: usernameOrEmail, password }) @fetch(options) makeCoursePrepaid: -> coursePrepaid = @get('coursePrepaid') return null unless coursePrepaid Prepaid = require 'models/Prepaid' return new Prepaid(coursePrepaid) 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) destudent: (options={}) -> options.url = _.result(@, 'url') + '/destudent' options.type = 'POST' @fetch(options) deteacher: (options={}) -> options.url = _.result(@, 'url') + '/deteacher' options.type = 'POST' @fetch(options) 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 ]