mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 08:50:58 -05:00
Fix bugquest bugs
Fix link to /teachers/classes (fixes bugquest#20) Fix edit button color/icon (bugquest#23) Fix bugquest#34 Fix password input width (bugquest#33) Center new pasword text Fix teacher password reset endpoint (bugquest#4) Refactor+use NewHomeView logic for user page button (Fixes bugquest#2) Refactor teacher-password-reset endpoint This makes it much easier to prevent collisions with other logic when PUTing new User attributes. Add regression test for converting to teacher account Fix email verified links, require login (fix bugquest#16) Fix me having stale emailVerified value (Fixes bugquest#40) Don't show JoinClassModal to students Add paragraph to JoinClassModal (fixes bugquest#14) Update change-password label text (fixes bugquest#30) Fix prompting for login on Account Settings page (bugquest #10) Show validation errors for teacher password reset (bugquest#36) Show yellow progress dot in My Classes if anyone has started (bugquest#55) Remove confusing text (bugquest#100)
This commit is contained in:
parent
f0fa88206d
commit
3d705e5d70
26 changed files with 127 additions and 68 deletions
|
@ -11,9 +11,14 @@ module.exports =
|
|||
instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
||||
continue if not instance
|
||||
instance.numCompleted = 0
|
||||
instance.numStarted = 0
|
||||
instance.started = false
|
||||
levels = classroom.getLevels({courseID: course.id, withoutLadderLevels: true})
|
||||
for userID in instance.get('members')
|
||||
instance.started = _.any levels.models, (level) ->
|
||||
return false if level.isLadder()
|
||||
session = _.find classroom.sessions.models, (session) ->
|
||||
session.get('creator') is userID and session.get('level').original is level.get('original')
|
||||
session?
|
||||
levelCompletes = _.map levels.models, (level) ->
|
||||
return true if level.isLadder()
|
||||
#TODO: Hella slow! Do the mapping first!
|
||||
|
@ -23,8 +28,6 @@ module.exports =
|
|||
session?.completed()
|
||||
if _.every levelCompletes
|
||||
instance.numCompleted += 1
|
||||
if _.any levelCompletes
|
||||
instance.numStarted += 1
|
||||
|
||||
calculateEarliestIncomplete: (classroom, courses, courseInstances, students) ->
|
||||
# Loop through all the combinations of things, return the first one that somebody hasn't finished
|
||||
|
|
|
@ -1322,6 +1322,9 @@
|
|||
update_account_not_sure: "Not sure which one to choose? Email"
|
||||
update_account_confirm_update_student: "Are you sure you want to update your account to a Student experience?\n\nYou will not be able to manage any classes that you have previously created or create new classes. Your previously created classes will be removed from CodeCombat and cannot be restored."
|
||||
instructor: "Instructor: "
|
||||
youve_been_invited_1: "You've been invited to join "
|
||||
youve_been_invited_2: ", where you'll learn "
|
||||
youve_been_invited_3: " with your classmates in CodeCombat."
|
||||
by_joining_1: "By joining "
|
||||
by_joining_2: "will be able to help reset your password if you forget or lose it. You can also verify your email address so that you can reset the password yourself!"
|
||||
sent_verification: "We've sent a verification email to:"
|
||||
|
@ -1415,9 +1418,9 @@
|
|||
total_unenrolled: "Total unenrolled"
|
||||
export_student_progress: "Export Student Progress (CSV)"
|
||||
send_email_to: "Send Recover Password Email to:"
|
||||
verified_email_address: "verified email address"
|
||||
email_sent: "Email sent"
|
||||
send_recovery_email: "Send recovery email"
|
||||
enter_new_password_below: "Enter new password below:"
|
||||
change_password: "Change Password"
|
||||
changed: "Changed"
|
||||
available_credits: "Available Credits"
|
||||
|
|
|
@ -52,6 +52,16 @@ module.exports = class Classroom extends CocoModel
|
|||
}
|
||||
_.extend options, opts
|
||||
@fetch(options)
|
||||
|
||||
setStudentPassword: (student, password, options) ->
|
||||
classroomID = @.id
|
||||
$.ajax {
|
||||
url: "/db/classroom/#{classroomID}/members/#{student.id}/reset-password"
|
||||
method: 'POST'
|
||||
data: { password }
|
||||
success: => @trigger 'save-password:success'
|
||||
error: (response) => @trigger 'save-password:error', response.responseJSON
|
||||
}
|
||||
|
||||
getLevels: (options={}) ->
|
||||
# options: courseID, withoutLadderLevels
|
||||
|
|
|
@ -68,6 +68,11 @@ module.exports = class User extends CocoModel
|
|||
|
||||
isTeacher: ->
|
||||
return @get('role') in ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent', 'parent']
|
||||
|
||||
justPlaysCourses: ->
|
||||
# This heuristic could be better, but currently we don't add to me.get('courseInstances') for single-player anonymous intro courses, so they have to beat a level without choosing a hero.
|
||||
return true if me.get('role') is 'student'
|
||||
return me.get('stats')?.gamesCompleted and not me.get('heroConfig')
|
||||
|
||||
isSessionless: ->
|
||||
# TODO: Fix old users who got mis-tagged as teachers
|
||||
|
|
|
@ -57,7 +57,7 @@ _.extend UserSchema.properties,
|
|||
gender: {type: 'string'} # , 'enum': ['male', 'female', 'secret', 'trans', 'other']
|
||||
# NOTE: ageRange enum changed on 4/27/16 from ['0-13', '14-17', '18-24', '25-34', '35-44', '45-100']
|
||||
ageRange: {type: 'string'} # 'enum': ['13-15', '16-17', '18-24', '25-34', '35-44', '45-100']
|
||||
password: {type: 'string', maxLength: 256, minLength: 2, title: 'Password'}
|
||||
password: c.passwordString
|
||||
passwordReset: {type: 'string'}
|
||||
photoURL: {type: 'string', format: 'image-file', title: 'Profile Picture', description: 'Upload a 256x256px or larger image to serve as your profile picture.'}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ me.object = (ext, props) -> combine({type: 'object', additionalProperties: false
|
|||
me.array = (ext, items) -> combine({type: 'array', items: items or {}}, ext)
|
||||
me.shortString = (ext) -> combine({type: 'string', maxLength: 100}, ext)
|
||||
me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext)
|
||||
me.passwordString = {type: 'string', maxLength: 256, minLength: 2, title: 'Password'}
|
||||
|
||||
# Dates should usually be strings, ObjectIds should be strings: https://github.com/codecombat/codecombat/issues/1384
|
||||
me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ext) # old
|
||||
|
|
|
@ -157,9 +157,14 @@
|
|||
.glyphicon
|
||||
color: $gray-light
|
||||
|
||||
.edit-student-link
|
||||
color: black
|
||||
|
||||
.remove-student-link
|
||||
color: $burgandy
|
||||
|
||||
.edit-student-link, .remove-student-link
|
||||
display: inline-block
|
||||
color: $burgandy
|
||||
font-weight: bold
|
||||
text-decoration: underline
|
||||
line-height: 16px
|
||||
|
|
4
app/styles/teachers/edit-student-modal.sass
Normal file
4
app/styles/teachers/edit-student-modal.sass
Normal file
|
@ -0,0 +1,4 @@
|
|||
#edit-student-modal
|
||||
.new-password-input
|
||||
width: 300px
|
||||
text-align: center
|
|
@ -6,3 +6,6 @@
|
|||
|
||||
.glyphicon
|
||||
font-size: 20pt
|
||||
|
||||
.btn-lg
|
||||
min-width: 246px
|
||||
|
|
|
@ -13,6 +13,12 @@ block modal-header-content
|
|||
|
||||
block modal-body-content
|
||||
if view.classroom.loaded
|
||||
p
|
||||
span.spr(data-i18n="courses.youve_been_invited_1")
|
||||
span= view.classroom.get('name')
|
||||
span.spr(data-i18n="courses.youve_been_invited_2")
|
||||
span= view.classroom.capitalLanguage
|
||||
span.spl(data-i18n="courses.youve_been_invited_3")
|
||||
p
|
||||
span.spr(data-i18n="courses.by_joining_1")
|
||||
span= view.classroom.get('name')
|
||||
|
|
|
@ -229,7 +229,7 @@ mixin studentRow(student)
|
|||
td
|
||||
.pull-right
|
||||
a.edit-student-link.small.center-block.text-center.m-r-2(data-student-id=student.id)
|
||||
div.glyphicon.glyphicon-remove
|
||||
div.glyphicon.glyphicon-edit
|
||||
div(data-i18n='teacher.edit')
|
||||
a.remove-student-link.small.center-block.text-center.m-r-2(data-student-id=student.id)
|
||||
div.glyphicon.glyphicon-remove
|
||||
|
|
|
@ -107,7 +107,7 @@ mixin progressDot(classroom, course, index)
|
|||
- var started = 0;
|
||||
if courseInstance
|
||||
- complete = courseInstance.numCompleted
|
||||
- started = courseInstance.numStarted
|
||||
- started = courseInstance.started
|
||||
- dotClass = complete === total ? 'forest' : started ? 'gold' : '';
|
||||
- var progressDotContext = {total: total, complete: complete};
|
||||
.progress-dot(class=dotClass, data-title=view.progressDotTemplate(progressDotContext))
|
||||
|
|
|
@ -85,7 +85,7 @@ mixin box
|
|||
h6(data-i18n="new_home.want_coco")
|
||||
a.btn.btn-primary.btn-lg.btn-block(href=view.demoRequestURL, data-i18n="new_home.get_started")
|
||||
|
||||
else if view.justPlaysCourses()
|
||||
else if me.justPlaysCourses()
|
||||
div
|
||||
a.btn.btn-forest.btn-lg.btn-block(href=view.playURL, data-i18n="courses.continue_playing")
|
||||
div
|
||||
|
|
|
@ -8,12 +8,10 @@ block modal-header-content
|
|||
block modal-body-content
|
||||
.text-center
|
||||
if view.user.get('emailVerified')
|
||||
div.m-b-1
|
||||
p
|
||||
span(data-i18n="teacher.send_email_to")
|
||||
div
|
||||
p.m-b-3
|
||||
= view.user.get('email')
|
||||
div.small
|
||||
span(data-i18n="teacher.verified_email_address")
|
||||
if state.get('emailSent')
|
||||
.send-recovery-email-btn.btn.btn-lg.btn-primary.uppercase.disabled
|
||||
span(data-i18n="teacher.email_sent")
|
||||
|
@ -22,10 +20,11 @@ block modal-body-content
|
|||
span(data-i18n="teacher.send_recovery_email")
|
||||
else
|
||||
div.m-b-1
|
||||
span(data-i18n="teacher.change_password")
|
||||
span :
|
||||
div
|
||||
span(data-i18n="teacher.enter_new_password_below")
|
||||
div.m-b-2.form-group(class=(state.get('errorMessage') ? 'has-error' : ''))
|
||||
input.new-password-input(placeholder="type a new password here" value=state.get('newPassword'))
|
||||
div.help-block.error-help-block.m-t-1.small
|
||||
span=state.get('errorMessage')
|
||||
if state.get('passwordChanged')
|
||||
button.change-password-btn.btn.btn-lg.btn-primary.uppercase.disabled
|
||||
span(data-i18n="teacher.changed")
|
||||
|
|
|
@ -10,18 +10,21 @@ block content
|
|||
.glyphicon.glyphicon-ok-circle.m-r-1
|
||||
span(data-i18n="account.successfully_verified")
|
||||
| You've successfully verified your email address!
|
||||
if view.user.isStudent()
|
||||
a.btn.btn-lg.btn-navy(href="/courses")
|
||||
span(data-i18n="account.back_to_student_page")
|
||||
| Go back to student things
|
||||
if view.userID !== me.id
|
||||
a.login-button.btn.btn-navy.btn-lg(data-i18n="login.log_in")
|
||||
else if view.user.isTeacher()
|
||||
a.btn.btn-lg.btn-navy(href="/teacher/classes")
|
||||
span(data-i18n="account.back_to_teacher_page")
|
||||
| Go to My Classes
|
||||
a.btn.btn-lg.btn-forest(href="/teachers/classes")
|
||||
span(data-i18n="new_home.goto_classes")
|
||||
else if me.justPlaysCourses()
|
||||
div.m-b-1
|
||||
a.btn.btn-forest.btn-lg(href="/courses", data-i18n="courses.continue_playing")
|
||||
div
|
||||
a.btn.btn-primary.btn-lg.play-btn(href="/courses", data-i18n="new_home.view_progress")
|
||||
else
|
||||
a.btn.btn-lg.btn-navy(href="/play")
|
||||
span(data-i18n="account.back_to_game")
|
||||
| Go play some more levels!
|
||||
div.m-b-1
|
||||
a.btn.btn-forest.btn-lg.play-btn(href="/play", data-i18n="courses.continue_playing")
|
||||
div
|
||||
a.btn.btn-primary.btn-lg(href="/user/#{me.getSlugOrID()}", data-i18n="new_home.view_profile")
|
||||
else if state.get('verifyStatus') === "error"
|
||||
.alert.alert-danger.center-block
|
||||
.glyphicon.glyphicon-remove-circle.m-r-1
|
||||
|
|
|
@ -44,12 +44,12 @@ module.exports = class NewHomeView extends RootView
|
|||
@supermodel.loadCollection(@trialRequests)
|
||||
|
||||
isHourOfCodeWeek = false # Temporary: default to /hoc flow during the main event week
|
||||
if isHourOfCodeWeek and (@isNewPlayer() or (@justPlaysCourses() and me.isAnonymous()))
|
||||
if isHourOfCodeWeek and (@isNewPlayer() or (me.justPlaysCourses() and me.isAnonymous()))
|
||||
# Go/return straight to playing single-player HoC course on Play click
|
||||
@playURL = '/hoc?go=true'
|
||||
@alternatePlayURL = '/play'
|
||||
@alternatePlayText = 'home.play_campaign_version'
|
||||
else if @justPlaysCourses()
|
||||
else if me.justPlaysCourses()
|
||||
# Save players who might be in a classroom from getting into the campaign
|
||||
@playURL = '/courses'
|
||||
@alternatePlayURL = '/play'
|
||||
|
@ -123,11 +123,6 @@ module.exports = class NewHomeView extends RootView
|
|||
$(@).find('.course-duration .unit').text($.i18n.t(if duration is '1' then 'units.hour' else 'units.hours'))
|
||||
@$el.find('#semester-duration').text levels[level].total
|
||||
|
||||
justPlaysCourses: ->
|
||||
# This heuristic could be better, but currently we don't add to me.get('courseInstances') for single-player anonymous intro courses, so they have to beat a level without choosing a hero.
|
||||
return true if me.get('role') is 'student'
|
||||
return me.get('stats')?.gamesCompleted and not me.get('heroConfig')
|
||||
|
||||
isNewPlayer: ->
|
||||
not me.get('stats')?.gamesCompleted and not me.get('heroConfig')
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/account/account-settings-root-view'
|
||||
AccountSettingsView = require './AccountSettingsView'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
|
||||
module.exports = class AccountSettingsRootView extends RootView
|
||||
id: "account-settings-root-view"
|
||||
|
@ -21,6 +22,9 @@ module.exports = class AccountSettingsRootView extends RootView
|
|||
@listenTo @accountSettingsView, 'save-user-success', @onUserSaveSuccess
|
||||
@listenTo @accountSettingsView, 'save-user-error', @onUserSaveError
|
||||
|
||||
afterInsert: ->
|
||||
@openModalView new CreateAccountModal() if me.get('anonymous')
|
||||
|
||||
onInputChanged: ->
|
||||
@$el.find('#save-button')
|
||||
.text($.i18n.t('common.save', defaultValue: 'Save'))
|
||||
|
@ -45,4 +49,3 @@ module.exports = class AccountSettingsRootView extends RootView
|
|||
.text($.i18n.t('account_settings.error_saving', defaultValue: 'Error Saving'))
|
||||
.removeClass('btn-success')
|
||||
.addClass('btn-danger', 500)
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ template = require 'templates/account/account-settings-view'
|
|||
{me} = require 'core/auth'
|
||||
forms = require 'core/forms'
|
||||
User = require 'models/User'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
ConfirmModal = require 'views/editor/modal/ConfirmModal'
|
||||
{logoutUser, me} = require('core/auth')
|
||||
|
||||
|
@ -26,10 +25,6 @@ module.exports = class AccountSettingsView extends CocoView
|
|||
require('core/services/filepicker')() unless window.application.isIPadApp # Initialize if needed
|
||||
@uploadFilePath = "db/user/#{me.id}"
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
@openModalView new CreateAccountModal() if me.get('anonymous')
|
||||
|
||||
getEmailSubsDict: ->
|
||||
subs = {}
|
||||
return subs unless me
|
||||
|
|
|
@ -92,7 +92,7 @@ module.exports = class CoursesView extends RootView
|
|||
@renderSelectors '#join-class-form'
|
||||
return
|
||||
@renderSelectors '#join-class-form'
|
||||
if me.get('emailVerified')
|
||||
if me.get('emailVerified') or me.isStudent()
|
||||
newClassroom = new Classroom()
|
||||
jqxhr = newClassroom.joinWithCode(@classCode)
|
||||
@listenTo newClassroom, 'join:success', -> @onJoinClassroomSuccess(newClassroom)
|
||||
|
|
|
@ -231,7 +231,7 @@ module.exports = class TeacherClassView extends RootView
|
|||
|
||||
onClickEditStudentLink: (e) ->
|
||||
user = @students.get($(e.currentTarget).data('student-id'))
|
||||
modal = new EditStudentModal({ user })
|
||||
modal = new EditStudentModal({ user, @classroom })
|
||||
@openModalView(modal)
|
||||
|
||||
onClickRemoveStudentLink: (e) ->
|
||||
|
|
|
@ -12,15 +12,21 @@ module.exports = class EditStudentModal extends ModalView
|
|||
'click .change-password-btn:not(.disabled)': 'onClickChangePassword'
|
||||
'input .new-password-input': 'onChangeNewPasswordInput'
|
||||
|
||||
initialize: ({ @user }) ->
|
||||
initialize: ({ @user, @classroom }) ->
|
||||
@supermodel.trackRequest @user.fetch()
|
||||
@utils = require 'core/utils'
|
||||
@state = new State({
|
||||
emailSent: false
|
||||
passwordChanged: false
|
||||
newPassword: ""
|
||||
errorMessage: ""
|
||||
})
|
||||
@listenTo @state, 'change', @render
|
||||
@listenTo @classroom, 'save-password:success', ->
|
||||
@state.set { passwordChanged: true, errorMessage: "" }
|
||||
@listenTo @classroom, 'save-password:error', (error) ->
|
||||
@state.set({ errorMessage: error.message })
|
||||
# TODO: Show an error. (password too short)
|
||||
|
||||
onClickSendRecoveryEmail: ->
|
||||
email = @user.get('email')
|
||||
|
@ -28,13 +34,7 @@ module.exports = class EditStudentModal extends ModalView
|
|||
@state.set { emailSent: true }
|
||||
|
||||
onClickChangePassword: ->
|
||||
@user.set({ password: @state.get('newPassword') })
|
||||
@user.save()
|
||||
@user.unset('password')
|
||||
@listenToOnce @user, 'save:success', ->
|
||||
@state.set { passwordChanged: true }
|
||||
@listenTo @user, 'invalid', ->
|
||||
# TODO: Show an error. (password too short)
|
||||
@classroom.setStudentPassword(@user, @state.get('newPassword'))
|
||||
|
||||
onChangeNewPasswordInput: (e) ->
|
||||
@state.set { newPassword: $(e.currentTarget).val() }, { silent: true }
|
||||
|
|
|
@ -7,6 +7,9 @@ module.exports = class EmailVerifiedView extends RootView
|
|||
id: 'email-verified-view'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click .login-button': 'onClickLoginButton'
|
||||
|
||||
initialize: (options, @userID, @verificationCode) ->
|
||||
super(options)
|
||||
@state = new State(@getInitialState())
|
||||
|
@ -16,8 +19,13 @@ module.exports = class EmailVerifiedView extends RootView
|
|||
@listenTo @state, 'change', @render
|
||||
@listenTo @user, 'email-verify-success', ->
|
||||
@state.set { verifyStatus: 'success' }
|
||||
me.fetch()
|
||||
@listenTo @user, 'email-verify-error', ->
|
||||
@state.set { verifyStatus: 'error' }
|
||||
|
||||
getInitialState: ->
|
||||
verifyStatus: 'pending'
|
||||
|
||||
onClickLoginButton: (e) ->
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
@openModalView(new AuthModal())
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
_ = require 'lodash'
|
||||
utils = require '../lib/utils'
|
||||
errors = require '../commons/errors'
|
||||
schemas = require '../../app/schemas/schemas'
|
||||
wrap = require 'co-express'
|
||||
Promise = require 'bluebird'
|
||||
database = require '../commons/database'
|
||||
|
@ -18,7 +19,7 @@ module.exports =
|
|||
fetchByCode: wrap (req, res, next) ->
|
||||
code = req.query.code
|
||||
return next() unless code
|
||||
classroom = yield Classroom.findOne({ code: code.toLowerCase() }).select('name ownerID')
|
||||
classroom = yield Classroom.findOne({ code: code.toLowerCase() }).select('name ownerID aceConfig')
|
||||
if not classroom
|
||||
res.status(404).send({})
|
||||
classroom = classroom.toObject()
|
||||
|
@ -185,3 +186,22 @@ module.exports =
|
|||
yield CourseInstance.update({_id: {$in: freeCourseInstanceIDs}}, { $addToSet: { members: req.user._id }})
|
||||
yield User.update({ _id: req.user._id }, { $addToSet: { courseInstances: { $each: freeCourseInstanceIDs } } })
|
||||
res.send(classroom.toObject({req: req}))
|
||||
|
||||
setStudentPassword: wrap (req, res, next) ->
|
||||
newPassword = req.body.password
|
||||
{ classroomID, memberID } = req.params
|
||||
teacherID = req.user.id
|
||||
return next() if teacherID is memberID or not newPassword
|
||||
ownedClassrooms = yield Classroom.find({ ownerID: mongoose.Types.ObjectId(teacherID) })
|
||||
ownedStudentIDs = _.flatten ownedClassrooms.map (c) ->
|
||||
c.get('members').map (id) ->
|
||||
id.toString()
|
||||
return next() unless memberID in ownedStudentIDs
|
||||
student = yield User.findById(memberID)
|
||||
if student.get('emailVerified')
|
||||
return next new errors.Forbidden("Can't reset password for a student that has verified their email address.")
|
||||
{ valid, error } = tv4.validateResult(newPassword, schemas.passwordString)
|
||||
unless valid
|
||||
throw new errors.UnprocessableEntity(error.message)
|
||||
yield student.update({ $set: { passwordHash: User.hashPassword(newPassword) } })
|
||||
res.status(200).send({})
|
||||
|
|
|
@ -94,19 +94,3 @@ module.exports =
|
|||
verify_link: "http://codecombat.com/user/#{user._id}/verify/#{user.verificationCode(timestamp)}"
|
||||
sendwithus.api.send context, (err, result) ->
|
||||
res.status(200).send({})
|
||||
|
||||
teacherPasswordReset: wrap (req, res, next) ->
|
||||
ownedClassrooms = yield Classroom.find({ ownerID: mongoose.Types.ObjectId(req.user.id) })
|
||||
ownedStudentIDs = _.flatten ownedClassrooms.map (c) ->
|
||||
c.get('members').map (id) ->
|
||||
id.toString()
|
||||
newPassword = req.body.password
|
||||
studentID = req.params.handle
|
||||
student = yield User.findById(studentID)
|
||||
if student.get('emailVerified')
|
||||
return next new errors.Forbidden("Can't reset password for a student that has verified their email address.")
|
||||
if newPassword and studentID in ownedStudentIDs
|
||||
yield student.update({ $set: { passwordHash: User.hashPassword(newPassword) } })
|
||||
res.status(200).send({})
|
||||
else
|
||||
next()
|
||||
|
|
|
@ -60,6 +60,7 @@ module.exports.setup = (app) ->
|
|||
app.get('/db/classroom/:handle/courses/:courseID/levels', mw.classrooms.fetchLevelsForCourse)
|
||||
app.get('/db/classroom/:handle/member-sessions', mw.classrooms.fetchMemberSessions)
|
||||
app.get('/db/classroom/:handle/members', mw.classrooms.fetchMembers) # TODO: Use mw.auth?
|
||||
app.post('/db/classroom/:classroomID/members/:memberID/reset-password', mw.classrooms.setStudentPassword)
|
||||
app.post('/db/classroom/:anything/members', mw.auth.checkLoggedIn(), mw.classrooms.join)
|
||||
app.get('/db/classroom/:handle', mw.auth.checkLoggedIn()) # TODO: Finish migrating route, adding now so 401 is returned
|
||||
|
||||
|
@ -77,8 +78,7 @@ module.exports.setup = (app) ->
|
|||
app.post('/db/course_instance/:handle/members', mw.auth.checkLoggedIn(), mw.courseInstances.addMembers)
|
||||
app.get('/db/course_instance/:handle/classroom', mw.auth.checkLoggedIn(), mw.courseInstances.fetchClassroom)
|
||||
|
||||
app.put('/db/user/:handle', mw.users.resetEmailVerifiedFlag, mw.users.teacherPasswordReset)
|
||||
app.patch('/db/user/:handle', mw.users.resetEmailVerifiedFlag, mw.users.teacherPasswordReset)
|
||||
app.put('/db/user/:handle', mw.users.resetEmailVerifiedFlag)
|
||||
app.delete('/db/user/:handle', mw.users.removeFromClassrooms)
|
||||
app.get('/db/user', mw.users.fetchByGPlusID, mw.users.fetchByFacebookID)
|
||||
app.put('/db/user/-/become-student', mw.users.becomeStudent)
|
||||
|
|
|
@ -235,6 +235,18 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
|
|||
classroom = yield Classroom.findById(classroom.id)
|
||||
expect(classroom.get('members').length).toBe(0)
|
||||
done()
|
||||
|
||||
it 'changes the role regardless of emailVerified', utils.wrap (done) ->
|
||||
user = yield utils.initUser()
|
||||
user.set('emailVerified', true)
|
||||
yield user.save()
|
||||
yield utils.loginUser(user)
|
||||
attrs = user.toObject()
|
||||
attrs.role = 'teacher'
|
||||
[res, body] = yield request.putAsync { uri: getURL('/db/user/'+user.id), json: attrs }
|
||||
user = yield User.findById(user.id)
|
||||
expect(user.get('role')).toBe('teacher')
|
||||
done()
|
||||
|
||||
it 'ignores attempts to change away from a teacher role', utils.wrap (done) ->
|
||||
user = yield utils.initUser()
|
||||
|
|
Loading…
Reference in a new issue