mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 08:50:58 -05:00
Implement POST /db/user/:handle/check-for-new-achievement, couple tweaks
* Enforce being logged in for POST /db/earned_achievement * Extend timeout for race condition user tests
This commit is contained in:
parent
f509c95a4b
commit
139efe4cf7
7 changed files with 157 additions and 47 deletions
|
@ -26,32 +26,5 @@ exports.post = wrap (req, res) ->
|
|||
if not trigger
|
||||
throw new errors.NotFound('Could not find trigger.')
|
||||
|
||||
if achievement.get('proportionalTo') and earned
|
||||
earnedAchievementDoc = yield EarnedAchievement.createForAchievement(achievement, trigger, {previouslyEarnedAchievement: earned})
|
||||
res.status(201).send((earnedAchievementDoc or earned)?.toObject({req}))
|
||||
|
||||
else if earned
|
||||
achievementEarned = achievement.get('rewards')
|
||||
actuallyEarned = earned.get('earnedRewards')
|
||||
if not _.isEqual(achievementEarned, actuallyEarned)
|
||||
earned.set('earnedRewards', achievementEarned)
|
||||
yield earned.save()
|
||||
|
||||
# make sure user has all the levels and items they should have
|
||||
update = {}
|
||||
for rewardType, rewards of achievement.get('rewards') ? {}
|
||||
continue if rewardType is 'gems'
|
||||
if rewards.length
|
||||
update.$addToSet ?= {}
|
||||
update.$addToSet["earned.#{rewardType}"] = { $each: rewards }
|
||||
yield req.user.update(update)
|
||||
|
||||
return res.status(200).send(earned.toObject({req}))
|
||||
|
||||
else
|
||||
earnedAchievementDoc = yield EarnedAchievement.createForAchievement(achievement, trigger)
|
||||
if not earnedAchievementDoc
|
||||
console.error "Couldn't create achievement", achievement, trigger
|
||||
throw new errors.NotFound("Couldn't create achievement")
|
||||
res.status(201).send(earnedAchievementDoc.toObject({res}))
|
||||
|
||||
finalEarned = yield EarnedAchievement.upsertFor(achievement, trigger, earned, req.user)
|
||||
res.status(201).send(finalEarned.toObject({req}))
|
||||
|
|
|
@ -16,7 +16,10 @@ CourseInstance = require '../models/CourseInstance'
|
|||
facebook = require '../lib/facebook'
|
||||
gplus = require '../lib/gplus'
|
||||
TrialRequest = require '../models/TrialRequest'
|
||||
Achievement = require '../models/Achievement'
|
||||
EarnedAchievement = require '../models/EarnedAchievement'
|
||||
log = require 'winston'
|
||||
LocalMongo = require '../../app/lib/LocalMongo'
|
||||
|
||||
module.exports =
|
||||
fetchByGPlusID: wrap (req, res, next) ->
|
||||
|
@ -254,3 +257,45 @@ module.exports =
|
|||
yield user.update({ $unset: {role: ''}})
|
||||
user.set('role', undefined)
|
||||
return res.status(200).send(user.toObject({req: req}))
|
||||
|
||||
|
||||
checkForNewAchievement: wrap (req, res) ->
|
||||
user = req.user
|
||||
|
||||
lastAchievementChecked = user.get('lastAchievementChecked') or user._id
|
||||
achievement = yield Achievement.findOne({ _id: { $gt: lastAchievementChecked }}).sort({_id:1})
|
||||
|
||||
if not achievement
|
||||
userUpdate = { 'lastAchievementChecked': new mongoose.Types.ObjectId() }
|
||||
user.update(userUpdate)
|
||||
return res.send(userUpdate)
|
||||
|
||||
userUpdate = { 'lastAchievementChecked': achievement._id }
|
||||
|
||||
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({
|
||||
'level.original': query['level.original']
|
||||
creator: user._id
|
||||
})
|
||||
else
|
||||
userUpdate = { 'lastAchievementChecked': new mongoose.Types.ObjectId() }
|
||||
user.update(userUpdate)
|
||||
return res.send(userUpdate)
|
||||
|
||||
trigger = _.find(triggers, (trigger) -> LocalMongo.matchesQuery(trigger.toObject(), query))
|
||||
|
||||
if not trigger
|
||||
user.update(userUpdate)
|
||||
return res.send(userUpdate)
|
||||
|
||||
earned = yield EarnedAchievement.findOne({ achievement: achievement.id, user: req.user })
|
||||
yield [
|
||||
EarnedAchievement.upsertFor(achievement, trigger, earned, req.user)
|
||||
user.update(userUpdate)
|
||||
]
|
||||
user = yield User.findById(user.id).select({points: 1, earned: 1})
|
||||
return res.send(_.assign({}, userUpdate, user.toObject()))
|
||||
|
|
|
@ -6,6 +6,7 @@ plugins = require('../plugins/plugins')
|
|||
AchievablePlugin = require '../plugins/achievements'
|
||||
TreemaUtils = require '../../bower_components/treema/treema-utils.js'
|
||||
config = require '../../server_config'
|
||||
co = require 'co'
|
||||
|
||||
# `pre` and `post` are not called for update operations executed directly on the database,
|
||||
# including `Model.update`,`.findByIdAndUpdate`,`.findOneAndUpdate`, `.findOneAndRemove`,and `.findByIdAndRemove`.order
|
||||
|
@ -53,27 +54,29 @@ AchievementSchema.statics.achievementCollections = {}
|
|||
|
||||
# Reloads all achievements into memory.
|
||||
# TODO might want to tweak this to only load new achievements
|
||||
AchievementSchema.statics.loadAchievements = (done) ->
|
||||
AchievementSchema.statics.loadAchievements = co.wrap ->
|
||||
AchievementSchema.statics.resetAchievements()
|
||||
t0 = new Date()
|
||||
Achievement = require('./Achievement')
|
||||
query = Achievement.find({collection: {$ne: 'level.sessions'}})
|
||||
query.exec (err, docs) ->
|
||||
_.each docs, (achievement) ->
|
||||
collection = achievement.get 'collection'
|
||||
AchievementSchema.statics.achievementCollections[collection] ?= []
|
||||
if _.find AchievementSchema.statics.achievementCollections[collection], ((a) -> a.get('_id').toHexString() is achievement.get('_id').toHexString())
|
||||
log.warn "Uh oh, we tried to add another copy of the same achievement #{achievement.get('_id')} #{achievement.get('name')} to the #{collection} achievement list..."
|
||||
else
|
||||
AchievementSchema.statics.achievementCollections[collection].push achievement
|
||||
unless achievement.get('query')
|
||||
log.error "Uh oh, there is an achievement with an empty query: #{achievement}"
|
||||
done?(AchievementSchema.statics.achievementCollections) # TODO: Return with err as first parameter
|
||||
achievements = yield Achievement.find({collection: {$ne: 'level.sessions'}})
|
||||
return if t0 < @lastReset # if a test has run resetAchievements during the fetch, abort
|
||||
for achievement in achievements
|
||||
collection = achievement.get 'collection'
|
||||
AchievementSchema.statics.achievementCollections[collection] ?= []
|
||||
if _.find AchievementSchema.statics.achievementCollections[collection], ((a) -> a.get('_id').toHexString() is achievement.get('_id').toHexString())
|
||||
log.warn "Uh oh, we tried to add another copy of the same achievement #{achievement.get('_id')} #{achievement.get('name')} to the #{collection} achievement list..."
|
||||
else
|
||||
AchievementSchema.statics.achievementCollections[collection].push achievement
|
||||
unless achievement.get('query')
|
||||
log.error "Uh oh, there is an achievement with an empty query: #{achievement}"
|
||||
return AchievementSchema.statics.achievementCollections
|
||||
|
||||
AchievementSchema.statics.getLoadedAchievements = ->
|
||||
AchievementSchema.statics.achievementCollections
|
||||
|
||||
AchievementSchema.statics.resetAchievements = ->
|
||||
delete AchievementSchema.statics.achievementCollections[collection] for collection of AchievementSchema.statics.achievementCollections
|
||||
@lastReset = new Date()
|
||||
|
||||
AchievementSchema.statics.editableProperties = [
|
||||
'name'
|
||||
|
|
|
@ -17,6 +17,38 @@ EarnedAchievementSchema.pre 'save', (next) ->
|
|||
EarnedAchievementSchema.index({user: 1, achievement: 1}, {unique: true, name: 'earned achievement index'})
|
||||
EarnedAchievementSchema.index({user: 1, changed: -1}, {name: 'latest '})
|
||||
|
||||
|
||||
EarnedAchievementSchema.statics.upsertFor = (achievement, trigger, earned, user) ->
|
||||
|
||||
if achievement.get('proportionalTo') and earned
|
||||
earnedAchievementDoc = yield @createForAchievement(achievement, trigger, {previouslyEarnedAchievement: earned})
|
||||
return earnedAchievementDoc or earned
|
||||
|
||||
else if earned
|
||||
achievementEarned = achievement.get('rewards')
|
||||
actuallyEarned = earned.get('earnedRewards')
|
||||
if not _.isEqual(achievementEarned, actuallyEarned)
|
||||
earned.set('earnedRewards', achievementEarned)
|
||||
yield earned.save()
|
||||
|
||||
# make sure user has all the levels and items they should have
|
||||
update = {}
|
||||
for rewardType, rewards of achievement.get('rewards') ? {}
|
||||
continue if rewardType is 'gems'
|
||||
if rewards.length
|
||||
update.$addToSet ?= {}
|
||||
update.$addToSet["earned.#{rewardType}"] = { $each: rewards }
|
||||
yield user.update(update)
|
||||
return earned
|
||||
|
||||
else
|
||||
earned = yield @createForAchievement(achievement, trigger)
|
||||
if not earned
|
||||
console.error "Couldn't create achievement", achievement, trigger
|
||||
throw new errors.NotFound("Couldn't create achievement")
|
||||
return earned
|
||||
|
||||
|
||||
EarnedAchievementSchema.statics.createForAchievement = co.wrap (achievement, doc, options={}) ->
|
||||
{ previouslyEarnedAchievement, originalDocObj } = options
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ module.exports.setup = (app) ->
|
|||
app.get('/db/course_instance/:handle/course', mw.auth.checkLoggedIn(), mw.courseInstances.fetchCourse)
|
||||
|
||||
EarnedAchievement = require '../models/EarnedAchievement'
|
||||
app.post('/db/earned_achievement', mw.earnedAchievements.post)
|
||||
app.post('/db/earned_achievement', mw.auth.checkLoggedIn(), mw.earnedAchievements.post)
|
||||
|
||||
Level = require '../models/Level'
|
||||
app.post('/db/level/:handle', mw.auth.checkLoggedIn(), mw.versions.postNewVersion(Level, { hasPermissionsOrTranslations: 'artisan' })) # TODO: add /new-version to route like Article has
|
||||
|
@ -118,7 +118,8 @@ module.exports.setup = (app) ->
|
|||
app.post('/db/user/:handle/signup-with-password', mw.users.signupWithPassword)
|
||||
app.post('/db/user/:handle/destudent', mw.auth.checkHasPermission(['admin']), mw.users.destudent)
|
||||
app.post('/db/user/:handle/deteacher', mw.auth.checkHasPermission(['admin']), mw.users.deteacher)
|
||||
|
||||
app.post('/db/user/:handle/check-for-new-achievement', mw.auth.checkLoggedIn(), mw.users.checkForNewAchievement)
|
||||
|
||||
app.get('/db/prepaid', mw.auth.checkLoggedIn(), mw.prepaids.fetchByCreator)
|
||||
app.get('/db/prepaid/-/active-schools', mw.auth.checkHasPermission(['admin']), mw.prepaids.fetchActiveSchools)
|
||||
app.post('/db/prepaid', mw.auth.checkHasPermission(['admin']), mw.prepaids.post)
|
||||
|
|
|
@ -240,7 +240,7 @@ describe 'automatically achieving achievements', ->
|
|||
it 'happens when an object\'s properties meet achievement goals', utils.wrap (done) ->
|
||||
# load achievements on server
|
||||
@achievements = yield Achievement.loadAchievements()
|
||||
expect(@achievements.length).toBe(2)
|
||||
expect(@achievements.users.length).toBe(2)
|
||||
loadedAchievements = Achievement.getLoadedAchievements()
|
||||
expect(Object.keys(loadedAchievements).length).toBe(1)
|
||||
|
||||
|
|
|
@ -13,6 +13,10 @@ facebook = require '../../../server/lib/facebook'
|
|||
gplus = require '../../../server/lib/gplus'
|
||||
sendwithus = require '../../../server/sendwithus'
|
||||
Promise = require 'bluebird'
|
||||
Achievement = require '../../../server/models/Achievement'
|
||||
EarnedAchievement = require '../../../server/models/EarnedAchievement'
|
||||
LevelSession = require '../../../server/models/LevelSession'
|
||||
mongoose = require 'mongoose'
|
||||
|
||||
describe 'POST /db/user', ->
|
||||
|
||||
|
@ -832,7 +836,7 @@ describe 'POST /db/user/:handle/signup-with-facebook', ->
|
|||
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels([User])
|
||||
yield new Promise((resolve) -> setTimeout(resolve, 10))
|
||||
yield new Promise((resolve) -> setTimeout(resolve, 50))
|
||||
done()
|
||||
|
||||
it 'signs up the user with the facebookID and sends welcome emails', utils.wrap (done) ->
|
||||
|
@ -921,7 +925,7 @@ describe 'POST /db/user/:handle/signup-with-gplus', ->
|
|||
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels([User])
|
||||
yield new Promise((resolve) -> setTimeout(resolve, 10))
|
||||
yield new Promise((resolve) -> setTimeout(resolve, 50))
|
||||
done()
|
||||
|
||||
it 'signs up the user with the gplusID and sends welcome emails', utils.wrap (done) ->
|
||||
|
@ -1032,3 +1036,55 @@ describe 'POST /db/user/:handle/deteacher', ->
|
|||
teacher = yield User.findById(teacher.id)
|
||||
expect(teacher.get('role')).toBeUndefined()
|
||||
done()
|
||||
|
||||
|
||||
describe 'POST /db/user/:handle/check-for-new-achievements', ->
|
||||
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels [Achievement, EarnedAchievement, LevelSession, User]
|
||||
Achievement.resetAchievements()
|
||||
done()
|
||||
|
||||
it 'finds new achievements and awards them to the user', 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 })
|
||||
|
||||
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 }
|
||||
achievementID = body._id
|
||||
expect(res.statusCode).toBe(201)
|
||||
|
||||
user = yield User.findById(user.id)
|
||||
expect(user.get('rewards')).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(achievementID)
|
||||
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue