2015-09-03 11:04:40 -07:00
async = require ' async '
2015-08-29 07:15:35 -07:00
Handler = require ' ../commons/Handler '
2016-04-06 10:56:06 -07:00
Campaign = require ' ../models/Campaign '
Classroom = require ' ../models/Classroom '
2016-03-30 13:57:19 -07:00
Course = require ' ../models/Course '
2016-04-06 10:56:06 -07:00
CourseInstance = require ' ./../models/CourseInstance '
LevelSession = require ' ../models/LevelSession '
2016-04-07 09:40:53 -07:00
LevelSessionHandler = require ' ./level_session_handler '
2016-04-06 10:56:06 -07:00
Prepaid = require ' ../models/Prepaid '
2016-04-07 09:40:53 -07:00
PrepaidHandler = require ' ./prepaid_handler '
2016-04-06 10:56:06 -07:00
User = require ' ../models/User '
2016-04-07 09:40:53 -07:00
UserHandler = require ' ./user_handler '
2015-10-01 15:23:20 -07:00
utils = require ' ../../app/core/utils '
2016-01-26 14:36:40 -08:00
{ objectIdFromTimestamp } = require ' ../lib/utils '
2015-10-05 16:00:47 -07:00
sendwithus = require ' ../sendwithus '
2015-11-03 14:00:51 -08:00
mongoose = require ' mongoose '
2015-08-29 07:15:35 -07:00
CourseInstanceHandler = class CourseInstanceHandler extends Handler
modelClass: CourseInstance
jsonSchema: require ' ../../app/schemas/models/course_instance.schema '
allowedMethods: [ ' GET ' , ' POST ' , ' PUT ' , ' DELETE ' ]
2015-09-03 11:04:40 -07:00
logError: (user, msg) ->
2015-10-01 15:23:20 -07:00
console . warn " Course instance error: #{ user . get ( ' slug ' ) } ( #{ user . _id } ): ' #{ msg } ' "
2015-09-03 11:04:40 -07:00
2015-08-29 07:15:35 -07:00
hasAccess: (req) ->
2015-09-12 22:01:59 -07:00
req . method in @ allowedMethods or req . user ? . isAdmin ( )
2015-08-29 07:15:35 -07:00
2015-09-03 11:04:40 -07:00
hasAccessToDocument: (req, document, method=null) ->
2015-09-12 22:01:59 -07:00
return true if document ? . get ( ' ownerID ' ) ? . equals ( req . user ? . get ( ' _id ' ) )
return true if req . method is ' GET ' and _ . find document ? . get ( ' members ' ) , (a) -> a . equals ( req . user ? . get ( ' _id ' ) )
2015-09-03 11:04:40 -07:00
req . user ? . isAdmin ( )
getByRelationship: (req, res, args...) ->
relationship = args [ 1 ]
2015-11-09 16:45:12 -08:00
return @ createHOCAPI ( req , res ) if relationship is ' create-for-hoc '
2015-09-12 22:01:59 -07:00
return @ getLevelSessionsAPI ( req , res , args [ 0 ] ) if args [ 1 ] is ' level_sessions '
2015-12-02 11:56:38 -08:00
return @ removeMember ( req , res , args [ 0 ] ) if req . method is ' DELETE ' and args [ 1 ] is ' members '
2015-09-12 22:01:59 -07:00
return @ getMembersAPI ( req , res , args [ 0 ] ) if args [ 1 ] is ' members '
2015-10-05 16:00:47 -07:00
return @ inviteStudents ( req , res , args [ 0 ] ) if relationship is ' invite_students '
2015-10-06 11:20:53 -07:00
return @ redeemPrepaidCodeAPI ( req , res ) if args [ 1 ] is ' redeem_prepaid '
2015-11-22 00:08:46 -08:00
return @ getMyCourseLevelSessionsAPI ( req , res , args [ 0 ] ) if args [ 1 ] is ' my-course-level-sessions '
2015-12-01 11:24:16 -08:00
return @ findByLevel ( req , res , args [ 2 ] ) if args [ 1 ] is ' find_by_level '
2015-09-03 11:04:40 -07:00
super arguments . . .
2015-11-09 16:45:12 -08:00
createHOCAPI: (req, res) ->
return @ sendUnauthorizedError ( res ) if not req . user ?
courseID = mongoose . Types . ObjectId ( ' 560f1a9f22961295f9427742 ' )
CourseInstance . findOne { courseID: courseID , ownerID: req . user . get ( ' _id ' ) , hourOfCode: true } , (err, courseInstance) =>
return @ sendDatabaseError ( res , err ) if err
if courseInstance
console . log ' already made a course instance '
return @ sendSuccess ( res , courseInstance ) if courseInstance
courseInstance = new CourseInstance ( {
courseID: courseID
members: [ req . user . get ( ' _id ' ) ]
name: ' Single Player '
ownerID: req . user . get ( ' _id ' )
aceConfig: { language: ' python ' }
2015-11-09 17:58:40 -08:00
hourOfCode: true
2015-11-09 16:45:12 -08:00
} )
courseInstance . save (err, courseInstance) =>
return @ sendDatabaseError ( res , err ) if err
@ sendCreated ( res , courseInstance )
2015-11-19 10:41:31 -08:00
2015-12-02 11:56:38 -08:00
removeMember: (req, res, courseInstanceID) ->
2015-12-06 12:13:10 -08:00
return @ sendUnauthorizedError ( res ) if not req . user ?
2015-12-02 11:56:38 -08:00
userID = req . body . userID
return @ sendBadInputError ( res , ' Input must be a MongoDB ID ' ) unless utils . isID ( userID )
CourseInstance . findById courseInstanceID , (err, courseInstance) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res , ' Course instance not found ' ) unless courseInstance
Classroom . findById courseInstance . get ( ' classroomID ' ) , (err, classroom) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res , ' Classroom referenced by course instance not found ' ) unless classroom
return @ sendForbiddenError ( res ) unless _ . any ( classroom . get ( ' members ' ) , (memberID) -> memberID . toString ( ) is userID )
ownsCourseInstance = courseInstance . get ( ' ownerID ' ) . equals ( req . user . get ( ' _id ' ) )
removingSelf = userID is req . user . id
return @ sendForbiddenError ( res ) unless ownsCourseInstance or removingSelf
alreadyNotInCourseInstance = not _ . any courseInstance . get ( ' members ' ) or [ ] , (memberID) -> memberID . toString ( ) is userID
return @ sendSuccess ( res , @ formatEntity ( req , courseInstance ) ) if alreadyNotInCourseInstance
members = _ . clone ( courseInstance . get ( ' members ' ) )
2015-12-02 13:59:47 -08:00
members = ( m for m in members when m . toString ( ) isnt userID )
2015-12-02 11:56:38 -08:00
courseInstance . set ( ' members ' , members )
courseInstance . save (err, courseInstance) =>
return @ sendDatabaseError ( res , err ) if err
User . update { _id: mongoose . Types . ObjectId ( userID ) } , { $pull: { courseInstances: courseInstance . get ( ' _id ' ) } } , (err) =>
return @ sendDatabaseError ( res , err ) if err
@ sendSuccess ( res , @ formatEntity ( req , courseInstance ) )
2015-11-03 11:18:44 -08:00
post: (req, res) ->
2015-12-06 12:13:10 -08:00
return @ sendUnauthorizedError ( res ) if not req . user ?
2015-11-03 11:18:44 -08:00
return @ sendBadInputError ( res , ' No classroomID ' ) unless req . body . classroomID
return @ sendBadInputError ( res , ' No courseID ' ) unless req . body . courseID
Classroom . findById req . body . classroomID , (err, classroom) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res , ' Classroom not found ' ) unless classroom
2015-11-03 14:00:51 -08:00
return @ sendForbiddenError ( res ) unless classroom . get ( ' ownerID ' ) . equals ( req . user . get ( ' _id ' ) )
2015-11-03 11:18:44 -08:00
Course . findById req . body . courseID , (err, course) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res , ' Course not found ' ) unless course
2016-03-30 13:57:19 -07:00
q = {
2016-02-08 12:48:15 -08:00
courseID: mongoose . Types . ObjectId ( req . body . courseID )
classroomID: mongoose . Types . ObjectId ( req . body . classroomID )
}
CourseInstance . findOne ( q ) . exec (err, doc) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendSuccess ( res , @ formatEntity ( req , doc ) ) if doc
super ( req , res )
2015-11-19 10:41:31 -08:00
2015-11-03 11:18:44 -08:00
makeNewInstance: (req) ->
doc = new CourseInstance ( {
members: [ ]
2015-09-03 11:04:40 -07:00
ownerID: req . user . get ( ' _id ' )
2015-11-03 11:18:44 -08:00
} )
doc . set ( ' aceConfig ' , { } ) # constructor will ignore empty objects
return doc
2015-09-03 11:04:40 -07:00
2015-09-12 22:01:59 -07:00
getLevelSessionsAPI: (req, res, courseInstanceID) ->
2015-12-06 12:13:10 -08:00
return @ sendUnauthorizedError ( res ) if not req . user ?
2015-09-12 22:01:59 -07:00
CourseInstance . findById courseInstanceID , (err, courseInstance) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res ) unless courseInstance
2015-10-07 16:58:59 -07:00
Course . findById courseInstance . get ( ' courseID ' ) , (err, course) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res ) unless course
Campaign . findById course . get ( ' campaignID ' ) , (err, campaign) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res ) unless campaign
levelIDs = ( levelID for levelID of campaign . get ( ' levels ' ) )
memberIDs = _ . map courseInstance . get ( ' members ' ) ? [ ] , (memberID) -> memberID . toHexString ? ( ) or memberID
query = { $and: [ { creator: { $in: memberIDs } } , { ' level.original ' : { $in: levelIDs } } ] }
2015-11-29 16:17:55 -05:00
cursor = LevelSession . find ( query )
cursor = cursor . select ( req . query . project ) if req . query . project
cursor . exec (err, documents) =>
2015-10-07 16:58:59 -07:00
return @ sendDatabaseError ( res , err ) if err ?
cleandocs = ( LevelSessionHandler . formatEntity ( req , doc ) for doc in documents )
@ sendSuccess ( res , cleandocs )
2015-09-12 22:01:59 -07:00
2015-11-22 00:08:46 -08:00
getMyCourseLevelSessionsAPI: (req, res, courseInstanceID) ->
2015-12-06 12:13:10 -08:00
return @ sendUnauthorizedError ( res ) if not req . user ?
2015-11-22 00:08:46 -08:00
CourseInstance . findById courseInstanceID , (err, courseInstance) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res ) unless courseInstance
Course . findById courseInstance . get ( ' courseID ' ) , (err, course) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res ) unless course
Campaign . findById course . get ( ' campaignID ' ) , (err, campaign) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res ) unless campaign
levelIDs = ( levelID for levelID , level of campaign . get ( ' levels ' ) when not _ . contains ( level . type , ' ladder ' ) )
query = { $and: [ { creator: req . user . id } , { ' level.original ' : { $in: levelIDs } } ] }
2015-11-29 16:17:55 -05:00
cursor = LevelSession . find ( query )
cursor = cursor . select ( req . query . project ) if req . query . project
cursor . exec (err, documents) =>
2015-11-22 00:08:46 -08:00
return @ sendDatabaseError ( res , err ) if err ?
cleandocs = ( LevelSessionHandler . formatEntity ( req , doc ) for doc in documents )
@ sendSuccess ( res , cleandocs )
2015-09-12 22:01:59 -07:00
getMembersAPI: (req, res, courseInstanceID) ->
2015-12-06 12:13:10 -08:00
return @ sendUnauthorizedError ( res ) if not req . user ?
2015-09-12 22:01:59 -07:00
CourseInstance . findById courseInstanceID , (err, courseInstance) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res ) unless courseInstance
memberIDs = courseInstance . get ( ' members ' ) ? [ ]
User . find { _id: { $in: memberIDs } } , (err, users) =>
return @ sendDatabaseError ( res , err ) if err
cleandocs = ( UserHandler . formatEntity ( req , doc ) for doc in users )
@ sendSuccess ( res , cleandocs )
2015-09-03 11:04:40 -07:00
2015-10-05 16:00:47 -07:00
inviteStudents: (req, res, courseInstanceID) ->
2015-12-06 12:13:10 -08:00
return @ sendUnauthorizedError ( res ) if not req . user ?
2015-10-05 16:00:47 -07:00
if not req . body . emails
return @ sendBadInputError ( res , ' Emails not included ' )
CourseInstance . findById courseInstanceID , (err, courseInstance) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res ) unless courseInstance
return @ sendForbiddenError ( res ) unless @ hasAccessToDocument ( req , courseInstance )
2015-10-07 17:24:28 -07:00
Course . findById courseInstance . get ( ' courseID ' ) , (err, course) =>
2015-10-05 16:00:47 -07:00
return @ sendDatabaseError ( res , err ) if err
2015-10-07 17:24:28 -07:00
return @ sendNotFoundError ( res ) unless course
Prepaid . findById courseInstance . get ( ' prepaidID ' ) , (err, prepaid) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res ) unless prepaid
return @ sendForbiddenError ( res ) unless prepaid . get ( ' maxRedeemers ' ) > prepaid . get ( ' redeemers ' ) . length
for email in req . body . emails
context =
email_id: sendwithus . templates . course_invite_email
recipient:
address: email
subject: course . get ( ' name ' )
email_data:
class_name: course . get ( ' name ' )
2015-10-08 05:47:08 -07:00
join_link: " https://codecombat.com/courses/students?_ppc= " + prepaid . get ( ' code ' )
2015-10-07 17:24:28 -07:00
sendwithus . api . send context , _ . noop
return @ sendSuccess ( res , { } )
2015-10-05 16:00:47 -07:00
2015-10-06 11:20:53 -07:00
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 ' )
2015-10-07 15:14:26 -07:00
if _ . find ( ( prepaid . get ( ' redeemers ' ) ? [ ] ) , (a) -> a . userID . equals ( req . user . id ) )
return @ sendSuccess ( res , courseInstances )
2015-10-06 11:20:53 -07:00
# 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 ' ) } } }
2015-12-09 11:56:18 -08:00
Prepaid . update query , update , (err, result) =>
2015-10-06 11:20:53 -07:00
return @ sendDatabaseError ( res , err ) if err
2015-12-09 11:56:18 -08:00
if result ? . nModified is 0
2015-10-06 11:20:53 -07:00
@ 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
2015-10-06 12:30:14 -07:00
@ sendSuccess ( res , courseInstances )
2015-10-05 16:00:47 -07:00
2015-12-01 11:24:16 -08:00
findByLevel: (req, res, levelOriginal) ->
2015-12-06 12:13:10 -08:00
return @ sendUnauthorizedError ( res ) if not req . user ?
2015-12-01 11:24:16 -08:00
# Find all CourseInstances that req.user is a part of that match the given level.
CourseInstance . find { _id: { $in: req . user . get ( ' courseInstances ' ) } } , (err, courseInstances) =>
return @ sendDatabaseError res , err if err
return @ sendSuccess res , [ ] unless courseInstances . length
courseIDs = _ . uniq ( ci . get ( ' courseID ' ) for ci in courseInstances )
Course . find { _id: { $in: courseIDs } } , { name: 1 , campaignID: 1 } , (err, courses) =>
return @ sendDatabaseError res , err if err
return @ sendSuccess res , [ ] unless courses . length
campaignIDs = _ . uniq ( c . get ( ' campaignID ' ) for c in courses )
Campaign . find { _id: { $in: campaignIDs } , " levels. #{ levelOriginal } " : { $exists: true } } , { _id: 1 } , (err, campaigns) =>
return @ sendDatabaseError res , err if err
return @ sendSuccess res , [ ] unless campaigns . length
courses = _ . filter courses , (course) -> _ . find campaigns , (campaign) -> campaign . get ( ' _id ' ) . toString ( ) is course . get ( ' campaignID ' ) . toString ( )
courseInstances = _ . filter courseInstances , (courseInstance) -> _ . find courses , (course) -> course . get ( ' _id ' ) . toString ( ) is courseInstance . get ( ' courseID ' ) . toString ( )
return @ sendSuccess res , courseInstances
2015-11-05 12:55:09 -08:00
get: (req, res) ->
if ownerID = req . query . ownerID
return @ sendForbiddenError ( res ) unless req . user and ( req . user . isAdmin ( ) or ownerID is req . user . id )
return @ sendBadInputError ( res , ' Bad ownerID ' ) unless utils . isID ownerID
CourseInstance . find { ownerID: mongoose . Types . ObjectId ( ownerID ) } , (err, courseInstances) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendSuccess ( res , ( @ formatEntity ( req , courseInstance ) for courseInstance in courseInstances ) )
else if memberID = req . query . memberID
return @ sendForbiddenError ( res ) unless req . user and ( req . user . isAdmin ( ) or memberID is req . user . id )
return @ sendBadInputError ( res , ' Bad memberID ' ) unless utils . isID memberID
CourseInstance . find { members: mongoose . Types . ObjectId ( memberID ) } , (err, courseInstances) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendSuccess ( res , ( @ formatEntity ( req , courseInstance ) for courseInstance in courseInstances ) )
2015-11-09 13:29:49 -08:00
else if classroomID = req . query . classroomID
return @ sendForbiddenError ( res ) unless req . user
return @ sendBadInputError ( res , ' Bad memberID ' ) unless utils . isID classroomID
Classroom . findById classroomID , (err, classroom) =>
2015-12-07 03:41:27 -08:00
return @ sendDatabaseError ( res , err ) if err
return @ sendNotFoundError ( res ) unless classroom
2015-11-09 13:29:49 -08:00
return @ sendForbiddenError ( res ) unless classroom . isMember ( req . user . _id ) or classroom . isOwner ( req . user . _id )
CourseInstance . find { classroomID: mongoose . Types . ObjectId ( classroomID ) } , (err, courseInstances) =>
return @ sendDatabaseError ( res , err ) if err
return @ sendSuccess ( res , ( @ formatEntity ( req , courseInstance ) for courseInstance in courseInstances ) )
2015-11-05 12:55:09 -08:00
else
super ( arguments . . . )
2015-08-29 07:15:35 -07:00
module.exports = new CourseInstanceHandler ( )