2016-03-30 13:57:19 -07:00
_ = require ' lodash '
utils = require ' ../lib/utils '
errors = require ' ../commons/errors '
2016-05-20 14:52:04 -07:00
schemas = require ' ../../app/schemas/schemas '
2016-03-30 13:57:19 -07:00
wrap = require ' co-express '
Promise = require ' bluebird '
database = require ' ../commons/database '
mongoose = require ' mongoose '
2016-04-06 10:56:06 -07:00
Classroom = require ' ../models/Classroom '
2016-04-13 09:54:24 -07:00
Course = require ' ../models/Course '
Campaign = require ' ../models/Campaign '
Level = require ' ../models/Level '
2016-03-30 13:57:19 -07:00
parse = require ' ../commons/parse '
2016-04-06 10:56:06 -07:00
LevelSession = require ' ../models/LevelSession '
User = require ' ../models/User '
2016-05-13 10:55:22 -07:00
CourseInstance = require ' ../models/CourseInstance '
2016-03-30 13:57:19 -07:00
module.exports =
2016-05-11 14:39:26 -07:00
fetchByCode: wrap (req, res, next) ->
code = req . query . code
return next ( ) unless code
2016-05-20 14:52:04 -07:00
classroom = yield Classroom . findOne ( { code: code . toLowerCase ( ) } ) . select ( ' name ownerID aceConfig ' )
2016-05-11 14:39:26 -07:00
if not classroom
2016-05-25 09:04:35 -07:00
throw new errors . NotFound ( ' Classroom not found. ' )
2016-05-11 14:39:26 -07:00
classroom = classroom . toObject ( )
# Tack on the teacher's name for display to the user
owner = ( yield User . findOne ( { _id: mongoose . Types . ObjectId ( classroom . ownerID ) } ) . select ( ' name ' ) ) . toObject ( )
res . status ( 200 ) . send ( { data: classroom , owner } )
2016-03-30 13:57:19 -07:00
getByOwner: wrap (req, res, next) ->
options = req . query
ownerID = options . ownerID
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
sanitizedOptions = { }
unless _ . isUndefined ( options . archived )
# Handles when .archived is true, vs false-or-null
sanitizedOptions.archived = { $ne: not ( options . archived is ' true ' ) }
dbq = Classroom . find _ . merge sanitizedOptions , { ownerID: mongoose . Types . ObjectId ( ownerID ) }
dbq . select ( parse . getProjectFromReq ( req ) )
classrooms = yield dbq
classrooms = ( classroom . toObject ( { req: req } ) for classroom in classrooms )
res . status ( 200 ) . send ( classrooms )
2016-04-13 09:54:24 -07:00
fetchAllLevels: wrap (req, res, next) ->
classroom = yield database . getDocFromHandle ( req , Classroom )
if not classroom
throw new errors . NotFound ( ' Classroom not found. ' )
levelOriginals = [ ]
for course in classroom . get ( ' courses ' ) or [ ]
for level in course . levels
levelOriginals . push ( level . original )
levels = yield Level . find ( { original: { $in: levelOriginals } , slug: { $exists: true } } ) . select ( parse . getProjectFromReq ( req ) )
levels = ( level . toObject ( { req: req } ) for level in levels )
# maintain course order
levelMap = { }
for level in levels
levelMap [ level . original ] = level
levels = ( levelMap [ levelOriginal . toString ( ) ] for levelOriginal in levelOriginals )
2016-05-13 10:55:22 -07:00
res . status ( 200 ) . send ( _ . filter ( levels ) ) # for dev server where not all levels will be found
2016-04-13 09:54:24 -07:00
fetchLevelsForCourse: wrap (req, res) ->
classroom = yield database . getDocFromHandle ( req , Classroom )
if not classroom
throw new errors . NotFound ( ' Classroom not found. ' )
levelOriginals = [ ]
for course in classroom . get ( ' courses ' ) or [ ]
if course . _id . toString ( ) isnt req . params . courseID
continue
for level in course . levels
levelOriginals . push ( level . original )
levels = yield Level . find ( { original: { $in: levelOriginals } , slug: { $exists: true } } ) . select ( parse . getProjectFromReq ( req ) )
levels = ( level . toObject ( { req: req } ) for level in levels )
# maintain course order
levelMap = { }
for level in levels
levelMap [ level . original ] = level
levels = ( levelMap [ levelOriginal . toString ( ) ] for levelOriginal in levelOriginals )
res . status ( 200 ) . send ( levels )
2016-03-30 13:57:19 -07:00
fetchMemberSessions: wrap (req, res, next) ->
throw new errors . Unauthorized ( ) unless req . user
memberLimit = parse . getLimitFromReq ( req , { default: 10 , max: 100 , param: ' memberLimit ' } )
memberSkip = parse . getSkipFromReq ( req , { param: ' memberSkip ' } )
classroom = yield database . getDocFromHandle ( req , Classroom )
throw new errors . NotFound ( ' Classroom not found. ' ) if not classroom
throw new errors . Forbidden ( ' You do not own this classroom. ' ) unless req . user . isAdmin ( ) or classroom . get ( ' ownerID ' ) . equals ( req . user . _id )
members = classroom . get ( ' members ' ) or [ ]
2016-03-30 16:20:37 -07:00
members = members . slice ( memberSkip , memberSkip + memberLimit )
2016-03-30 13:57:19 -07:00
dbqs = [ ]
2016-04-19 13:44:48 -07:00
select = ' state.complete level creator playtime changed dateFirstCompleted '
2016-03-30 13:57:19 -07:00
for member in members
2016-03-30 16:20:37 -07:00
dbqs . push ( LevelSession . find ( { creator: member . toHexString ( ) } ) . select ( select ) . exec ( ) )
2016-03-30 13:57:19 -07:00
results = yield dbqs
sessions = _ . flatten ( results )
res . status ( 200 ) . send ( sessions )
fetchMembers: wrap (req, res, next) ->
throw new errors . Unauthorized ( ) unless req . user
memberLimit = parse . getLimitFromReq ( req , { default: 10 , max: 100 , param: ' memberLimit ' } )
memberSkip = parse . getSkipFromReq ( req , { param: ' memberSkip ' } )
classroom = yield database . getDocFromHandle ( req , Classroom )
throw new errors . NotFound ( ' Classroom not found. ' ) if not classroom
2016-03-30 15:55:20 -07:00
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
throw new errors . Forbidden ( ' You do not own this classroom. ' )
2016-03-30 13:57:19 -07:00
memberIDs = classroom . get ( ' members ' ) or [ ]
2016-03-30 16:20:37 -07:00
memberIDs = memberIDs . slice ( memberSkip , memberSkip + memberLimit )
2016-03-30 13:57:19 -07:00
members = yield User . find ( { _id: { $in: memberIDs } } ) . select ( parse . getProjectFromReq ( req ) )
2016-04-07 14:55:42 -07:00
# members = yield User.find({ _id: { $in: memberIDs }, deleted: { $ne: true }}).select(parse.getProjectFromReq(req))
2016-03-30 13:57:19 -07:00
memberObjects = ( member . toObject ( { req: req , includedPrivates: [ " name " , " email " ] } ) for member in members )
res . status ( 200 ) . send ( memberObjects )
2016-04-13 11:39:17 -07:00
post: wrap (req, res) ->
throw new errors . Unauthorized ( ) unless req . user and not req . user . isAnonymous ( )
throw new errors . Forbidden ( ) unless req . user ? . isTeacher ( )
classroom = database . initDoc ( req , Classroom )
classroom . set ' ownerID ' , req . user . _id
classroom . set ' members ' , [ ]
database . assignBody ( req , classroom )
2016-04-13 09:54:24 -07:00
# copy over data from how courses are right now
courses = yield Course . find ( )
campaigns = yield Campaign . find ( { _id: { $in: ( course . get ( ' campaignID ' ) for course in courses ) } } )
campaignMap = { }
campaignMap [ campaign . id ] = campaign for campaign in campaigns
coursesData = [ ]
for course in courses
courseData = { _id: course . _id , levels: [ ] }
campaign = campaignMap [ course . get ( ' campaignID ' ) . toString ( ) ]
levels = _ . values ( campaign . get ( ' levels ' ) )
levels = _ . sortBy ( levels , ' campaignIndex ' )
for level in levels
levelData = { original: mongoose . Types . ObjectId ( level . original ) }
_ . extend ( levelData , _ . pick ( level , ' type ' , ' slug ' , ' name ' ) )
courseData . levels . push ( levelData )
coursesData . push ( courseData )
classroom . set ( ' courses ' , coursesData )
# finish
2016-04-13 11:39:17 -07:00
database . validateDoc ( classroom )
classroom = yield classroom . save ( )
2016-04-27 13:16:30 -07:00
res . status ( 201 ) . send ( classroom . toObject ( { req: req } ) )
2016-05-13 10:55:22 -07:00
join: wrap (req, res) ->
unless req . body ? . code
throw new errors . UnprocessableEntity ( ' Need a code ' )
if req . user . isTeacher ( )
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
2016-05-11 14:39:26 -07:00
throw new errors . NotFound ( ' Classroom not found. ' )
2016-05-13 10:55:22 -07:00
members = _ . clone ( classroom . get ( ' members ' ) )
if _ . any ( members , (memberID) -> memberID . equals ( req . user . _id ) )
return res . send ( classroom . toObject ( { req: req } ) )
update = { $push: { members : req . user . _id } }
yield classroom . update ( update )
members . push req . user . _id
classroom . set ( ' members ' , members )
# make user role student
if not req . user . get ( ' role ' )
req . user . set ( ' role ' , ' student ' )
yield req . user . save ( )
# join any course instances for free courses in the classroom
courseIDs = ( course . _id for course in classroom . get ( ' courses ' ) )
courses = yield Course . find ( { _id: { $in: courseIDs } , free: true } )
freeCourseIDs = ( course . _id for course in courses )
freeCourseInstances = yield CourseInstance . find ( { classroomID: classroom . _id , courseID: { $in: freeCourseIDs } } ) . select ( ' _id ' )
freeCourseInstanceIDs = ( courseInstance . _id for courseInstance in freeCourseInstances )
yield CourseInstance . update ( { _id: { $in: freeCourseInstanceIDs } } , { $addToSet: { members: req . user . _id } } )
yield User . update ( { _id: req . user . _id } , { $addToSet: { courseInstances: { $each: freeCourseInstanceIDs } } } )
res . send ( classroom . toObject ( { req: req } ) )
2016-05-20 14:52:04 -07:00
setStudentPassword: wrap (req, res, next) ->
newPassword = req . body . password
{ classroomID , memberID } = req . params
teacherID = req . user . id
return next ( ) if teacherID is memberID or not newPassword
ownedClassrooms = yield Classroom . find ( { ownerID: mongoose . Types . ObjectId ( teacherID ) } )
ownedStudentIDs = _ . flatten ownedClassrooms . map (c) ->
c . get ( ' members ' ) . map (id) ->
id . toString ( )
return next ( ) unless memberID in ownedStudentIDs
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. " )
{ valid , error } = tv4 . validateResult ( newPassword , schemas . passwordString )
unless valid
throw new errors . UnprocessableEntity ( error . message )
yield student . update ( { $set: { passwordHash: User . hashPassword ( newPassword ) } } )
res . status ( 200 ) . send ( { } )