Refactor POST /db/earned_achievement

This commit is contained in:
Scott Erickson 2016-08-25 15:24:27 -07:00
parent 5c8b8832b3
commit f509c95a4b
5 changed files with 100 additions and 71 deletions

View file

@ -15,10 +15,9 @@ class EarnedAchievementHandler extends Handler
editableProperties: ['notified']
# Don't allow POSTs or anything yet
hasAccess: (req) ->
return false unless req.user
req.method in ['GET', 'POST', 'PUT'] # or req.user.isAdmin()
req.method in ['GET', 'PUT'] # or req.user.isAdmin()
get: (req, res) ->
return @getByAchievementIDs(req, res) if req.query.view is 'get-by-achievement-ids'
@ -45,71 +44,6 @@ class EarnedAchievementHandler extends Handler
documents = (@formatEntity(req, doc) for doc in documents)
@sendSuccess(res, documents)
post: (req, res) ->
achievementID = req.body.achievement
triggeredBy = req.body.triggeredBy
collection = req.body.collection
if collection isnt 'level.sessions' and not testing # TODO: remove this restriction
return @sendBadInputError(res, 'Only doing level session achievements for now.')
model = mongoose.modelNameByCollection(collection)
async.parallel({
achievement: (callback) ->
Achievement.findById achievementID, (err, achievement) -> callback(err, achievement)
trigger: (callback) ->
model.findById triggeredBy, (err, trigger) -> callback(err, trigger)
earned: (callback) ->
q = { achievement: achievementID, user: req.user._id+'' }
EarnedAchievement.findOne q, (err, earned) -> callback(err, earned)
}, (err, { achievement, trigger, earned } ) =>
return @sendDatabaseError(res, err) if err
if not achievement
return @sendNotFoundError(res, 'Could not find achievement.')
else if not trigger
return @sendNotFoundError(res, 'Could not find trigger.')
else if achievement.get('proportionalTo') and earned
EarnedAchievement.createForAchievement(achievement, trigger, {previouslyEarnedAchievement: earned}).then (earnedAchievementDoc) =>
@sendCreated(res, (earnedAchievementDoc or earned)?.toObject())
else if earned
achievementEarned = achievement.get('rewards')
actuallyEarned = earned.get('earnedRewards')
if not _.isEqual(achievementEarned, actuallyEarned)
earned.set('earnedRewards', achievementEarned)
earned.save((err) =>
return @sendDatabaseError(res, err) if err
@upsertNonNumericRewards(req.user, achievement, (err) =>
return @sendDatabaseError(res, err) if err
return @sendSuccess(res, earned.toObject())
)
)
else
@upsertNonNumericRewards(req.user, achievement, (err) =>
return @sendDatabaseError(res, err) if err
return @sendSuccess(res, earned.toObject())
)
else
EarnedAchievement.createForAchievement(achievement, trigger).then (earnedAchievementDoc) =>
if earnedAchievementDoc
@sendCreated(res, earnedAchievementDoc.toObject())
else
console.error "Couldn't create achievement", achievement, trigger
@sendNotFoundError res, "Couldn't create achievement"
)
upsertNonNumericRewards: (user, achievement, done) ->
update = {}
for rewardType, rewards of achievement.get('rewards') ? {}
continue if rewardType is 'gems'
if rewards.length
update.$addToSet ?= {}
update.$addToSet["earned.#{rewardType}"] = $each: rewards
User.update {_id: user._id}, update, {}, (err, result) ->
log.error err if err?
done?(err)
getByAchievementIDs: (req, res) ->
query = { user: req.user._id+''}
ids = req.query.achievementIDs

View file

@ -0,0 +1,57 @@
log = require 'winston'
mongoose = require 'mongoose'
Achievement = require './../models/Achievement'
EarnedAchievement = require './../models/EarnedAchievement'
errors = require '../commons/errors'
wrap = require 'co-express'
exports.post = wrap (req, res) ->
achievementID = req.body.achievement
triggeredBy = req.body.triggeredBy
collection = req.body.collection
if collection isnt 'level.sessions' and not testing # TODO: remove this restriction
throw new errors.UnprocessableEntity('Only doing level session achievements for now.')
model = mongoose.modelNameByCollection(collection)
[achievement, trigger, earned] = yield [
Achievement.findById(achievementID),
model.findById(triggeredBy)
EarnedAchievement.findOne({ achievement: achievementID, user: req.user.id })
]
if not achievement
throw new errors.NotFound('Could not find achievement.')
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}))

