mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 08:50:58 -05:00
All edits are now tracked
intermediate intermediate
This commit is contained in:
parent
b951205681
commit
fce9f0031b
13 changed files with 146 additions and 51 deletions
|
@ -1,4 +1,4 @@
|
|||
CocoClass = require 'lib/CocoClass'
|
||||
CocoClass = require './CocoClass'
|
||||
|
||||
namesCache = {}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SystemNameLoader = require 'lib/SystemNameLoader'
|
||||
SystemNameLoader = require './SystemNameLoader'
|
||||
###
|
||||
Good-to-knows:
|
||||
dataPath: an array of keys that walks you up a JSON object that's being patched
|
||||
|
@ -10,7 +10,6 @@ SystemNameLoader = require 'lib/SystemNameLoader'
|
|||
module.exports.expandDelta = (delta, left, schema) ->
|
||||
flattenedDeltas = flattenDelta(delta)
|
||||
(expandFlattenedDelta(fd, left, schema) for fd in flattenedDeltas)
|
||||
|
||||
|
||||
flattenDelta = (delta, dataPath=null, deltaPath=null) ->
|
||||
# takes a single jsondiffpatch delta and returns an array of objects with
|
||||
|
@ -27,9 +26,7 @@ flattenDelta = (delta, dataPath=null, deltaPath=null) ->
|
|||
results = results.concat flattenDelta(
|
||||
childDelta, dataPath.concat([dataIndex]), deltaPath.concat([deltaIndex]))
|
||||
results
|
||||
|
||||
|
||||
expandFlattenedDelta = (delta, left, schema) ->
|
||||
pandFlattenedDelta = (delta, left, schema) ->
|
||||
# takes a single flattened delta and converts into an object that can be
|
||||
# easily formatted into something human readable.
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ PatchSchema = c.object({title:'Patch', required:['target', 'delta', 'commitMessa
|
|||
major: { type: 'number', minimum: 0 }
|
||||
minor: { type: 'number', minimum: 0 }
|
||||
})
|
||||
|
||||
_wasPending: type: 'boolean'
|
||||
})
|
||||
|
||||
c.extendBasicProperties(PatchSchema, 'patch')
|
||||
|
|
|
@ -160,7 +160,13 @@ UserSchema = c.object {},
|
|||
data: c.object {description: "Cached LinkedIn data slurped from profile.", additionalProperties: true}
|
||||
points: {type:'number'}
|
||||
activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity}
|
||||
|
||||
stats: c.object {additionalProperties: true}, # TODO set to false after dev
|
||||
gamesCompleted: type: 'integer'
|
||||
articleEdits: type: 'integer'
|
||||
levelEdits: type: 'integer'
|
||||
levelSystemEdits: type: 'integer'
|
||||
levelComponentEdits: type: 'integer'
|
||||
thangTypeEdits: type: 'integer'
|
||||
|
||||
|
||||
c.extendBasicProperties UserSchema, 'user'
|
||||
|
|
|
@ -8,16 +8,4 @@ ArticleSchema.plugin(plugins.VersionedPlugin)
|
|||
ArticleSchema.plugin(plugins.SearchablePlugin, {searchable: ['body', 'name']})
|
||||
ArticleSchema.plugin(plugins.PatchablePlugin)
|
||||
|
||||
# Assumes every article save is a new version
|
||||
ArticleSchema.pre 'save', (next) ->
|
||||
return next() unless @get('creator')
|
||||
User = require '../users/User' # Avoid mutual inclusion cycles
|
||||
|
||||
userID = @get('creator').toHexString()
|
||||
User.update {_id: userID}, {$inc: 'stats.articleEdits': 1}, {}, (err, docs) ->
|
||||
log.error err if err?
|
||||
|
||||
next()
|
||||
|
||||
|
||||
module.exports = mongoose.model('article', ArticleSchema)
|
||||
|
|
|
@ -21,16 +21,5 @@ LevelSchema.pre 'init', (next) ->
|
|||
LevelSchema.post 'init', (doc) ->
|
||||
if _.isString(doc.get('nextLevel'))
|
||||
doc.set('nextLevel', undefined)
|
||||
|
||||
# Assumes every level save is a new level
|
||||
LevelSchema.pre 'save', (next) ->
|
||||
return next() unless @get('creator')
|
||||
User = require '../users/User' # Avoid mutual inclusion cycles
|
||||
|
||||
userID = @get('creator').toHexString()
|
||||
User.update {_id: userID}, {$inc: 'stats.levelEdits': 1}, {}, (err, docs) ->
|
||||
log.error err if err?
|
||||
|
||||
next()
|
||||
|
||||
module.exports = Level = mongoose.model('level', LevelSchema)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
mongoose = require('mongoose')
|
||||
deltas = require '../../app/lib/deltas'
|
||||
{handlers} = require '../commons/mapping'
|
||||
|
||||
PatchSchema = new mongoose.Schema({}, {strict: false})
|
||||
PatchSchema = new mongoose.Schema({status: String}, {strict: false})
|
||||
|
||||
PatchSchema.pre 'save', (next) ->
|
||||
return next() unless @isNew # patch can't be altered after creation, so only need to check data once
|
||||
|
@ -45,4 +46,31 @@ PatchSchema.pre 'save', (next) ->
|
|||
@targetLoaded = document
|
||||
document.save (err) -> next(err)
|
||||
|
||||
PatchSchema.methods.isTranslationPatch = ->
|
||||
console.log @get 'delta'
|
||||
expanded = deltas.expandDelta @get('delta')
|
||||
console.log 'expanded'
|
||||
_.some expanded, (delta) -> 'i18n' in expanded.dataPath
|
||||
|
||||
PatchSchema.methods.isMiscPatch = ->
|
||||
expanded = deltas.expandDelta @get 'delta'
|
||||
_.some expanded, (delta) -> 'i18n' not in expanded.dataPath
|
||||
|
||||
# Keep track of when a patch is pending. Accepted patches can be rejected still.
|
||||
PatchSchema.path('status').set (newVal) ->
|
||||
@set '_wasPending', @status is 'pending' and newVal isnt 'pending'
|
||||
newVal
|
||||
|
||||
PatchSchema.pre 'save', (next) ->
|
||||
User = require '../users/User'
|
||||
userID = @get('creator').toHexString()
|
||||
|
||||
if @get('status') is 'accepted'
|
||||
User.incrementStat userID, 'stats.patchesContributed' # accepted patches
|
||||
else if @get('status') is 'pending'
|
||||
User.incrementStat userID, 'stats.patchesSubmitted' # submitted patches
|
||||
|
||||
next()
|
||||
|
||||
|
||||
module.exports = mongoose.model('patch', PatchSchema)
|
||||
|
|
|
@ -48,11 +48,18 @@ PatchHandler = class PatchHandler extends Handler
|
|||
|
||||
if newStatus is 'withdrawn'
|
||||
return @sendUnauthorizedError(res) unless req.user.get('_id').equals patch.get('creator')
|
||||
|
||||
|
||||
# newly accepted
|
||||
if newStatus is 'accepted' and patch.get '_wasPending'
|
||||
accepter = req.user.get 'id'
|
||||
User.incrementStat accepter, 'stats.'
|
||||
|
||||
# these require callbacks
|
||||
patch.update {$set:{status:newStatus}}, {}, ->
|
||||
target.update {$pull:{patches:patch.get('_id')}}, {}, ->
|
||||
@sendSuccess(res, null)
|
||||
patch.set 'status', newStatus
|
||||
patch.save (err) =>
|
||||
log.error err if err?
|
||||
target.update {$pull:{patches:patch.get('_id')}}, {}, ->
|
||||
@sendSuccess(res, null)
|
||||
|
||||
onPostSuccess: (req, doc) ->
|
||||
log.error "Error sending patch created: could not find the loaded target on the patch object." unless doc.targetLoaded
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
mongoose = require('mongoose')
|
||||
textSearch = require('mongoose-text-search')
|
||||
log = require 'winston'
|
||||
|
||||
module.exports.MigrationPlugin = (schema, migrations) ->
|
||||
# Property name migrations made EZ
|
||||
|
@ -256,13 +257,14 @@ module.exports.VersionedPlugin = (schema) ->
|
|||
)
|
||||
)
|
||||
|
||||
# Assume ever save is a new version, hence an edit
|
||||
schema.pre 'save', (next) ->
|
||||
return next() unless @get('creator')
|
||||
User = require '../users/User' # Avoid mutual inclusion cycles
|
||||
userID = @get('creator')?.toHexString()
|
||||
return next() unless userID?
|
||||
|
||||
userID = @get('creator').toHexString()
|
||||
User.update {_id: userID}, {$inc: 'stats.levelEdits': 1}, {}, (err, docs) ->
|
||||
log.error err if err?
|
||||
statName = User.statsMapping.edits[@constructor.modelName]
|
||||
User.incrementStat userID, statName
|
||||
|
||||
next()
|
||||
|
||||
|
|
|
@ -108,6 +108,19 @@ UserSchema.statics.updateMailChimp = (doc, callback) ->
|
|||
|
||||
mc?.lists.subscribe params, onSuccess, onFailure
|
||||
|
||||
UserSchema.statics.statsMapping =
|
||||
edits:
|
||||
article: 'stats.articleEdits'
|
||||
level: 'stats.levelEdits'
|
||||
'level.component': 'stats.levelComponentEdits'
|
||||
'level.system': 'stats.levelSystemEdits'
|
||||
'thang.type': 'stats.thangTypeEdits'
|
||||
|
||||
UserSchema.statics.incrementStat = (id, statName, done, inc=1) ->
|
||||
update = $inc: {}
|
||||
update.$inc[statName] = inc
|
||||
@update {_id:id}, update, {}, (err) ->
|
||||
done err if done?
|
||||
|
||||
UserSchema.pre('save', (next) ->
|
||||
@set('emailLower', @get('email')?.toLowerCase())
|
||||
|
|
|
@ -375,7 +375,9 @@ UserHandler = class UserHandler extends Handler
|
|||
return @sendNotFoundError res unless remark?
|
||||
@sendSuccess res, remark
|
||||
|
||||
countEdits = (model, statKey, done) ->
|
||||
countEdits = (model, done) ->
|
||||
statKey = User.statsMapping.edits[model.modelName]
|
||||
return done(new Error 'Could not resolve statKey for model') unless statKey?
|
||||
User.find {}, (err, users) ->
|
||||
async.eachSeries users, ((user, doneWithUser) ->
|
||||
userID = user.get('_id').toHexString()
|
||||
|
@ -409,23 +411,23 @@ UserHandler = class UserHandler extends Handler
|
|||
|
||||
articleEdits: (done) ->
|
||||
Article = require '../articles/Article'
|
||||
countEdits Article, 'stats.articleEdits', done
|
||||
countEdits Article, done
|
||||
|
||||
levelEdits: (done) ->
|
||||
Level = require '../levels/Level'
|
||||
countEdits Level, 'stats.levelEdits', done
|
||||
countEdits Level, done
|
||||
|
||||
levelComponentEdits: (done) ->
|
||||
LevelComponent = require '../levels/components/LevelComponent'
|
||||
countEdits LevelComponent, 'stats.levelComponentEdits', done
|
||||
countEdits LevelComponent, done
|
||||
|
||||
levelSystemEdits: (done) ->
|
||||
LevelSystem = require '../levels/systems/LevelSystem'
|
||||
countEdits LevelSystem, 'stats.levelSystemEdits', done
|
||||
countEdits LevelSystem, done
|
||||
|
||||
thangTypeEdits: (done) ->
|
||||
ThangType = require '../levels/thangs/ThangType'
|
||||
countEdits ThangType, 'stats.thangTypeEdits', done
|
||||
countEdits ThangType, done
|
||||
|
||||
|
||||
recalculate: (req, res, statName) ->
|
||||
|
|
|
@ -272,6 +272,10 @@ describe 'GET /db/user', ->
|
|||
describe 'Statistics', ->
|
||||
LevelSession = require '../../../server/levels/sessions/LevelSession'
|
||||
Article = require '../../../server/articles/Article'
|
||||
Level = require '../../../server/levels/Level'
|
||||
LevelSystem = require '../../../server/levels/systems/LevelSystem'
|
||||
LevelComponent = require '../../../server/levels/components/LevelComponent'
|
||||
ThangType = require '../../../server/levels/thangs/ThangType'
|
||||
User = require '../../../server/users/User'
|
||||
UserHandler = require '../../../server/users/user_handler'
|
||||
|
||||
|
@ -315,7 +319,7 @@ describe 'Statistics', ->
|
|||
url = getURL('/db/article')
|
||||
|
||||
loginAdmin (carl) ->
|
||||
expect(carl.get 'stats.articleEdits').toBeUndefined()
|
||||
expect(carl.get User.statsMapping.edits.article).toBeUndefined()
|
||||
article.creator = carl.get 'id'
|
||||
|
||||
# Create major version 1.0
|
||||
|
@ -326,7 +330,7 @@ describe 'Statistics', ->
|
|||
|
||||
User.findById carl.get('id'), (err, guy) ->
|
||||
expect(err).toBeNull()
|
||||
expect(guy.get 'stats.articleEdits').toBe 1
|
||||
expect(guy.get User.statsMapping.edits.article).toBe 1
|
||||
|
||||
# Create minor version 1.1
|
||||
request.post {uri:url, json: article}, (err, res, body) ->
|
||||
|
@ -334,7 +338,7 @@ describe 'Statistics', ->
|
|||
|
||||
User.findById carl.get('id'), (err, guy) ->
|
||||
expect(err).toBeNull()
|
||||
expect(guy.get 'stats.articleEdits').toBe 2
|
||||
expect(guy.get User.statsMapping.edits.article).toBe 2
|
||||
|
||||
done()
|
||||
|
||||
|
@ -342,16 +346,52 @@ describe 'Statistics', ->
|
|||
loginAdmin (carl) ->
|
||||
User.findByIdAndUpdate carl.get('id'), {$unset:'stats.articleEdits': ''}, (err, guy) ->
|
||||
expect(err).toBeNull()
|
||||
expect(guy.get 'stats.articleEdits').toBeUndefined()
|
||||
expect(guy.get User.statsMapping.edits.article).toBeUndefined()
|
||||
|
||||
UserHandler.statHandlers.articleEdits ->
|
||||
User.findById carl.get('id'), (err, guy) ->
|
||||
expect(err).toBeNull()
|
||||
expect(guy.get 'stats.articleEdits').toBe 2
|
||||
expect(guy.get User.statsMapping.edits.article).toBe 2
|
||||
done()
|
||||
|
||||
it 'keeps track of level edits', (done) ->
|
||||
level = new Level
|
||||
name: "King's Peak 3"
|
||||
description: 'Climb a mountain.'
|
||||
permissions: simplePermissions
|
||||
scripts: []
|
||||
thangs: []
|
||||
|
||||
loginAdmin (carl) ->
|
||||
expect(carl.get User.statsMapping.edits.level).toBeUndefined()
|
||||
level.creator = carl.get 'id'
|
||||
level.save (err) ->
|
||||
expect(err).toBeNull()
|
||||
|
||||
User.findById carl.get('id'), (err, guy) ->
|
||||
expect(err).toBeNull()
|
||||
expect(guy.get 'id').toBe carl.get 'id'
|
||||
expect(guy.get User.statsMapping.edits.level).toBe 1
|
||||
|
||||
done()
|
||||
|
||||
it 'recalculates level edits', (done) ->
|
||||
unittest.getAdmin (jose) ->
|
||||
User.findByIdAndUpdate jose.get('id'), {$unset:'stats.levelEdits':''}, (err, guy) ->
|
||||
expect(err).toBeNull()
|
||||
expect(guy.get User.statsMapping.edits.level).toBeUndefined()
|
||||
|
||||
UserHandler.statHandlers.levelEdits ->
|
||||
User.findById jose.get('id'), (err, guy) ->
|
||||
expect(err).toBeNull()
|
||||
expect(guy.get User.statsMapping.edits.level).toBe 1
|
||||
done()
|
||||
|
||||
it 'cleans up', (done) ->
|
||||
clearModels [LevelSession, Article], (err) ->
|
||||
clearModels [LevelSession, Article, Level, LevelSystem, LevelComponent, ThangType], (err) ->
|
||||
expect(err).toBeNull()
|
||||
|
||||
done()
|
||||
|
||||
|
||||
|
||||
|
|
21
test/server/unit/patch.spec.coffee
Normal file
21
test/server/unit/patch.spec.coffee
Normal file
|
@ -0,0 +1,21 @@
|
|||
require '../common'
|
||||
|
||||
describe 'schema methods', ->
|
||||
patch = new Patch
|
||||
delta:
|
||||
scripts: 0: i18n: 'aaahw yeahh'
|
||||
_t: 'a'
|
||||
|
||||
it 'is translation patch', ->
|
||||
expect(patch.isTranslationPatch()).toBeTruthy()
|
||||
patch.set 'delta.i18n', undefined
|
||||
expect(patch.isTranslationPatch()).toBeFalsy()
|
||||
|
||||
it 'is miscellaneous patch', ->
|
||||
expect(patch.isMiscPatch()).toBeTruthy()
|
||||
patch.set 'delta.thangs', undefined
|
||||
expect(patch.isMiscPatch()).toBeFalsy()
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in a new issue