mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-02 11:58:10 -05:00
609884eb51
Adding a second table, setting range to be 60 and 365 days. Updating teacher columns to prioritize student paid status over furthest student course.
343 lines
19 KiB
CoffeeScript
343 lines
19 KiB
CoffeeScript
async = require 'async'
|
|
Handler = require '../commons/Handler'
|
|
Campaign = require '../campaigns/Campaign'
|
|
Classroom = require '../classrooms/Classroom'
|
|
Course = require './Course'
|
|
CourseInstance = require './CourseInstance'
|
|
LevelSession = require '../levels/sessions/LevelSession'
|
|
LevelSessionHandler = require '../levels/sessions/level_session_handler'
|
|
Prepaid = require '../prepaids/Prepaid'
|
|
PrepaidHandler = require '../prepaids/prepaid_handler'
|
|
User = require '../users/User'
|
|
UserHandler = require '../users/user_handler'
|
|
utils = require '../../app/core/utils'
|
|
{objectIdFromTimestamp} = require '../lib/utils'
|
|
sendwithus = require '../sendwithus'
|
|
mongoose = require 'mongoose'
|
|
|
|
CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|
modelClass: CourseInstance
|
|
jsonSchema: require '../../app/schemas/models/course_instance.schema'
|
|
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE']
|
|
|
|
logError: (user, msg) ->
|
|
console.warn "Course instance error: #{user.get('slug')} (#{user._id}): '#{msg}'"
|
|
|
|
hasAccess: (req) ->
|
|
req.method in @allowedMethods or req.user?.isAdmin()
|
|
|
|
hasAccessToDocument: (req, document, method=null) ->
|
|
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'))
|
|
req.user?.isAdmin()
|
|
|
|
getByRelationship: (req, res, args...) ->
|
|
relationship = args[1]
|
|
return @createHOCAPI(req, res) if relationship is 'create-for-hoc'
|
|
return @getLevelSessionsAPI(req, res, args[0]) if args[1] is 'level_sessions'
|
|
return @addMember(req, res, args[0]) if req.method is 'POST' and args[1] is 'members'
|
|
return @removeMember(req, res, args[0]) if req.method is 'DELETE' and args[1] is 'members'
|
|
return @getMembersAPI(req, res, args[0]) if args[1] is 'members'
|
|
return @inviteStudents(req, res, args[0]) if relationship is 'invite_students'
|
|
return @getRecentAPI(req, res) if relationship is 'recent'
|
|
return @redeemPrepaidCodeAPI(req, res) if args[1] is 'redeem_prepaid'
|
|
return @getMyCourseLevelSessionsAPI(req, res, args[0]) if args[1] is 'my-course-level-sessions'
|
|
return @findByLevel(req, res, args[2]) if args[1] is 'find_by_level'
|
|
super arguments...
|
|
|
|
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' }
|
|
hourOfCode: true
|
|
})
|
|
courseInstance.save (err, courseInstance) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
@sendCreated(res, courseInstance)
|
|
|
|
addMember: (req, res, courseInstanceID) ->
|
|
return @sendUnauthorizedError(res) if not req.user?
|
|
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'))
|
|
addingSelf = userID is req.user.id
|
|
return @sendForbiddenError(res) unless ownsCourseInstance or addingSelf
|
|
alreadyInCourseInstance = _.any courseInstance.get('members') or [], (memberID) -> memberID.toString() is userID
|
|
return @sendSuccess(res, @formatEntity(req, courseInstance)) if alreadyInCourseInstance
|
|
Prepaid.find({ 'redeemers.userID': mongoose.Types.ObjectId(userID) }).count (err, userIsPrepaid) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
Course.findById courseInstance.get('courseID'), (err, course) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
return @sendNotFoundError(res, 'Course referenced by course instance not found') unless course
|
|
if not (course.get('free') or userIsPrepaid)
|
|
return @sendPaymentRequiredError(res, 'Cannot add this user to a course instance until they are added to a prepaid')
|
|
members = courseInstance.get('members')
|
|
members.push(userID)
|
|
courseInstance.set('members', members)
|
|
courseInstance.save (err, courseInstance) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
User.update {_id: mongoose.Types.ObjectId(userID)}, {$addToSet: {courseInstances: courseInstance.get('_id')}}, (err) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
@sendSuccess(res, @formatEntity(req, courseInstance))
|
|
|
|
removeMember: (req, res, courseInstanceID) ->
|
|
return @sendUnauthorizedError(res) if not req.user?
|
|
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'))
|
|
members = (m for m in members when m.toString() isnt userID)
|
|
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))
|
|
|
|
post: (req, res) ->
|
|
return @sendUnauthorizedError(res) if not req.user?
|
|
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
|
|
return @sendForbiddenError(res) unless classroom.get('ownerID').equals(req.user.get('_id'))
|
|
Course.findById req.body.courseID, (err, course) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
return @sendNotFoundError(res, 'Course not found') unless course
|
|
q = {
|
|
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)
|
|
|
|
makeNewInstance: (req) ->
|
|
doc = new CourseInstance({
|
|
members: []
|
|
ownerID: req.user.get('_id')
|
|
})
|
|
doc.set('aceConfig', {}) # constructor will ignore empty objects
|
|
return doc
|
|
|
|
getLevelSessionsAPI: (req, res, courseInstanceID) ->
|
|
return @sendUnauthorizedError(res) if not req.user?
|
|
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 of campaign.get('levels'))
|
|
memberIDs = _.map courseInstance.get('members') ? [], (memberID) -> memberID.toHexString?() or memberID
|
|
query = {$and: [{creator: {$in: memberIDs}}, {'level.original': {$in: levelIDs}}]}
|
|
cursor = LevelSession.find(query)
|
|
cursor = cursor.select(req.query.project) if req.query.project
|
|
cursor.exec (err, documents) =>
|
|
return @sendDatabaseError(res, err) if err?
|
|
cleandocs = (LevelSessionHandler.formatEntity(req, doc) for doc in documents)
|
|
@sendSuccess(res, cleandocs)
|
|
|
|
getMyCourseLevelSessionsAPI: (req, res, courseInstanceID) ->
|
|
return @sendUnauthorizedError(res) if not req.user?
|
|
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}}]}
|
|
cursor = LevelSession.find(query)
|
|
cursor = cursor.select(req.query.project) if req.query.project
|
|
cursor.exec (err, documents) =>
|
|
return @sendDatabaseError(res, err) if err?
|
|
cleandocs = (LevelSessionHandler.formatEntity(req, doc) for doc in documents)
|
|
@sendSuccess(res, cleandocs)
|
|
|
|
getMembersAPI: (req, res, courseInstanceID) ->
|
|
return @sendUnauthorizedError(res) if not req.user?
|
|
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)
|
|
|
|
getRecentAPI: (req, res) ->
|
|
return @sendUnauthorizedError(res) unless req.user?.isAdmin()
|
|
query = {$and: [{name: {$ne: 'Single Player'}}, {hourOfCode: {$ne: true}}]}
|
|
query["$and"].push(_id: {$gte: objectIdFromTimestamp(req.body.startDay + "T00:00:00.000Z")}) if req.body.startDay?
|
|
query["$and"].push(_id: {$lt: objectIdFromTimestamp(req.body.endDay + "T00:00:00.000Z")}) if req.body.endDay?
|
|
CourseInstance.find query, {courseID: 1, members: 1, ownerID: 1}, (err, courseInstances) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
userIDs = []
|
|
for courseInstance in courseInstances
|
|
if members = courseInstance.get('members')
|
|
userIDs.push(userID) for userID in members
|
|
|
|
User.find {_id: {$in: userIDs}}, {coursePrepaidID: 1}, (err, users) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
prepaidIDs = []
|
|
for user in users
|
|
if prepaidID = user.get('coursePrepaidID')
|
|
prepaidIDs.push(prepaidID)
|
|
|
|
Prepaid.find {_id: {$in: prepaidIDs}}, {properties: 1}, (err, prepaids) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
data =
|
|
courseInstances: (@formatEntity(req, courseInstance) for courseInstance in courseInstances)
|
|
students: (@formatEntity(req, user) for user in users)
|
|
prepaids: (@formatEntity(req, prepaid) for prepaid in prepaids)
|
|
@sendSuccess(res, data)
|
|
|
|
inviteStudents: (req, res, courseInstanceID) ->
|
|
return @sendUnauthorizedError(res) if not req.user?
|
|
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)
|
|
|
|
Course.findById courseInstance.get('courseID'), (err, course) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
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')
|
|
join_link: "https://codecombat.com/courses/students?_ppc=" + prepaid.get('code')
|
|
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')
|
|
|
|
if _.find((prepaid.get('redeemers') ? []), (a) -> a.userID.equals(req.user.id))
|
|
return @sendSuccess(res, courseInstances)
|
|
|
|
# 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, result) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
if result?.nModified 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, courseInstances)
|
|
|
|
findByLevel: (req, res, levelOriginal) ->
|
|
return @sendUnauthorizedError(res) if not req.user?
|
|
# 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
|
|
|
|
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))
|
|
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) =>
|
|
return @sendDatabaseError(res, err) if err
|
|
return @sendNotFoundError(res) unless classroom
|
|
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))
|
|
else
|
|
super(arguments...)
|
|
|
|
module.exports = new CourseInstanceHandler()
|