View file

@ -7,6 +7,7 @@ module.exports =
contact: require './contact'
courseInstances: require './course-instances'
courses: require './courses'
earnedAchievements: require './earned-achievements'
files: require './files'
healthcheck: require './healthcheck'
levels: require './levels'

View file

@ -96,7 +96,10 @@ module.exports.setup = (app) ->
app.post('/db/course_instance/:handle/members', mw.auth.checkLoggedIn(), mw.courseInstances.addMembers)
app.get('/db/course_instance/:handle/classroom', mw.auth.checkLoggedIn(), mw.courseInstances.fetchClassroom)
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)
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
app.get('/db/level/:handle/session', mw.auth.checkHasUser(), mw.levels.upsertSession)

View file

@ -7,12 +7,15 @@ LevelSession = require '../../../server/models/LevelSession'
User = require '../../../server/models/User'
request = require '../request'
EarnedAchievementHandler = require '../../../server/handlers/earned_achievement_handler'
mongoose = require 'mongoose'
url = getURL('/db/achievement')
# Fixtures
lockedLevelID = new mongoose.Types.ObjectId().toString()
unlockable =
name: 'Dungeon Arena Started'
description: 'Started playing Dungeon Arena.'
@ -22,6 +25,9 @@ unlockable =
userField: 'creator'
recalculable: true
related: 'a'
rewards: {
levels: [lockedLevelID]
}
unlockable2 = _.clone unlockable
unlockable2.name = 'This one is obsolete'
@ -163,6 +169,7 @@ describe 'DELETE /db/achievement/:handle', ->
describe 'POST /db/earned_achievement', ->
beforeEach addAllAchievements
eaURL = getURL('/db/earned_achievement')
it 'manually creates earned achievements for level achievements, which do not happen automatically', utils.wrap (done) ->
session = new LevelSession({
@ -174,7 +181,7 @@ describe 'POST /db/earned_achievement', ->
earnedAchievements = yield EarnedAchievement.find()
expect(earnedAchievements.length).toBe(0)
json = {achievement: @unlockable.id, triggeredBy: session._id, collection: 'level.sessions'}
[res, body] = yield request.postAsync {uri: getURL('/db/earned_achievement'), json: json}
[res, body] = yield request.postAsync { url: eaURL, json }
expect(res.statusCode).toBe(201)
expect(body.achievement).toBe @unlockable.id
expect(body.user).toBe @admin.id
@ -191,14 +198,41 @@ describe 'POST /db/earned_achievement', ->
yield utils.loginUser(user)
yield user.update({simulatedBy: 10})
json = {achievement: @repeatable.id, triggeredBy: user.id, collection: 'users'}
[res, body] = yield request.postAsync {uri: getURL('/db/earned_achievement'), json: json}
[res, body] = yield request.postAsync { url: eaURL, json }
expect(res.statusCode).toBe(201)
expect(body.earnedPoints).toBe(10)
yield user.update({simulatedBy: 30})
[res, body] = yield request.postAsync {uri: getURL('/db/earned_achievement'), json: json}
[res, body] = yield request.postAsync { url: eaURL, json }
expect(res.statusCode).toBe(201)
expect(body.earnedPoints).toBe(20) # this is kinda weird, TODO: just return total amounts
done()
it 'ensures the user has the rewards they earned', utils.wrap (done) ->
user = yield utils.initUser()
yield utils.loginUser(user)
# get the User the unlockable achievement, check they got their reward
session = new LevelSession({
permissions: simplePermissions
creator: user._id
level: original: 'dungeon-arena'
})
yield session.save()
json = {achievement: @unlockable.id, triggeredBy: session._id, collection: 'level.sessions'}
[res, body] = yield request.postAsync { url: eaURL, json }
user = yield User.findById(user.id)
expect(user.get('earned').levels[0]).toBe(lockedLevelID)
# mess with the user's earned levels, make sure they don't have it anymore
yield user.update({$unset: {earned:1}})
user = yield User.findById(user.id)
expect(user.get('earned')).toBeUndefined()
# hit the endpoint again, make sure the level was restored
[res, body] = yield request.postAsync { url: eaURL, json }
user = yield User.findById(user.id)
expect(user.get('earned').levels[0]).toBe(lockedLevelID)
done()
describe 'automatically achieving achievements', ->
beforeEach addAllAchievements