mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 08:41:46 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
864e18d23b
30 changed files with 145 additions and 49 deletions
|
@ -69,15 +69,15 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'contribute/diplomat': go('contribute/DiplomatView')
|
||||
'contribute/scribe': go('contribute/ScribeView')
|
||||
|
||||
'courses': go('courses/CoursesView') # , { studentsOnly: true }) # TODO: Enforce after session-less play for teachers
|
||||
'Courses': go('courses/CoursesView') # , { studentsOnly: true })
|
||||
'courses': go('courses/CoursesView', { studentsOnly: true })
|
||||
'Courses': go('courses/CoursesView', { studentsOnly: true })
|
||||
'courses/students': redirect('/courses')
|
||||
'courses/teachers': redirect('/teachers/classes')
|
||||
'courses/purchase': redirect('/teachers/licenses')
|
||||
'courses/enroll(/:courseID)': redirect('/teachers/licenses')
|
||||
'courses/update-account': go('courses/CoursesUpdateAccountView')
|
||||
'courses/:classroomID': go('courses/ClassroomView') #, { studentsOnly: true })
|
||||
'courses/:courseID/:courseInstanceID': go('courses/CourseDetailsView')
|
||||
'courses/:classroomID': go('courses/ClassroomView', { studentsOnly: true })
|
||||
'courses/:courseID/:courseInstanceID': go('courses/CourseDetailsView', { studentsOnly: true })
|
||||
|
||||
'db/*path': 'routeToServer'
|
||||
'demo(/*subpath)': go('DemoView')
|
||||
|
@ -142,14 +142,14 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'SEEN': go('NewHomeView')
|
||||
|
||||
'teachers': redirect('/teachers/classes')
|
||||
'teachers/classes': go('courses/TeacherClassesView') #, { teachersOnly: true })
|
||||
'teachers/classes/:classroomID': go('courses/TeacherClassView') #, { teachersOnly: true })
|
||||
'teachers/classes': go('courses/TeacherClassesView', { teachersOnly: true })
|
||||
'teachers/classes/:classroomID': go('courses/TeacherClassView', { teachersOnly: true })
|
||||
'teachers/courses': go('courses/TeacherCoursesView')
|
||||
'teachers/demo': go('teachers/RequestQuoteView')
|
||||
'teachers/enrollments': redirect('/teachers/licenses')
|
||||
'teachers/licenses': go('courses/EnrollmentsView') #, { teachersOnly: true })
|
||||
'teachers/licenses': go('courses/EnrollmentsView', { teachersOnly: true })
|
||||
'teachers/freetrial': go('teachers/RequestQuoteView')
|
||||
'teachers/quote': go('teachers/RequestQuoteView')
|
||||
'teachers/quote': redirect('/teachers/demo')
|
||||
'teachers/signup': ->
|
||||
return @routeDirectly('teachers/CreateTeacherAccountView', []) if me.isAnonymous()
|
||||
@navigate('/teachers/update-account', {trigger: true, replace: true})
|
||||
|
@ -174,7 +174,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
routeDirectly: (path, args=[], options={}) ->
|
||||
if options.teachersOnly and not me.isTeacher()
|
||||
return @routeDirectly('teachers/RestrictedToTeachersView')
|
||||
if options.studentsOnly and me.isTeacher()
|
||||
if options.studentsOnly and not me.isStudent()
|
||||
return @routeDirectly('courses/RestrictedToStudentsView')
|
||||
leavingMessage = _.result(window.currentView, 'onLeaveMessage')
|
||||
if leavingMessage
|
||||
|
|
|
@ -1279,7 +1279,8 @@
|
|||
student_age_range_to: "to"
|
||||
create_class: "Create Class"
|
||||
class_name: "Class Name"
|
||||
teacher_account_restricted: "Your account is a teacher account, and so cannot access student content."
|
||||
teacher_account_restricted: "Your account is a teacher account and cannot access student content." # {change}
|
||||
account_restricted: "A student account is required to access this page."
|
||||
update_account_login_title: "Log in to update your account"
|
||||
update_account_title: "Your account needs attention!"
|
||||
update_account_blurb: "Before you can access your classes, choose how you want to use this account."
|
||||
|
|
|
@ -25,7 +25,7 @@ module.exports = class User extends CocoModel
|
|||
return name if name
|
||||
[emailName, emailDomain] = @get('email')?.split('@') or []
|
||||
return emailName if emailName
|
||||
return 'Anoner'
|
||||
return 'Anonymous'
|
||||
|
||||
getPhotoURL: (size=80, useJobProfilePhoto=false, useEmployerPageAvatar=false) ->
|
||||
photoURL = if useJobProfilePhoto then @get('jobProfile')?.photoURL else null
|
||||
|
|
|
@ -6,7 +6,7 @@ UserSchema = c.object
|
|||
default:
|
||||
visa: 'Authorized to work in the US'
|
||||
music: true
|
||||
name: 'Anoner'
|
||||
name: 'Anonymous'
|
||||
autocastDelay: 5000
|
||||
emails: {}
|
||||
permissions: []
|
||||
|
|
|
@ -123,7 +123,7 @@ block content
|
|||
if memberLanguageMap && memberLanguageMap[member.id]
|
||||
span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id])
|
||||
div
|
||||
a(href="/user/#{member.id}")= member.get('name') || 'Anoner'
|
||||
a(href="/user/#{member.id}")= member.get('name') || 'Anonymous'
|
||||
div Level #{member.level()}
|
||||
if isOwner && member.id !== clan.get('ownerID')
|
||||
button.btn.btn-xs.btn-warning.remove-member-btn(data-id="#{member.id}", data-i18n="clans.rem_hero") Remove Hero
|
||||
|
@ -220,7 +220,7 @@ block content
|
|||
if memberLanguageMap && memberLanguageMap[member.id]
|
||||
span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id])
|
||||
td.name-cell
|
||||
a(href="/user/#{member.id}")= member.get('name') || 'Anoner'
|
||||
a(href="/user/#{member.id}")= member.get('name') || 'Anonymous'
|
||||
td.level-cell= member.level()
|
||||
td.achievements-cell
|
||||
if memberAchievementsMap && memberAchievementsMap[member.id]
|
||||
|
|
|
@ -45,7 +45,7 @@ block content
|
|||
if view.idNameMap && view.idNameMap[clan.get('ownerID')]
|
||||
a(href="/user/#{clan.get('ownerID')}")= view.idNameMap[clan.get('ownerID')]
|
||||
else
|
||||
a(href="/user/#{clan.get('ownerID')}") Anoner
|
||||
a(href="/user/#{clan.get('ownerID')}") Anonymous
|
||||
td
|
||||
if view.myClanIDs.indexOf(clan.id) < 0
|
||||
button.btn.btn-success.join-clan-btn(data-id="#{clan.id}", data-i18n="clans.join_clan") Join Clan
|
||||
|
@ -75,7 +75,7 @@ block content
|
|||
if view.idNameMap && view.idNameMap[clan.get('ownerID')]
|
||||
a(href="/user/#{clan.get('ownerID')}")= view.idNameMap[clan.get('ownerID')]
|
||||
else
|
||||
a(href="/user/#{clan.get('ownerID')}") Anoner
|
||||
a(href="/user/#{clan.get('ownerID')}") Anonymous
|
||||
td= clan.get('type')
|
||||
td
|
||||
if clan.get('ownerID') !== me.id
|
||||
|
|
|
@ -3,10 +3,19 @@ extends /templates/base-flat
|
|||
block content
|
||||
.access-restricted.container.text-center.m-y-3
|
||||
h5(data-i18n='teacher.access_restricted')
|
||||
p(data-i18n='courses.teacher_account_restricted')
|
||||
a.btn.btn-lg.btn-primary(href="/teachers/classes" data-i18n="new_home.goto_classes")
|
||||
button#logout-button.btn.btn-lg.btn-primary-alt(data-i18n="login.log_out")
|
||||
if me.isTeacher()
|
||||
p(data-i18n='courses.teacher_account_restricted')
|
||||
a.btn.btn-lg.btn-primary(href="/teachers/classes" data-i18n="new_home.goto_classes")
|
||||
button#logout-button.btn.btn-lg.btn-primary-alt(data-i18n="login.log_out")
|
||||
else
|
||||
p(data-i18n='courses.account_restricted')
|
||||
if me.isAnonymous()
|
||||
.login-button.btn.btn-lg.btn-primary(data-i18n='login.log_in')
|
||||
else
|
||||
a.btn.btn-lg.btn-primary(href="/courses/update-account" data-i18n="courses.update_account_update_student")
|
||||
button#logout-button.btn.btn-lg.btn-primary-alt(data-i18n="login.log_out")
|
||||
|
||||
.teacher-account-blurb.text-center.col-xs-6.col-xs-offset-3.m-y-3
|
||||
h5(data-i18n='teacher.what_is_a_teacher_account')
|
||||
p(data-i18n='teacher.teacher_account_explanation')
|
||||
if me.isTeacher()
|
||||
.teacher-account-blurb.text-center.col-xs-6.col-xs-offset-3.m-y-3
|
||||
h5(data-i18n='teacher.what_is_a_teacher_account')
|
||||
p(data-i18n='teacher.teacher_account_explanation')
|
|
@ -11,7 +11,7 @@ ul.user-feedback-list.list-group
|
|||
em= moment(new Date(feedback.created)).fromNow()
|
||||
span.spl.spr -
|
||||
a(href="/user/#{feedback.creator}")
|
||||
strong= feedback.creatorName || 'Anoner'
|
||||
strong= feedback.creatorName || 'Anonymous'
|
||||
if feedback.review
|
||||
span.spr :
|
||||
span= feedback.review
|
||||
|
|
|
@ -12,7 +12,7 @@ for player in view.players
|
|||
.player-gold
|
||||
.gold-icon
|
||||
.gold-value
|
||||
.player-name= player.name || 'Anoner'
|
||||
.player-name= player.name || 'Anonymous'
|
||||
.player-health
|
||||
.health-icon
|
||||
.health-bar-container
|
||||
|
|
|
@ -81,7 +81,7 @@ block append content
|
|||
if idNameMap && idNameMap[clan.get('ownerID')]
|
||||
a(href="/user/#{clan.get('ownerID')}")= idNameMap[clan.get('ownerID')]
|
||||
else
|
||||
a(href="/user/#{clan.get('ownerID')}") Anoner
|
||||
a(href="/user/#{clan.get('ownerID')}") Anonymous
|
||||
td= clan.get('members').length
|
||||
else
|
||||
.panel-body
|
||||
|
|
|
@ -23,6 +23,8 @@ module.exports = class AboutView extends RootView
|
|||
'left': 'onLeftPressed'
|
||||
'esc': 'onEscapePressed'
|
||||
|
||||
getTitle: -> return $.i18n.t('nav.about')
|
||||
|
||||
afterRender: ->
|
||||
super(arguments...)
|
||||
@$('#fixed-nav').affix({
|
||||
|
|
|
@ -19,7 +19,9 @@ module.exports = class MainAdminView extends RootView
|
|||
'click #user-search-result': 'onClickUserSearchResult'
|
||||
'click #create-free-sub-btn': 'onClickFreeSubLink'
|
||||
'click #terminal-create': 'onClickTerminalSubLink'
|
||||
|
||||
|
||||
getTitle: -> return $.i18n.t('account_settings.admin')
|
||||
|
||||
initialize: ->
|
||||
if window.amActually
|
||||
@amActually = new User({_id: window.amActually})
|
||||
|
@ -65,7 +67,7 @@ module.exports = class MainAdminView extends RootView
|
|||
forms.enableSubmit(@$('#user-search-button'))
|
||||
result = ''
|
||||
if users.length
|
||||
result = ("<tr data-user-id='#{user._id}'><td><code>#{user._id}</code></td><td>#{_.escape(user.name or 'Anoner')}</td><td>#{_.escape(user.email)}</td></tr>" for user in users)
|
||||
result = ("<tr data-user-id='#{user._id}'><td><code>#{user._id}</code></td><td>#{_.escape(user.name or 'Anonymous')}</td><td>#{_.escape(user.email)}</td></tr>" for user in users)
|
||||
result = "<table class=\"table\">#{result.join('\n')}</table>"
|
||||
@$el.find('#user-search-result').html(result)
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ module.exports = class ClanDetailsView extends RootView
|
|||
return unless @members? and @memberSort?
|
||||
switch @memberSort
|
||||
when "nameDesc"
|
||||
@members.comparator = (a, b) -> return (b.get('name') or 'Anoner').localeCompare(a.get('name') or 'Anoner')
|
||||
@members.comparator = (a, b) -> return (b.get('name') or 'Anonymous').localeCompare(a.get('name') or 'Anonymous')
|
||||
when "progressAsc"
|
||||
@members.comparator = (a, b) ->
|
||||
aComplete = (concept for concept, state of userConceptsMap[a.id] when state is 'complete')
|
||||
|
@ -151,7 +151,7 @@ module.exports = class ClanDetailsView extends RootView
|
|||
else if aStarted > bStarted then return 1
|
||||
if highestUserLevelCountMap[a.id] < highestUserLevelCountMap[b.id] then return -1
|
||||
else if highestUserLevelCountMap[a.id] > highestUserLevelCountMap[b.id] then return 1
|
||||
(a.get('name') or 'Anoner').localeCompare(b.get('name') or 'Anoner')
|
||||
(a.get('name') or 'Anonymous').localeCompare(b.get('name') or 'Anonymous')
|
||||
when "progressDesc"
|
||||
@members.comparator = (a, b) ->
|
||||
aComplete = (concept for concept, state of userConceptsMap[a.id] when state is 'complete')
|
||||
|
@ -164,9 +164,9 @@ module.exports = class ClanDetailsView extends RootView
|
|||
else if aStarted < bStarted then return 1
|
||||
if highestUserLevelCountMap[a.id] > highestUserLevelCountMap[b.id] then return -1
|
||||
else if highestUserLevelCountMap[a.id] < highestUserLevelCountMap[b.id] then return 1
|
||||
(b.get('name') or 'Anoner').localeCompare(a.get('name') or 'Anoner')
|
||||
(b.get('name') or 'Anonymous').localeCompare(a.get('name') or 'Anonymous')
|
||||
else
|
||||
@members.comparator = (a, b) -> return (a.get('name') or 'Anoner').localeCompare(b.get('name') or 'Anoner')
|
||||
@members.comparator = (a, b) -> return (a.get('name') or 'Anonymous').localeCompare(b.get('name') or 'Anonymous')
|
||||
@members.sort()
|
||||
|
||||
updateHeroIcons: ->
|
||||
|
|
|
@ -110,10 +110,8 @@ module.exports = class RootView extends CocoView
|
|||
@buildLanguages()
|
||||
$('body').removeClass('is-playing')
|
||||
|
||||
if application.isProduction()
|
||||
title = 'CodeCombat - ' + (@getTitle() or 'Learn how to code by playing a game')
|
||||
else
|
||||
title = @getTitle() or @constructor.name
|
||||
if title = @getTitle() then title += ' | CodeCombat'
|
||||
else title = 'CodeCombat - Learn how to code by playing a game'
|
||||
|
||||
$('title').text(title)
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ module.exports = class CoursesView extends RootView
|
|||
'submit #join-class-form': 'onSubmitJoinClassForm'
|
||||
'click #change-language-link': 'onClickChangeLanguageLink'
|
||||
|
||||
getTitle: -> return $.i18n.t('teacher.students')
|
||||
|
||||
initialize: ->
|
||||
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
|
||||
@courseInstances.comparator = (ci) -> return ci.get('classroomID') + ci.get('courseID')
|
||||
|
|
|
@ -19,6 +19,8 @@ module.exports = class EnrollmentsView extends RootView
|
|||
'click #how-to-enroll-link': 'onClickHowToEnrollLink'
|
||||
'click #contact-us-btn': 'onClickContactUsButton'
|
||||
|
||||
getTitle: -> return $.i18n.t('teacher.enrollments')
|
||||
|
||||
initialize: ->
|
||||
@state = new State({
|
||||
totalEnrolled: 0
|
||||
|
|
|
@ -63,6 +63,8 @@ module.exports = class TeacherClassView extends RootView
|
|||
enrolledUsers: ""
|
||||
}
|
||||
|
||||
getTitle: -> return @classroom?.get('name')
|
||||
|
||||
initialize: (options, classroomID) ->
|
||||
super(options)
|
||||
@singleStudentCourseProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-single-student-course'
|
||||
|
@ -116,7 +118,7 @@ module.exports = class TeacherClassView extends RootView
|
|||
@supermodel.trackRequest @levels.fetchForClassroom(classroomID, {data: {project: 'original,concepts'}})
|
||||
|
||||
@attachMediatorEvents()
|
||||
|
||||
|
||||
attachMediatorEvents: () ->
|
||||
@listenTo @state, 'sync change', ->
|
||||
if _.isEmpty(_.omit(@state.changed, 'searchTerm'))
|
||||
|
@ -277,7 +279,6 @@ module.exports = class TeacherClassView extends RootView
|
|||
@state.set('searchTerm', $(e.target).val())
|
||||
|
||||
onChangeCourseSelect: (e) ->
|
||||
console.log '??'
|
||||
@trigger 'course-select:change', { selectedCourse: @courses.get($(e.currentTarget).val()) }
|
||||
|
||||
getSelectedStudentIDs: ->
|
||||
|
|
|
@ -25,6 +25,8 @@ module.exports = class TeacherClassesView extends RootView
|
|||
'click .add-students-btn': 'onClickAddStudentsButton'
|
||||
'click .create-classroom-btn': 'onClickCreateClassroomButton'
|
||||
|
||||
getTitle: -> return $.i18n.t('teacher.my_classes')
|
||||
|
||||
initialize: (options) ->
|
||||
super(options)
|
||||
@classrooms = new Classrooms()
|
||||
|
|
|
@ -39,6 +39,8 @@ module.exports = class TeacherCoursesView extends RootView
|
|||
"569ed916efa72b0ced971447": null
|
||||
}
|
||||
|
||||
getTitle: -> return $.i18n.t('teacher.courses')
|
||||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
@ownedClassrooms = new Classrooms()
|
||||
|
|
|
@ -61,7 +61,7 @@ module.exports = class LadderPlayModal extends ModalView
|
|||
|
||||
success = (@nameMap) =>
|
||||
for challenger in _.values(@challengers)
|
||||
challenger.opponentName = @nameMap[challenger.opponentID]?.name or 'Anoner'
|
||||
challenger.opponentName = @nameMap[challenger.opponentID]?.name or 'Anonymous'
|
||||
challenger.opponentWizard = @nameMap[challenger.opponentID]?.wizard or {}
|
||||
@checkWizardLoaded()
|
||||
|
||||
|
|
|
@ -207,7 +207,7 @@ module.exports = class SpectateLevelView extends RootView
|
|||
findPlayerNames: ->
|
||||
playerNames = {}
|
||||
for session in [@session, @otherSession] when session?.get('team')
|
||||
playerNames[session.get('team')] = session.get('creatorName') or 'Anoner'
|
||||
playerNames[session.get('team')] = session.get('creatorName') or 'Anonymous'
|
||||
playerNames
|
||||
|
||||
initGoalManager: ->
|
||||
|
|
|
@ -353,7 +353,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
return {} unless @level.get('type') in ['ladder', 'hero-ladder', 'course-ladder']
|
||||
playerNames = {}
|
||||
for session in [@session, @otherSession] when session?.get('team')
|
||||
playerNames[session.get('team')] = session.get('creatorName') or 'Anoner'
|
||||
playerNames[session.get('team')] = session.get('creatorName') or 'Anonymous'
|
||||
playerNames
|
||||
|
||||
# Once Surface is Loaded ####################################################
|
||||
|
|
|
@ -87,7 +87,7 @@ emailUserInitialRecruiting = (user, callback) ->
|
|||
return callback null, false if DEBUGGING and (totalEmailsSent > 1 or Math.random() > 0.05)
|
||||
++totalEmailsSent
|
||||
name = if user.firstName and user.lastName then "#{user.firstName}" else user.name
|
||||
name = 'Wizard' if not name or name is 'Anoner'
|
||||
name = 'Wizard' if not name or name in ['Anoner', 'Anonymous']
|
||||
team = user.session.levelInfo.team
|
||||
team = team.substr(0, team.length - 1)
|
||||
context =
|
||||
|
|
|
@ -69,6 +69,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
return _.omit(doc.toObject(), 'code', 'codeCamel')
|
||||
|
||||
inviteStudents: (req, res, classroomID) ->
|
||||
return @sendUnauthorizedError(res) if not req.user?
|
||||
if not req.body.emails
|
||||
return @sendBadInputError(res, 'Emails not included')
|
||||
|
||||
|
@ -86,6 +87,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
recipient:
|
||||
address: email
|
||||
email_data:
|
||||
teacher_name: req.user.broadName()
|
||||
class_name: classroom.get('name')
|
||||
join_link: "https://codecombat.com/courses?_cc=" + joinCode
|
||||
join_code: joinCode
|
||||
|
@ -103,7 +105,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
return @sendSuccess(res, (@formatEntity(req, classroom) for classroom in classrooms))
|
||||
else if memberID = req.query.memberID
|
||||
unless req.user and (req.user.isAdmin() or memberID is req.user.id)
|
||||
log.debug "classroom_handler.get: memberID (#{memberID}) must be yourself (#{req.user.id})"
|
||||
log.debug "classroom_handler.get: memberID (#{memberID}) must be yourself (#{req.user?.id})"
|
||||
return @sendForbiddenError(res)
|
||||
return @sendBadInputError(res, 'Bad memberID') unless utils.isID memberID
|
||||
Classroom.find {members: mongoose.Types.ObjectId(memberID)}, (err, classrooms) =>
|
||||
|
|
|
@ -192,6 +192,7 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|||
address: email
|
||||
subject: course.get('name')
|
||||
email_data:
|
||||
teacher_name: req.user.broadName()
|
||||
class_name: course.get('name')
|
||||
join_link: "https://codecombat.com/courses/students?_ppc=" + prepaid.get('code')
|
||||
sendwithus.api.send context, _.noop
|
||||
|
|
|
@ -68,4 +68,5 @@ module.exports =
|
|||
return done(null, activity.sender) if /@codecombat\.com/ig.test(activity.sender)
|
||||
return done(null, config.mail.supportSchools)
|
||||
catch error
|
||||
log.error("closeIO.getSalesContactEmail Error for #{email}: #{JSON.stringify(error)}")
|
||||
return done(error, config.mail.supportSchools)
|
||||
|
|
|
@ -132,7 +132,7 @@ module.exports =
|
|||
post: wrap (req, res) ->
|
||||
throw new errors.Unauthorized() unless req.user and not req.user.isAnonymous()
|
||||
unless req.user?.isTeacher()
|
||||
console.log "classrooms.post: Can't create classroom if you (#{req.user?.id}) aren't a teacher."
|
||||
log.debug "classrooms.post: Can't create classroom if you (#{req.user?.id}) aren't a teacher."
|
||||
throw new errors.Forbidden()
|
||||
classroom = database.initDoc(req, Classroom)
|
||||
classroom.set 'ownerID', req.user._id
|
||||
|
@ -205,7 +205,8 @@ module.exports =
|
|||
ownedStudentIDs = _.flatten ownedClassrooms.map (c) ->
|
||||
c.get('members').map (id) ->
|
||||
id.toString()
|
||||
return next() unless memberID in ownedStudentIDs
|
||||
unless memberID in ownedStudentIDs
|
||||
throw new errors.Forbidden("Can't reset the password of a student that's not in one of your classrooms.")
|
||||
student = yield User.findById(memberID)
|
||||
if student.get('emailVerified')
|
||||
log.debug "classrooms.setStudentPassword: Can't reset password for a student (#{memberID}) that has verified their email address."
|
||||
|
|
|
@ -49,7 +49,7 @@ UserSchema.methods.broadName = ->
|
|||
return name if name
|
||||
[emailName, emailDomain] = @get('email').split('@')
|
||||
return emailName if emailName
|
||||
return 'Anoner'
|
||||
return 'Anonymous'
|
||||
|
||||
UserSchema.methods.isInGodMode = ->
|
||||
p = @get('permissions')
|
||||
|
|
|
@ -578,7 +578,7 @@ sendLadderUpdateEmail = (session, now, daysAgo) ->
|
|||
#log.info "Not sending email to #{user.get('email')} #{user.get('name')} because the session had levelName #{session.levelName} or team #{session.team} in it."
|
||||
return
|
||||
name = if user.get('firstName') and user.get('lastName') then "#{user.get('firstName')}" else user.get('name')
|
||||
name = 'Wizard' if not name or name is 'Anoner'
|
||||
name = 'Wizard' if not name or name is 'Anonymous'
|
||||
|
||||
# Fetch the most recent defeat and victory, if there are any.
|
||||
# (We could look at strongest/weakest, but we'd have to fetch everyone, or denormalize more.)
|
||||
|
@ -622,13 +622,13 @@ sendLadderUpdateEmail = (session, now, daysAgo) ->
|
|||
if err
|
||||
log.error "Couldn't find defeateded opponent: #{err}"
|
||||
defeatedOpponent = null
|
||||
victoryContext = {opponent_name: defeatedOpponent?.name ? 'Anoner', url: urlForMatch(victory)} if victory
|
||||
victoryContext = {opponent_name: defeatedOpponent?.name ? 'Anonymous', url: urlForMatch(victory)} if victory
|
||||
|
||||
onFetchedVictoriousOpponent = (err, victoriousOpponent) ->
|
||||
if err
|
||||
log.error "Couldn't find victorious opponent: #{err}"
|
||||
victoriousOpponent = null
|
||||
defeatContext = {opponent_name: victoriousOpponent?.name ? 'Anoner', url: urlForMatch(defeat)} if defeat
|
||||
defeatContext = {opponent_name: victoriousOpponent?.name ? 'Anonymous', url: urlForMatch(defeat)} if defeat
|
||||
|
||||
Level.find({original: session.level.original, created: {$gt: session.submitDate}}).select('created commitMessage version').sort('-created').lean().exec (err, levelVersions) ->
|
||||
sendEmail defeatContext, victoryContext, (if levelVersions.length then levelVersions else null)
|
||||
|
@ -706,7 +706,7 @@ sendNextStepsEmail = (user, now, daysAgo) ->
|
|||
do (err, nextLevel) ->
|
||||
return log.error "Couldn't find next level for #{user.get('email')}: #{err}" if err
|
||||
name = if user.get('firstName') and user.get('lastName') then "#{user.get('firstName')}" else user.get('name')
|
||||
name = 'hero' if not name or name is 'Anoner'
|
||||
name = 'Hero' if not name or name in ['Anoner', 'Anonymous']
|
||||
#secretLevel = switch user.get('testGroupNumber') % 8
|
||||
# when 0, 1, 2, 3 then name: 'Forgetful Gemsmith', slug: 'forgetful-gemsmith'
|
||||
# when 4, 5, 6, 7 then name: 'Signs and Portents', slug: 'signs-and-portents'
|
||||
|
|
|
@ -447,3 +447,73 @@ describe 'GET /db/classroom/:handle/members', ->
|
|||
expect(user.email).toBeDefined()
|
||||
expect(user.passwordHash).toBeUndefined()
|
||||
done()
|
||||
|
||||
describe 'POST /db/classroom/:classroomID/members/:memberID/reset-password', ->
|
||||
it 'changes the password', utils.wrap (done) ->
|
||||
yield utils.clearModels([User, Classroom])
|
||||
teacher = yield utils.initUser()
|
||||
yield utils.loginUser(teacher)
|
||||
student = yield utils.initUser({ name: "Firstname Lastname" })
|
||||
newPassword = "this is a new password"
|
||||
classroom = yield new Classroom({name: 'Classroom', ownerID: teacher._id, members: [student._id] }).save()
|
||||
expect(student.get('passwordHash')).not.toEqual(User.hashPassword(newPassword))
|
||||
[res, body] = yield request.postAsync({
|
||||
uri: getURL("/db/classroom/#{classroom.id}/members/#{student.id}/reset-password")
|
||||
json: { password: newPassword }
|
||||
})
|
||||
expect(res.statusCode).toBe(200)
|
||||
changedStudent = yield User.findById(student.id)
|
||||
expect(changedStudent.get('passwordHash')).toEqual(User.hashPassword(newPassword))
|
||||
done()
|
||||
|
||||
it "doesn't change the password if you're not their teacher", utils.wrap (done) ->
|
||||
yield utils.clearModels([User, Classroom])
|
||||
teacher = yield utils.initUser()
|
||||
yield utils.loginUser(teacher)
|
||||
student = yield utils.initUser({ name: "Firstname Lastname" })
|
||||
student2 = yield utils.initUser({ name: "Firstname Lastname 2" })
|
||||
newPassword = "this is a new password"
|
||||
classroom = yield new Classroom({name: 'Classroom', ownerID: teacher._id, members: [student2._id] }).save()
|
||||
expect(student.get('passwordHash')).not.toEqual(User.hashPassword(newPassword))
|
||||
[res, body] = yield request.postAsync({
|
||||
uri: getURL("/db/classroom/#{classroom.id}/members/#{student.id}/reset-password")
|
||||
json: { password: newPassword }
|
||||
})
|
||||
expect(res.statusCode).toBe(403)
|
||||
changedStudent = yield User.findById(student.id)
|
||||
expect(changedStudent.get('passwordHash')).toEqual(student.get('passwordHash'))
|
||||
done()
|
||||
|
||||
it "doesn't change the password if their email is verified", utils.wrap (done) ->
|
||||
yield utils.clearModels([User, Classroom])
|
||||
teacher = yield utils.initUser()
|
||||
yield utils.loginUser(teacher)
|
||||
student = yield utils.initUser({ name: "Firstname Lastname", emailVerified: true })
|
||||
newPassword = "this is a new password"
|
||||
classroom = yield new Classroom({name: 'Classroom', ownerID: teacher._id, members: [student._id] }).save()
|
||||
expect(student.get('passwordHash')).not.toEqual(User.hashPassword(newPassword))
|
||||
[res, body] = yield request.postAsync({
|
||||
uri: getURL("/db/classroom/#{classroom.id}/members/#{student.id}/reset-password")
|
||||
json: { password: newPassword }
|
||||
})
|
||||
expect(res.statusCode).toBe(403)
|
||||
changedStudent = yield User.findById(student.id)
|
||||
expect(changedStudent.get('passwordHash')).toEqual(student.get('passwordHash'))
|
||||
done()
|
||||
|
||||
it "doesn't let you set a 1-character password", utils.wrap (done) ->
|
||||
yield utils.clearModels([User, Classroom])
|
||||
teacher = yield utils.initUser()
|
||||
yield utils.loginUser(teacher)
|
||||
student = yield utils.initUser({ name: "Firstname Lastname" })
|
||||
newPassword = "e"
|
||||
classroom = yield new Classroom({name: 'Classroom', ownerID: teacher._id, members: [student._id] }).save()
|
||||
expect(student.get('passwordHash')).not.toEqual(User.hashPassword(newPassword))
|
||||
[res, body] = yield request.postAsync({
|
||||
uri: getURL("/db/classroom/#{classroom.id}/members/#{student.id}/reset-password")
|
||||
json: { password: newPassword }
|
||||
})
|
||||
expect(res.statusCode).toBe(422)
|
||||
changedStudent = yield User.findById(student.id)
|
||||
expect(changedStudent.get('passwordHash')).toEqual(student.get('passwordHash'))
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue