mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 17:45:40 -05:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
27f270856a
39 changed files with 289 additions and 60 deletions
9
Vagrantfile
vendored
9
Vagrantfile
vendored
|
@ -17,7 +17,14 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||
config.vm.network "forwarded_port", guest: 3000, host: 13000
|
||||
config.vm.network "forwarded_port", guest: 9485, host: 19485
|
||||
|
||||
config.vm.provision "shell", path: "scripts/vagrant/core/provision.sh", privileged: false
|
||||
config.vm.define "default" do |default|
|
||||
default.vm.provision "shell", path: "scripts/vagrant/core/provision.sh", privileged: false
|
||||
end
|
||||
|
||||
config.vm.define "brunchv2", autostart: false do |brunchv2|
|
||||
brunchv2.vm.provision "shell", path: "scripts/vagrant/core/provision.sh", privileged: false
|
||||
brunchv2.vm.provision "shell", path: "scripts/vagrant/core/update-brunchv2.sh", privileged: false
|
||||
end
|
||||
|
||||
config.vm.provider "virtualbox" do |v|
|
||||
v.memory = 2048
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -48,6 +48,9 @@ class Rand
|
|||
arr[j] = arr[i]
|
||||
arr[i] = t
|
||||
arr
|
||||
|
||||
choice: (arr) =>
|
||||
return arr[@rand arr.length]
|
||||
|
||||
|
||||
module.exports = Rand
|
||||
|
|
|
@ -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."
|
||||
|
@ -1767,7 +1768,7 @@
|
|||
|
||||
coppa_deny:
|
||||
text1: "Can’t wait to learn programming?"
|
||||
text2: "Ask your parents to create an account for you!"
|
||||
text2: "Your parents will need to create an account for you to use! Email team@codecombat.com if you have any questions." # {change}
|
||||
close: "Close Window"
|
||||
|
||||
loading_error:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -276,9 +276,16 @@ c.extendNamedProperties LevelSchema # let's have the name be the first property
|
|||
_.extend LevelSchema.properties,
|
||||
description: {title: 'Description', description: 'A short explanation of what this level is about.', type: 'string', maxLength: 65536, format: 'markdown'}
|
||||
loadingTip: { type: 'string', title: 'Loading Tip', description: 'What to show for this level while it\'s loading.' }
|
||||
documentation: c.object {title: 'Documentation', description: 'Documentation articles relating to this level.', required: ['specificArticles', 'generalArticles'], 'default': {specificArticles: [], generalArticles: []}},
|
||||
documentation: c.object {title: 'Documentation', description: 'Documentation articles relating to this level.', 'default': {specificArticles: [], generalArticles: []}},
|
||||
specificArticles: c.array {title: 'Specific Articles', description: 'Specific documentation articles that live only in this level.', uniqueItems: true }, SpecificArticleSchema
|
||||
generalArticles: c.array {title: 'General Articles', description: 'General documentation articles that can be linked from multiple levels.', uniqueItems: true}, GeneralArticleSchema
|
||||
hints: c.array {title: 'Hints', description: 'Hints that will be gradually revealed to the player.', uniqueItems: true }, {
|
||||
type: 'object'
|
||||
properties: {
|
||||
body: {type: 'string', title: 'Content', description: 'The body content of the article, in Markdown.', format: 'markdown'}
|
||||
i18n: {type: 'object', format: 'i18n', props: ['body'], description: 'Help translate this hint'}
|
||||
}
|
||||
}
|
||||
background: c.objectId({format: 'hidden'})
|
||||
nextLevel: {
|
||||
type: 'object',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -41,6 +41,7 @@ module.exports = class TeacherClassView extends RootView
|
|||
'click .select-all': 'onClickSelectAll'
|
||||
'click .student-checkbox': 'onClickStudentCheckbox'
|
||||
'keyup #student-search': 'onKeyPressStudentSearch'
|
||||
'change .course-select, .bulk-course-select': 'onChangeCourseSelect'
|
||||
|
||||
getInitialState: ->
|
||||
{
|
||||
|
@ -62,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'
|
||||
|
@ -115,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'))
|
||||
|
@ -149,6 +152,8 @@ module.exports = class TeacherClassView extends RootView
|
|||
@listenTo @students, 'sort', ->
|
||||
@state.set students: @students
|
||||
@render()
|
||||
@listenTo @, 'course-select:change', ({ selectedCourse }) ->
|
||||
@state.set selectedCourse: selectedCourse
|
||||
|
||||
setCourseMembers: =>
|
||||
for course in @courses.models
|
||||
|
@ -273,6 +278,9 @@ module.exports = class TeacherClassView extends RootView
|
|||
onKeyPressStudentSearch: (e) ->
|
||||
@state.set('searchTerm', $(e.target).val())
|
||||
|
||||
onChangeCourseSelect: (e) ->
|
||||
@trigger 'course-select:change', { selectedCourse: @courses.get($(e.currentTarget).val()) }
|
||||
|
||||
getSelectedStudentIDs: ->
|
||||
@$('.student-row .checkbox-flat input:checked').map (index, checkbox) ->
|
||||
$(checkbox).data('student-id')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -29,6 +29,12 @@ module.exports = class I18NEditLevelView extends I18NEditModelView
|
|||
@wrapRow 'Guide article name', ['name'], doc.name, i18n[lang]?.name, ['documentation', 'specificArticles', index]
|
||||
@wrapRow "'#{doc.name}' body", ['body'], doc.body, i18n[lang]?.body, ['documentation', 'specificArticles', index], 'markdown'
|
||||
|
||||
# hints
|
||||
for hint, index in @model.get('documentation')?.hints ? []
|
||||
if i18n = hint.i18n
|
||||
name = "Hint #{index+1}"
|
||||
@wrapRow "'#{name}' body", ['body'], hint.body, i18n[lang]?.body, ['documentation', 'hints', index], 'markdown'
|
||||
|
||||
# sprite dialogues
|
||||
for script, scriptIndex in @model.get('scripts') ? []
|
||||
for noteGroup, noteGroupIndex in script.noteChain ? []
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -28,11 +28,11 @@ var deteacher = function deteacher(email) {
|
|||
else {
|
||||
print('Unset role', db.users.update({_id: user._id}, {$unset: {role: ''}}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
db.system.js.save(
|
||||
{
|
||||
_id: 'deteacher',
|
||||
value: deteacher
|
||||
}
|
||||
)
|
||||
);
|
57
scripts/mongodb/stored/getPrepaidsFor.js
Normal file
57
scripts/mongodb/stored/getPrepaidsFor.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Script for changing prepaid start/end dates and propagating them to users.
|
||||
|
||||
/*
|
||||
* Usage
|
||||
* ---------------
|
||||
* In mongo shell
|
||||
*
|
||||
* > db.loadServerScripts();
|
||||
* > var prepaids = getPrepaidsFor('some@email.com'); // prints basic stats for prepaids found
|
||||
* > prepaids.models // Raw prepaid data
|
||||
* > prepaids.setStart(2001,1,1) // Set start date
|
||||
* > prepaids.setEnd(2100,1,1) // Set end date
|
||||
*/
|
||||
|
||||
|
||||
function getPrepaidsFor(email) {
|
||||
var user = db.users.findOne({emailLower: email.toLowerCase()});
|
||||
if (!user) {
|
||||
print('User not found');
|
||||
return;
|
||||
}
|
||||
|
||||
var result = {};
|
||||
result.models = db.prepaids.find({creator: user._id}).toArray();
|
||||
result.setStart = function(year, month, day) {
|
||||
var startDate = new Date(Date.UTC(year, month-1, day)).toISOString();
|
||||
print('setting to', startDate);
|
||||
for (var i in this.models) {
|
||||
var prepaid = this.models[i];
|
||||
print('Prepaid update', db.prepaids.update({_id: prepaid._id}, {$set: {startDate: startDate}}));
|
||||
print('User update', db.users.update({'coursePrepaid._id': prepaid._id}, {$set: {'coursePrepaid.startDate': startDate}}, {multi: true}));
|
||||
}
|
||||
};
|
||||
result.setEnd = function(year, month, day) {
|
||||
var endDate = new Date(Date.UTC(year, month-1, day)).toISOString();
|
||||
print('setting to', endDate);
|
||||
for (var i in this.models) {
|
||||
var prepaid = this.models[i];
|
||||
print('Prepaid update', db.prepaids.update({_id: prepaid._id}, {$set: {endDate: endDate}}));
|
||||
print('User update', db.users.update({'coursePrepaid._id': prepaid._id}, {$set: {'coursePrepaid.endDate': endDate}}, {multi: true}));
|
||||
}
|
||||
};
|
||||
|
||||
for (var i in result.models) {
|
||||
var prepaid = result.models[i];
|
||||
print('Prepaid:', prepaid.startDate, 'to', prepaid.endDate, 'with', prepaid.redeemers.length, '/', prepaid.maxRedeemers, 'uses');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
db.system.js.save(
|
||||
{
|
||||
_id: 'getPrepaidsFor',
|
||||
value: getPrepaidsFor
|
||||
}
|
||||
);
|
13
scripts/vagrant/core/update-brunchv2.sh
Normal file
13
scripts/vagrant/core/update-brunchv2.sh
Normal file
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash -e
|
||||
# Original content copyright (c) 2014 dpen2000 licensed under the MIT license
|
||||
|
||||
echo "updating brunch to v2..."
|
||||
cd /vagrant
|
||||
npm install \
|
||||
brunch@">=2.0.0" \
|
||||
auto-reload-brunch@">=2.0.0" \
|
||||
coffee-script-brunch@">=2.0.0" \
|
||||
coffeelint-brunch@">=2.0.0" \
|
||||
css-brunch@">=2.0.0" \
|
||||
javascript-brunch@">=2.0.0" \
|
||||
sass-brunch@">=2.0.0" --no-bin-links
|
|
@ -5,6 +5,7 @@ Classroom = require './../models/Classroom'
|
|||
User = require '../models/User'
|
||||
sendwithus = require '../sendwithus'
|
||||
utils = require '../lib/utils'
|
||||
log = require 'winston'
|
||||
UserHandler = require './user_handler'
|
||||
|
||||
ClassroomHandler = class ClassroomHandler extends Handler
|
||||
|
@ -68,13 +69,16 @@ 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')
|
||||
|
||||
Classroom.findById classroomID, (err, classroom) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless classroom
|
||||
return @sendForbiddenError(res) unless classroom.get('ownerID').equals(req.user.get('_id'))
|
||||
unless classroom.get('ownerID').equals(req.user.get('_id'))
|
||||
log.debug "classroom_handler.inviteStudents: Can't invite to classroom (#{classroom.id}) you (#{req.user.get('_id')}) don't own"
|
||||
return @sendForbiddenError(res)
|
||||
|
||||
for email in req.body.emails
|
||||
joinCode = (classroom.get('codeCamel') or classroom.get('code'))
|
||||
|
@ -83,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
|
||||
|
@ -91,13 +96,17 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
|
||||
get: (req, res) ->
|
||||
if ownerID = req.query.ownerID
|
||||
return @sendForbiddenError(res) unless req.user and (req.user.isAdmin() or ownerID is req.user.id)
|
||||
unless req.user and (req.user.isAdmin() or ownerID is req.user.id)
|
||||
log.debug "classroom_handler.get: ownerID (#{ownerID}) must be yourself (#{req.user?.id})"
|
||||
return @sendForbiddenError(res)
|
||||
return @sendBadInputError(res, 'Bad ownerID') unless utils.isID ownerID
|
||||
Classroom.find {ownerID: mongoose.Types.ObjectId(ownerID)}, (err, classrooms) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendSuccess(res, (@formatEntity(req, classroom) for classroom in classrooms))
|
||||
else if memberID = req.query.memberID
|
||||
return @sendForbiddenError(res) unless req.user and (req.user.isAdmin() or memberID is req.user.id)
|
||||
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})"
|
||||
return @sendForbiddenError(res)
|
||||
return @sendBadInputError(res, 'Bad memberID') unless utils.isID memberID
|
||||
Classroom.find {members: mongoose.Types.ObjectId(memberID)}, (err, classrooms) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -3,6 +3,7 @@ utils = require '../lib/utils'
|
|||
errors = require '../commons/errors'
|
||||
schemas = require '../../app/schemas/schemas'
|
||||
wrap = require 'co-express'
|
||||
log = require 'winston'
|
||||
Promise = require 'bluebird'
|
||||
database = require '../commons/database'
|
||||
mongoose = require 'mongoose'
|
||||
|
@ -21,6 +22,7 @@ module.exports =
|
|||
return next() unless code
|
||||
classroom = yield Classroom.findOne({ code: code.toLowerCase() }).select('name ownerID aceConfig')
|
||||
if not classroom
|
||||
log.debug("classrooms.fetchByCode: Couldn't find Classroom with code: #{code}")
|
||||
throw new errors.NotFound('Classroom not found.')
|
||||
classroom = classroom.toObject()
|
||||
# Tack on the teacher's name for display to the user
|
||||
|
@ -33,7 +35,9 @@ module.exports =
|
|||
return next() unless ownerID
|
||||
throw new errors.UnprocessableEntity('Bad ownerID') unless utils.isID ownerID
|
||||
throw new errors.Unauthorized() unless req.user
|
||||
throw new errors.Forbidden('"ownerID" must be yourself') unless req.user.isAdmin() or ownerID is req.user.id
|
||||
unless req.user.isAdmin() or ownerID is req.user.id
|
||||
log.debug("classrooms.getByOwner: Can't fetch classroom you don't own. User: #{req.user.id} Owner: #{ownerID}")
|
||||
throw new errors.Forbidden('"ownerID" must be yourself')
|
||||
sanitizedOptions = {}
|
||||
unless _.isUndefined(options.archived)
|
||||
# Handles when .archived is true, vs false-or-null
|
||||
|
@ -114,6 +118,7 @@ module.exports =
|
|||
isOwner = classroom.get('ownerID').equals(req.user._id)
|
||||
isMember = req.user.id in (m.toString() for m in classroom.get('members'))
|
||||
unless req.user.isAdmin() or isOwner or isMember
|
||||
log.debug "classrooms.fetchMembers: Can't fetch members for class (#{classroom.id}) you (#{req.user.id}) don't own and aren't a member of."
|
||||
throw new errors.Forbidden('You do not own this classroom.')
|
||||
memberIDs = classroom.get('members') or []
|
||||
memberIDs = memberIDs.slice(memberSkip, memberSkip + memberLimit)
|
||||
|
@ -126,7 +131,9 @@ module.exports =
|
|||
|
||||
post: wrap (req, res) ->
|
||||
throw new errors.Unauthorized() unless req.user and not req.user.isAnonymous()
|
||||
throw new errors.Forbidden() unless req.user?.isTeacher()
|
||||
unless req.user?.isTeacher()
|
||||
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
|
||||
classroom.set 'members', []
|
||||
|
@ -159,11 +166,13 @@ module.exports =
|
|||
unless req.body?.code
|
||||
throw new errors.UnprocessableEntity('Need a code')
|
||||
if req.user.isTeacher()
|
||||
log.debug("classrooms.join: Cannot join a classroom as a teacher: #{req.user.id}")
|
||||
throw new errors.Forbidden('Cannot join a classroom as a teacher')
|
||||
code = req.body.code.toLowerCase()
|
||||
classroom = yield Classroom.findOne({code: code})
|
||||
if not classroom
|
||||
throw new errors.NotFound('Classroom not found.')
|
||||
log.debug("classrooms.join: Classroom not found with code #{code}")
|
||||
throw new errors.NotFound("Classroom not found with code #{code}")
|
||||
members = _.clone(classroom.get('members'))
|
||||
if _.any(members, (memberID) -> memberID.equals(req.user._id))
|
||||
return res.send(classroom.toObject({req: req}))
|
||||
|
@ -196,10 +205,12 @@ 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')
|
||||
return next new errors.Forbidden("Can't reset password for a student that has verified their email address.")
|
||||
log.debug "classrooms.setStudentPassword: Can't reset password for a student (#{memberID}) that has verified their email address."
|
||||
throw 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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -55,6 +55,7 @@ createMailContext = (req, done) ->
|
|||
email_data:
|
||||
subject: "[CodeCombat] #{subject ? ('Feedback - ' + fromAddress)}"
|
||||
content: content
|
||||
contentHTML: content.replace /\n/g, '\n<br>'
|
||||
if recipientID is 'schools@codecombat.com' or teacher
|
||||
req.user.update({$set: { enrollmentRequestSent: true }}).exec(_.noop) if recipientID is 'schools@codecombat.com'
|
||||
closeIO.getSalesContactEmail fromAddress, (err, salesContactEmail) ->
|
||||
|
@ -78,7 +79,7 @@ createMailContext = (req, done) ->
|
|||
], (err, results) ->
|
||||
console.error "Error getting contact message context for #{sender}: #{err}" if err
|
||||
if req.body.screenshotURL
|
||||
context.email_data.content += "\n<img src='#{req.body.screenshotURL}' />"
|
||||
context.email_data.contentHTML += "\n<br><img src='#{req.body.screenshotURL}' />"
|
||||
done context
|
||||
|
||||
fetchRecentSessions = (user, context, sentFromLevel, callback) ->
|
||||
|
@ -98,5 +99,5 @@ fetchRecentSessions = (user, context, sentFromLevel, callback) ->
|
|||
if sentFromLevel?.levelID is s.levelID and sentFromLevel?.courseID
|
||||
url += "&course=#{sentFromLevel.courseID}&course-instance=#{sentFromLevel.courseInstanceID}"
|
||||
urlName += ' (course)'
|
||||
context.email_data.content += "\n<a href='#{url}'>#{urlName}</a>#{sessionStatus}"
|
||||
context.email_data.contentHTML += "\n<br><a href='#{url}'>#{urlName}</a>#{sessionStatus}"
|
||||
callback null
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -39,6 +39,21 @@ describe 'Level', ->
|
|||
body = JSON.parse(body)
|
||||
expect(body.type).toBeDefined()
|
||||
done()
|
||||
|
||||
|
||||
describe 'POST /db/level/:handle', ->
|
||||
it 'creates a new version', utils.wrap (done) ->
|
||||
yield utils.clearModels([Campaign, Course, CourseInstance, Level, User])
|
||||
admin = yield utils.initAdmin()
|
||||
yield utils.loginUser(admin)
|
||||
@level = yield utils.makeLevel()
|
||||
levelJSON = @level.toObject()
|
||||
levelJSON.name = 'New name'
|
||||
|
||||
url = getURL("/db/level/#{@level.id}")
|
||||
[res, body] = yield request.postAsync({url: url, json: levelJSON})
|
||||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
||||
|
||||
describe 'GET /db/level/:handle/session', ->
|
||||
|
|
Loading…
Reference in a new issue