mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 08:50:58 -05:00
parent
ff69bb8c89
commit
9c7345fed0
6 changed files with 180 additions and 14 deletions
|
@ -68,9 +68,9 @@ mixin student-dialog(course)
|
|||
.container-fluid
|
||||
.row
|
||||
.col-md-8
|
||||
input.code-input(type='text', data-i18n="[placeholder]courses.enter_code1", placeholder="Enter unlock code")
|
||||
input.code-input(type='text', data-course-id="#{course.id}", data-i18n="[placeholder]courses.enter_code1", placeholder="Enter unlock code")
|
||||
.col-md-4
|
||||
button.btn.btn-success.btn-enroll(data-i18n="courses.enroll")
|
||||
button.btn.btn-success.btn-enroll(data-course-id="#{course.id}", data-i18n="courses.enroll")
|
||||
|
||||
mixin teacher-dialog(course)
|
||||
.modal.continue-dialog(id="continueModal#{course.id}")
|
||||
|
@ -104,7 +104,10 @@ mixin teacher-dialog(course)
|
|||
div.or(data-i18n="courses.or")
|
||||
.row.button-row.center
|
||||
.col-md-12
|
||||
button.btn.btn-success.btn-lg.btn-buy(data-course-id="#{course.id}", data-i18n="courses.buy_course1")
|
||||
if course.get('pricePerSeat') === 0
|
||||
button.btn.btn-success.btn-lg.btn-buy(data-course-id="#{course.id}") Start new class
|
||||
else
|
||||
button.btn.btn-success.btn-lg.btn-buy(data-course-id="#{course.id}", data-i18n="courses.buy_course1")
|
||||
|
||||
mixin course-block(course)
|
||||
if studentMode
|
||||
|
|
|
@ -283,17 +283,17 @@ module.exports = class CourseDetailsView extends RootView
|
|||
when "progressAsc"
|
||||
@sortedMembers.sort (a, b) =>
|
||||
for levelID, level of @campaign.get('levels')
|
||||
if @userLevelStateMap[a][levelID] isnt 'complete' and @userLevelStateMap[b][levelID] is 'complete'
|
||||
if @userLevelStateMap[a]?[levelID] isnt 'complete' and @userLevelStateMap[b]?[levelID] is 'complete'
|
||||
return -1
|
||||
else if @userLevelStateMap[a][levelID] is 'complete' and @userLevelStateMap[b][levelID] isnt 'complete'
|
||||
else if @userLevelStateMap[a]?[levelID] is 'complete' and @userLevelStateMap[b]?[levelID] isnt 'complete'
|
||||
return 1
|
||||
0
|
||||
when "progressDesc"
|
||||
@sortedMembers.sort (a, b) =>
|
||||
for levelID, level of @campaign.get('levels')
|
||||
if @userLevelStateMap[a][levelID] isnt 'complete' and @userLevelStateMap[b][levelID] is 'complete'
|
||||
if @userLevelStateMap[a]?[levelID] isnt 'complete' and @userLevelStateMap[b]?[levelID] is 'complete'
|
||||
return 1
|
||||
else if @userLevelStateMap[a][levelID] is 'complete' and @userLevelStateMap[b][levelID] isnt 'complete'
|
||||
else if @userLevelStateMap[a]?[levelID] is 'complete' and @userLevelStateMap[b]?[levelID] isnt 'complete'
|
||||
return -1
|
||||
0
|
||||
else
|
||||
|
|
|
@ -70,7 +70,27 @@ module.exports = class CoursesView extends RootView
|
|||
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
||||
|
||||
onClickEnroll: (e) ->
|
||||
alert('TODO: redeem course prepaid and navigate to correct course instance')
|
||||
$('.continue-dialog').modal('hide')
|
||||
courseID = $(e.target).data('course-id')
|
||||
prepaidCode = $(".code-input[data-course-id=#{courseID}]").val()
|
||||
data = prepaidCode: prepaidCode
|
||||
jqxhr = $.post('/db/course_instance/-/redeem_prepaid', data)
|
||||
jqxhr.done (data, textStatus, jqXHR) =>
|
||||
application.tracker?.trackEvent 'Redeemed course prepaid code', {courseID: courseID, prepaidCode: prepaidCode}
|
||||
# TODO: handle fetch errors
|
||||
me.fetch(cache: false).always =>
|
||||
route = "/courses/#{courseID}"
|
||||
viewArgs = [{}, courseID]
|
||||
Backbone.Mediator.publish 'router:navigate',
|
||||
route: route
|
||||
viewClass: 'views/courses/CourseDetailsView'
|
||||
viewArgs: viewArgs
|
||||
jqxhr.fail (xhr, textStatus, errorThrown) =>
|
||||
console.error 'Got an error redeeming a course prepaid code:', textStatus, errorThrown
|
||||
application.tracker?.trackEvent 'Failed to redeem course prepaid code', status: textStatus
|
||||
@state = 'unknown_error'
|
||||
@stateMessage = "#{xhr.status}: #{xhr.responseText}"
|
||||
@render?()
|
||||
|
||||
onClickEnter: (e) ->
|
||||
$('.continue-dialog').modal('hide')
|
||||
|
|
|
@ -33,6 +33,7 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|||
return @getLevelSessionsAPI(req, res, args[0]) if args[1] is 'level_sessions'
|
||||
return @getMembersAPI(req, res, args[0]) if args[1] is 'members'
|
||||
return @inviteStudents(req, res, args[0]) if relationship is 'invite_students'
|
||||
return @redeemPrepaidCodeAPI(req, res) if args[1] is 'redeem_prepaid'
|
||||
super arguments...
|
||||
|
||||
createAPI: (req, res) ->
|
||||
|
@ -126,5 +127,39 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|||
sendwithus.api.send context, _.noop
|
||||
return @sendSuccess(res, {})
|
||||
|
||||
redeemPrepaidCodeAPI: (req, res) ->
|
||||
return @sendUnauthorizedError(res) if not req.user? or req.user?.isAnonymous()
|
||||
return @sendBadInputError(res) unless req.body?.prepaidCode
|
||||
|
||||
prepaidCode = req.body?.prepaidCode
|
||||
Prepaid.find code: prepaidCode, (err, prepaids) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) if prepaids.length < 1
|
||||
return @sendDatabaseError(res, "Multiple prepaid codes found for #{prepaidCode}") if prepaids.length > 1
|
||||
prepaid = prepaids[0]
|
||||
|
||||
CourseInstance.find prepaidID: prepaid.get('_id'), (err, courseInstances) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendForbiddenError(res) if prepaid.get('redeemers')?.length >= prepaid.get('maxRedeemers')
|
||||
|
||||
# Add to prepaid redeemers
|
||||
query =
|
||||
_id: prepaid.get('_id')
|
||||
'redeemers.userID': { $ne: req.user.get('_id') }
|
||||
$where: "this.redeemers.length < #{prepaid.get('maxRedeemers')}"
|
||||
update = { $push: { redeemers : { date: new Date(), userID: req.user.get('_id') } }}
|
||||
Prepaid.update query, update, (err, nMatched) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
if nMatched is 0
|
||||
@logError(req.user, "Course instance update prepaid lost race on maxRedeemers")
|
||||
return @sendForbiddenError(res)
|
||||
|
||||
# Add to each course instance
|
||||
makeAddMemberToCourseInstanceFn = (courseInstance) =>
|
||||
(done) => courseInstance.update({$addToSet: { members: req.user.get('_id')}}, done)
|
||||
tasks = (makeAddMemberToCourseInstanceFn(courseInstance) for courseInstance in courseInstances)
|
||||
async.parallel tasks, (err, results) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
@sendSuccess(res)
|
||||
|
||||
module.exports = new CourseInstanceHandler()
|
||||
|
|
|
@ -19,7 +19,7 @@ PrepaidHandler = class PrepaidHandler extends Handler
|
|||
console.warn "Prepaid Error: [#{user.get('slug')} (#{user._id})] '#{msg}'"
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.user?.isAdmin()
|
||||
req.method is 'GET' || req.user?.isAdmin()
|
||||
|
||||
getByRelationship: (req, res, args...) ->
|
||||
relationship = args[1]
|
||||
|
|
|
@ -6,7 +6,8 @@ stripe = require('stripe')(config.stripe.secretKey)
|
|||
# TODO: add permissiosn tests
|
||||
|
||||
describe 'CourseInstance', ->
|
||||
courseInstanceURL = getURL('/db/course_instance/-/create')
|
||||
courseInstanceCreateURL = getURL('/db/course_instance/-/create')
|
||||
courseInstanceRedeemURL = getURL('/db/course_instance/-/redeem_prepaid')
|
||||
userURL = getURL('/db/user')
|
||||
|
||||
createCourseInstances = (user, courseID, seats, token, done) ->
|
||||
|
@ -17,7 +18,7 @@ describe 'CourseInstance', ->
|
|||
seats: seats
|
||||
stripe:
|
||||
token: token
|
||||
request.post {uri: courseInstanceURL, json: requestBody }, (err, res) ->
|
||||
request.post {uri: courseInstanceCreateURL, json: requestBody }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(201)
|
||||
CourseInstance.find {name: name}, (err, courseInstances) ->
|
||||
|
@ -81,7 +82,7 @@ describe 'CourseInstance', ->
|
|||
requestBody =
|
||||
courseID: course.get('_id')
|
||||
name: createName('course instance ')
|
||||
request.post {uri: courseInstanceURL, json: requestBody }, (err, res) ->
|
||||
request.post {uri: courseInstanceCreateURL, json: requestBody }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
@ -145,7 +146,7 @@ describe 'CourseInstance', ->
|
|||
courseID: course.get('_id')
|
||||
name: createName('course instance ')
|
||||
seats: 1
|
||||
request.post {uri: courseInstanceURL, json: requestBody }, (err, res) ->
|
||||
request.post {uri: courseInstanceCreateURL, json: requestBody }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
@ -163,7 +164,7 @@ describe 'CourseInstance', ->
|
|||
courseID: course.get('_id')
|
||||
name: createName('course instance ')
|
||||
seats: -1
|
||||
request.post {uri: courseInstanceURL, json: requestBody }, (err, res) ->
|
||||
request.post {uri: courseInstanceCreateURL, json: requestBody }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
@ -218,3 +219,110 @@ describe 'CourseInstance', ->
|
|||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
||||
describe 'Redeem prepaid code', ->
|
||||
|
||||
it 'Redeem prepaid code an instance of max 2', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
loginNewUser (user1) ->
|
||||
createCourse 0, (err, course) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
createCourseInstances user1, course.get('_id'), 2, token.id, (err, courseInstances) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
expect(courseInstances.length).toEqual(1)
|
||||
Prepaid.findById courseInstances[0].get('prepaidID'), (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
loginNewUser (user2) ->
|
||||
request.post {uri: courseInstanceRedeemURL, json: {prepaidCode: prepaid.get('code')} }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
|
||||
# Check prepaid
|
||||
Prepaid.findById prepaid.id, (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
expect(prepaid.get('redeemers')?.length).toEqual(1)
|
||||
expect(prepaid.get('redeemers')[0].date).toBeLessThan(new Date())
|
||||
expect(prepaid.get('redeemers')[0].userID).toEqual(user2.get('_id'))
|
||||
|
||||
# Check course instance
|
||||
CourseInstance.findById courseInstances[0].id, (err, courseInstance) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
members = courseInstance.get('members')
|
||||
expect(members?.length).toEqual(2)
|
||||
# TODO: must be a better way to check membership
|
||||
usersFound = 0
|
||||
for memberID in members
|
||||
usersFound++ if memberID.equals(user1.get('_id'))
|
||||
usersFound++ if memberID.equals(user2.get('_id'))
|
||||
expect(usersFound).toEqual(2)
|
||||
done()
|
||||
|
||||
it 'Redeem full prepaid code on instance of max 1', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
loginNewUser (user1) ->
|
||||
createCourse 0, (err, course) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
createCourseInstances user1, course.get('_id'), 1, token.id, (err, courseInstances) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
expect(courseInstances.length).toEqual(1)
|
||||
Prepaid.findById courseInstances[0].get('prepaidID'), (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
loginNewUser (user2) ->
|
||||
request.post {uri: courseInstanceRedeemURL, json: {prepaidCode: prepaid.get('code')} }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
loginNewUser (user3) ->
|
||||
request.post {uri: courseInstanceRedeemURL, json: {prepaidCode: prepaid.get('code')} }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
it 'Redeem 50 count course prepaid codes 51 times, in parallel', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
seatCount = 50
|
||||
loginNewUser (user1) ->
|
||||
createCourse 0, (err, course) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
createCourseInstances user1, course.get('_id'), seatCount, token.id, (err, courseInstances) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
expect(courseInstances.length).toEqual(1)
|
||||
Prepaid.findById courseInstances[0].get('prepaidID'), (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
|
||||
forbiddenResults = 0
|
||||
makeRedeemCall = ->
|
||||
(callback) ->
|
||||
loginNewUser (user2) ->
|
||||
request.post {uri: courseInstanceRedeemURL, json: {prepaidCode: prepaid.get('code')} }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
if res.statusCode is 403
|
||||
forbiddenResults++
|
||||
else
|
||||
expect(res.statusCode).toBe(200)
|
||||
callback err
|
||||
tasks = (makeRedeemCall() for i in [1..seatCount+1])
|
||||
async.parallel tasks, (err, results) ->
|
||||
expect(err?).toEqual(false)
|
||||
expect(forbiddenResults).toEqual(1)
|
||||
Prepaid.findById courseInstances[0].get('prepaidID'), (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
return done(err) if err
|
||||
expect(prepaid.get('redeemers')?.length).toEqual(prepaid.get('maxRedeemers'))
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue