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
|
@ -4,13 +4,18 @@ language: node_js
|
||||||
|
|
||||||
node_js:
|
node_js:
|
||||||
- 5.1.1
|
- 5.1.1
|
||||||
|
|
||||||
|
env:
|
||||||
|
- CXX=g++-4.8
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
sources:
|
||||||
- mongodb-upstart
|
- mongodb-upstart
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
packages:
|
packages:
|
||||||
- mongodb-org-server
|
- mongodb-org-server
|
||||||
|
- g++-4.8
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
|
|
|
@ -34,9 +34,10 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'admin/design-elements': go('admin/DesignElementsView')
|
'admin/design-elements': go('admin/DesignElementsView')
|
||||||
'admin/files': go('admin/FilesView')
|
'admin/files': go('admin/FilesView')
|
||||||
'admin/analytics': go('admin/AnalyticsView')
|
'admin/analytics': go('admin/AnalyticsView')
|
||||||
'admin/school-counts': go('admin/SchoolCountsView')
|
|
||||||
'admin/analytics/subscriptions': go('admin/AnalyticsSubscriptionsView')
|
'admin/analytics/subscriptions': go('admin/AnalyticsSubscriptionsView')
|
||||||
'admin/level-sessions': go('admin/LevelSessionsView')
|
'admin/level-sessions': go('admin/LevelSessionsView')
|
||||||
|
'admin/school-counts': go('admin/SchoolCountsView')
|
||||||
|
'admin/school-licenses': go('admin/SchoolLicensesView')
|
||||||
'admin/users': go('admin/UsersView')
|
'admin/users': go('admin/UsersView')
|
||||||
'admin/base': go('admin/BaseView')
|
'admin/base': go('admin/BaseView')
|
||||||
'admin/demo-requests': go('admin/DemoRequestsView')
|
'admin/demo-requests': go('admin/DemoRequestsView')
|
||||||
|
|
|
@ -961,7 +961,7 @@
|
||||||
manage_subscription: "Click here to manage your subscription."
|
manage_subscription: "Click here to manage your subscription."
|
||||||
new_password: "New Password"
|
new_password: "New Password"
|
||||||
new_password_verify: "Verify"
|
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_email_progress: "Type in your email to confirm deleting your progress."
|
||||||
type_in_password: "Also, type in your password."
|
type_in_password: "Also, type in your password."
|
||||||
email_subscriptions: "Email Subscriptions"
|
email_subscriptions: "Email Subscriptions"
|
||||||
|
@ -1333,7 +1333,7 @@
|
||||||
update_account_title: "Your account needs attention!"
|
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_blurb: "Before you can access your classes, choose how you want to use this account."
|
||||||
update_account_current_type: "Current Account Type:"
|
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_am_teacher: "I am a teacher"
|
||||||
update_account_keep_access: "Keep access to classes I've created"
|
update_account_keep_access: "Keep access to classes I've created"
|
||||||
update_account_teachers_can: "Teacher accounts can:"
|
update_account_teachers_can: "Teacher accounts can:"
|
||||||
|
|
|
@ -438,6 +438,8 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
||||||
tome_available_spells: "Доступные заклинания"
|
tome_available_spells: "Доступные заклинания"
|
||||||
tome_your_skills: "Ваши навыки"
|
tome_your_skills: "Ваши навыки"
|
||||||
tome_current_method: "Текущий метод"
|
tome_current_method: "Текущий метод"
|
||||||
|
hints: "Советы"
|
||||||
|
hints_title: "Совет {{number}}"
|
||||||
code_saved: "Код сохранен"
|
code_saved: "Код сохранен"
|
||||||
skip_tutorial: "Пропуск (Esc)"
|
skip_tutorial: "Пропуск (Esc)"
|
||||||
keyboard_shortcuts: "Горячие клавиши"
|
keyboard_shortcuts: "Горячие клавиши"
|
||||||
|
|
|
@ -271,11 +271,11 @@ module.exports = class User extends CocoModel
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
@fetch(options)
|
@fetch(options)
|
||||||
|
|
||||||
signupWithPassword: (email, password, options={}) ->
|
signupWithPassword: (name, email, password, options={}) ->
|
||||||
options.url = _.result(@, 'url') + '/signup-with-password'
|
options.url = _.result(@, 'url') + '/signup-with-password'
|
||||||
options.type = 'POST'
|
options.type = 'POST'
|
||||||
options.data ?= {}
|
options.data ?= {}
|
||||||
_.extend(options.data, {email, password})
|
_.extend(options.data, {name, email, password})
|
||||||
jqxhr = @fetch(options)
|
jqxhr = @fetch(options)
|
||||||
jqxhr.then ->
|
jqxhr.then ->
|
||||||
window.tracker?.trackEvent 'Finished Signup', category: "Signup", label: 'CodeCombat'
|
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
|
|
@ -35,6 +35,9 @@
|
||||||
|
|
||||||
.help-block
|
.help-block
|
||||||
margin: 0
|
margin: 0
|
||||||
|
|
||||||
|
.optional-help-block
|
||||||
|
font-style: italic
|
||||||
|
|
||||||
.form-container
|
.form-container
|
||||||
width: 800px
|
width: 800px
|
||||||
|
|
|
@ -10,7 +10,7 @@ else
|
||||||
.panel-body
|
.panel-body
|
||||||
.form
|
.form
|
||||||
- var name = me.get('name') || '';
|
- 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 admin = me.get('permissions', true).indexOf('admin') != -1;
|
||||||
- var godmode = me.get('permissions', true).indexOf('godmode') != -1;
|
- var godmode = me.get('permissions', true).indexOf('godmode') != -1;
|
||||||
.form-group
|
.form-group
|
||||||
|
@ -71,11 +71,11 @@ else
|
||||||
.panel-body
|
.panel-body
|
||||||
.form#delete-account-form
|
.form#delete-account-form
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="email1", data-i18n="account_settings.type_in_email")
|
label.control-label(for="delete-account-email-or-username", data-i18n="account_settings.type_in_email")
|
||||||
input#email1.form-control(name="email", type="email")
|
input#delete-account-email-or-username.form-control(name="emailOrUsername")
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="password1", data-i18n="account_settings.type_in_password")
|
label.control-label(for="delete-account-password", data-i18n="account_settings.type_in_password")
|
||||||
input#password1.form-control(name="password", type="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")
|
button#delete-account-btn.btn.form-control.btn-primary(data-i18n="account_settings.delete_this_account")
|
||||||
|
|
||||||
.col-md-6
|
.col-md-6
|
||||||
|
|
|
@ -10,10 +10,10 @@ block content
|
||||||
.col-sm-1
|
.col-sm-1
|
||||||
button.btn.btn-primary.btn-large#enter-espionage-mode 007
|
button.btn.btn-primary.btn-large#enter-espionage-mode 007
|
||||||
label.control-label.col-sm-5(for="espionage-name-or-email")
|
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
|
if view.amActually
|
||||||
br
|
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
|
br
|
||||||
button#stop-spying-btn.btn.btn-xs Stop Spying
|
button#stop-spying-btn.btn.btn-xs Stop Spying
|
||||||
form#user-search-form.form-group
|
form#user-search-form.form-group
|
||||||
|
@ -47,6 +47,8 @@ block content
|
||||||
input.classroom-progress-class-code(type=text value="<class code>")
|
input.classroom-progress-class-code(type=text value="<class code>")
|
||||||
li
|
li
|
||||||
a(href="/admin/analytics") Dashboard
|
a(href="/admin/analytics") Dashboard
|
||||||
|
li
|
||||||
|
a(href="/admin/school-licenses") School Active Licenses
|
||||||
li
|
li
|
||||||
a(href="/admin/school-counts") School Counts
|
a(href="/admin/school-counts") School Counts
|
||||||
li
|
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")
|
span(data-i18n="share_progress_modal.form_label")
|
||||||
.col-xs-5.col-xs-offset-3
|
.col-xs-5.col-xs-offset-3
|
||||||
input.form-control.input-lg#email-input(name="email" type="email")
|
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
|
.col-xs-4.email-check
|
||||||
- var checkEmailState = view.state.get('checkEmailState');
|
- var checkEmailState = view.state.get('checkEmailState');
|
||||||
if checkEmailState === 'checking'
|
if checkEmailState === 'checking'
|
||||||
|
@ -53,6 +56,7 @@ form#basic-info-form.modal-body.basic-info
|
||||||
span.text-forest.glyphicon.glyphicon-ok-circle
|
span.text-forest.glyphicon.glyphicon-ok-circle
|
||||||
=" "
|
=" "
|
||||||
span(data-i18n="signup.email_good")
|
span(data-i18n="signup.email_good")
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
.row
|
.row
|
||||||
.col-xs-7.col-xs-offset-3
|
.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.text-forest.glyphicon.glyphicon-ok-circle
|
||||||
=" "
|
=" "
|
||||||
span(data-i18n="signup.name_available")
|
span(data-i18n="signup.name_available")
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
.row
|
.row
|
||||||
.col-xs-7.col-xs-offset-3
|
.col-xs-7.col-xs-offset-3
|
||||||
|
@ -81,6 +86,7 @@ form#basic-info-form.modal-body.basic-info
|
||||||
span(data-i18n="general.password")
|
span(data-i18n="general.password")
|
||||||
.col-xs-5.col-xs-offset-3
|
.col-xs-5.col-xs-offset-3
|
||||||
input.form-control.input-lg#password-input(name="password" type="password")
|
input.form-control.input-lg#password-input(name="password" type="password")
|
||||||
|
|
||||||
.form-group.checkbox.subscribe
|
.form-group.checkbox.subscribe
|
||||||
.row
|
.row
|
||||||
.col-xs-7.col-xs-offset-3
|
.col-xs-7.col-xs-offset-3
|
||||||
|
|
|
@ -27,7 +27,7 @@ block content
|
||||||
if view.accountType
|
if view.accountType
|
||||||
div #{view.accountType}
|
div #{view.accountType}
|
||||||
div
|
div
|
||||||
span.spr #{me.get('email')}
|
span.spr #{me.get('email') || me.get('name')}
|
||||||
span.not_you
|
span.not_you
|
||||||
span.spr(data-i18n="courses.not_you")
|
span.spr(data-i18n="courses.not_you")
|
||||||
a.logout-btn(data-i18n="login.log_out", href="#")
|
a.logout-btn(data-i18n="login.log_out", href="#")
|
||||||
|
|
|
@ -4,7 +4,10 @@ block modal-header-content
|
||||||
.text-center
|
.text-center
|
||||||
h1.modal-title(data-i18n="courses.remove_student1")
|
h1.modal-title(data-i18n="courses.remove_student1")
|
||||||
span.glyphicon.glyphicon-warning-sign.text-danger
|
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")
|
h2(data-i18n="courses.are_you_sure")
|
||||||
|
|
||||||
block modal-body-content
|
block modal-body-content
|
||||||
|
|
|
@ -87,16 +87,16 @@ module.exports = class AccountSettingsView extends CocoView
|
||||||
|
|
||||||
validateCredentialsForDestruction: ($form, onSuccess) ->
|
validateCredentialsForDestruction: ($form, onSuccess) ->
|
||||||
forms.clearFormAlerts($form)
|
forms.clearFormAlerts($form)
|
||||||
enteredEmail = $form.find('input[type="email"]').val()
|
enteredEmailOrUsername = $form.find('input[name="emailOrUsername"]').val()
|
||||||
enteredPassword = $form.find('input[type="password"]').val()
|
enteredPassword = $form.find('input[name="password"]').val()
|
||||||
if enteredEmail and enteredEmail is me.get('email')
|
if enteredEmailOrUsername and enteredEmailOrUsername in [me.get('email'), me.get('name')]
|
||||||
isPasswordCorrect = false
|
isPasswordCorrect = false
|
||||||
toBeDelayed = true
|
toBeDelayed = true
|
||||||
$.ajax
|
$.ajax
|
||||||
url: '/auth/login'
|
url: '/auth/login'
|
||||||
type: 'POST'
|
type: 'POST'
|
||||||
data:
|
data:
|
||||||
username: enteredEmail
|
username: enteredEmailOrUsername
|
||||||
password: enteredPassword
|
password: enteredPassword
|
||||||
parse: true
|
parse: true
|
||||||
error: (error) ->
|
error: (error) ->
|
||||||
|
@ -225,9 +225,16 @@ module.exports = class AccountSettingsView extends CocoView
|
||||||
return unless res
|
return unless res
|
||||||
|
|
||||||
res.error =>
|
res.error =>
|
||||||
errors = JSON.parse(res.responseText)
|
if res.responseJSON?.property
|
||||||
forms.applyErrorsToForm(@$el, errors)
|
errors = res.responseJSON
|
||||||
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
|
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'
|
@trigger 'save-user-error'
|
||||||
res.success (model, response, options) =>
|
res.success (model, response, options) =>
|
||||||
@trigger 'save-user-success'
|
@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: ->
|
checkEmail: ->
|
||||||
email = @$('[name="email"]').val()
|
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')
|
return @state.get('checkEmailPromise')
|
||||||
|
|
||||||
if not (email and forms.validateEmail(email))
|
if not (email and forms.validateEmail(email))
|
||||||
|
@ -155,7 +156,7 @@ module.exports = class BasicInfoView extends CocoView
|
||||||
email: User.schema.properties.email
|
email: User.schema.properties.email
|
||||||
name: User.schema.properties.name
|
name: User.schema.properties.name
|
||||||
password: User.schema.properties.password
|
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'
|
onClickBackButton: -> @trigger 'nav-back'
|
||||||
|
|
||||||
|
@ -176,20 +177,20 @@ module.exports = class BasicInfoView extends CocoView
|
||||||
@checkEmail()
|
@checkEmail()
|
||||||
.then @checkName()
|
.then @checkName()
|
||||||
.then =>
|
.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
|
throw AbortError
|
||||||
|
|
||||||
# update User
|
# update User
|
||||||
emails = _.assign({}, me.get('emails'))
|
emails = _.assign({}, me.get('emails'))
|
||||||
emails.generalNews ?= {}
|
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)
|
me.set('emails', emails)
|
||||||
|
|
||||||
unless _.isNaN(@signupState.get('birthday').getTime())
|
unless _.isNaN(@signupState.get('birthday').getTime())
|
||||||
me.set('birthday', @signupState.get('birthday').toISOString())
|
me.set('birthday', @signupState.get('birthday').toISOString())
|
||||||
|
|
||||||
me.set(_.omit(@signupState.get('ssoAttrs') or {}, 'email', 'facebookID', 'gplusID'))
|
me.set(_.omit(@signupState.get('ssoAttrs') or {}, 'email', 'facebookID', 'gplusID'))
|
||||||
me.set('name', @$('input[name="name"]').val())
|
|
||||||
jqxhr = me.save()
|
jqxhr = me.save()
|
||||||
if not jqxhr
|
if not jqxhr
|
||||||
console.error(me.validationError)
|
console.error(me.validationError)
|
||||||
|
@ -203,13 +204,15 @@ module.exports = class BasicInfoView extends CocoView
|
||||||
switch @signupState.get('ssoUsed')
|
switch @signupState.get('ssoUsed')
|
||||||
when 'gplus'
|
when 'gplus'
|
||||||
{ email, gplusID } = @signupState.get('ssoAttrs')
|
{ email, gplusID } = @signupState.get('ssoAttrs')
|
||||||
jqxhr = me.signupWithGPlus(email, gplusID)
|
{ name } = forms.formToObject(@$el)
|
||||||
|
jqxhr = me.signupWithGPlus(name, email, gplusID)
|
||||||
when 'facebook'
|
when 'facebook'
|
||||||
{ email, facebookID } = @signupState.get('ssoAttrs')
|
{ email, facebookID } = @signupState.get('ssoAttrs')
|
||||||
jqxhr = me.signupWithFacebook(email, facebookID)
|
{ name } = forms.formToObject(@$el)
|
||||||
|
jqxhr = me.signupWithFacebook(name, email, facebookID)
|
||||||
else
|
else
|
||||||
{ email, password } = forms.formToObject(@$el)
|
{ name, email, password } = forms.formToObject(@$el)
|
||||||
jqxhr = me.signupWithPassword(email, password)
|
jqxhr = me.signupWithPassword(name, email, password)
|
||||||
|
|
||||||
return new Promise(jqxhr.then)
|
return new Promise(jqxhr.then)
|
||||||
|
|
||||||
|
|
|
@ -373,7 +373,7 @@ module.exports = class TeacherClassView extends RootView
|
||||||
coursePlaytimesString += "0,"
|
coursePlaytimesString += "0,"
|
||||||
else
|
else
|
||||||
coursePlaytimesString += "#{moment.duration(coursePlaytime.playtime, 'seconds').humanize()},"
|
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)
|
csvContent = csvContent.substring(0, csvContent.length - 1)
|
||||||
encodedUri = encodeURI(csvContent)
|
encodedUri = encodeURI(csvContent)
|
||||||
window.open(encodedUri)
|
window.open(encodedUri)
|
||||||
|
|
|
@ -81,6 +81,7 @@ grabUser = (session, callback) ->
|
||||||
|
|
||||||
totalEmailsSent = 0
|
totalEmailsSent = 0
|
||||||
emailUserInitialRecruiting = (user, callback) ->
|
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?.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.emails?.recruitNotes?.enabled is false
|
||||||
return callback null, false if user.email in alreadyEmailed
|
return callback null, false if user.email in alreadyEmailed
|
||||||
|
@ -129,6 +130,7 @@ grabEmail = (winner, callback) ->
|
||||||
callback null, winner
|
callback null, winner
|
||||||
|
|
||||||
emailUserTournamentResults = (winner, callback) ->
|
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)
|
return callback null, false if DEBUGGING and (winner.team is 'humans' or totalEmailsSent > 1)
|
||||||
++totalEmailsSent
|
++totalEmailsSent
|
||||||
name = winner.name
|
name = winner.name
|
||||||
|
|
|
@ -52,6 +52,36 @@ var courses =
|
||||||
duration: NumberInt(5),
|
duration: NumberInt(5),
|
||||||
free: false,
|
free: false,
|
||||||
screenshot: "/images/pages/courses/105_info.png"
|
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()) {
|
if (cursor.hasNext()) {
|
||||||
var doc = cursor.next();
|
var doc = cursor.next();
|
||||||
for (var levelID in doc.levels) {
|
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;
|
concepts[doc.levels[levelID].concepts[j]] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,8 @@ module.exports = class Handler
|
||||||
sendBadInputError: (res, message) -> errors.badInput(res, message)
|
sendBadInputError: (res, message) -> errors.badInput(res, message)
|
||||||
sendPaymentRequiredError: (res, message) -> errors.paymentRequired(res, message)
|
sendPaymentRequiredError: (res, message) -> errors.paymentRequired(res, message)
|
||||||
sendDatabaseError: (res, err) ->
|
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
|
return @sendError(res, err.code, err.response) if err?.response and err?.code
|
||||||
log.error "Database error, #{err}"
|
log.error "Database error, #{err}"
|
||||||
errors.serverError(res, '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
|
||||||
|
|
||||||
notifyWatcherOfChange: (editor, watcher, changedDocument, editPath) ->
|
notifyWatcherOfChange: (editor, watcher, changedDocument, editPath) ->
|
||||||
|
return if not watcher.get('email')
|
||||||
context =
|
context =
|
||||||
email_id: sendwithus.templates.change_made_notify_watcher
|
email_id: sendwithus.templates.change_made_notify_watcher
|
||||||
recipient:
|
recipient:
|
||||||
|
|
|
@ -100,7 +100,7 @@ errorResponseSchema = {
|
||||||
}
|
}
|
||||||
errorProps = _.keys(errorResponseSchema.properties)
|
errorProps = _.keys(errorResponseSchema.properties)
|
||||||
|
|
||||||
class NetworkError
|
class NetworkError extends Error
|
||||||
code: 0
|
code: 0
|
||||||
|
|
||||||
constructor: (@message, options) ->
|
constructor: (@message, options) ->
|
||||||
|
|
|
@ -98,7 +98,8 @@ PatchHandler = class PatchHandler extends Handler
|
||||||
@sendPatchCreatedEmail req.user, watcher, doc, doc.targetLoaded, docLink
|
@sendPatchCreatedEmail req.user, watcher, doc, doc.targetLoaded, docLink
|
||||||
|
|
||||||
sendPatchCreatedEmail: (patchCreator, watcher, patch, target, 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 =
|
context =
|
||||||
email_id: sendwithus.templates.patch_created
|
email_id: sendwithus.templates.patch_created
|
||||||
recipient:
|
recipient:
|
||||||
|
|
|
@ -270,6 +270,9 @@ class SubscriptionHandler extends Handler
|
||||||
if (not req.user) or req.user.isAnonymous() or user.isAnonymous()
|
if (not req.user) or req.user.isAnonymous() or user.isAnonymous()
|
||||||
return done({res: 'You must be signed in to subscribe.', code: 403})
|
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
|
token = req.body.stripe.token
|
||||||
prepaidCode = req.body.stripe.prepaidCode
|
prepaidCode = req.body.stripe.prepaidCode
|
||||||
customerID = user.get('stripe')?.customerID
|
customerID = user.get('stripe')?.customerID
|
||||||
|
|
|
@ -110,9 +110,9 @@ UserHandler = class UserHandler extends Handler
|
||||||
|
|
||||||
# Name setting
|
# Name setting
|
||||||
(req, user, callback) ->
|
(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()
|
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 user.get 'anonymous' # anonymous users can have any name
|
||||||
return callback(null, req, user) if nameLower is user.get('nameLower')
|
return callback(null, req, user) if nameLower is user.get('nameLower')
|
||||||
User.findOne({nameLower: nameLower, anonymous: false}).exec (err, otherUser) ->
|
User.findOne({nameLower: nameLower, anonymous: false}).exec (err, otherUser) ->
|
||||||
|
|
|
@ -115,7 +115,7 @@ module.exports =
|
||||||
_type: "lead"
|
_type: "lead"
|
||||||
lead_id: leadID
|
lead_id: leadID
|
||||||
assigned_to: userID
|
assigned_to: userID
|
||||||
text: "Call #{teacherEmail}"
|
text: "Call license inquiry #{teacherEmail}"
|
||||||
is_complete: false
|
is_complete: false
|
||||||
options =
|
options =
|
||||||
uri: "https://#{apiKey}:X@app.close.io/api/v1/task/"
|
uri: "https://#{apiKey}:X@app.close.io/api/v1/task/"
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
wrap = require 'co-express'
|
wrap = require 'co-express'
|
||||||
errors = require '../commons/errors'
|
errors = require '../commons/errors'
|
||||||
database = require '../commons/database'
|
database = require '../commons/database'
|
||||||
Prepaid = require '../models/Prepaid'
|
|
||||||
User = require '../models/User'
|
|
||||||
mongoose = require 'mongoose'
|
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)
|
cutoffDate = new Date(2015,11,11)
|
||||||
cutoffID = mongoose.Types.ObjectId(Math.floor(cutoffDate/1000).toString(16)+'0000000000000000')
|
cutoffID = mongoose.Types.ObjectId(Math.floor(cutoffDate/1000).toString(16)+'0000000000000000')
|
||||||
|
@ -11,14 +13,14 @@ cutoffID = mongoose.Types.ObjectId(Math.floor(cutoffDate/1000).toString(16)+'000
|
||||||
module.exports =
|
module.exports =
|
||||||
logError: (user, msg) ->
|
logError: (user, msg) ->
|
||||||
console.warn "Prepaid Error: [#{user.get('slug')} (#{user._id})] '#{msg}'"
|
console.warn "Prepaid Error: [#{user.get('slug')} (#{user._id})] '#{msg}'"
|
||||||
|
|
||||||
|
|
||||||
post: wrap (req, res) ->
|
post: wrap (req, res) ->
|
||||||
validTypes = ['course']
|
validTypes = ['course']
|
||||||
unless req.body.type in validTypes
|
unless req.body.type in validTypes
|
||||||
throw new errors.UnprocessableEntity("type must be on of: #{validTypes}.")
|
throw new errors.UnprocessableEntity("type must be on of: #{validTypes}.")
|
||||||
# TODO: deprecate or refactor other prepaid types
|
# TODO: deprecate or refactor other prepaid types
|
||||||
|
|
||||||
if req.body.creator
|
if req.body.creator
|
||||||
user = yield User.search(req.body.creator)
|
user = yield User.search(req.body.creator)
|
||||||
if not user
|
if not user
|
||||||
|
@ -32,16 +34,16 @@ module.exports =
|
||||||
database.validateDoc(prepaid)
|
database.validateDoc(prepaid)
|
||||||
yield prepaid.save()
|
yield prepaid.save()
|
||||||
res.status(201).send(prepaid.toObject())
|
res.status(201).send(prepaid.toObject())
|
||||||
|
|
||||||
|
|
||||||
redeem: wrap (req, res) ->
|
redeem: wrap (req, res) ->
|
||||||
if not req.user?.isTeacher()
|
if not req.user?.isTeacher()
|
||||||
throw new errors.Forbidden('Must be a teacher to use licenses')
|
throw new errors.Forbidden('Must be a teacher to use licenses')
|
||||||
|
|
||||||
prepaid = yield database.getDocFromHandle(req, Prepaid)
|
prepaid = yield database.getDocFromHandle(req, Prepaid)
|
||||||
if not prepaid
|
if not prepaid
|
||||||
throw new errors.NotFound('Prepaid not found.')
|
throw new errors.NotFound('Prepaid not found.')
|
||||||
|
|
||||||
if prepaid._id.getTimestamp().getTime() < cutoffDate.getTime()
|
if prepaid._id.getTimestamp().getTime() < cutoffDate.getTime()
|
||||||
throw new errors.Forbidden('Cannot redeem from prepaids older than November 11, 2015')
|
throw new errors.Forbidden('Cannot redeem from prepaids older than November 11, 2015')
|
||||||
unless prepaid.get('creator').equals(req.user._id)
|
unless prepaid.get('creator').equals(req.user._id)
|
||||||
|
@ -61,7 +63,7 @@ module.exports =
|
||||||
return res.status(200).send(prepaid.toObject({req: req}))
|
return res.status(200).send(prepaid.toObject({req: req}))
|
||||||
if user.isTeacher()
|
if user.isTeacher()
|
||||||
throw new errors.Forbidden('Teachers may not be enrolled')
|
throw new errors.Forbidden('Teachers may not be enrolled')
|
||||||
|
|
||||||
query =
|
query =
|
||||||
_id: prepaid._id
|
_id: prepaid._id
|
||||||
'redeemers.userID': { $ne: user._id }
|
'redeemers.userID': { $ne: user._id }
|
||||||
|
@ -71,7 +73,7 @@ module.exports =
|
||||||
if result.nModified is 0
|
if result.nModified is 0
|
||||||
@logError(req.user, "POST prepaid redeemer lost race on maxRedeemers")
|
@logError(req.user, "POST prepaid redeemer lost race on maxRedeemers")
|
||||||
throw new errors.Forbidden('This prepaid is exhausted')
|
throw new errors.Forbidden('This prepaid is exhausted')
|
||||||
|
|
||||||
update = {
|
update = {
|
||||||
$set: {
|
$set: {
|
||||||
coursePrepaid: {
|
coursePrepaid: {
|
||||||
|
@ -84,7 +86,7 @@ module.exports =
|
||||||
if not user.get('role')
|
if not user.get('role')
|
||||||
update.$set.role = 'student'
|
update.$set.role = 'student'
|
||||||
yield user.update(update)
|
yield user.update(update)
|
||||||
|
|
||||||
# return prepaid with new redeemer added locally
|
# return prepaid with new redeemer added locally
|
||||||
redeemers = _.clone(prepaid.get('redeemers') or [])
|
redeemers = _.clone(prepaid.get('redeemers') or [])
|
||||||
redeemers.push({ date: new Date(), userID: user._id })
|
redeemers.push({ date: new Date(), userID: user._id })
|
||||||
|
@ -94,12 +96,12 @@ module.exports =
|
||||||
fetchByCreator: wrap (req, res, next) ->
|
fetchByCreator: wrap (req, res, next) ->
|
||||||
creator = req.query.creator
|
creator = req.query.creator
|
||||||
return next() if not creator
|
return next() if not creator
|
||||||
|
|
||||||
unless req.user.isAdmin() or creator is req.user.id
|
unless req.user.isAdmin() or creator is req.user.id
|
||||||
throw new errors.Forbidden('Must be logged in as given creator')
|
throw new errors.Forbidden('Must be logged in as given creator')
|
||||||
unless database.isID(creator)
|
unless database.isID(creator)
|
||||||
throw new errors.UnprocessableEntity('Invalid creator')
|
throw new errors.UnprocessableEntity('Invalid creator')
|
||||||
|
|
||||||
q = {
|
q = {
|
||||||
_id: { $gt: cutoffID }
|
_id: { $gt: cutoffID }
|
||||||
creator: mongoose.Types.ObjectId(creator)
|
creator: mongoose.Types.ObjectId(creator)
|
||||||
|
@ -108,3 +110,43 @@ module.exports =
|
||||||
|
|
||||||
prepaids = yield Prepaid.find(q)
|
prepaids = yield Prepaid.find(q)
|
||||||
res.send((prepaid.toObject({req: req}) for prepaid in prepaids))
|
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()
|
timestamp = (new Date).getTime()
|
||||||
if not user
|
if not user
|
||||||
throw new errors.NotFound('User not found')
|
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 =
|
context =
|
||||||
email_id: sendwithus.templates.verify_email
|
email_id: sendwithus.templates.verify_email
|
||||||
recipient:
|
recipient:
|
||||||
|
@ -127,14 +129,18 @@ module.exports =
|
||||||
unless req.user.isAnonymous()
|
unless req.user.isAnonymous()
|
||||||
throw new errors.Forbidden('You are already signed in.')
|
throw new errors.Forbidden('You are already signed in.')
|
||||||
|
|
||||||
{ password, email } = req.body
|
{ name, email, password } = req.body
|
||||||
unless _.all([password, email])
|
unless password
|
||||||
throw new errors.UnprocessableEntity('Requires password and email')
|
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')
|
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)
|
yield module.exports.finishSignup(req, res)
|
||||||
|
|
||||||
signupWithFacebook: wrap (req, res) ->
|
signupWithFacebook: wrap (req, res) ->
|
||||||
|
|
|
@ -105,6 +105,7 @@ module.exports =
|
||||||
if watchers.length
|
if watchers.length
|
||||||
User.find({_id:{$in:watchers}}).select({email:1, name:1}).exec (err, watchers) ->
|
User.find({_id:{$in:watchers}}).select({email:1, name:1}).exec (err, watchers) ->
|
||||||
for watcher in watchers
|
for watcher in watchers
|
||||||
|
continue if not watcher.get('email')
|
||||||
context =
|
context =
|
||||||
email_id: sendwithus.templates.change_made_notify_watcher
|
email_id: sendwithus.templates.change_made_notify_watcher
|
||||||
recipient:
|
recipient:
|
||||||
|
|
|
@ -122,6 +122,10 @@ UserSchema.statics.findByEmail = (email, done=_.noop) ->
|
||||||
emailLower = email.toLowerCase()
|
emailLower = email.toLowerCase()
|
||||||
User.findOne({emailLower: emailLower}).exec(done)
|
User.findOne({emailLower: emailLower}).exec(done)
|
||||||
|
|
||||||
|
UserSchema.statics.findByName = (name, done=_.noop) ->
|
||||||
|
nameLower = name.toLowerCase()
|
||||||
|
User.findOne({nameLower: nameLower}).exec(done)
|
||||||
|
|
||||||
emailNameMap =
|
emailNameMap =
|
||||||
generalNews: 'announcement'
|
generalNews: 'announcement'
|
||||||
adventurerNews: 'tester'
|
adventurerNews: 'tester'
|
||||||
|
@ -267,6 +271,7 @@ UserSchema.statics.unconflictName = unconflictName = (name, done) ->
|
||||||
unconflictName name + suffix, done
|
unconflictName name + suffix, done
|
||||||
|
|
||||||
UserSchema.methods.sendWelcomeEmail = ->
|
UserSchema.methods.sendWelcomeEmail = ->
|
||||||
|
return if not @get('email')
|
||||||
{ welcome_email_student, welcome_email_user } = sendwithus.templates
|
{ welcome_email_student, welcome_email_user } = sendwithus.templates
|
||||||
timestamp = (new Date).getTime()
|
timestamp = (new Date).getTime()
|
||||||
data =
|
data =
|
||||||
|
@ -345,14 +350,25 @@ UserSchema.methods.saveActiveUser = (event, done=null) ->
|
||||||
|
|
||||||
UserSchema.pre('save', (next) ->
|
UserSchema.pre('save', (next) ->
|
||||||
if _.isNaN(@get('purchased')?.gems)
|
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'
|
Classroom = require './Classroom'
|
||||||
if @isTeacher() and not @wasTeacher
|
if @isTeacher() and not @wasTeacher
|
||||||
Classroom.update({members: @_id}, {$pull: {members: @_id}}, {multi: true}).exec (err, res) ->
|
Classroom.update({members: @_id}, {$pull: {members: @_id}}, {multi: true}).exec (err, res) ->
|
||||||
|
|
||||||
if email = @get('email')
|
if email = @get('email')
|
||||||
@set('emailLower', email.toLowerCase())
|
@set('emailLower', email.toLowerCase())
|
||||||
|
else
|
||||||
|
@set('email', undefined)
|
||||||
|
@set('emailLower', undefined)
|
||||||
if name = @get('name')
|
if name = @get('name')
|
||||||
@set('nameLower', name.toLowerCase())
|
@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')
|
pwd = @get('password')
|
||||||
if @get('password')
|
if @get('password')
|
||||||
@set('passwordHash', User.hashPassword(pwd))
|
@set('passwordHash', User.hashPassword(pwd))
|
||||||
|
|
|
@ -26,7 +26,7 @@ module.exports = createNewTask = (req, res) ->
|
||||||
|
|
||||||
|
|
||||||
validatePermissions = (req, sessionID, callback) ->
|
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()
|
return callback null if req.user?.isAdmin()
|
||||||
|
|
||||||
findParameters = _id: sessionID
|
findParameters = _id: sessionID
|
||||||
|
|
|
@ -26,7 +26,7 @@ module.exports = dispatchTaskToConsumer = (req, res) ->
|
||||||
|
|
||||||
|
|
||||||
checkSimulationPermissions = (req, cb) ->
|
checkSimulationPermissions = (req, cb) ->
|
||||||
if req.user?.get('email')
|
if req.user and not req.user.isAnonymous()
|
||||||
cb null
|
cb null
|
||||||
else
|
else
|
||||||
cb 'You need to be logged in to simulate games'
|
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.post('/db/user/:handle/signup-with-password', mw.users.signupWithPassword)
|
||||||
|
|
||||||
app.get('/db/prepaid', mw.auth.checkLoggedIn(), mw.prepaids.fetchByCreator)
|
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', mw.auth.checkHasPermission(['admin']), mw.prepaids.post)
|
||||||
app.post('/db/prepaid/:handle/redeemers', mw.prepaids.redeem)
|
app.post('/db/prepaid/:handle/redeemers', mw.prepaids.redeem)
|
||||||
|
|
||||||
|
|
|
@ -471,6 +471,7 @@ taskReminderAlreadySentThisWeekFilter = (task, cb) ->
|
||||||
sendUserRemarkTaskEmail = (task, cb) ->
|
sendUserRemarkTaskEmail = (task, cb) ->
|
||||||
mailTaskName = @mailTaskName
|
mailTaskName = @mailTaskName
|
||||||
User.findOne("_id":task.contact).select("email").lean().exec (err, contact) ->
|
User.findOne("_id":task.contact).select("email").lean().exec (err, contact) ->
|
||||||
|
return if not contact.email
|
||||||
if err? then return cb err
|
if err? then return cb err
|
||||||
User.findOne("_id":task.user).select("jobProfile.name").lean().exec (err, user) ->
|
User.findOne("_id":task.user).select("jobProfile.name").lean().exec (err, user) ->
|
||||||
if err? then return cb err
|
if err? then return cb err
|
||||||
|
@ -567,6 +568,7 @@ handleLadderUpdate = (req, res) ->
|
||||||
|
|
||||||
sendLadderUpdateEmail = (session, now, daysAgo) ->
|
sendLadderUpdateEmail = (session, now, daysAgo) ->
|
||||||
User.findOne({_id: session.creator}).select('name email firstName lastName emailSubscriptions emails preferredLanguage').exec (err, user) ->
|
User.findOne({_id: session.creator}).select('name email firstName lastName emailSubscriptions emails preferredLanguage').exec (err, user) ->
|
||||||
|
return if not user.get('email')
|
||||||
if err
|
if err
|
||||||
log.error "Couldn't find user for #{session.creator} from session #{session._id}"
|
log.error "Couldn't find user for #{session.creator} from session #{session._id}"
|
||||||
return
|
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
|
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 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')
|
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
|
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
|
return
|
||||||
|
|
||||||
LevelSession.find({creator: user.get('_id') + ''}).select('levelName levelID changed state.complete playtime').lean().exec (err, sessions) ->
|
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)
|
complete = (s for s in sessions when s.state?.complete)
|
||||||
incomplete = (s for s in sessions when not s.state?.complete)
|
incomplete = (s for s in sessions when not s.state?.complete)
|
||||||
return if complete.length < 2
|
return if complete.length < 2
|
||||||
|
@ -704,7 +707,7 @@ sendNextStepsEmail = (user, now, daysAgo) ->
|
||||||
nextLevel = null
|
nextLevel = null
|
||||||
err = null
|
err = null
|
||||||
do (err, nextLevel) ->
|
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 = 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']
|
name = 'Hero' if not name or name in ['Anoner', 'Anonymous']
|
||||||
#secretLevel = switch user.get('testGroupNumber') % 8
|
#secretLevel = switch user.get('testGroupNumber') % 8
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
require '../common'
|
require '../common'
|
||||||
|
utils = require '../utils'
|
||||||
mail = require '../../../server/routes/mail'
|
mail = require '../../../server/routes/mail'
|
||||||
|
sendwithus = require '../../../server/sendwithus'
|
||||||
User = require '../../../server/models/User'
|
User = require '../../../server/models/User'
|
||||||
request = require '../request'
|
request = require '../request'
|
||||||
|
LevelSession = require '../../../server/models/LevelSession'
|
||||||
|
|
||||||
testPost =
|
testPost =
|
||||||
data:
|
data:
|
||||||
|
@ -37,3 +40,30 @@ describe 'handleUnsubscribe', ->
|
||||||
expect(u.isEmailSubscriptionEnabled('ambassadorNews')).toBeFalsy()
|
expect(u.isEmailSubscriptionEnabled('ambassadorNews')).toBeFalsy()
|
||||||
expect(u.isEmailSubscriptionEnabled('artisanNews')).toBeFalsy()
|
expect(u.isEmailSubscriptionEnabled('artisanNews')).toBeFalsy()
|
||||||
done()
|
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'
|
async = require 'async'
|
||||||
config = require '../../../server_config'
|
config = require '../../../server_config'
|
||||||
require '../common'
|
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'
|
mongoose = require 'mongoose'
|
||||||
TRAVIS = process.env.COCO_TRAVIS_TEST
|
TRAVIS = process.env.COCO_TRAVIS_TEST
|
||||||
nockUtils = require '../nock-utils'
|
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) ->
|
request.put {uri: userURL, json: body, headers: headers}, (err, res, body) ->
|
||||||
expect(res.statusCode).toBe 403
|
expect(res.statusCode).toBe 403
|
||||||
done()
|
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
|
#- shared data between tests
|
||||||
joeData = null
|
joeData = null
|
||||||
|
@ -327,7 +335,7 @@ describe 'Subscriptions', ->
|
||||||
return done() unless subscription?
|
return done() unless subscription?
|
||||||
expect(subscription.plan.amount).toEqual(1)
|
expect(subscription.plan.amount).toEqual(1)
|
||||||
expect(subscription.customer).toEqual(sponsorCustomerID)
|
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
|
# Verify sponsor payment
|
||||||
# May be greater than expected amount due to multiple subscribes and unsubscribes
|
# May be greater than expected amount due to multiple subscribes and unsubscribes
|
||||||
|
@ -336,7 +344,7 @@ describe 'Subscriptions', ->
|
||||||
recipient: mongoose.Types.ObjectId(sponsorUserID)
|
recipient: mongoose.Types.ObjectId(sponsorUserID)
|
||||||
"stripe.customerID": sponsorCustomerID
|
"stripe.customerID": sponsorCustomerID
|
||||||
"stripe.subscriptionID": sponsorStripe.sponsorSubscriptionID
|
"stripe.subscriptionID": sponsorStripe.sponsorSubscriptionID
|
||||||
expectedAmount = utils.getSponsoredSubsAmount(subPrice, numSponsored, sponsorStripe.subscriptionID?)
|
expectedAmount = appUtils.getSponsoredSubsAmount(subPrice, numSponsored, sponsorStripe.subscriptionID?)
|
||||||
Payment.find paymentQuery, (err, payments) ->
|
Payment.find paymentQuery, (err, payments) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(payments).not.toBeNull()
|
expect(payments).not.toBeNull()
|
||||||
|
@ -1192,7 +1200,7 @@ describe 'Subscriptions', ->
|
||||||
for invoice in invoices.data
|
for invoice in invoices.data
|
||||||
line = invoice.lines.data[0]
|
line = invoice.lines.data[0]
|
||||||
if line.type is 'invoiceitem' and line.proration
|
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).toBeLessThan(totalAmount)
|
||||||
expect(invoice.total).toEqual(totalAmount - subPrice)
|
expect(invoice.total).toEqual(totalAmount - subPrice)
|
||||||
Payment.findOne "stripe.invoiceID": invoice.id, (err, payment) ->
|
Payment.findOne "stripe.invoiceID": invoice.id, (err, payment) ->
|
||||||
|
|
|
@ -234,6 +234,14 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
|
||||||
expect(body.role).toBe('advisor')
|
expect(body.role).toBe('advisor')
|
||||||
done()
|
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', ->
|
describe 'PUT /db/user/-/become-student', ->
|
||||||
beforeEach utils.wrap (done) ->
|
beforeEach utils.wrap (done) ->
|
||||||
@url = getURL('/db/user/-/become-student')
|
@url = getURL('/db/user/-/become-student')
|
||||||
|
@ -697,6 +705,50 @@ describe 'POST /db/user/:handle/signup-with-password', ->
|
||||||
expect(sendwithus.api.send).toHaveBeenCalled()
|
expect(sendwithus.api.send).toHaveBeenCalled()
|
||||||
done()
|
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) ->
|
it 'returns 409 if there is already a user with the given email', utils.wrap (done) ->
|
||||||
email = 'some@email.com'
|
email = 'some@email.com'
|
||||||
initialUser = yield utils.initUser({email})
|
initialUser = yield utils.initUser({email})
|
||||||
|
@ -707,6 +759,17 @@ describe 'POST /db/user/:handle/signup-with-password', ->
|
||||||
[res, body] = yield request.postAsync({url, json})
|
[res, body] = yield request.postAsync({url, json})
|
||||||
expect(res.statusCode).toBe(409)
|
expect(res.statusCode).toBe(409)
|
||||||
done()
|
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) ->
|
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()
|
user = yield utils.becomeAnonymous()
|
||||||
|
@ -739,7 +802,7 @@ describe 'POST /db/user/:handle/signup-with-facebook', ->
|
||||||
facebookID = '12345'
|
facebookID = '12345'
|
||||||
facebookEmail = 'some@email.com'
|
facebookEmail = 'some@email.com'
|
||||||
|
|
||||||
validFacebookResponse = new Promise((resolve) -> resolve({
|
validFacebookResponse = new Promise((resolve) -> resolve({
|
||||||
id: facebookID,
|
id: facebookID,
|
||||||
email: facebookEmail,
|
email: facebookEmail,
|
||||||
first_name: 'Some',
|
first_name: 'Some',
|
||||||
|
@ -753,12 +816,12 @@ describe 'POST /db/user/:handle/signup-with-facebook', ->
|
||||||
verified: true
|
verified: true
|
||||||
}))
|
}))
|
||||||
|
|
||||||
invalidFacebookResponse = new Promise((resolve) -> resolve({
|
invalidFacebookResponse = new Promise((resolve) -> resolve({
|
||||||
error: {
|
error: {
|
||||||
message: 'Invalid OAuth access token.',
|
message: 'Invalid OAuth access token.',
|
||||||
type: 'OAuthException',
|
type: 'OAuthException',
|
||||||
code: 190,
|
code: 190,
|
||||||
fbtrace_id: 'EC4dEdeKHBH'
|
fbtrace_id: 'EC4dEdeKHBH'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,8 @@ module.exports = mw =
|
||||||
options = {}
|
options = {}
|
||||||
options = _.extend({
|
options = _.extend({
|
||||||
permissions: []
|
permissions: []
|
||||||
email: 'user'+_.uniqueId()+'@gmail.com'
|
name: 'Name Nameyname '+_.uniqueId()
|
||||||
|
email: 'user'+_.uniqueId()+'@example.com'
|
||||||
password: 'password'
|
password: 'password'
|
||||||
anonymous: false
|
anonymous: false
|
||||||
}, options)
|
}, options)
|
||||||
|
@ -49,7 +50,7 @@ module.exports = mw =
|
||||||
done = options
|
done = options
|
||||||
options = {}
|
options = {}
|
||||||
form = {
|
form = {
|
||||||
username: user.get('email')
|
username: user.get('email') or user.get('name')
|
||||||
password: 'password'
|
password: 'password'
|
||||||
}
|
}
|
||||||
(options.request or request).post mw.getURL('/auth/login'), { form: form }, (err, res) ->
|
(options.request or request).post mw.getURL('/auth/login'), { form: form }, (err, res) ->
|
||||||
|
@ -89,7 +90,7 @@ module.exports = mw =
|
||||||
args = Array.from(arguments)
|
args = Array.from(arguments)
|
||||||
[done, [data, sources]] = [args.pop(), args]
|
[done, [data, sources]] = [args.pop(), args]
|
||||||
|
|
||||||
data = _.extend({}, {
|
data = _.extend({}, {
|
||||||
name: _.uniqueId('Level ')
|
name: _.uniqueId('Level ')
|
||||||
permissions: [{target: mw.lastLogin.id, access: 'owner'}]
|
permissions: [{target: mw.lastLogin.id, access: 'owner'}]
|
||||||
}, data)
|
}, data)
|
||||||
|
|
Loading…
Reference in a new issue