mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 00:31:21 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
011f0191dc
16 changed files with 292 additions and 57 deletions
|
@ -62,7 +62,24 @@ Application = {
|
|||
$(document).bind 'keydown', preventBackspace
|
||||
preload(COMMON_FILES)
|
||||
CocoModel.pollAchievements()
|
||||
# @checkForNewAchievement() # TODO: Enable once thoroughly tested
|
||||
unless me.get('anonymous')
|
||||
# TODO: Remove logging later, once this system has proved stable
|
||||
me.on 'change:earned', (user, newEarned) ->
|
||||
oldEarned = user.previous('earned') ? {}
|
||||
if oldEarned.gems isnt newEarned.gems
|
||||
console.log 'Gems changed', oldEarned.gems, '->', newEarned.gems
|
||||
newLevels = _.difference(newEarned.levels, oldEarned.levels)
|
||||
if newLevels.length
|
||||
console.log 'Levels added', newLevels
|
||||
newItems = _.difference(newEarned.items, oldEarned.items)
|
||||
if newItems.length
|
||||
console.log 'Items added', newItems
|
||||
newHeroes = _.difference(newEarned.heroes, oldEarned.heroes)
|
||||
if newHeroes.length
|
||||
console.log 'Heroes added', newHeroes
|
||||
me.on 'change:points', (user, newPoints) ->
|
||||
console.log 'Points changed', user.previous('points'), '->', newPoints
|
||||
@checkForNewAchievement()
|
||||
$.i18n.init {
|
||||
lng: me.get('preferredLanguage', true)
|
||||
fallbackLng: 'en'
|
||||
|
@ -84,12 +101,14 @@ Application = {
|
|||
@idleTracker.start()
|
||||
|
||||
checkForNewAchievement: ->
|
||||
id = me.get('lastAchievementChecked') or me.id
|
||||
lastAchievementChecked = new Date(parseInt(id.substring(0, 8), 16) * 1000)
|
||||
daysSince = moment.duration(new Date() - lastAchievementChecked).asDays()
|
||||
if me.get('lastAchievementChecked')
|
||||
startFrom = new Date(me.get('lastAchievementChecked'))
|
||||
else
|
||||
startFrom = me.created()
|
||||
|
||||
daysSince = moment.duration(new Date() - startFrom).asDays()
|
||||
if daysSince > 1
|
||||
me.checkForNewAchievement()
|
||||
setTimeout(_.bind(@checkForNewAchievement, @), moment.duration(1, 'minute').asMilliseconds())
|
||||
me.checkForNewAchievement().then => @checkForNewAchievement()
|
||||
}
|
||||
|
||||
module.exports = Application
|
||||
|
|
|
@ -6,10 +6,10 @@ mapred = (left, right, func) ->
|
|||
result or (_.reduce (_.map right, (singleRight) -> func(singleLeft, singleRight)),
|
||||
((intermediate, value) -> intermediate or value), false)), false)
|
||||
|
||||
doQuerySelector = (value, operatorObj) ->
|
||||
value = [value] unless _.isArray value # left hand can be an array too
|
||||
for operator, body of operatorObj
|
||||
body = [body] unless _.isArray body # right hand can be an array too
|
||||
doQuerySelector = (originalValue, operatorObj) ->
|
||||
value = if _.isArray originalValue then originalValue else [originalValue] # left hand can be an array too
|
||||
for operator, originalBody of operatorObj
|
||||
body = if _.isArray originalBody then originalBody else [originalBody] # right hand can be an array too
|
||||
switch operator
|
||||
when '$gt' then return false unless mapred value, body, (l, r) -> l > r
|
||||
when '$gte' then return false unless mapred value, body, (l, r) -> l >= r
|
||||
|
@ -19,7 +19,9 @@ doQuerySelector = (value, operatorObj) ->
|
|||
when '$in' then return false unless _.reduce value, ((result, val) -> result or val in body), false
|
||||
when '$nin' then return false if _.reduce value, ((result, val) -> result or val in body), false
|
||||
when '$exists' then return false if value[0]? isnt body[0]
|
||||
else return false
|
||||
else
|
||||
trimmedOperator = _.pick(operatorObj, operator)
|
||||
return false unless _.isObject(originalValue) and matchesQuery(originalValue, trimmedOperator)
|
||||
true
|
||||
|
||||
matchesQuery = (target, queryObj) ->
|
||||
|
|
|
@ -13,6 +13,7 @@ module.exports =
|
|||
instance.numCompleted = 0
|
||||
instance.started = false
|
||||
levels = classroom.getLevels({courseID: course.id})
|
||||
levels.remove(levels.filter((level) => level.get('practice')))
|
||||
for userID in instance.get('members')
|
||||
instance.started ||= _.any levels.models, (level) ->
|
||||
session = _.find classroom.sessions.models, (session) ->
|
||||
|
@ -180,7 +181,7 @@ module.exports =
|
|||
if _.find(sessions, (s) -> s.completed()) # have finished this level
|
||||
courseProgress.completed &&= true #no-op
|
||||
courseProgress[userID].completed &&= true #no-op
|
||||
courseProgress[userID].levelsCompleted += 1
|
||||
courseProgress[userID].levelsCompleted += 1 unless level.get('practice')
|
||||
courseProgress[levelID].completed &&= true #no-op
|
||||
# courseProgress[levelID].numCompleted += 1
|
||||
courseProgress[levelID][userID].completed = true
|
||||
|
|
|
@ -82,6 +82,7 @@ _.extend AchievementSchema.properties,
|
|||
i18n: {type: 'object', format: 'i18n', props: ['name', 'description'], description: 'Help translate this achievement'}
|
||||
rewards: c.RewardSchema 'awarded by this achievement'
|
||||
hidden: {type: 'boolean', description: 'Hide achievement from user if true'}
|
||||
updated: c.stringDate({ description: 'When the achievement was changed in such a way that earned achievements should get updated.' })
|
||||
|
||||
|
||||
_.extend AchievementSchema, # Let's have these on the bottom
|
||||
|
|
|
@ -344,7 +344,7 @@ _.extend UserSchema.properties,
|
|||
schoolName: {type: 'string'}
|
||||
role: {type: 'string', enum: ["God", "advisor", "parent", "principal", "student", "superintendent", "teacher", "technology coordinator"]}
|
||||
birthday: c.stringDate({title: "Birthday"})
|
||||
lastAchievementChecked: c.objectId({ name: 'Last Achievement Checked' })
|
||||
lastAchievementChecked: c.stringDate({ name: 'Last Achievement Checked' })
|
||||
|
||||
c.extendBasicProperties UserSchema, 'user'
|
||||
|
||||
|
|
|
@ -226,7 +226,7 @@ mixin studentRow(student)
|
|||
- var instance = view.courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
||||
if instance && instance.hasMember(student)
|
||||
- var progress = state.get('progressData').get({ classroom: view.classroom, course: course, user: student })
|
||||
- var levelsTotal = trimCourse.levels.length
|
||||
- var levelsTotal = _.reject(trimCourse.levels, 'practice').length
|
||||
//- - var level = ???
|
||||
- var label = courseLabelsArray[index];
|
||||
+studentCourseProgressDot(progress, levelsTotal, level, label)
|
||||
|
|
|
@ -34,6 +34,7 @@ module.exports = class TeacherClassesView extends RootView
|
|||
initialize: (options) ->
|
||||
super(options)
|
||||
@classrooms = new Classrooms()
|
||||
@classrooms.comparator = (a, b) -> b.id.localeCompare(a.id)
|
||||
@classrooms.fetchMine()
|
||||
@supermodel.trackCollection(@classrooms)
|
||||
@listenTo @classrooms, 'sync', ->
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
var d = new Date();
|
||||
|
||||
db.achievements.find({}, {_id:1}).sort({_id:1}).forEach(function(achievement) {
|
||||
db.achievements.update({_id: achievement._id}, {$set: {updated: d.toISOString()}})
|
||||
print('set', achievement._id, 'to', d);
|
||||
d.setSeconds(d.getSeconds() + 1)
|
||||
});
|
|
@ -19,7 +19,12 @@ module.exports =
|
|||
unless hasPermission or database.isJustFillingTranslations(req, achievement)
|
||||
throw new errors.Forbidden('Must be an admin, artisan or submitting translations to edit an achievement')
|
||||
|
||||
propsWatching = ['query', 'proportionalTo', 'rewards', 'worth', 'function']
|
||||
oldCopy = _.pick(achievement.toObject(), propsWatching)
|
||||
database.assignBody(req, achievement)
|
||||
newCopy = _.pick(achievement.toObject(), propsWatching)
|
||||
unless _.isEqual(oldCopy, newCopy)
|
||||
achievement.set('updated', new Date().toISOString())
|
||||
database.validateDoc(achievement)
|
||||
achievement = yield achievement.save()
|
||||
res.status(200).send(achievement.toObject({req: req}))
|
||||
|
|
|
@ -20,6 +20,7 @@ Achievement = require '../models/Achievement'
|
|||
EarnedAchievement = require '../models/EarnedAchievement'
|
||||
log = require 'winston'
|
||||
LocalMongo = require '../../app/lib/LocalMongo'
|
||||
LevelSession = require '../models/LevelSession'
|
||||
|
||||
module.exports =
|
||||
fetchByGPlusID: wrap (req, res, next) ->
|
||||
|
@ -261,28 +262,27 @@ module.exports =
|
|||
|
||||
checkForNewAchievement: wrap (req, res) ->
|
||||
user = req.user
|
||||
|
||||
lastAchievementChecked = user.get('lastAchievementChecked') or user._id
|
||||
achievement = yield Achievement.findOne({ _id: { $gt: lastAchievementChecked }}).sort({_id:1})
|
||||
lastAchievementChecked = user.get('lastAchievementChecked') or user._id.getTimestamp().toISOString()
|
||||
checkTimestamp = new Date().toISOString()
|
||||
achievement = yield Achievement.findOne({ updated: { $gt: lastAchievementChecked }}).sort({updated:1})
|
||||
|
||||
if not achievement
|
||||
userUpdate = { 'lastAchievementChecked': new mongoose.Types.ObjectId() }
|
||||
userUpdate = { 'lastAchievementChecked': checkTimestamp }
|
||||
user.update({$set: userUpdate}).exec()
|
||||
return res.send(userUpdate)
|
||||
|
||||
userUpdate = { 'lastAchievementChecked': achievement._id }
|
||||
userUpdate = { 'lastAchievementChecked': achievement.get('updated') }
|
||||
|
||||
query = achievement.get('query')
|
||||
collection = achievement.get('collection')
|
||||
if collection is 'users'
|
||||
triggers = [user]
|
||||
else if collection is 'level.sessions' and query['level.original']
|
||||
triggers = yield LevelSessions.find({
|
||||
triggers = yield LevelSession.find({
|
||||
'level.original': query['level.original']
|
||||
creator: user._id
|
||||
creator: user.id
|
||||
})
|
||||
else
|
||||
userUpdate = { 'lastAchievementChecked': new mongoose.Types.ObjectId() }
|
||||
user.update({$set: userUpdate}).exec()
|
||||
return res.send(userUpdate)
|
||||
|
||||
|
@ -292,10 +292,8 @@ module.exports =
|
|||
user.update({$set: userUpdate}).exec()
|
||||
return res.send(userUpdate)
|
||||
|
||||
earned = yield EarnedAchievement.findOne({ achievement: achievement.id, user: req.user })
|
||||
yield [
|
||||
EarnedAchievement.upsertFor(achievement, trigger, earned, req.user)
|
||||
user.update({$set: userUpdate})
|
||||
]
|
||||
earned = yield EarnedAchievement.findOne({ achievement: achievement.id, user: req.user.id })
|
||||
yield EarnedAchievement.upsertFor(achievement, trigger, earned, req.user)
|
||||
yield user.update({$set: userUpdate})
|
||||
user = yield User.findById(user.id).select({points: 1, earned: 1})
|
||||
return res.send(_.assign({}, userUpdate, user.toObject()))
|
||||
|
|
|
@ -33,6 +33,7 @@ AchievementSchema.index(
|
|||
AchievementSchema.index({i18nCoverage: 1}, {name: 'translation coverage index', sparse: true})
|
||||
AchievementSchema.index({slug: 1}, {name: 'slug index', sparse: true, unique: true})
|
||||
AchievementSchema.index({related: 1}, {name: 'related index', sparse: true})
|
||||
AchievementSchema.index({updated: 1}, {name: 'updated index'})
|
||||
|
||||
AchievementSchema.methods.objectifyQuery = ->
|
||||
try
|
||||
|
@ -105,6 +106,8 @@ AchievementSchema.post 'init', (doc) -> doc.objectifyQuery()
|
|||
|
||||
AchievementSchema.pre 'save', (next) ->
|
||||
@stringifyQuery()
|
||||
if not @get('updated')
|
||||
@set('updated', new Date().toISOString())
|
||||
next()
|
||||
|
||||
# Reload achievements upon save
|
||||
|
|
|
@ -3,6 +3,7 @@ jsonschema = require '../../app/schemas/models/earned_achievement'
|
|||
util = require '../../app/core/utils'
|
||||
log = require 'winston'
|
||||
co = require 'co'
|
||||
errors = require '../commons/errors'
|
||||
|
||||
EarnedAchievementSchema = new mongoose.Schema({
|
||||
notified:
|
||||
|
@ -27,15 +28,19 @@ EarnedAchievementSchema.statics.upsertFor = (achievement, trigger, earned, user)
|
|||
else if earned
|
||||
achievementEarned = achievement.get('rewards')
|
||||
actuallyEarned = earned.get('earnedRewards')
|
||||
if not _.isEqual(achievementEarned, actuallyEarned)
|
||||
earned.set('earnedRewards', achievementEarned)
|
||||
yield earned.save()
|
||||
oldPoints = earned.get('earnedPoints') ? 0
|
||||
newPoints = achievement.get('worth') ? 10
|
||||
if (not _.isEqual(achievementEarned, actuallyEarned)) or (oldPoints isnt newPoints)
|
||||
earned.set('earnedRewards', achievementEarned)
|
||||
earned.set('earnedPoints', newPoints)
|
||||
yield earned.save()
|
||||
|
||||
# make sure user has all the levels, heroes, gems, items and points they should have
|
||||
update = {$inc: { points: newPoints - oldPoints }}
|
||||
|
||||
# make sure user has all the levels and items they should have
|
||||
update = {}
|
||||
for rewardType, rewards of achievement.get('rewards') ? {}
|
||||
if rewardType is 'gems'
|
||||
update.$inc = { 'earned.gems': rewards - (actuallyEarned.gems ? 0) }
|
||||
update.$inc['earned.gems'] = rewards - (actuallyEarned.gems ? 0)
|
||||
else if rewards.length
|
||||
update.$addToSet ?= {}
|
||||
update.$addToSet["earned.#{rewardType}"] = { $each: rewards }
|
||||
|
|
|
@ -109,6 +109,45 @@ describe 'PUT /db/achievement', ->
|
|||
expect(res.body.name).toBe('whatev')
|
||||
done()
|
||||
|
||||
it 'touches "updated" if query, proportionalTo, worth, rewards or function change', utils.wrap (done) ->
|
||||
lastUpdated = @unlockable.get('updated')
|
||||
expect(lastUpdated).toBeDefined()
|
||||
[res, body] = yield request.putAsync {uri: url + '/'+@unlockable.id, json: {
|
||||
name: 'whatev'
|
||||
rewards: @unlockable.get('rewards')
|
||||
query: @unlockable.get('query')
|
||||
proportionalTo: @unlockable.get('proportionalTo')
|
||||
}}
|
||||
achievement = yield Achievement.findById(@unlockable.id)
|
||||
expect(achievement.get('updated')).toBeDefined()
|
||||
expect(achievement.get('updated')).toBe(lastUpdated) # unchanged
|
||||
|
||||
newRewards = _.assign({}, @unlockable.get('rewards'), {gems: 1000})
|
||||
[res, body] = yield request.putAsync {uri: url + '/'+@unlockable.id, json: {rewards: newRewards}}
|
||||
expect(res.body.updated).not.toBe(lastUpdated)
|
||||
lastUpdated = res.body.updated
|
||||
|
||||
newQuery = _.assign({}, @unlockable.get('query'), {'state.complete': true})
|
||||
[res, body] = yield request.putAsync {uri: url + '/'+@unlockable.id, json: {query: newQuery}}
|
||||
expect(res.body.updated).not.toBe(lastUpdated)
|
||||
lastUpdated = res.body.updated
|
||||
|
||||
newProportionalTo = 'playtime'
|
||||
[res, body] = yield request.putAsync {uri: url + '/'+@unlockable.id, json: {proportionalTo: newProportionalTo}}
|
||||
expect(res.body.updated).not.toBe(lastUpdated)
|
||||
lastUpdated = res.body.updated
|
||||
|
||||
newWorth = 1000
|
||||
[res, body] = yield request.putAsync {uri: url + '/'+@unlockable.id, json: {worth: newWorth}}
|
||||
expect(res.body.updated).not.toBe(lastUpdated)
|
||||
lastUpdated = res.body.updated
|
||||
|
||||
newFunction = { kind: 'logarithmic', parameters: { a: 1, b: 2, c: 3 } }
|
||||
[res, body] = yield request.putAsync {uri: url + '/'+@unlockable.id, json: {function: newFunction}}
|
||||
expect(res.body.updated).not.toBe(lastUpdated)
|
||||
done()
|
||||
|
||||
|
||||
describe 'GET /db/achievement', ->
|
||||
beforeEach addAllAchievements
|
||||
|
||||
|
@ -187,7 +226,7 @@ describe 'POST /db/earned_achievement', ->
|
|||
expect(body.achievement).toBe @unlockable.id
|
||||
expect(body.user).toBe user.id
|
||||
expect(body.notified).toBeFalsy()
|
||||
expect(body.earnedPoints).toBe unlockable.worth
|
||||
expect(body.earnedPoints).toBe @unlockable.get('worth')
|
||||
expect(body.achievedAmount).toBeUndefined()
|
||||
expect(body.previouslyAchievedAmount).toBeUndefined()
|
||||
earnedAchievements = yield EarnedAchievement.find()
|
||||
|
@ -344,7 +383,7 @@ describe 'POST /admin/earned_achievement/recalculate', ->
|
|||
expect(earnedAchievements.length).toBe(1)
|
||||
|
||||
user = yield User.findById(@admin.id)
|
||||
expect(user.get 'points').toBe unlockable.worth
|
||||
expect(user.get 'points').toBe @unlockable.get('worth')
|
||||
done()
|
||||
|
||||
it 'can recalculate all achievements', utils.wrap (done) ->
|
||||
|
@ -371,8 +410,8 @@ describe 'POST /admin/earned_achievement/recalculate', ->
|
|||
earnedAchievements = yield EarnedAchievement.find({})
|
||||
expect(earnedAchievements.length).toBe 3
|
||||
user = yield User.findById(@admin.id)
|
||||
expect(user.get 'points').toBe unlockable.worth + 4 * repeatable.worth + (Math.log(.5 * (4 + .5)) + 1) * diminishing.worth
|
||||
expect(user.get('earned').gems).toBe 4 * repeatable.rewards.gems
|
||||
expect(user.get 'points').toBe @unlockable.get('worth') + 4 * @repeatable.get('worth') + (Math.log(.5 * (4 + .5)) + 1) * @diminishing.get('worth')
|
||||
expect(user.get('earned').gems).toBe 4 * @repeatable.get('rewards').gems
|
||||
done()
|
||||
|
||||
afterEach utils.wrap (done) ->
|
||||
|
|
|
@ -1039,6 +1039,22 @@ describe 'POST /db/user/:handle/deteacher', ->
|
|||
|
||||
|
||||
describe 'POST /db/user/:handle/check-for-new-achievements', ->
|
||||
achievementURL = getURL('/db/achievement')
|
||||
achievementJSON = {
|
||||
collection: 'users'
|
||||
query: {'points': {$gt: 50}}
|
||||
userField: '_id'
|
||||
recalculable: true
|
||||
worth: 75
|
||||
rewards: {
|
||||
gems: 50
|
||||
levels: [new mongoose.Types.ObjectId().toString()]
|
||||
}
|
||||
name: 'Dungeon Arena Started'
|
||||
description: 'Started playing Dungeon Arena.'
|
||||
related: 'a'
|
||||
}
|
||||
|
||||
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels [Achievement, EarnedAchievement, LevelSession, User]
|
||||
|
@ -1055,26 +1071,35 @@ describe 'POST /db/user/:handle/check-for-new-achievements', ->
|
|||
earned = yield EarnedAchievement.count()
|
||||
expect(earned).toBe(0)
|
||||
|
||||
achievementURL = getURL('/db/achievement')
|
||||
achievementJSON = {
|
||||
collection: 'users'
|
||||
query: {'points': {$gt: 50}}
|
||||
userField: '_id'
|
||||
recalculable: true
|
||||
worth: 75
|
||||
rewards: {
|
||||
gems: 50
|
||||
levels: [new mongoose.Types.ObjectId().toString()]
|
||||
}
|
||||
name: 'Dungeon Arena Started'
|
||||
description: 'Started playing Dungeon Arena.'
|
||||
related: 'a'
|
||||
}
|
||||
admin = yield utils.initAdmin()
|
||||
yield utils.loginUser(admin)
|
||||
[res, body] = yield request.postAsync { uri: achievementURL, json: achievementJSON }
|
||||
achievementUpdated = res.body.updated
|
||||
expect(res.statusCode).toBe(201)
|
||||
|
||||
user = yield User.findById(user.id)
|
||||
expect(user.get('earned')).toBeUndefined()
|
||||
|
||||
yield utils.loginUser(user)
|
||||
[res, body] = yield request.postAsync({ url, json })
|
||||
expect(body.points).toBe(175)
|
||||
earned = yield EarnedAchievement.count()
|
||||
expect(earned).toBe(1)
|
||||
expect(body.lastAchievementChecked).toBe(achievementUpdated)
|
||||
|
||||
done()
|
||||
|
||||
it 'updates the user if they already earned the achievement but the rewards changed', utils.wrap (done) ->
|
||||
user = yield utils.initUser({points: 100})
|
||||
yield utils.loginUser(user)
|
||||
url = utils.getURL("/db/user/#{user.id}/check-for-new-achievement")
|
||||
json = true
|
||||
[res, body] = yield request.postAsync({ url, json })
|
||||
|
||||
admin = yield utils.initAdmin()
|
||||
yield utils.loginUser(admin)
|
||||
[res, body] = yield request.postAsync { uri: achievementURL, json: achievementJSON }
|
||||
achievementID = body._id
|
||||
achievement = yield Achievement.findById(body._id)
|
||||
expect(res.statusCode).toBe(201)
|
||||
|
||||
user = yield User.findById(user.id)
|
||||
|
@ -1082,9 +1107,96 @@ describe 'POST /db/user/:handle/check-for-new-achievements', ->
|
|||
|
||||
yield utils.loginUser(user)
|
||||
[res, body] = yield request.postAsync({ url, json })
|
||||
expect(body.points).toBe(175)
|
||||
earned = yield EarnedAchievement.count()
|
||||
expect(earned).toBe(1)
|
||||
expect(body.lastAchievementChecked).toBe(achievementID)
|
||||
expect(res.body.points).toBe(175)
|
||||
expect(res.body.earned.gems).toBe(50)
|
||||
|
||||
achievement.set({
|
||||
updated: new Date().toISOString()
|
||||
rewards: { gems: 100 }
|
||||
worth: 200
|
||||
})
|
||||
yield achievement.save()
|
||||
|
||||
[res, body] = yield request.postAsync({ url, json })
|
||||
expect(res.body.earned.gems).toBe(100)
|
||||
expect(res.body.points).toBe(300)
|
||||
expect(res.statusCode).toBe(200)
|
||||
|
||||
# special case: no worth, should default to 10
|
||||
|
||||
yield achievement.update({
|
||||
$set: {updated: new Date().toISOString()},
|
||||
$unset: {worth:''}
|
||||
})
|
||||
[res, body] = yield request.postAsync({ url, json })
|
||||
expect(res.body.earned.gems).toBe(100)
|
||||
expect(res.body.points).toBe(110)
|
||||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
||||
it 'works for level sessions', utils.wrap (done) ->
|
||||
admin = yield utils.initAdmin()
|
||||
yield utils.loginUser(admin)
|
||||
level = yield utils.makeLevel()
|
||||
achievement = yield utils.makeAchievement({
|
||||
collection: 'level.sessions'
|
||||
userField: 'creator'
|
||||
query: {
|
||||
'level.original': level.get('original').toString()
|
||||
'state': {complete: true}
|
||||
}
|
||||
worth: 100
|
||||
proportionalTo: 'state.difficulty'
|
||||
})
|
||||
levelSession = yield utils.makeLevelSession({state: {complete: true, difficulty:2}}, { creator:admin, level })
|
||||
url = utils.getURL("/db/user/#{admin.id}/check-for-new-achievement")
|
||||
json = true
|
||||
[res, body] = yield request.postAsync({ url, json })
|
||||
expect(body.points).toBe(200)
|
||||
|
||||
# check idempotency
|
||||
achievement.set('updated', new Date().toISOString())
|
||||
yield achievement.save()
|
||||
[res, body] = yield request.postAsync({ url, json })
|
||||
expect(body.points).toBe(200)
|
||||
admin = yield User.findById(admin.id)
|
||||
done()
|
||||
|
||||
it 'skips achievements which have not been satisfied', utils.wrap (done) ->
|
||||
admin = yield utils.initAdmin()
|
||||
yield utils.loginUser(admin)
|
||||
level = yield utils.makeLevel()
|
||||
achievement = yield utils.makeAchievement({
|
||||
collection: 'level.sessions'
|
||||
userField: 'creator'
|
||||
query: {
|
||||
'level.original': 'does not matter'
|
||||
}
|
||||
worth: 100
|
||||
})
|
||||
expect(admin.get('lastAchievementChecked')).toBeUndefined()
|
||||
url = utils.getURL("/db/user/#{admin.id}/check-for-new-achievement")
|
||||
json = true
|
||||
[res, body] = yield request.postAsync({ url, json })
|
||||
expect(body.points).toBeUndefined()
|
||||
admin = yield User.findById(admin.id)
|
||||
expect(admin.get('lastAchievementChecked')).toBe(achievement.get('updated'))
|
||||
done()
|
||||
|
||||
it 'skips achievements which are not either for the users collection or the level sessions collection with level.original included', utils.wrap (done) ->
|
||||
admin = yield utils.initAdmin()
|
||||
yield utils.loginUser(admin)
|
||||
achievement = yield utils.makeAchievement({
|
||||
collection: 'not.supported'
|
||||
userField: 'creator'
|
||||
query: {}
|
||||
worth: 100
|
||||
})
|
||||
expect(admin.get('lastAchievementChecked')).toBeUndefined()
|
||||
url = utils.getURL("/db/user/#{admin.id}/check-for-new-achievement")
|
||||
json = true
|
||||
[res, body] = yield request.postAsync({ url, json })
|
||||
expect(body.points).toBeUndefined()
|
||||
admin = yield User.findById(admin.id)
|
||||
expect(admin.get('lastAchievementChecked')).toBe(achievement.get('updated'))
|
||||
done()
|
||||
|
|
|
@ -4,6 +4,7 @@ co = require 'co'
|
|||
Promise = require 'bluebird'
|
||||
User = require '../../server/models/User'
|
||||
Level = require '../../server/models/Level'
|
||||
LevelSession = require '../../server/models/LevelSession'
|
||||
Achievement = require '../../server/models/Achievement'
|
||||
Campaign = require '../../server/models/Campaign'
|
||||
Course = require '../../server/models/Course'
|
||||
|
@ -99,6 +100,38 @@ module.exports = mw =
|
|||
return done(err) if err
|
||||
Level.findById(res.body._id).exec done
|
||||
|
||||
makeLevelSession: Promise.promisify (data, sources, done) ->
|
||||
args = Array.from(arguments)
|
||||
[done, [data, sources]] = [args.pop(), args]
|
||||
|
||||
data = _.extend({}, {
|
||||
state:
|
||||
complete: false
|
||||
scripts:
|
||||
currentScript: null
|
||||
}, data)
|
||||
|
||||
if sources?.level and not data.level
|
||||
data.level = {
|
||||
original: sources.level.get('original').toString()
|
||||
majorVersion: sources.level.get('version').major
|
||||
}
|
||||
|
||||
if sources?.creator and not data.creator
|
||||
data.creator = sources.creator.id
|
||||
|
||||
if data.creator and not data.permissions
|
||||
data.permissions = [
|
||||
{ target: data.creator, access: 'owner' }
|
||||
{ target: 'public', access: 'write' }
|
||||
]
|
||||
|
||||
if not data.codeLanguage
|
||||
data.codeLanguage = 'javascript'
|
||||
|
||||
session = new LevelSession(data)
|
||||
session.save(done)
|
||||
|
||||
makeAchievement: Promise.promisify (data, sources, done) ->
|
||||
args = Array.from(arguments)
|
||||
[done, [data, sources]] = [args.pop(), args]
|
||||
|
@ -106,12 +139,13 @@ module.exports = mw =
|
|||
data = _.extend({}, {
|
||||
name: _.uniqueId('Achievement ')
|
||||
}, data)
|
||||
if sources.related and not data.related
|
||||
if sources?.related and not data.related
|
||||
related = sources.related
|
||||
data.related = (related.get('original') or related._id).valueOf()
|
||||
|
||||
request.post { uri: getURL('/db/achievement'), json: data }, (err, res) ->
|
||||
return done(err) if err
|
||||
expect(res.statusCode).toBe(201)
|
||||
Achievement.findById(res.body._id).exec done
|
||||
|
||||
makeCampaign: Promise.promisify (data, sources, done) ->
|
||||
|
|
|
@ -9,6 +9,9 @@ describe 'Local Mongo queries', ->
|
|||
'worth': 6
|
||||
'type': 'unicorn'
|
||||
'likes': ['poptarts', 'popsicles', 'popcorn']
|
||||
nested: {
|
||||
str:'ing'
|
||||
}
|
||||
|
||||
@fixture2 = this: is: so: 'deep'
|
||||
|
||||
|
@ -24,6 +27,11 @@ describe 'Local Mongo queries', ->
|
|||
|
||||
it 'nested match', ->
|
||||
expect(LocalMongo.matchesQuery(@fixture2, 'this.is.so': 'deep')).toBeTruthy()
|
||||
expect(LocalMongo.matchesQuery(@fixture2, {this:{is:{so: 'deep'}}})).toBeTruthy()
|
||||
expect(LocalMongo.matchesQuery(@fixture2, {'this.is':{so: 'deep'}})).toBeTruthy()
|
||||
mixedQuery = { nested: {str:'ing'}, worth: {$gt:3} }
|
||||
expect(LocalMongo.matchesQuery(@fixture1, mixedQuery)).toBeTruthy()
|
||||
expect(LocalMongo.matchesQuery(@fixture2, mixedQuery)).toBeFalsy()
|
||||
|
||||
it '$gt selector', ->
|
||||
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gt': 8000)).toBeTruthy()
|
||||
|
|
Loading…
Reference in a new issue