mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Allow username-only signup for classroom users
Address some code review feedback Correct error code in test Don't try to send emails to empty addresses Add tests for subscriptions Add tests for Next Steps email Fix specs Add reason for disabled test
This commit is contained in:
parent
ff523c2431
commit
bb6262483f
27 changed files with 215 additions and 56 deletions
|
@ -961,7 +961,7 @@
|
|||
manage_subscription: "Click here to manage your subscription."
|
||||
new_password: "New Password"
|
||||
new_password_verify: "Verify"
|
||||
type_in_email: "Type in your email to confirm account deletion."
|
||||
type_in_email: "Type in your email or username to confirm account deletion." # {change}
|
||||
type_in_email_progress: "Type in your email to confirm deleting your progress."
|
||||
type_in_password: "Also, type in your password."
|
||||
email_subscriptions: "Email Subscriptions"
|
||||
|
@ -1333,7 +1333,7 @@
|
|||
update_account_title: "Your account needs attention!"
|
||||
update_account_blurb: "Before you can access your classes, choose how you want to use this account."
|
||||
update_account_current_type: "Current Account Type:"
|
||||
update_account_account_email: "Account Email:"
|
||||
update_account_account_email: "Account Email/Username:" # {change}
|
||||
update_account_am_teacher: "I am a teacher"
|
||||
update_account_keep_access: "Keep access to classes I've created"
|
||||
update_account_teachers_can: "Teacher accounts can:"
|
||||
|
|
|
@ -271,11 +271,11 @@ module.exports = class User extends CocoModel
|
|||
window.location.reload()
|
||||
@fetch(options)
|
||||
|
||||
signupWithPassword: (email, password, options={}) ->
|
||||
signupWithPassword: (name, email, password, options={}) ->
|
||||
options.url = _.result(@, 'url') + '/signup-with-password'
|
||||
options.type = 'POST'
|
||||
options.data ?= {}
|
||||
_.extend(options.data, {email, password})
|
||||
_.extend(options.data, {name, email, password})
|
||||
jqxhr = @fetch(options)
|
||||
jqxhr.then ->
|
||||
window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'CodeCombat'
|
||||
|
|
|
@ -35,6 +35,9 @@
|
|||
|
||||
.help-block
|
||||
margin: 0
|
||||
|
||||
.optional-help-block
|
||||
font-style: italic
|
||||
|
||||
.form-container
|
||||
width: 800px
|
||||
|
|
|
@ -10,7 +10,7 @@ else
|
|||
.panel-body
|
||||
.form
|
||||
- var name = me.get('name') || '';
|
||||
- var email = me.get('email');
|
||||
- var email = me.get('email') || '';
|
||||
- var admin = me.get('permissions', true).indexOf('admin') != -1;
|
||||
- var godmode = me.get('permissions', true).indexOf('godmode') != -1;
|
||||
.form-group
|
||||
|
@ -71,11 +71,11 @@ else
|
|||
.panel-body
|
||||
.form#delete-account-form
|
||||
.form-group
|
||||
label.control-label(for="email1", data-i18n="account_settings.type_in_email")
|
||||
input#email1.form-control(name="email", type="email")
|
||||
label.control-label(for="delete-account-email-or-username", data-i18n="account_settings.type_in_email")
|
||||
input#delete-account-email-or-username.form-control(name="emailOrUsername")
|
||||
.form-group
|
||||
label.control-label(for="password1", data-i18n="account_settings.type_in_password")
|
||||
input#password1.form-control(name="password", type="password")
|
||||
label.control-label(for="delete-account-password", data-i18n="account_settings.type_in_password")
|
||||
input#delete-account-password.form-control(name="password", type="password")
|
||||
button#delete-account-btn.btn.form-control.btn-primary(data-i18n="account_settings.delete_this_account")
|
||||
|
||||
.col-md-6
|
||||
|
|
|
@ -10,10 +10,10 @@ block content
|
|||
.col-sm-1
|
||||
button.btn.btn-primary.btn-large#enter-espionage-mode 007
|
||||
label.control-label.col-sm-5(for="espionage-name-or-email")
|
||||
em you are currently #{me.get('name')} at #{me.get('email')}
|
||||
em you are currently #{me.get('name') || '(no username)'} at #{me.get('email') || '(no email)'}
|
||||
if view.amActually
|
||||
br
|
||||
em but you are actually #{view.amActually.get('name')} at #{view.amActually.get('email')}
|
||||
em but you are actually #{view.amActually.get('name') || '(no username)'} at #{view.amActually.get('email') || '(no email)'}
|
||||
br
|
||||
button#stop-spying-btn.btn.btn-xs Stop Spying
|
||||
form#user-search-form.form-group
|
||||
|
|
|
@ -36,6 +36,9 @@ form#basic-info-form.modal-body.basic-info
|
|||
span(data-i18n="share_progress_modal.form_label")
|
||||
.col-xs-5.col-xs-offset-3
|
||||
input.form-control.input-lg#email-input(name="email" type="email")
|
||||
if view.signupState.get('path') === 'student'
|
||||
.help-block.optional-help-block.pull-right
|
||||
span(data-i18n="signup.optional")
|
||||
.col-xs-4.email-check
|
||||
- var checkEmailState = view.state.get('checkEmailState');
|
||||
if checkEmailState === 'checking'
|
||||
|
@ -53,6 +56,7 @@ form#basic-info-form.modal-body.basic-info
|
|||
span.text-forest.glyphicon.glyphicon-ok-circle
|
||||
=" "
|
||||
span(data-i18n="signup.email_good")
|
||||
|
||||
.form-group
|
||||
.row
|
||||
.col-xs-7.col-xs-offset-3
|
||||
|
@ -74,6 +78,7 @@ form#basic-info-form.modal-body.basic-info
|
|||
span.text-forest.glyphicon.glyphicon-ok-circle
|
||||
=" "
|
||||
span(data-i18n="signup.name_available")
|
||||
|
||||
.form-group
|
||||
.row
|
||||
.col-xs-7.col-xs-offset-3
|
||||
|
@ -81,6 +86,7 @@ form#basic-info-form.modal-body.basic-info
|
|||
span(data-i18n="general.password")
|
||||
.col-xs-5.col-xs-offset-3
|
||||
input.form-control.input-lg#password-input(name="password" type="password")
|
||||
|
||||
.form-group.checkbox.subscribe
|
||||
.row
|
||||
.col-xs-7.col-xs-offset-3
|
||||
|
|
|
@ -27,7 +27,7 @@ block content
|
|||
if view.accountType
|
||||
div #{view.accountType}
|
||||
div
|
||||
span.spr #{me.get('email')}
|
||||
span.spr #{me.get('email') || me.get('name')}
|
||||
span.not_you
|
||||
span.spr(data-i18n="courses.not_you")
|
||||
a.logout-btn(data-i18n="login.log_out", href="#")
|
||||
|
|
|
@ -4,7 +4,10 @@ block modal-header-content
|
|||
.text-center
|
||||
h1.modal-title(data-i18n="courses.remove_student1")
|
||||
span.glyphicon.glyphicon-warning-sign.text-danger
|
||||
p= view.user.get('name', true) + ' - ' + view.user.get('email')
|
||||
p
|
||||
span= view.user.get('name', true)
|
||||
if view.user.get('email')
|
||||
span= " — " + view.user.get('email')
|
||||
h2(data-i18n="courses.are_you_sure")
|
||||
|
||||
block modal-body-content
|
||||
|
|
|
@ -87,16 +87,16 @@ module.exports = class AccountSettingsView extends CocoView
|
|||
|
||||
validateCredentialsForDestruction: ($form, onSuccess) ->
|
||||
forms.clearFormAlerts($form)
|
||||
enteredEmail = $form.find('input[type="email"]').val()
|
||||
enteredPassword = $form.find('input[type="password"]').val()
|
||||
if enteredEmail and enteredEmail is me.get('email')
|
||||
enteredEmailOrUsername = $form.find('input[name="emailOrUsername"]').val()
|
||||
enteredPassword = $form.find('input[name="password"]').val()
|
||||
if enteredEmailOrUsername and enteredEmailOrUsername in [me.get('email'), me.get('name')]
|
||||
isPasswordCorrect = false
|
||||
toBeDelayed = true
|
||||
$.ajax
|
||||
url: '/auth/login'
|
||||
type: 'POST'
|
||||
data:
|
||||
username: enteredEmail
|
||||
username: enteredEmailOrUsername
|
||||
password: enteredPassword
|
||||
parse: true
|
||||
error: (error) ->
|
||||
|
@ -225,9 +225,16 @@ module.exports = class AccountSettingsView extends CocoView
|
|||
return unless res
|
||||
|
||||
res.error =>
|
||||
errors = JSON.parse(res.responseText)
|
||||
forms.applyErrorsToForm(@$el, errors)
|
||||
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
|
||||
if res.responseJSON?.property
|
||||
errors = res.responseJSON
|
||||
forms.applyErrorsToForm(@$el, errors)
|
||||
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
|
||||
else
|
||||
noty
|
||||
text: res.responseText
|
||||
type: 'error'
|
||||
layout: 'topCenter'
|
||||
timeout: 5000
|
||||
@trigger 'save-user-error'
|
||||
res.success (model, response, options) =>
|
||||
@trigger 'save-user-success'
|
||||
|
|
|
@ -64,7 +64,8 @@ module.exports = class BasicInfoView extends CocoView
|
|||
|
||||
checkEmail: ->
|
||||
email = @$('[name="email"]').val()
|
||||
if email is @state.get('checkEmailValue')
|
||||
|
||||
if @signupState.get('path') isnt 'student' and (not _.isEmpty(email) and email is @state.get('checkEmailValue'))
|
||||
return @state.get('checkEmailPromise')
|
||||
|
||||
if not (email and forms.validateEmail(email))
|
||||
|
@ -155,7 +156,7 @@ module.exports = class BasicInfoView extends CocoView
|
|||
email: User.schema.properties.email
|
||||
name: User.schema.properties.name
|
||||
password: User.schema.properties.password
|
||||
required: ['email', 'name', 'password'].concat (if @signupState.get('path') is 'student' then ['firstName', 'lastName'] else [])
|
||||
required: ['name', 'password'].concat (if @signupState.get('path') is 'student' then ['firstName', 'lastName'] else ['email'])
|
||||
|
||||
onClickBackButton: -> @trigger 'nav-back'
|
||||
|
||||
|
@ -176,20 +177,20 @@ module.exports = class BasicInfoView extends CocoView
|
|||
@checkEmail()
|
||||
.then @checkName()
|
||||
.then =>
|
||||
if not (@state.get('checkEmailState') is 'available' and @state.get('checkNameState') is 'available')
|
||||
if not (@state.get('checkEmailState') in ['available', 'standby'] and @state.get('checkNameState') is 'available')
|
||||
throw AbortError
|
||||
|
||||
|
||||
# update User
|
||||
emails = _.assign({}, me.get('emails'))
|
||||
emails.generalNews ?= {}
|
||||
emails.generalNews.enabled = @$('#subscribe-input').is(':checked')
|
||||
emails.generalNews.enabled = @$('#subscribe-input').is(':checked') and not _.isEmpty(@state.get('checkEmailValue'))
|
||||
me.set('emails', emails)
|
||||
|
||||
unless _.isNaN(@signupState.get('birthday').getTime())
|
||||
me.set('birthday', @signupState.get('birthday').toISOString())
|
||||
|
||||
me.set(_.omit(@signupState.get('ssoAttrs') or {}, 'email', 'facebookID', 'gplusID'))
|
||||
me.set('name', @$('input[name="name"]').val())
|
||||
|
||||
jqxhr = me.save()
|
||||
if not jqxhr
|
||||
console.error(me.validationError)
|
||||
|
@ -203,13 +204,15 @@ module.exports = class BasicInfoView extends CocoView
|
|||
switch @signupState.get('ssoUsed')
|
||||
when 'gplus'
|
||||
{ email, gplusID } = @signupState.get('ssoAttrs')
|
||||
jqxhr = me.signupWithGPlus(email, gplusID)
|
||||
{ name } = forms.formToObject(@$el)
|
||||
jqxhr = me.signupWithGPlus(name, email, gplusID)
|
||||
when 'facebook'
|
||||
{ email, facebookID } = @signupState.get('ssoAttrs')
|
||||
jqxhr = me.signupWithFacebook(email, facebookID)
|
||||
{ name } = forms.formToObject(@$el)
|
||||
jqxhr = me.signupWithFacebook(name, email, facebookID)
|
||||
else
|
||||
{ email, password } = forms.formToObject(@$el)
|
||||
jqxhr = me.signupWithPassword(email, password)
|
||||
{ name, email, password } = forms.formToObject(@$el)
|
||||
jqxhr = me.signupWithPassword(name, email, password)
|
||||
|
||||
return new Promise(jqxhr.then)
|
||||
|
||||
|
|
|
@ -373,7 +373,7 @@ module.exports = class TeacherClassView extends RootView
|
|||
coursePlaytimesString += "0,"
|
||||
else
|
||||
coursePlaytimesString += "#{moment.duration(coursePlaytime.playtime, 'seconds').humanize()},"
|
||||
csvContent += "#{student.get('name')},#{student.get('email')},#{playtimeString},#{coursePlaytimesString}\"#{conceptsString}\"\n"
|
||||
csvContent += "#{student.get('name')},#{student.get('email') or ''},#{playtimeString},#{coursePlaytimesString}\"#{conceptsString}\"\n"
|
||||
csvContent = csvContent.substring(0, csvContent.length - 1)
|
||||
encodedUri = encodeURI(csvContent)
|
||||
window.open(encodedUri)
|
||||
|
|
|
@ -81,6 +81,7 @@ grabUser = (session, callback) ->
|
|||
|
||||
totalEmailsSent = 0
|
||||
emailUserInitialRecruiting = (user, callback) ->
|
||||
return callback null, false if not user.email
|
||||
#return callback null, false if user.emails?.anyNotes?.enabled is false # TODO: later, uncomment to obey also 'anyNotes' when that's untangled
|
||||
return callback null, false if user.emails?.recruitNotes?.enabled is false
|
||||
return callback null, false if user.email in alreadyEmailed
|
||||
|
@ -129,6 +130,7 @@ grabEmail = (winner, callback) ->
|
|||
callback null, winner
|
||||
|
||||
emailUserTournamentResults = (winner, callback) ->
|
||||
return callback null, false if not winner.email
|
||||
return callback null, false if DEBUGGING and (winner.team is 'humans' or totalEmailsSent > 1)
|
||||
++totalEmailsSent
|
||||
name = winner.name
|
||||
|
|
|
@ -82,6 +82,8 @@ module.exports = class Handler
|
|||
sendBadInputError: (res, message) -> errors.badInput(res, message)
|
||||
sendPaymentRequiredError: (res, message) -> errors.paymentRequired(res, message)
|
||||
sendDatabaseError: (res, err) ->
|
||||
if err instanceof errors.NetworkError
|
||||
return res.status(err.code).send(err.toJSON())
|
||||
return @sendError(res, err.code, err.response) if err?.response and err?.code
|
||||
log.error "Database error, #{err}"
|
||||
errors.serverError(res, 'Database error, ' + err)
|
||||
|
@ -467,6 +469,7 @@ module.exports = class Handler
|
|||
@notifyWatcherOfChange editor, watcher, changedDocument, editPath
|
||||
|
||||
notifyWatcherOfChange: (editor, watcher, changedDocument, editPath) ->
|
||||
return if not watcher.get('email')
|
||||
context =
|
||||
email_id: sendwithus.templates.change_made_notify_watcher
|
||||
recipient:
|
||||
|
|
|
@ -100,7 +100,7 @@ errorResponseSchema = {
|
|||
}
|
||||
errorProps = _.keys(errorResponseSchema.properties)
|
||||
|
||||
class NetworkError
|
||||
class NetworkError extends Error
|
||||
code: 0
|
||||
|
||||
constructor: (@message, options) ->
|
||||
|
|
|
@ -98,7 +98,8 @@ PatchHandler = class PatchHandler extends Handler
|
|||
@sendPatchCreatedEmail req.user, watcher, doc, doc.targetLoaded, docLink
|
||||
|
||||
sendPatchCreatedEmail: (patchCreator, watcher, patch, target, docLink) ->
|
||||
# return if watcher._id is patchCreator._id
|
||||
return if not watcher.get('email')
|
||||
# return if watcher._id is patchCreator._id
|
||||
context =
|
||||
email_id: sendwithus.templates.patch_created
|
||||
recipient:
|
||||
|
|
|
@ -270,6 +270,9 @@ class SubscriptionHandler extends Handler
|
|||
if (not req.user) or req.user.isAnonymous() or user.isAnonymous()
|
||||
return done({res: 'You must be signed in to subscribe.', code: 403})
|
||||
|
||||
if not req.user.get('email')
|
||||
return done({res: 'Your account needs an email address to subscribe.', code: 403})
|
||||
|
||||
token = req.body.stripe.token
|
||||
prepaidCode = req.body.stripe.prepaidCode
|
||||
customerID = user.get('stripe')?.customerID
|
||||
|
|
|
@ -110,9 +110,9 @@ UserHandler = class UserHandler extends Handler
|
|||
|
||||
# Name setting
|
||||
(req, user, callback) ->
|
||||
return callback(null, req, user) unless req.body.name
|
||||
return callback(null, req, user) unless req.body.name?
|
||||
nameLower = req.body.name?.toLowerCase()
|
||||
return callback(null, req, user) unless nameLower
|
||||
return callback(null, req, user) unless nameLower?
|
||||
return callback(null, req, user) if user.get 'anonymous' # anonymous users can have any name
|
||||
return callback(null, req, user) if nameLower is user.get('nameLower')
|
||||
User.findOne({nameLower: nameLower, anonymous: false}).exec (err, otherUser) ->
|
||||
|
|
|
@ -89,6 +89,8 @@ module.exports =
|
|||
timestamp = (new Date).getTime()
|
||||
if not user
|
||||
throw new errors.NotFound('User not found')
|
||||
if not user.get('email')
|
||||
throw new errors.UnprocessableEntity('User must have an email address to receive a verification email')
|
||||
context =
|
||||
email_id: sendwithus.templates.verify_email
|
||||
recipient:
|
||||
|
@ -127,14 +129,18 @@ module.exports =
|
|||
unless req.user.isAnonymous()
|
||||
throw new errors.Forbidden('You are already signed in.')
|
||||
|
||||
{ password, email } = req.body
|
||||
unless _.all([password, email])
|
||||
throw new errors.UnprocessableEntity('Requires password and email')
|
||||
{ name, email, password } = req.body
|
||||
unless password
|
||||
throw new errors.UnprocessableEntity('Requires password')
|
||||
unless name or email
|
||||
throw new errors.UnprocessableEntity('Requires username or email')
|
||||
|
||||
if yield User.findByEmail(email)
|
||||
if not _.isEmpty(email) and yield User.findByEmail(email)
|
||||
throw new errors.Conflict('Email already taken')
|
||||
if not _.isEmpty(name) and yield User.findByName(name)
|
||||
throw new errors.Conflict('Name already taken')
|
||||
|
||||
req.user.set({ password, email, anonymous: false })
|
||||
req.user.set({ name, email, password, anonymous: false })
|
||||
yield module.exports.finishSignup(req, res)
|
||||
|
||||
signupWithFacebook: wrap (req, res) ->
|
||||
|
|
|
@ -105,6 +105,7 @@ module.exports =
|
|||
if watchers.length
|
||||
User.find({_id:{$in:watchers}}).select({email:1, name:1}).exec (err, watchers) ->
|
||||
for watcher in watchers
|
||||
continue if not watcher.get('email')
|
||||
context =
|
||||
email_id: sendwithus.templates.change_made_notify_watcher
|
||||
recipient:
|
||||
|
|
|
@ -122,6 +122,10 @@ UserSchema.statics.findByEmail = (email, done=_.noop) ->
|
|||
emailLower = email.toLowerCase()
|
||||
User.findOne({emailLower: emailLower}).exec(done)
|
||||
|
||||
UserSchema.statics.findByName = (name, done=_.noop) ->
|
||||
nameLower = name.toLowerCase()
|
||||
User.findOne({nameLower: nameLower}).exec(done)
|
||||
|
||||
emailNameMap =
|
||||
generalNews: 'announcement'
|
||||
adventurerNews: 'tester'
|
||||
|
@ -267,6 +271,7 @@ UserSchema.statics.unconflictName = unconflictName = (name, done) ->
|
|||
unconflictName name + suffix, done
|
||||
|
||||
UserSchema.methods.sendWelcomeEmail = ->
|
||||
return if not @get('email')
|
||||
{ welcome_email_student, welcome_email_user } = sendwithus.templates
|
||||
timestamp = (new Date).getTime()
|
||||
data =
|
||||
|
@ -345,14 +350,25 @@ UserSchema.methods.saveActiveUser = (event, done=null) ->
|
|||
|
||||
UserSchema.pre('save', (next) ->
|
||||
if _.isNaN(@get('purchased')?.gems)
|
||||
return next(new errors.InternalServerError('Attempting to save NaN to user'))
|
||||
return next(new errors.InternalServerError('Attempting to save NaN to user'))
|
||||
Classroom = require './Classroom'
|
||||
if @isTeacher() and not @wasTeacher
|
||||
Classroom.update({members: @_id}, {$pull: {members: @_id}}, {multi: true}).exec (err, res) ->
|
||||
|
||||
if email = @get('email')
|
||||
@set('emailLower', email.toLowerCase())
|
||||
else
|
||||
@set('email', undefined)
|
||||
@set('emailLower', undefined)
|
||||
if name = @get('name')
|
||||
@set('nameLower', name.toLowerCase())
|
||||
else
|
||||
@set('name', undefined)
|
||||
@set('nameLower', undefined)
|
||||
|
||||
unless email or name or @get('anonymous') or @get('deleted')
|
||||
return next(new errors.UnprocessableEntity('User needs a username or email address'))
|
||||
|
||||
pwd = @get('password')
|
||||
if @get('password')
|
||||
@set('passwordHash', User.hashPassword(pwd))
|
||||
|
|
|
@ -26,7 +26,7 @@ module.exports = createNewTask = (req, res) ->
|
|||
|
||||
|
||||
validatePermissions = (req, sessionID, callback) ->
|
||||
return callback 'You are unauthorized to submit that game to the simulator.' unless req.user?.get('email')
|
||||
return callback 'You are unauthorized to submit that game to the simulator.' if (not req.user) or req.user.isAnonymous()
|
||||
return callback null if req.user?.isAdmin()
|
||||
|
||||
findParameters = _id: sessionID
|
||||
|
|
|
@ -26,7 +26,7 @@ module.exports = dispatchTaskToConsumer = (req, res) ->
|
|||
|
||||
|
||||
checkSimulationPermissions = (req, cb) ->
|
||||
if req.user?.get('email')
|
||||
if req.user and not req.user.isAnonymous()
|
||||
cb null
|
||||
else
|
||||
cb 'You need to be logged in to simulate games'
|
||||
|
|
|
@ -471,6 +471,7 @@ taskReminderAlreadySentThisWeekFilter = (task, cb) ->
|
|||
sendUserRemarkTaskEmail = (task, cb) ->
|
||||
mailTaskName = @mailTaskName
|
||||
User.findOne("_id":task.contact).select("email").lean().exec (err, contact) ->
|
||||
return if not contact.email
|
||||
if err? then return cb err
|
||||
User.findOne("_id":task.user).select("jobProfile.name").lean().exec (err, user) ->
|
||||
if err? then return cb err
|
||||
|
@ -567,6 +568,7 @@ handleLadderUpdate = (req, res) ->
|
|||
|
||||
sendLadderUpdateEmail = (session, now, daysAgo) ->
|
||||
User.findOne({_id: session.creator}).select('name email firstName lastName emailSubscriptions emails preferredLanguage').exec (err, user) ->
|
||||
return if not user.get('email')
|
||||
if err
|
||||
log.error "Couldn't find user for #{session.creator} from session #{session._id}"
|
||||
return
|
||||
|
@ -686,13 +688,14 @@ handleNextSteps = (req, res) ->
|
|||
log.info "Found #{results.length} next-steps users to email updates about for #{daysAgo} day(s) ago." if DEBUGGING
|
||||
sendNextStepsEmail result, now, daysAgo for result in results
|
||||
|
||||
sendNextStepsEmail = (user, now, daysAgo) ->
|
||||
module.exports.sendNextStepsEmail = sendNextStepsEmail = (user, now, daysAgo) ->
|
||||
return log.info "Not sending next steps email to user with no email address" if not user.get('email')
|
||||
unless user.isEmailSubscriptionEnabled('generalNews') and user.isEmailSubscriptionEnabled('anyNotes')
|
||||
log.info "Not sending email to #{user.get('email')} #{user.get('name')} because they only want emails about #{JSON.stringify(user.get('emails'))}" if DEBUGGING
|
||||
return
|
||||
|
||||
LevelSession.find({creator: user.get('_id') + ''}).select('levelName levelID changed state.complete playtime').lean().exec (err, sessions) ->
|
||||
return log.error "Couldn't find sessions for #{user.get('email')}: #{err}" if err
|
||||
return log.error "Couldn't find sessions for #{user.get('email')} #{user.get('name')}: #{err}" if err
|
||||
complete = (s for s in sessions when s.state?.complete)
|
||||
incomplete = (s for s in sessions when not s.state?.complete)
|
||||
return if complete.length < 2
|
||||
|
@ -704,7 +707,7 @@ sendNextStepsEmail = (user, now, daysAgo) ->
|
|||
nextLevel = null
|
||||
err = null
|
||||
do (err, nextLevel) ->
|
||||
return log.error "Couldn't find next level for #{user.get('email')}: #{err}" if err
|
||||
return log.error "Couldn't find next level for #{user.get('email')} #{user.get('name')}: #{err}" if err
|
||||
name = if user.get('firstName') and user.get('lastName') then "#{user.get('firstName')}" else user.get('name')
|
||||
name = 'Hero' if not name or name in ['Anoner', 'Anonymous']
|
||||
#secretLevel = switch user.get('testGroupNumber') % 8
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
require '../common'
|
||||
utils = require '../utils'
|
||||
mail = require '../../../server/routes/mail'
|
||||
sendwithus = require '../../../server/sendwithus'
|
||||
User = require '../../../server/models/User'
|
||||
request = require '../request'
|
||||
LevelSession = require '../../../server/models/LevelSession'
|
||||
|
||||
testPost =
|
||||
data:
|
||||
|
@ -37,3 +40,30 @@ describe 'handleUnsubscribe', ->
|
|||
expect(u.isEmailSubscriptionEnabled('ambassadorNews')).toBeFalsy()
|
||||
expect(u.isEmailSubscriptionEnabled('artisanNews')).toBeFalsy()
|
||||
done()
|
||||
|
||||
# This can be re-enabled on demand to test it, but for some async reason this
|
||||
# crashes jasmine soon afterward.
|
||||
describe 'sendNextStepsEmail', ->
|
||||
xit 'Sends the email', utils.wrap (done) ->
|
||||
user = yield utils.initUser({generalNews: {enabled: true}, anyNotes: {enabled: true}})
|
||||
expect(user.id).toBeDefined()
|
||||
yield new LevelSession({
|
||||
creator: user.id
|
||||
permissions: simplePermissions
|
||||
level: original: 'dungeon-arena'
|
||||
state: complete: true
|
||||
}).save()
|
||||
yield new LevelSession({
|
||||
creator: user.id
|
||||
permissions: simplePermissions
|
||||
level: original: 'dungeon-arena-2'
|
||||
state: complete: true
|
||||
}).save()
|
||||
|
||||
spyOn(sendwithus.api, 'send').and.callFake (options, cb) ->
|
||||
expect(options.recipient.address).toBe(user.get('email'))
|
||||
cb()
|
||||
done()
|
||||
|
||||
mail.sendNextStepsEmail(user, new Date, 5)
|
||||
.pend('Breaks other tests — must be run alone')
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
async = require 'async'
|
||||
config = require '../../../server_config'
|
||||
require '../common'
|
||||
utils = require '../../../app/core/utils' # Must come after require /common
|
||||
appUtils = require '../../../app/core/utils' # Must come after require /common
|
||||
utils = require '../utils'
|
||||
mongoose = require 'mongoose'
|
||||
TRAVIS = process.env.COCO_TRAVIS_TEST
|
||||
nockUtils = require '../nock-utils'
|
||||
|
@ -113,6 +114,13 @@ describe '/db/user, editing stripe property', ->
|
|||
request.put {uri: userURL, json: body, headers: headers}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe 403
|
||||
done()
|
||||
|
||||
it 'denies username-only users trying to subscribe', utils.wrap (done) ->
|
||||
user = yield utils.initUser({ email: undefined, })
|
||||
yield utils.loginUser(user)
|
||||
[res, body] = yield request.putAsync(getURL("/db/user/#{user.id}"), { headers, json: { stripe: { planID: 'basic', token: '12345' } } })
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
#- shared data between tests
|
||||
joeData = null
|
||||
|
@ -327,7 +335,7 @@ describe 'Subscriptions', ->
|
|||
return done() unless subscription?
|
||||
expect(subscription.plan.amount).toEqual(1)
|
||||
expect(subscription.customer).toEqual(sponsorCustomerID)
|
||||
expect(subscription.quantity).toEqual(utils.getSponsoredSubsAmount(subPrice, numSponsored, sponsorStripe.subscriptionID?))
|
||||
expect(subscription.quantity).toEqual(appUtils.getSponsoredSubsAmount(subPrice, numSponsored, sponsorStripe.subscriptionID?))
|
||||
|
||||
# Verify sponsor payment
|
||||
# May be greater than expected amount due to multiple subscribes and unsubscribes
|
||||
|
@ -336,7 +344,7 @@ describe 'Subscriptions', ->
|
|||
recipient: mongoose.Types.ObjectId(sponsorUserID)
|
||||
"stripe.customerID": sponsorCustomerID
|
||||
"stripe.subscriptionID": sponsorStripe.sponsorSubscriptionID
|
||||
expectedAmount = utils.getSponsoredSubsAmount(subPrice, numSponsored, sponsorStripe.subscriptionID?)
|
||||
expectedAmount = appUtils.getSponsoredSubsAmount(subPrice, numSponsored, sponsorStripe.subscriptionID?)
|
||||
Payment.find paymentQuery, (err, payments) ->
|
||||
expect(err).toBeNull()
|
||||
expect(payments).not.toBeNull()
|
||||
|
@ -1192,7 +1200,7 @@ describe 'Subscriptions', ->
|
|||
for invoice in invoices.data
|
||||
line = invoice.lines.data[0]
|
||||
if line.type is 'invoiceitem' and line.proration
|
||||
totalAmount = utils.getSponsoredSubsAmount(subPrice, 2, false)
|
||||
totalAmount = appUtils.getSponsoredSubsAmount(subPrice, 2, false)
|
||||
expect(invoice.total).toBeLessThan(totalAmount)
|
||||
expect(invoice.total).toEqual(totalAmount - subPrice)
|
||||
Payment.findOne "stripe.invoiceID": invoice.id, (err, payment) ->
|
||||
|
|
|
@ -234,6 +234,14 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
|
|||
expect(body.role).toBe('advisor')
|
||||
done()
|
||||
|
||||
it 'returns 422 if both email and name would be unset for a registered user', utils.wrap (done) ->
|
||||
user = yield utils.initUser()
|
||||
yield utils.loginUser(user)
|
||||
[res, body] = yield request.putAsync { uri: getURL('/db/user/'+user.id), json: { email: '', name: '' }}
|
||||
expect(body.code).toBe(422)
|
||||
expect(body.message).toEqual('User needs a username or email address')
|
||||
done()
|
||||
|
||||
describe 'PUT /db/user/-/become-student', ->
|
||||
beforeEach utils.wrap (done) ->
|
||||
@url = getURL('/db/user/-/become-student')
|
||||
|
@ -697,6 +705,50 @@ describe 'POST /db/user/:handle/signup-with-password', ->
|
|||
expect(sendwithus.api.send).toHaveBeenCalled()
|
||||
done()
|
||||
|
||||
it 'signs up the user with just a name and password', utils.wrap (done) ->
|
||||
user = yield utils.becomeAnonymous()
|
||||
url = getURL("/db/user/#{user.id}/signup-with-password")
|
||||
name = 'someusername'
|
||||
json = { name, password: '12345' }
|
||||
[res, body] = yield request.postAsync({url, json})
|
||||
expect(res.statusCode).toBe(200)
|
||||
updatedUser = yield User.findById(user.id)
|
||||
expect(updatedUser.get('name')).toBe(name)
|
||||
expect(updatedUser.get('nameLower')).toBe(name.toLowerCase())
|
||||
expect(updatedUser.get('slug')).toBe(name.toLowerCase())
|
||||
expect(updatedUser.get('passwordHash')).toBeDefined()
|
||||
expect(updatedUser.get('email')).toBeUndefined()
|
||||
expect(updatedUser.get('emailLower')).toBeUndefined()
|
||||
done()
|
||||
|
||||
it 'signs up the user with a username, email, and password', utils.wrap (done) ->
|
||||
user = yield utils.becomeAnonymous()
|
||||
url = getURL("/db/user/#{user.id}/signup-with-password")
|
||||
name = 'someusername'
|
||||
email = 'user@example.com'
|
||||
json = { name, email, password: '12345' }
|
||||
[res, body] = yield request.postAsync({url, json})
|
||||
expect(res.statusCode).toBe(200)
|
||||
updatedUser = yield User.findById(user.id)
|
||||
expect(updatedUser.get('name')).toBe(name)
|
||||
expect(updatedUser.get('nameLower')).toBe(name.toLowerCase())
|
||||
expect(updatedUser.get('slug')).toBe(name.toLowerCase())
|
||||
expect(updatedUser.get('email')).toBe(email)
|
||||
expect(updatedUser.get('emailLower')).toBe(email.toLowerCase())
|
||||
expect(updatedUser.get('passwordHash')).toBeDefined()
|
||||
done()
|
||||
|
||||
it 'returns 422 if neither username or email were provided', utils.wrap (done) ->
|
||||
user = yield utils.becomeAnonymous()
|
||||
url = getURL("/db/user/#{user.id}/signup-with-password")
|
||||
json = { password: '12345' }
|
||||
[res, body] = yield request.postAsync({url, json})
|
||||
expect(res.statusCode).toBe(422)
|
||||
updatedUser = yield User.findById(user.id)
|
||||
expect(updatedUser.get('anonymous')).toBe(true)
|
||||
expect(updatedUser.get('passwordHash')).toBeUndefined()
|
||||
done()
|
||||
|
||||
it 'returns 409 if there is already a user with the given email', utils.wrap (done) ->
|
||||
email = 'some@email.com'
|
||||
initialUser = yield utils.initUser({email})
|
||||
|
@ -707,6 +759,17 @@ describe 'POST /db/user/:handle/signup-with-password', ->
|
|||
[res, body] = yield request.postAsync({url, json})
|
||||
expect(res.statusCode).toBe(409)
|
||||
done()
|
||||
|
||||
it 'returns 409 if there is already a user with the given username', utils.wrap (done) ->
|
||||
name = 'someusername'
|
||||
initialUser = yield utils.initUser({name})
|
||||
expect(initialUser.get('nameLower')).toBeDefined()
|
||||
user = yield utils.becomeAnonymous()
|
||||
url = getURL("/db/user/#{user.id}/signup-with-password")
|
||||
json = { name, password: '12345' }
|
||||
[res, body] = yield request.postAsync({url, json})
|
||||
expect(res.statusCode).toBe(409)
|
||||
done()
|
||||
|
||||
it 'disassociates the user from their trial request if the trial request email and signup email do not match', utils.wrap (done) ->
|
||||
user = yield utils.becomeAnonymous()
|
||||
|
@ -739,7 +802,7 @@ describe 'POST /db/user/:handle/signup-with-facebook', ->
|
|||
facebookID = '12345'
|
||||
facebookEmail = 'some@email.com'
|
||||
|
||||
validFacebookResponse = new Promise((resolve) -> resolve({
|
||||
validFacebookResponse = new Promise((resolve) -> resolve({
|
||||
id: facebookID,
|
||||
email: facebookEmail,
|
||||
first_name: 'Some',
|
||||
|
@ -753,12 +816,12 @@ describe 'POST /db/user/:handle/signup-with-facebook', ->
|
|||
verified: true
|
||||
}))
|
||||
|
||||
invalidFacebookResponse = new Promise((resolve) -> resolve({
|
||||
invalidFacebookResponse = new Promise((resolve) -> resolve({
|
||||
error: {
|
||||
message: 'Invalid OAuth access token.',
|
||||
type: 'OAuthException',
|
||||
code: 190,
|
||||
fbtrace_id: 'EC4dEdeKHBH'
|
||||
fbtrace_id: 'EC4dEdeKHBH'
|
||||
}
|
||||
}))
|
||||
|
||||
|
|
|
@ -36,7 +36,8 @@ module.exports = mw =
|
|||
options = {}
|
||||
options = _.extend({
|
||||
permissions: []
|
||||
email: 'user'+_.uniqueId()+'@gmail.com'
|
||||
name: 'Name Nameyname '+_.uniqueId()
|
||||
email: 'user'+_.uniqueId()+'@example.com'
|
||||
password: 'password'
|
||||
anonymous: false
|
||||
}, options)
|
||||
|
@ -49,7 +50,7 @@ module.exports = mw =
|
|||
done = options
|
||||
options = {}
|
||||
form = {
|
||||
username: user.get('email')
|
||||
username: user.get('email') or user.get('name')
|
||||
password: 'password'
|
||||
}
|
||||
(options.request or request).post mw.getURL('/auth/login'), { form: form }, (err, res) ->
|
||||
|
@ -89,7 +90,7 @@ module.exports = mw =
|
|||
args = Array.from(arguments)
|
||||
[done, [data, sources]] = [args.pop(), args]
|
||||
|
||||
data = _.extend({}, {
|
||||
data = _.extend({}, {
|
||||
name: _.uniqueId('Level ')
|
||||
permissions: [{target: mw.lastLogin.id, access: 'owner'}]
|
||||
}, data)
|
||||
|
|
Loading…
Reference in a new issue