codecombat/server/levels/level_handler.coffee

306 lines
11 KiB
CoffeeScript
Raw Normal View History

2014-06-30 22:16:26 -04:00
Level = require './Level'
Session = require './sessions/LevelSession'
User = require '../users/User'
2014-06-30 22:16:26 -04:00
SessionHandler = require './sessions/level_session_handler'
Feedback = require './feedbacks/LevelFeedback'
Handler = require '../commons/Handler'
mongoose = require 'mongoose'
async = require 'async'
2014-06-30 22:16:26 -04:00
2014-01-03 13:32:13 -05:00
LevelHandler = class LevelHandler extends Handler
modelClass: Level
jsonSchema: require '../../app/schemas/models/level'
2014-01-03 13:32:13 -05:00
editableProperties: [
'description'
'documentation'
'background'
'nextLevel'
'scripts'
'thangs'
'systems'
'victory'
'name'
'i18n'
'icon'
'goals'
'type'
'showsGuide'
'banner'
'employerDescription'
2014-01-03 13:32:13 -05:00
]
postEditableProperties: ['name']
2014-01-03 13:32:13 -05:00
getByRelationship: (req, res, args...) ->
return @getSession(req, res, args[0]) if args[1] is 'session'
2014-02-07 18:51:05 -05:00
return @getLeaderboard(req, res, args[0]) if args[1] is 'leaderboard'
return @getMyLeaderboardRank(req, res, args[0]) if args[1] is 'leaderboard_rank'
return @getMySessions(req, res, args[0]) if args[1] is 'my_sessions'
2014-01-03 13:32:13 -05:00
return @getFeedback(req, res, args[0]) if args[1] is 'feedback'
2014-06-30 22:16:26 -04:00
return @getRandomSessionPair(req, res, args[0]) if args[1] is 'random_session_pair'
return @getLeaderboardFacebookFriends(req, res, args[0]) if args[1] is 'leaderboard_facebook_friends'
return @getLeaderboardGPlusFriends(req, res, args[0]) if args[1] is 'leaderboard_gplus_friends'
2014-04-04 16:38:36 -04:00
return @getHistogramData(req, res, args[0]) if args[1] is 'histogram_data'
return @checkExistence(req, res, args[0]) if args[1] is 'exists'
2014-08-28 01:23:24 -04:00
return @getPlayCountsBySlugs(req, res) if args[1] is 'play_counts'
super(arguments...)
2014-01-03 13:32:13 -05:00
fetchLevelByIDAndHandleErrors: (id, req, res, callback) ->
2014-01-03 13:32:13 -05:00
@getDocumentForIdOrSlug id, (err, level) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless level?
return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, level, 'get')
callback err, level
2014-01-03 13:32:13 -05:00
getSession: (req, res, id) ->
return @sendNotFoundError(res) unless req.user
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
sessionQuery =
level:
original: level.original.toString()
majorVersion: level.version.major
2014-01-03 13:32:13 -05:00
creator: req.user.id
if req.query.team?
sessionQuery.team = req.query.team
# TODO: generalize this for levels based on their teams
else if level.get('type') is 'ladder'
sessionQuery.team = 'humans'
2014-01-03 13:32:13 -05:00
Session.findOne(sessionQuery).exec (err, doc) =>
return @sendDatabaseError(res, err) if err
return @sendSuccess(res, doc) if doc?
@createAndSaveNewSession sessionQuery, req, res
2014-01-03 13:32:13 -05:00
createAndSaveNewSession: (sessionQuery, req, res) =>
initVals = sessionQuery
initVals.state =
2014-06-30 22:16:26 -04:00
complete: false
scripts:
2014-06-30 22:16:26 -04:00
currentScript: null # will not save empty objects
initVals.permissions = [
{
2014-06-30 22:16:26 -04:00
target: req.user.id
access: 'owner'
}
{
2014-06-30 22:16:26 -04:00
target: 'public'
access: 'write'
}
]
initVals.codeLanguage = req.user.get('aceConfig')?.language ? 'javascript'
session = new Session(initVals)
session.save (err) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res, @formatEntity(req, session))
# TODO: tying things like @formatEntity and saveChangesToDocument don't make sense
# associated with the handler, because the handler might return a different type
# of model, like in this case. Refactor to move that logic to the model instead.
getMySessions: (req, res, slugOrID) ->
findParameters = {}
if Handler.isID slugOrID
2014-06-30 22:16:26 -04:00
findParameters['_id'] = slugOrID
else
2014-06-30 22:16:26 -04:00
findParameters['slug'] = slugOrID
selectString = 'original version.major permissions'
query = Level.findOne(findParameters)
.select(selectString)
.lean()
query.exec (err, level) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless level?
sessionQuery =
level:
original: level.original.toString()
majorVersion: level.version.major
creator: req.user._id+''
query = Session.find(sessionQuery).select('-screenshot')
query.exec (err, results) =>
if err then @sendDatabaseError(res, err) else @sendSuccess res, results
2014-06-30 22:16:26 -04:00
getHistogramData: (req, res, slug) ->
2014-04-04 16:38:36 -04:00
query = Session.aggregate [
2014-06-30 22:16:26 -04:00
{$match: {'levelID': slug, 'submitted': true, 'team': req.query.team}}
2014-04-04 16:38:36 -04:00
{$project: {totalScore: 1, _id: 0}}
]
2014-04-04 16:38:36 -04:00
query.exec (err, data) =>
if err? then return @sendDatabaseError res, err
2014-06-30 22:16:26 -04:00
valueArray = _.pluck data, 'totalScore'
2014-04-04 16:38:36 -04:00
@sendSuccess res, valueArray
checkExistence: (req, res, slugOrID) ->
findParameters = {}
if Handler.isID slugOrID
2014-06-30 22:16:26 -04:00
findParameters['_id'] = slugOrID
else
2014-06-30 22:16:26 -04:00
findParameters['slug'] = slugOrID
selectString = 'original version.major permissions'
query = Level.findOne(findParameters)
.select(selectString)
.lean()
query.exec (err, level) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless level?
2014-06-30 22:16:26 -04:00
res.send({'exists': true})
res.end()
2014-02-13 18:14:08 -05:00
getLeaderboard: (req, res, id) ->
sessionsQueryParameters = @makeLeaderboardQueryParameters(req, id)
2014-02-13 18:14:08 -05:00
sortParameters =
2014-06-30 22:16:26 -04:00
'totalScore': req.query.order
selectProperties = ['totalScore', 'creatorName', 'creator', 'submittedCodeLanguage']
query = Session
.find(sessionsQueryParameters)
.limit(req.query.limit)
.sort(sortParameters)
.select(selectProperties.join ' ')
query.exec (err, resultSessions) =>
2014-01-03 13:32:13 -05:00
return @sendDatabaseError(res, err) if err
resultSessions ?= []
@sendSuccess res, resultSessions
getMyLeaderboardRank: (req, res, id) ->
req.query.order = 1
sessionsQueryParameters = @makeLeaderboardQueryParameters(req, id)
Session.count sessionsQueryParameters, (err, count) =>
return @sendDatabaseError(res, err) if err
res.send JSON.stringify(count + 1)
makeLeaderboardQueryParameters: (req, id) ->
@validateLeaderboardRequestParameters req
[original, version] = id.split '.'
version = parseInt(version) ? 0
scoreQuery = {}
2014-06-30 22:16:26 -04:00
scoreQuery[if req.query.order is 1 then '$gt' else '$lt'] = req.query.scoreOffset
query =
level:
original: original
majorVersion: version
team: req.query.team
totalScore: scoreQuery
submitted: true
query
validateLeaderboardRequestParameters: (req) ->
req.query.order = parseInt(req.query.order) ? -1
req.query.scoreOffset = parseFloat(req.query.scoreOffset) ? 100000
req.query.team ?= 'humans'
req.query.limit = parseInt(req.query.limit) ? 20
getLeaderboardFacebookFriends: (req, res, id) -> @getLeaderboardFriends(req, res, id, 'facebookID')
getLeaderboardGPlusFriends: (req, res, id) -> @getLeaderboardFriends(req, res, id, 'gplusID')
getLeaderboardFriends: (req, res, id, serviceProperty) ->
friendIDs = req.body.friendIDs or []
return res.send([]) unless friendIDs.length
q = {}
2014-06-30 22:16:26 -04:00
q[serviceProperty] = {$in: friendIDs}
query = User.find(q).select("#{serviceProperty} name").lean()
query.exec (err, userResults) ->
return res.send([]) unless userResults.length
[id, version] = id.split('.')
userIDs = (r._id+'' for r in userResults)
2014-06-30 22:16:26 -04:00
q = {'level.original': id, 'level.majorVersion': parseInt(version), creator: {$in: userIDs}, totalScore: {$exists: true}}
query = Session.find(q)
.select('creator creatorName totalScore team')
.lean()
query.exec (err, sessionResults) ->
return res.send([]) unless sessionResults.length
userMap = {}
userMap[u._id] = u[serviceProperty] for u in userResults
session[serviceProperty] = userMap[session.creator] for session in sessionResults
res.send(sessionResults)
2014-03-14 15:54:52 -04:00
getRandomSessionPair: (req, res, slugOrID) ->
findParameters = {}
2014-03-14 15:54:52 -04:00
if Handler.isID slugOrID
2014-06-30 22:16:26 -04:00
findParameters['_id'] = slugOrID
2014-03-14 15:54:52 -04:00
else
2014-06-30 22:16:26 -04:00
findParameters['slug'] = slugOrID
2014-03-14 15:54:52 -04:00
selectString = 'original version'
query = Level.findOne(findParameters)
.select(selectString)
.lean()
2014-03-14 15:54:52 -04:00
query.exec (err, level) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless level?
2014-03-14 15:54:52 -04:00
sessionsQueryParameters =
level:
original: level.original.toString()
majorVersion: level.version.major
2014-06-30 22:16:26 -04:00
submitted: true
2014-06-30 22:16:26 -04:00
query = Session.find(sessionsQueryParameters).distinct('team')
query.exec (err, teams) =>
return @sendDatabaseError res, err if err? or not teams
findTop20Players = (sessionQueryParams, team, cb) ->
2014-06-30 22:16:26 -04:00
sessionQueryParams['team'] = team
Session.aggregate [
{$match: sessionQueryParams}
2014-06-30 22:16:26 -04:00
{$project: {'totalScore': 1}}
{$sort: {'totalScore': -1}}
{$limit: 20}
], cb
async.map teams, findTop20Players.bind(@, sessionsQueryParameters), (err, map) =>
if err? then return @sendDatabaseError(res, err)
sessions = []
for mapItem in map
sessions.push _.sample(mapItem)
2014-06-30 22:16:26 -04:00
if map.length != 2 then return @sendDatabaseError res, 'There aren\'t sessions of 2 teams, so cannot choose random opponents!'
@sendSuccess res, sessions
getFeedback: (req, res, id) ->
return @sendNotFoundError(res) unless req.user
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
feedbackQuery =
2014-01-03 13:32:13 -05:00
creator: mongoose.Types.ObjectId(req.user.id.toString())
'level.original': level.original.toString()
'level.majorVersion': level.version.major
Feedback.findOne(feedbackQuery).exec (err, doc) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless doc?
@sendSuccess(res, doc)
2014-01-03 13:32:13 -05:00
2014-08-28 01:23:24 -04:00
getPlayCountsBySlugs: (req, res) ->
# This is hella slow (4s on my box), so relying on some dumb caching for it.
# If we can't make this faster with indexing or something, we might want to maintain the counts another way.
levelIDs = req.query.ids or req.body.ids
@playCountCache ?= {}
@playCountCachedSince ?= new Date()
if (new Date()) - @playCountCachedSince > 86400 * 1000 # Dumb cache expiration
@playCountCache = {}
@playCountCacheSince = new Date()
cacheKey = levelIDs.join ','
if playCounts = @playCountCache[cacheKey]
return @sendSuccess res, playCounts
query = Session.aggregate [
{$match: {levelID: {$in: levelIDs}}},
{$group: {_id: "$levelID", playtime: {$sum: "$playtime"}, sessions: {$sum: 1}}},
{$sort: {sessions: -1}}
]
query.exec (err, data) =>
if err? then return @sendDatabaseError res, err
@playCountCache[cacheKey] = data
@sendSuccess res, data
2014-01-03 13:32:13 -05:00
module.exports = new LevelHandler()