mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 09:35:39 -05:00
Added admin/pending-patches view. Fixed accepting several kinds of patches. Added keyboard shortcuts for accepting (a) and rejecting (r) patches. Fixed #2490. Fixed #2515. Fixed #2304.
This commit is contained in:
parent
37d7b4661c
commit
0b1bb6a4aa
23 changed files with 269 additions and 17 deletions
|
@ -39,6 +39,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'admin/users': go('admin/UsersView')
|
||||
'admin/base': go('admin/BaseView')
|
||||
'admin/user-code-problems': go('admin/UserCodeProblemsView')
|
||||
'admin/pending-patches': go('admin/PendingPatchesView')
|
||||
|
||||
'beta': go('HomeView')
|
||||
|
||||
|
|
|
@ -5,3 +5,44 @@ module.exports = class Poll extends CocoModel
|
|||
@className: 'Poll'
|
||||
@schema: schema
|
||||
urlRoot: '/db/poll'
|
||||
|
||||
applyDelta: (delta) ->
|
||||
# Hackiest hacks ever, just manually mauling the delta (whose format I don't understand) to not overwrite votes and other languages' nested translations.
|
||||
# One still must be careful about patches that accidentally delete keys from the top-level i18n object.
|
||||
i18nDelta = {}
|
||||
if delta.i18n
|
||||
i18nDelta.i18n = $.extend true, {}, delta.i18n
|
||||
for answerIndex, answerChanges of delta.answers ? {}
|
||||
i18nDelta.answers ?= {}
|
||||
if _.isArray answerChanges
|
||||
i18nDelta.answers[answerIndex] ?= []
|
||||
for change in answerChanges
|
||||
if _.isNumber change
|
||||
pickedChange = change
|
||||
else
|
||||
pickedChange = $.extend true, {}, change
|
||||
for key of pickedChange
|
||||
answerIndexNum = parseInt(answerIndex.replace('_', ''), 10)
|
||||
unless _.isNaN answerIndexNum
|
||||
oldValue = @get('answers')[answerIndexNum][key]
|
||||
isDeletion = _.string.startsWith answerIndex, '_'
|
||||
isI18N = key is 'i18n'
|
||||
if isI18N and not isDeletion
|
||||
# Use the new change, but make sure we're not deleting any other languages' translations.
|
||||
value = pickedChange[key]
|
||||
for language, oldTranslations of oldValue ? {}
|
||||
for translationKey, translationValue of oldTranslations ? {}
|
||||
value[language] ?= {}
|
||||
value[language][translationKey] ?= translationValue
|
||||
else
|
||||
value = oldValue
|
||||
pickedChange[key] = value
|
||||
i18nDelta.answers[answerIndex].push pickedChange
|
||||
else
|
||||
i18nDelta.answers[answerIndex] = answerChanges
|
||||
if answerChanges?.votes
|
||||
i18nDelta.answers[answerIndex] = _.omit answerChanges, 'votes'
|
||||
|
||||
#console.log 'got delta', delta
|
||||
#console.log 'got i18nDelta', i18nDelta
|
||||
super i18nDelta
|
||||
|
|
|
@ -21,3 +21,9 @@
|
|||
bottom: 0
|
||||
right: 0
|
||||
width: 75%
|
||||
|
||||
.patches-view
|
||||
position: absolute
|
||||
left: 20px
|
||||
top: 20px
|
||||
z-index: 30
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
.status-buttons
|
||||
margin-bottom: 10px
|
||||
|
||||
.patch-icon
|
||||
.patch-row
|
||||
cursor: pointer
|
||||
|
|
|
@ -43,6 +43,8 @@ block content
|
|||
a(href="/admin/base", data-i18n="admin.av_other_debug_base_url") Base (for debugging base.jade)
|
||||
li
|
||||
a(href="/admin/clas", data-i18n="admin.clas") CLAs
|
||||
li
|
||||
a(href="/admin/pending-patches", data-i18n="resources.patches") Patches
|
||||
if me.isAdmin()
|
||||
li Analytics
|
||||
ul
|
||||
|
|
20
app/templates/admin/pending-patches-view.jade
Normal file
20
app/templates/admin/pending-patches-view.jade
Normal file
|
@ -0,0 +1,20 @@
|
|||
extends /templates/base
|
||||
|
||||
block content
|
||||
|
||||
h1(data-i18n="resources.patches") Patches
|
||||
|
||||
table.table.table-striped.table-bordered.table-condensed#patches
|
||||
tbody
|
||||
each patch in patches
|
||||
tr
|
||||
td #{patch.target.collection}
|
||||
|
||||
td
|
||||
if patch.url
|
||||
a(href=patch.url)= patch.name
|
||||
else
|
||||
span= patch.target.original
|
||||
td #{patch.creatorName}
|
||||
td #{patch.commitMessage}
|
||||
|
|
@ -33,6 +33,11 @@ block header
|
|||
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
|
||||
li(class=anonymous ? "disabled": "")
|
||||
a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n
|
||||
if me.isAdmin()
|
||||
li#patches-button
|
||||
a
|
||||
span.spr.glyphicon-wrench.glyphicon
|
||||
span(data-i18n="resources.patches") Patches
|
||||
li.divider
|
||||
li.dropdown-header(data-i18n="common.info") Info
|
||||
|
||||
|
@ -44,5 +49,6 @@ block outer_content
|
|||
#right-column
|
||||
#campaign-view
|
||||
#campaign-level-view.hidden
|
||||
.patches-view.hidden
|
||||
|
||||
block footer
|
||||
|
|
|
@ -20,11 +20,8 @@ else
|
|||
th(data-i18n="general.submitter") Submitter
|
||||
th(data-i18n="general.submitted") Submitted
|
||||
th(data-i18n="general.commit_msg") Commit Message
|
||||
th(data-i18n="general.review") Review
|
||||
for patch in patches
|
||||
tr
|
||||
tr.patch-row(data-patch-id=patch.id)
|
||||
td= patch.userName
|
||||
td= moment(patch.get('created')).format('llll')
|
||||
td= patch.get('commitMessage')
|
||||
td
|
||||
span.glyphicon.glyphicon-wrench(data-patch-id=patch.id).patch-icon
|
||||
|
|
110
app/views/admin/PendingPatchesView.coffee
Normal file
110
app/views/admin/PendingPatchesView.coffee
Normal file
|
@ -0,0 +1,110 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/admin/pending-patches-view'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Patch = require 'models/Patch'
|
||||
|
||||
class PendingPatchesCollection extends CocoCollection
|
||||
url: '/db/patch?view=pending'
|
||||
model: Patch
|
||||
|
||||
module.exports = class PatchesView extends RootView
|
||||
id: 'pending-patches-view'
|
||||
template: template
|
||||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
@nameMap = {}
|
||||
@patches = @supermodel.loadCollection(new PendingPatchesCollection(), 'patches', {cache: false}).model
|
||||
|
||||
onLoaded: ->
|
||||
super()
|
||||
@loadUserNames()
|
||||
@loadAllModelNames()
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.patches = []
|
||||
if @supermodel.finished()
|
||||
comparator = (m) -> m.target.collection + ' ' + m.target.original
|
||||
patches = _.sortBy (_.clone(patch.attributes) for patch in @patches.models), comparator
|
||||
c.patches = _.uniq patches, comparator
|
||||
for patch in c.patches
|
||||
patch.creatorName = @nameMap[patch.creator] or patch.creator
|
||||
if name = @nameMap[patch.target.original]
|
||||
patch.name = name
|
||||
patch.slug = _.string.slugify name
|
||||
patch.url = '/editor/' + switch patch.target.collection
|
||||
when 'level', 'achievement', 'article', 'campaign', 'poll'
|
||||
"#{patch.target.collection}/#{patch.slug}"
|
||||
when 'thang_type'
|
||||
"thang/#{patch.slug}"
|
||||
when 'level_system', 'level_component'
|
||||
"level/items?#{patch.target.collection}=#{patch.slug}"
|
||||
else
|
||||
console.log "Where do we review a #{patch.target.collection} patch?"
|
||||
''
|
||||
c
|
||||
|
||||
loadUserNames: ->
|
||||
# Only fetch the names for the userIDs we don't already have in @nameMap
|
||||
ids = []
|
||||
for patch in @patches.models
|
||||
unless id = patch.get('creator')
|
||||
console.error 'Found bad user ID in malformed patch', patch
|
||||
continue
|
||||
ids.push id unless @nameMap[id]
|
||||
ids = _.uniq ids
|
||||
return unless ids.length
|
||||
|
||||
success = (nameMap) =>
|
||||
return if @destroyed
|
||||
for patch in @patches.models
|
||||
creatorID = patch.get 'creator'
|
||||
continue if @nameMap[creatorID]
|
||||
creator = nameMap[creatorID]
|
||||
name = creator?.name
|
||||
name ||= creator.firstName + ' ' + creator.lastName if creator?.firstName
|
||||
name ||= "Anonymous #{creatorID.substr(18)}" if creator
|
||||
name ||= '<bad patch data>'
|
||||
if name.length > 21
|
||||
name = name.substr(0, 18) + '...'
|
||||
@nameMap[creatorID] = name
|
||||
@render()
|
||||
|
||||
userNamesRequest = @supermodel.addRequestResource 'user_names', {
|
||||
url: '/db/user/-/names'
|
||||
data: {ids: ids}
|
||||
method: 'POST'
|
||||
success: success
|
||||
}, 0
|
||||
userNamesRequest.load()
|
||||
|
||||
loadAllModelNames: ->
|
||||
allPatches = (p.attributes for p in @patches.models)
|
||||
allPatches = _.groupBy allPatches, (p) -> p.target.collection
|
||||
@loadCollectionModelNames collection, patches for collection, patches of allPatches
|
||||
|
||||
loadCollectionModelNames: (collection, patches) ->
|
||||
ids = (patch.target.original for patch in patches when not @nameMap[patch.target.original])
|
||||
ids = _.uniq ids
|
||||
return unless ids.length
|
||||
success = (nameMapArray) =>
|
||||
return if @destroyed
|
||||
nameMap = {}
|
||||
for model in nameMapArray
|
||||
nameMap[model.original or model._id] = model.name
|
||||
for patch in patches
|
||||
original = patch.target.original
|
||||
name = nameMap[original]
|
||||
if name and name.length > 60
|
||||
name = name.substr(0, 57) + '...'
|
||||
@nameMap[original] = name
|
||||
@render()
|
||||
|
||||
modelNamesRequest = @supermodel.addRequestResource 'patches', {
|
||||
url: "/db/#{collection}/names"
|
||||
data: {ids: ids}
|
||||
method: 'POST'
|
||||
success: success
|
||||
}, 0
|
||||
modelNamesRequest.load()
|
|
@ -9,12 +9,17 @@ module.exports = class PatchModal extends ModalView
|
|||
template: template
|
||||
plain: true
|
||||
modalWidthPercent: 60
|
||||
instant: true
|
||||
|
||||
events:
|
||||
'click #withdraw-button': 'withdrawPatch'
|
||||
'click #reject-button': 'rejectPatch'
|
||||
'click #accept-button': 'acceptPatch'
|
||||
|
||||
shortcuts:
|
||||
'a': 'acceptPatch'
|
||||
'r': 'rejectPatch'
|
||||
|
||||
constructor: (@patch, @targetModel, options) ->
|
||||
super(options)
|
||||
targetID = @patch.get('target').id
|
||||
|
|
|
@ -11,7 +11,7 @@ module.exports = class PatchesView extends CocoView
|
|||
|
||||
events:
|
||||
'change .status-buttons': 'onStatusButtonsChanged'
|
||||
'click .patch-icon': 'openPatchModal'
|
||||
'click .patch-row': 'openPatchModal'
|
||||
|
||||
constructor: (@model, options) ->
|
||||
super(options)
|
||||
|
@ -51,8 +51,8 @@ module.exports = class PatchesView extends CocoView
|
|||
@render()
|
||||
|
||||
openPatchModal: (e) ->
|
||||
console.log 'open patch modal'
|
||||
patch = _.find @patches.models, {id: $(e.target).data('patch-id')}
|
||||
row = $(e.target).closest '.patch-row'
|
||||
patch = _.find @patches.models, {id: row.data('patch-id')}
|
||||
modal = new PatchModal(patch, @model)
|
||||
@openModalView(modal)
|
||||
@listenTo modal, 'accepted-patch', -> @trigger 'accepted-patch'
|
||||
|
|
|
@ -28,6 +28,9 @@ module.exports = class AchievementEditView extends RootView
|
|||
onLoaded: ->
|
||||
super()
|
||||
@buildTreema()
|
||||
@listenTo @achievement, 'change', =>
|
||||
@achievement.updateI18NCoverage()
|
||||
@treema.set('/', @achievement.attributes)
|
||||
|
||||
buildTreema: ->
|
||||
return if @treema? or (not @achievement.loaded)
|
||||
|
|
|
@ -29,6 +29,9 @@ module.exports = class ArticleEditView extends RootView
|
|||
onLoaded: ->
|
||||
super()
|
||||
@buildTreema()
|
||||
@listenTo @article, 'change', =>
|
||||
@article.updateI18NCoverage()
|
||||
@treema.set('/', @article.attributes)
|
||||
|
||||
buildTreema: ->
|
||||
return if @treema? or (not @article.loaded)
|
||||
|
|
|
@ -11,6 +11,7 @@ RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollecti
|
|||
CampaignAnalyticsModal = require './CampaignAnalyticsModal'
|
||||
CampaignLevelView = require './CampaignLevelView'
|
||||
SaveCampaignModal = require './SaveCampaignModal'
|
||||
PatchesView = require 'views/editor/PatchesView'
|
||||
|
||||
achievementProject = ['related', 'rewards', 'name', 'slug']
|
||||
thangTypeProject = ['name', 'original']
|
||||
|
@ -23,6 +24,7 @@ module.exports = class CampaignEditorView extends RootView
|
|||
events:
|
||||
'click #analytics-button': 'onClickAnalyticsButton'
|
||||
'click #save-button': 'onClickSaveButton'
|
||||
'click #patches-button': 'onClickPatches'
|
||||
|
||||
subscriptions:
|
||||
'editor:campaign-analytics-modal-closed' : 'onAnalyticsModalClosed'
|
||||
|
@ -31,6 +33,9 @@ module.exports = class CampaignEditorView extends RootView
|
|||
super(options)
|
||||
@campaign = new Campaign({_id:@campaignHandle})
|
||||
@supermodel.loadModel(@campaign, 'campaign')
|
||||
@listenToOnce @campaign, 'sync', (model, response, jqXHR) ->
|
||||
@campaign.set '_id', response._id
|
||||
@campaign.url = -> '/db/campaign/' + @id
|
||||
|
||||
# Save reference to data used by anlytics modal so it persists across modal open/closes.
|
||||
@campaignAnalytics = {}
|
||||
|
@ -140,6 +145,11 @@ module.exports = class CampaignEditorView extends RootView
|
|||
c.campaign = @campaign
|
||||
c
|
||||
|
||||
onClickPatches: (e) ->
|
||||
@patchesView = @insertSubView(new PatchesView(@campaign), @$el.find('.patches-view'))
|
||||
@patchesView.load()
|
||||
@patchesView.$el.removeClass 'hidden'
|
||||
|
||||
onClickAnalyticsButton: ->
|
||||
@openModalView new CampaignAnalyticsModal {}, @campaignHandle, @campaignAnalytics
|
||||
|
||||
|
@ -183,6 +193,11 @@ module.exports = class CampaignEditorView extends RootView
|
|||
@listenTo @campaignView, 'adjacent-campaign-moved', @onAdjacentCampaignMoved
|
||||
@listenTo @campaignView, 'level-clicked', @onCampaignLevelClicked
|
||||
@listenTo @campaignView, 'level-double-clicked', @onCampaignLevelDoubleClicked
|
||||
@listenTo @campaign, 'change:i18n', =>
|
||||
@campaign.updateI18NCoverage()
|
||||
@treema.set('/i18n', @campaign.get('i18n'))
|
||||
@treema.set('/i18nCoverage', @campaign.get('i18nCoverage'))
|
||||
|
||||
@insertSubView @campaignView
|
||||
|
||||
onTreemaChanged: (e, nodes) =>
|
||||
|
|
|
@ -40,6 +40,9 @@ module.exports = class PollEditView extends RootView
|
|||
onLoaded: ->
|
||||
super()
|
||||
@buildTreema()
|
||||
@listenTo @poll, 'change', =>
|
||||
@poll.updateI18NCoverage()
|
||||
@treema.set('/', @poll.attributes)
|
||||
|
||||
buildTreema: ->
|
||||
return if @treema? or (not @poll.loaded)
|
||||
|
|
|
@ -31,6 +31,7 @@ module.exports = class MyMatchesTabView extends CocoView
|
|||
continue
|
||||
ids.push id unless @nameMap[id]
|
||||
|
||||
ids = _.uniq ids
|
||||
return unless ids.length
|
||||
|
||||
success = (nameMap) =>
|
||||
|
|
|
@ -7,12 +7,18 @@ for(var i in patches) {
|
|||
if(patch.target.collection === 'level') collection = db.levels;
|
||||
if(patch.target.collection === 'level_component') collection = db.level.components;
|
||||
if(patch.target.collection === 'level_system') collection = db.level.systems;
|
||||
if(patch.target.collection === 'thang_type') collection = db.level.thang.types;
|
||||
if(patch.target.collection === 'thang_type') collection = db.thang.types;
|
||||
if(patch.target.collection === 'achievement') collection = db.achievements;
|
||||
if(patch.target.collection === 'article') collection = db.articles;
|
||||
if(patch.target.collection === 'campaign') collection = db.campaigns;
|
||||
if(patch.target.collection === 'poll') collection = db.polls;
|
||||
if(collection === null) {
|
||||
print('could not find collection', patch.target.collection);
|
||||
continue;
|
||||
}
|
||||
var target = collection.findOne({original:patch.target.original, name:{$exists:true}});
|
||||
if(target === null)
|
||||
target = collection.findOne({_id:ObjectId(patch.target.original), name:{$exists:true}});
|
||||
var creator = db.users.findOne({_id:patch.creator});
|
||||
if(target === null) {
|
||||
print('No target for patch from', patch.target.collection);
|
||||
|
@ -23,5 +29,11 @@ for(var i in patches) {
|
|||
continue;
|
||||
}
|
||||
|
||||
print(target.name, 'made by', creator.name);
|
||||
var editor = patch.target.collection + '/';
|
||||
if(editor === 'level_component/' || editor === 'level_system/')
|
||||
editor = 'level/items?' + patch.target.collection + '=';
|
||||
if(editor === 'thang_type/')
|
||||
editor = 'thang/';
|
||||
var url = 'http://localhost:3000/editor/' + editor + target.slug;
|
||||
print(url + '\t' + creator.name + '\t' + target.name);
|
||||
}
|
|
@ -57,4 +57,6 @@ class AchievementHandler extends Handler
|
|||
return @sendDatabaseError(res, err) if err
|
||||
@sendNoContent res
|
||||
|
||||
getNamesByIDs: (req, res) -> @getNamesByOriginals req, res, true
|
||||
|
||||
module.exports = new AchievementHandler()
|
||||
|
|
|
@ -100,4 +100,6 @@ CampaignHandler = class CampaignHandler extends Handler
|
|||
docLink = "http://codecombat.com#{req.headers['x-current-path']}"
|
||||
@sendChangedHipChatMessage creator: req.user, target: doc, docLink: docLink
|
||||
|
||||
getNamesByIDs: (req, res) -> @getNamesByOriginals req, res, true
|
||||
|
||||
module.exports = new CampaignHandler()
|
||||
|
|
|
@ -228,7 +228,7 @@ module.exports = class Handler
|
|||
return @getNamesByOriginals(req, res)
|
||||
@getPropertiesFromMultipleDocuments res, User, 'name', ids
|
||||
|
||||
getNamesByOriginals: (req, res) ->
|
||||
getNamesByOriginals: (req, res, nonVersioned=false) ->
|
||||
ids = req.query.ids or req.body.ids
|
||||
ids = ids.split(',') if _.isString ids
|
||||
ids = _.uniq ids
|
||||
|
@ -236,11 +236,12 @@ module.exports = class Handler
|
|||
# Hack: levels loading thang types need the components returned as well.
|
||||
# Need a way to specify a projection for a query.
|
||||
project = {name: 1, original: 1, kind: 1, components: 1}
|
||||
sort = {'version.major':-1, 'version.minor':-1}
|
||||
sort = if nonVersioned then {} else {'version.major': -1, 'version.minor': -1}
|
||||
|
||||
makeFunc = (id) =>
|
||||
(callback) =>
|
||||
criteria = {original:mongoose.Types.ObjectId(id)}
|
||||
criteria = {}
|
||||
criteria[if nonVersioned then '_id' else 'original'] = mongoose.Types.ObjectId(id)
|
||||
@modelClass.findOne(criteria, project).sort(sort).exec (err, document) ->
|
||||
return done(err) if err
|
||||
callback(null, document?.toObject() or null)
|
||||
|
|
|
@ -25,6 +25,17 @@ PatchHandler = class PatchHandler extends Handler
|
|||
return @setStatus(req, res, args[0]) if req.route.method is 'put' and args[1] is 'status'
|
||||
super(arguments...)
|
||||
|
||||
get: (req, res) ->
|
||||
if req.query.view in ['pending']
|
||||
query = status: 'pending'
|
||||
q = Patch.find(query)
|
||||
q.exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
documents = (@formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, documents)
|
||||
else
|
||||
super(arguments...)
|
||||
|
||||
setStatus: (req, res, id) ->
|
||||
newStatus = req.body.status
|
||||
unless newStatus in ['rejected', 'accepted', 'withdrawn']
|
||||
|
@ -37,7 +48,7 @@ PatchHandler = class PatchHandler extends Handler
|
|||
targetHandler = require('../' + handlers[targetInfo.collection])
|
||||
targetModel = targetHandler.modelClass
|
||||
|
||||
query = { 'original': targetInfo.original }
|
||||
query = { $or: [{'original': targetInfo.original}, {'_id': mongoose.Types.ObjectId(targetInfo.original)}] }
|
||||
sort = { 'version.major': -1, 'version.minor': -1 }
|
||||
targetModel.findOne(query).sort(sort).exec (err, target) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
|
|
|
@ -52,4 +52,6 @@ PollHandler = class PollHandler extends Handler
|
|||
return @sendDatabaseError(res, err) if err
|
||||
@sendNoContent res
|
||||
|
||||
getNamesByIDs: (req, res) -> @getNamesByOriginals req, res, true
|
||||
|
||||
module.exports = new PollHandler()
|
||||
|
|
|
@ -159,18 +159,27 @@ UserSchema.statics.statsMapping =
|
|||
'level.component': 'stats.levelComponentEdits'
|
||||
'level.system': 'stats.levelSystemEdits'
|
||||
'thang.type': 'stats.thangTypeEdits'
|
||||
'Achievement': 'stats.achievementEdits'
|
||||
'campaign': 'stats.campaignEdits'
|
||||
'poll': 'stats.pollEdits'
|
||||
translations:
|
||||
article: 'stats.articleTranslationPatches'
|
||||
level: 'stats.levelTranslationPatches'
|
||||
'level.component': 'stats.levelComponentTranslationPatches'
|
||||
'level.system': 'stats.levelSystemTranslationPatches'
|
||||
'thang.type': 'stats.thangTypeTranslationPatches'
|
||||
'Achievement': 'stats.achievementTranslationPatches'
|
||||
'campaign': 'stats.campaignTranslationPatches'
|
||||
'poll': 'stats.pollTranslationPatches'
|
||||
misc:
|
||||
article: 'stats.articleMiscPatches'
|
||||
level: 'stats.levelMiscPatches'
|
||||
'level.component': 'stats.levelComponentMiscPatches'
|
||||
'level.system': 'stats.levelSystemMiscPatches'
|
||||
'thang.type': 'stats.thangTypeMiscPatches'
|
||||
'Achievement': 'stats.achievementMiscPatches'
|
||||
'campaign': 'stats.campaignMiscPatches'
|
||||
'poll': 'stats.pollMiscPatches'
|
||||
|
||||
UserSchema.statics.incrementStat = (id, statName, done, inc=1) ->
|
||||
id = mongoose.Types.ObjectId id if _.isString id
|
||||
|
@ -178,7 +187,7 @@ UserSchema.statics.incrementStat = (id, statName, done, inc=1) ->
|
|||
log.error err if err?
|
||||
err = new Error "Could't find user with id '#{id}'" unless user or err
|
||||
return done() if err?
|
||||
user.incrementStat statName, done, inc=1
|
||||
user.incrementStat statName, done, inc
|
||||
|
||||
UserSchema.methods.incrementStat = (statName, done, inc=1) ->
|
||||
@set statName, (@get(statName) or 0) + inc
|
||||
|
|
Loading…
Reference in a new issue