mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 00:31:21 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
96fa50afe4
37 changed files with 446 additions and 72 deletions
|
@ -5,12 +5,17 @@ language: node_js
|
|||
node_js:
|
||||
- 5.1.1
|
||||
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- mongodb-upstart
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- mongodb-org-server
|
||||
- g++-4.8
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
|
|
@ -34,9 +34,10 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'admin/design-elements': go('admin/DesignElementsView')
|
||||
'admin/files': go('admin/FilesView')
|
||||
'admin/analytics': go('admin/AnalyticsView')
|
||||
'admin/school-counts': go('admin/SchoolCountsView')
|
||||
'admin/analytics/subscriptions': go('admin/AnalyticsSubscriptionsView')
|
||||
'admin/level-sessions': go('admin/LevelSessionsView')
|
||||
'admin/school-counts': go('admin/SchoolCountsView')
|
||||
'admin/school-licenses': go('admin/SchoolLicensesView')
|
||||
'admin/users': go('admin/UsersView')
|
||||
'admin/base': go('admin/BaseView')
|
||||
'admin/demo-requests': go('admin/DemoRequestsView')
|
||||
|
|
|
@ -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:"
|
||||
|
|
|
@ -438,6 +438,8 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
tome_available_spells: "Доступные заклинания"
|
||||
tome_your_skills: "Ваши навыки"
|
||||
tome_current_method: "Текущий метод"
|
||||
hints: "Советы"
|
||||
hints_title: "Совет {{number}}"
|
||||
code_saved: "Код сохранен"
|
||||
skip_tutorial: "Пропуск (Esc)"
|
||||
keyboard_shortcuts: "Горячие клавиши"
|
||||
|
|
|
@ -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'
|
||||
|
|
21
app/styles/admin/admin-school-licenses.sass
Normal file
21
app/styles/admin/admin-school-licenses.sass
Normal file
|
@ -0,0 +1,21 @@
|
|||
#admin-school-licenses-view
|
||||
|
||||
table
|
||||
td, th
|
||||
padding: 0px
|
||||
|
||||
.range-container
|
||||
position: relative
|
||||
width: 100%
|
||||
.range-background
|
||||
position: absolute
|
||||
height: 100%
|
||||
left: 0px
|
||||
top: 0px
|
||||
background-color: green
|
||||
opacity: 0.25
|
||||
.range-dates
|
||||
position: absolute
|
||||
height: 100%
|
||||
left: 0px
|
||||
top: 0px
|
|
@ -36,5 +36,8 @@
|
|||
.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
|
||||
|
@ -47,6 +47,8 @@ block content
|
|||
input.classroom-progress-class-code(type=text value="<class code>")
|
||||
li
|
||||
a(href="/admin/analytics") Dashboard
|
||||
li
|
||||
a(href="/admin/school-licenses") School Active Licenses
|
||||
li
|
||||
a(href="/admin/school-counts") School Counts
|
||||
li
|
||||
|
|
39
app/templates/admin/school-licenses.jade
Normal file
39
app/templates/admin/school-licenses.jade
Normal file
|
@ -0,0 +1,39 @@
|
|||
extends /templates/base-flat
|
||||
|
||||
//- DO NOT TRANSLATE
|
||||
|
||||
block content
|
||||
|
||||
if !me.isAdmin()
|
||||
div You must be logged in as an admin to view this page.
|
||||
else if !view.schools
|
||||
h3 Loading...
|
||||
else
|
||||
h3 School Active Licenses
|
||||
.small Max: total licenses
|
||||
.small Used: licenses redeemed
|
||||
.small Activity: level sessions created in last 30 days
|
||||
table.table.table-condensed
|
||||
thead
|
||||
th School
|
||||
th Max
|
||||
th Used
|
||||
th Activity
|
||||
tr
|
||||
td(style="height:26px;").range-container
|
||||
each rangeKey in view.rangeKeys
|
||||
span.range-background(style="left:#{rangeKey.startScale}%;width:#{rangeKey.width}%;background-color:#{rangeKey.color}")
|
||||
span.range-dates(style="left:#{rangeKey.startScale}%;width:#{rangeKey.width}%;") #{rangeKey.name}
|
||||
td(colspan=2)
|
||||
each school in view.schools
|
||||
each prepaid in school.prepaids
|
||||
tr
|
||||
td.range-container
|
||||
span.range-background(style="left:#{prepaid.startScale}%;width:#{prepaid.rangeScale}%;")
|
||||
span.range-dates(style="left:#{prepaid.startScale}%;")
|
||||
span.spr #{prepaid.startDate.substring(0, 10)}
|
||||
strong.spr #{school.name}
|
||||
span #{prepaid.endDate.substring(0, 10)}
|
||||
td #{prepaid.max}
|
||||
td #{prepaid.used}
|
||||
td= school.activity
|
|
@ -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)
|
||||
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'
|
||||
|
|
72
app/views/admin/SchoolLicensesView.coffee
Normal file
72
app/views/admin/SchoolLicensesView.coffee
Normal file
|
@ -0,0 +1,72 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Prepaid = require 'models/Prepaid'
|
||||
TrialRequests = require 'collections/TrialRequests'
|
||||
|
||||
# TODO: year ranges hard-coded
|
||||
|
||||
module.exports = class SchoolLicensesView extends RootView
|
||||
id: 'admin-school-licenses-view'
|
||||
template: require 'templates/admin/school-licenses'
|
||||
|
||||
initialize: ->
|
||||
return super() unless me.isAdmin()
|
||||
@startDateRange = new Date()
|
||||
@endDateRange = new Date()
|
||||
@endDateRange.setUTCFullYear(@endDateRange.getUTCFullYear() + 2)
|
||||
@supermodel.addRequestResource({
|
||||
url: '/db/prepaid/-/active-schools'
|
||||
method: 'GET'
|
||||
success: ({prepaidActivityMap, schoolPrepaidsMap}) =>
|
||||
@updateSchools(prepaidActivityMap, schoolPrepaidsMap)
|
||||
}, 0).load()
|
||||
super()
|
||||
|
||||
updateSchools: (prepaidActivityMap, schoolPrepaidsMap) ->
|
||||
timeStart = @startDateRange.getTime()
|
||||
time2017 = new Date('2017').getTime()
|
||||
time2018 = new Date('2018').getTime()
|
||||
timeEnd = @endDateRange.getTime()
|
||||
rangeMilliseconds = timeEnd - timeStart
|
||||
@rangeKeys = [
|
||||
{name :'Today', color: 'blue', startScale: 0, width: Math.round((time2017 - timeStart) / rangeMilliseconds * 100)}
|
||||
{name: '2017', color: 'red', startScale: Math.round((time2017 - timeStart) / rangeMilliseconds * 100), width: Math.round((time2018 - time2017) / rangeMilliseconds * 100)}
|
||||
{name: '2018', color: 'yellow', startScale: Math.round((time2018 - timeStart) / rangeMilliseconds * 100), width: Math.round((timeEnd - time2018) / rangeMilliseconds * 100)}
|
||||
]
|
||||
|
||||
@schools = []
|
||||
for school, prepaids of schoolPrepaidsMap
|
||||
activity = 0
|
||||
schoolMax = 0
|
||||
schoolUsed = 0
|
||||
collapsedPrepaids = []
|
||||
for prepaid in prepaids
|
||||
activity += prepaidActivityMap[prepaid._id] ? 0
|
||||
startDate = prepaid.startDate
|
||||
endDate = prepaid.endDate
|
||||
max = parseInt(prepaid.maxRedeemers)
|
||||
used = parseInt(prepaid.redeemers?.length ? 0)
|
||||
schoolMax += max
|
||||
schoolUsed += used
|
||||
foundIdenticalDates = false
|
||||
for collapsedPrepaid in collapsedPrepaids
|
||||
if collapsedPrepaid.startDate.substring(0, 10) is startDate.substring(0, 10) and collapsedPrepaid.endDate.substring(0, 10) is endDate.substring(0, 10)
|
||||
collapsedPrepaid.max += parseInt(prepaid.maxRedeemers)
|
||||
collapsedPrepaid.used += parseInt(prepaid.redeemers?.length ? 0)
|
||||
foundIdenticalDates = true
|
||||
break
|
||||
unless foundIdenticalDates
|
||||
collapsedPrepaids.push({startDate, endDate, max, used})
|
||||
|
||||
for collapsedPrepaid in collapsedPrepaids
|
||||
collapsedPrepaid.startScale = Math.round((new Date(collapsedPrepaid.startDate).getTime() - @startDateRange.getTime()) / rangeMilliseconds * 100)
|
||||
collapsedPrepaid.startScale = 0 if collapsedPrepaid.startScale < 0
|
||||
collapsedPrepaid.rangeScale = Math.round((new Date(collapsedPrepaid.endDate).getTime() - new Date(collapsedPrepaid.startDate).getTime()) / rangeMilliseconds * 100)
|
||||
collapsedPrepaid.rangeScale = 100 - collapsedPrepaid.startScale if collapsedPrepaid.rangeScale + collapsedPrepaid.startScale > 100
|
||||
@schools.push {name: school, activity, max: schoolMax, used: schoolUsed, prepaids: collapsedPrepaids, startDate: collapsedPrepaids[0].startDate, endDate: collapsedPrepaids[0].endDate}
|
||||
|
||||
@schools.sort (a, b) ->
|
||||
b.activity - a.activity or new Date(a.endDate).getTime() - new Date(b.endDate).getTime() or b.max - a.max or b.used - a.used or b.prepaids.length - a.prepaids.length or b.name.localeCompare(a.name)
|
||||
|
||||
# console.log @schools
|
||||
@render()
|
|
@ -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
|
||||
|
|
|
@ -52,6 +52,36 @@ var courses =
|
|||
duration: NumberInt(5),
|
||||
free: false,
|
||||
screenshot: "/images/pages/courses/105_info.png"
|
||||
},
|
||||
{
|
||||
name: "CS: Game Development 1",
|
||||
slug: "game-dev-1",
|
||||
campaignID: ObjectId("5789236960deed1f00ec2ab8"),
|
||||
description: "Learn to create your owns games which you can share with your friends.",
|
||||
duration: NumberInt(5),
|
||||
free: false,
|
||||
//screenshot: "/images/pages/courses/105_info.png",
|
||||
adminOnly: true
|
||||
},
|
||||
{
|
||||
name: "CS: Web Development 1",
|
||||
slug: "web-dev-1",
|
||||
campaignID: ObjectId("578913f2c8871ac2326fa3e4"),
|
||||
description: "Learn the basics of web development in this introductory HTML & CSS course.",
|
||||
duration: NumberInt(5),
|
||||
free: false,
|
||||
//screenshot: "/images/pages/courses/105_info.png",
|
||||
adminOnly: true
|
||||
},
|
||||
{
|
||||
name: "CS: Web Development 2",
|
||||
slug: "web-dev-2",
|
||||
campaignID: ObjectId("57891570c8871ac2326fa3f8"),
|
||||
description: "Learn more advanced web development, including scripting to make interactive webpages.",
|
||||
duration: NumberInt(5),
|
||||
free: false,
|
||||
//screenshot: "/images/pages/courses/105_info.png",
|
||||
adminOnly: true
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -62,7 +92,7 @@ for (var i = 0; i < courses.length; i++) {
|
|||
if (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
for (var levelID in doc.levels) {
|
||||
for (var j = 0; j < doc.levels[levelID].concepts.length; j++) {
|
||||
for (var j = 0; j < (doc.levels[levelID].concepts || []).length; j++) {
|
||||
concepts[doc.levels[levelID].concepts[j]] = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,6 +98,7 @@ PatchHandler = class PatchHandler extends Handler
|
|||
@sendPatchCreatedEmail req.user, watcher, doc, doc.targetLoaded, docLink
|
||||
|
||||
sendPatchCreatedEmail: (patchCreator, watcher, patch, target, docLink) ->
|
||||
return if not watcher.get('email')
|
||||
# return if watcher._id is patchCreator._id
|
||||
context =
|
||||
email_id: sendwithus.templates.patch_created
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -115,7 +115,7 @@ module.exports =
|
|||
_type: "lead"
|
||||
lead_id: leadID
|
||||
assigned_to: userID
|
||||
text: "Call #{teacherEmail}"
|
||||
text: "Call license inquiry #{teacherEmail}"
|
||||
is_complete: false
|
||||
options =
|
||||
uri: "https://#{apiKey}:X@app.close.io/api/v1/task/"
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
wrap = require 'co-express'
|
||||
errors = require '../commons/errors'
|
||||
database = require '../commons/database'
|
||||
Prepaid = require '../models/Prepaid'
|
||||
User = require '../models/User'
|
||||
mongoose = require 'mongoose'
|
||||
LevelSession = require '../models/LevelSession'
|
||||
Prepaid = require '../models/Prepaid'
|
||||
TrialRequest = require '../models/TrialRequest'
|
||||
User = require '../models/User'
|
||||
|
||||
cutoffDate = new Date(2015,11,11)
|
||||
cutoffID = mongoose.Types.ObjectId(Math.floor(cutoffDate/1000).toString(16)+'0000000000000000')
|
||||
|
@ -108,3 +110,43 @@ module.exports =
|
|||
|
||||
prepaids = yield Prepaid.find(q)
|
||||
res.send((prepaid.toObject({req: req}) for prepaid in prepaids))
|
||||
|
||||
fetchActiveSchools: wrap (req, res) ->
|
||||
unless req.user.isAdmin() or creator is req.user.id
|
||||
throw new errors.Forbidden('Must be logged in as given creator')
|
||||
prepaids = yield Prepaid.find({type: 'course'}, {creator: 1, properties: 1, startDate: 1, endDate: 1, maxRedeemers: 1, redeemers: 1}).lean()
|
||||
userPrepaidsMap = {}
|
||||
today = new Date()
|
||||
userIDs = []
|
||||
redeemerIDs = []
|
||||
redeemerPrepaidMap = {}
|
||||
for prepaid in prepaids
|
||||
continue if new Date(prepaid.endDate ? prepaid.properties?.endDate ? '2000') < today
|
||||
continue if new Date(prepaid.endDate) < new Date(prepaid.startDate)
|
||||
userPrepaidsMap[prepaid.creator.valueOf()] ?= []
|
||||
userPrepaidsMap[prepaid.creator.valueOf()].push(prepaid)
|
||||
userIDs.push prepaid.creator
|
||||
for redeemer in prepaid.redeemers ? []
|
||||
redeemerIDs.push redeemer.userID + ""
|
||||
redeemerPrepaidMap[redeemer.userID + ""] = prepaid._id.valueOf()
|
||||
|
||||
# Find recently created level sessions for redeemers
|
||||
lastMonth = new Date()
|
||||
lastMonth.setUTCDate(lastMonth.getUTCDate() - 30)
|
||||
levelSessions = yield LevelSession.find({$and: [{created: {$gte: lastMonth}}, {creator: {$in: redeemerIDs}}]}, {creator: 1}).lean()
|
||||
prepaidActivityMap = {}
|
||||
for levelSession in levelSessions
|
||||
prepaidActivityMap[redeemerPrepaidMap[levelSession.creator.valueOf()]] ?= 0
|
||||
prepaidActivityMap[redeemerPrepaidMap[levelSession.creator.valueOf()]]++
|
||||
|
||||
trialRequests = yield TrialRequest.find({$and: [{type: 'course'}, {applicant: {$in: userIDs}}]}, {applicant: 1, properties: 1}).lean()
|
||||
schoolPrepaidsMap = {}
|
||||
for trialRequest in trialRequests
|
||||
school = trialRequest.properties?.organization ? trialRequest.properties?.school
|
||||
continue unless school
|
||||
if userPrepaidsMap[trialRequest.applicant.valueOf()]?.length > 0
|
||||
schoolPrepaidsMap[school] ?= []
|
||||
for prepaid in userPrepaidsMap[trialRequest.applicant.valueOf()]
|
||||
schoolPrepaidsMap[school].push prepaid
|
||||
|
||||
res.send({prepaidActivityMap, schoolPrepaidsMap})
|
||||
|
|
|
@ -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 =
|
||||
|
@ -349,10 +354,21 @@ UserSchema.pre('save', (next) ->
|
|||
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'
|
||||
|
|
|
@ -104,6 +104,7 @@ module.exports.setup = (app) ->
|
|||
app.post('/db/user/:handle/signup-with-password', mw.users.signupWithPassword)
|
||||
|
||||
app.get('/db/prepaid', mw.auth.checkLoggedIn(), mw.prepaids.fetchByCreator)
|
||||
app.get('/db/prepaid/-/active-schools', mw.auth.checkHasPermission(['admin']), mw.prepaids.fetchActiveSchools)
|
||||
app.post('/db/prepaid', mw.auth.checkHasPermission(['admin']), mw.prepaids.post)
|
||||
app.post('/db/prepaid/:handle/redeemers', mw.prepaids.redeem)
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
@ -114,6 +115,13 @@ describe '/db/user, editing stripe property', ->
|
|||
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
|
||||
firstSubscriptionID = 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})
|
||||
|
@ -708,6 +760,17 @@ describe 'POST /db/user/:handle/signup-with-password', ->
|
|||
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()
|
||||
trialRequest = yield utils.makeTrialRequest({ properties: { email: 'one@email.com' } })
|
||||
|
|
|
@ -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) ->
|
||||
|
|
Loading…
Reference in a new issue