codecombat/server/middleware/prepaids.coffee
2016-07-18 09:41:42 -07:00

152 lines
6 KiB
CoffeeScript

wrap = require 'co-express'
errors = require '../commons/errors'
database = require '../commons/database'
mongoose = require 'mongoose'
LevelSession = require '../models/LevelSession'
Prepaid = require '../models/Prepaid'
TrialRequest = require '../models/TrialRequest'
User = require '../models/User'
cutoffDate = new Date(2015,11,11)
cutoffID = mongoose.Types.ObjectId(Math.floor(cutoffDate/1000).toString(16)+'0000000000000000')
module.exports =
logError: (user, msg) ->
console.warn "Prepaid Error: [#{user.get('slug')} (#{user._id})] '#{msg}'"
post: wrap (req, res) ->
validTypes = ['course']
unless req.body.type in validTypes
throw new errors.UnprocessableEntity("type must be on of: #{validTypes}.")
# TODO: deprecate or refactor other prepaid types
if req.body.creator
user = yield User.search(req.body.creator)
if not user
throw new errors.NotFound('User not found')
req.body.creator = user.id
prepaid = database.initDoc(req, Prepaid)
database.assignBody(req, prepaid)
prepaid.set('code', yield Prepaid.generateNewCodeAsync())
prepaid.set('redeemers', [])
database.validateDoc(prepaid)
yield prepaid.save()
res.status(201).send(prepaid.toObject())
redeem: wrap (req, res) ->
if not req.user?.isTeacher()
throw new errors.Forbidden('Must be a teacher to use licenses')
prepaid = yield database.getDocFromHandle(req, Prepaid)
if not prepaid
throw new errors.NotFound('Prepaid not found.')
if prepaid._id.getTimestamp().getTime() < cutoffDate.getTime()
throw new errors.Forbidden('Cannot redeem from prepaids older than November 11, 2015')
unless prepaid.get('creator').equals(req.user._id)
throw new errors.Forbidden('You may not redeem licenses from this prepaid')
if prepaid.get('redeemers')? and _.size(prepaid.get('redeemers')) >= prepaid.get('maxRedeemers')
throw new errors.Forbidden('This prepaid is exhausted')
unless prepaid.get('type') is 'course'
throw new errors.Forbidden('This prepaid is not of type "course"')
if prepaid.get('endDate') and new Date(prepaid.get('endDate')) < new Date()
throw new errors.Forbidden('This prepaid is expired')
user = yield User.findById(req.body?.userID)
if not user
throw new errors.NotFound('User not found.')
if user.isEnrolled()
return res.status(200).send(prepaid.toObject({req: req}))
if user.isTeacher()
throw new errors.Forbidden('Teachers may not be enrolled')
query =
_id: prepaid._id
'redeemers.userID': { $ne: user._id }
$where: "this.maxRedeemers > 0 && (!this.redeemers || this.redeemers.length < #{prepaid.get('maxRedeemers')})"
update = { $push: { redeemers : { date: new Date(), userID: user._id } }}
result = yield Prepaid.update(query, update)
if result.nModified is 0
@logError(req.user, "POST prepaid redeemer lost race on maxRedeemers")
throw new errors.Forbidden('This prepaid is exhausted')
update = {
$set: {
coursePrepaid: {
_id: prepaid._id
startDate: prepaid.get('startDate')
endDate: prepaid.get('endDate')
}
}
}
if not user.get('role')
update.$set.role = 'student'
yield user.update(update)
# return prepaid with new redeemer added locally
redeemers = _.clone(prepaid.get('redeemers') or [])
redeemers.push({ date: new Date(), userID: user._id })
prepaid.set('redeemers', redeemers)
res.status(201).send(prepaid.toObject({req: req}))
fetchByCreator: wrap (req, res, next) ->
creator = req.query.creator
return next() if not creator
unless req.user.isAdmin() or creator is req.user.id
throw new errors.Forbidden('Must be logged in as given creator')
unless database.isID(creator)
throw new errors.UnprocessableEntity('Invalid creator')
q = {
_id: { $gt: cutoffID }
creator: mongoose.Types.ObjectId(creator)
type: 'course'
}
prepaids = yield Prepaid.find(q)
res.send((prepaid.toObject({req: req}) for prepaid in prepaids))
fetchActiveSchools: wrap (req, res) ->
unless req.user.isAdmin() or creator is req.user.id
throw new errors.Forbidden('Must be logged in as given creator')
prepaids = yield Prepaid.find({type: 'course'}, {creator: 1, properties: 1, startDate: 1, endDate: 1, maxRedeemers: 1, redeemers: 1}).lean()
userPrepaidsMap = {}
today = new Date()
userIDs = []
redeemerIDs = []
redeemerPrepaidMap = {}
for prepaid in prepaids
continue if new Date(prepaid.endDate ? prepaid.properties?.endDate ? '2000') < today
continue if new Date(prepaid.endDate) < new Date(prepaid.startDate)
userPrepaidsMap[prepaid.creator.valueOf()] ?= []
userPrepaidsMap[prepaid.creator.valueOf()].push(prepaid)
userIDs.push prepaid.creator
for redeemer in prepaid.redeemers ? []
redeemerIDs.push redeemer.userID + ""
redeemerPrepaidMap[redeemer.userID + ""] = prepaid._id.valueOf()
# Find recently created level sessions for redeemers
lastMonth = new Date()
lastMonth.setUTCDate(lastMonth.getUTCDate() - 30)
levelSessions = yield LevelSession.find({$and: [{created: {$gte: lastMonth}}, {creator: {$in: redeemerIDs}}]}, {creator: 1}).lean()
prepaidActivityMap = {}
for levelSession in levelSessions
prepaidActivityMap[redeemerPrepaidMap[levelSession.creator.valueOf()]] ?= 0
prepaidActivityMap[redeemerPrepaidMap[levelSession.creator.valueOf()]]++
trialRequests = yield TrialRequest.find({$and: [{type: 'course'}, {applicant: {$in: userIDs}}]}, {applicant: 1, properties: 1}).lean()
schoolPrepaidsMap = {}
for trialRequest in trialRequests
school = trialRequest.properties?.organization ? trialRequest.properties?.school
continue unless school
if userPrepaidsMap[trialRequest.applicant.valueOf()]?.length > 0
schoolPrepaidsMap[school] ?= []
for prepaid in userPrepaidsMap[trialRequest.applicant.valueOf()]
schoolPrepaidsMap[school].push prepaid
res.send({prepaidActivityMap, schoolPrepaidsMap})