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
app
core
models
styles/editor
templates
views
scripts/mongodb/queries
server
achievements
campaigns
commons
patches
polls
users
|
@ -39,6 +39,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'admin/users': go('admin/UsersView')
|
'admin/users': go('admin/UsersView')
|
||||||
'admin/base': go('admin/BaseView')
|
'admin/base': go('admin/BaseView')
|
||||||
'admin/user-code-problems': go('admin/UserCodeProblemsView')
|
'admin/user-code-problems': go('admin/UserCodeProblemsView')
|
||||||
|
'admin/pending-patches': go('admin/PendingPatchesView')
|
||||||
|
|
||||||
'beta': go('HomeView')
|
'beta': go('HomeView')
|
||||||
|
|
||||||
|
|
|
@ -5,3 +5,44 @@ module.exports = class Poll extends CocoModel
|
||||||
@className: 'Poll'
|
@className: 'Poll'
|
||||||
@schema: schema
|
@schema: schema
|
||||||
urlRoot: '/db/poll'
|
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
|
bottom: 0
|
||||||
right: 0
|
right: 0
|
||||||
width: 75%
|
width: 75%
|
||||||
|
|
||||||
|
.patches-view
|
||||||
|
position: absolute
|
||||||
|
left: 20px
|
||||||
|
top: 20px
|
||||||
|
z-index: 30
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
.status-buttons
|
.status-buttons
|
||||||
margin-bottom: 10px
|
margin-bottom: 10px
|
||||||
|
|
||||||
.patch-icon
|
.patch-row
|
||||||
cursor: pointer
|
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)
|
a(href="/admin/base", data-i18n="admin.av_other_debug_base_url") Base (for debugging base.jade)
|
||||||
li
|
li
|
||||||
a(href="/admin/clas", data-i18n="admin.clas") CLAs
|
a(href="/admin/clas", data-i18n="admin.clas") CLAs
|
||||||
|
li
|
||||||
|
a(href="/admin/pending-patches", data-i18n="resources.patches") Patches
|
||||||
if me.isAdmin()
|
if me.isAdmin()
|
||||||
li Analytics
|
li Analytics
|
||||||
ul
|
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
|
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
|
||||||
li(class=anonymous ? "disabled": "")
|
li(class=anonymous ? "disabled": "")
|
||||||
a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n
|
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.divider
|
||||||
li.dropdown-header(data-i18n="common.info") Info
|
li.dropdown-header(data-i18n="common.info") Info
|
||||||
|
|
||||||
|
@ -44,5 +49,6 @@ block outer_content
|
||||||
#right-column
|
#right-column
|
||||||
#campaign-view
|
#campaign-view
|
||||||
#campaign-level-view.hidden
|
#campaign-level-view.hidden
|
||||||
|
.patches-view.hidden
|
||||||
|
|
||||||
block footer
|
block footer
|
||||||
|
|
|
@ -20,11 +20,8 @@ else
|
||||||
th(data-i18n="general.submitter") Submitter
|
th(data-i18n="general.submitter") Submitter
|
||||||
th(data-i18n="general.submitted") Submitted
|
th(data-i18n="general.submitted") Submitted
|
||||||
th(data-i18n="general.commit_msg") Commit Message
|
th(data-i18n="general.commit_msg") Commit Message
|
||||||
th(data-i18n="general.review") Review
|
|
||||||
for patch in patches
|
for patch in patches
|
||||||
tr
|
tr.patch-row(data-patch-id=patch.id)
|
||||||
td= patch.userName
|
td= patch.userName
|
||||||
td= moment(patch.get('created')).format('llll')
|
td= moment(patch.get('created')).format('llll')
|
||||||
td= patch.get('commitMessage')
|
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
|
template: template
|
||||||
plain: true
|
plain: true
|
||||||
modalWidthPercent: 60
|
modalWidthPercent: 60
|
||||||
|
instant: true
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click #withdraw-button': 'withdrawPatch'
|
'click #withdraw-button': 'withdrawPatch'
|
||||||
'click #reject-button': 'rejectPatch'
|
'click #reject-button': 'rejectPatch'
|
||||||
'click #accept-button': 'acceptPatch'
|
'click #accept-button': 'acceptPatch'
|
||||||
|
|
||||||
|
shortcuts:
|
||||||
|
'a': 'acceptPatch'
|
||||||
|
'r': 'rejectPatch'
|
||||||
|
|
||||||
constructor: (@patch, @targetModel, options) ->
|
constructor: (@patch, @targetModel, options) ->
|
||||||
super(options)
|
super(options)
|
||||||
targetID = @patch.get('target').id
|
targetID = @patch.get('target').id
|
||||||
|
|
|
@ -11,7 +11,7 @@ module.exports = class PatchesView extends CocoView
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'change .status-buttons': 'onStatusButtonsChanged'
|
'change .status-buttons': 'onStatusButtonsChanged'
|
||||||
'click .patch-icon': 'openPatchModal'
|
'click .patch-row': 'openPatchModal'
|
||||||
|
|
||||||
constructor: (@model, options) ->
|
constructor: (@model, options) ->
|
||||||
super(options)
|
super(options)
|
||||||
|
@ -51,8 +51,8 @@ module.exports = class PatchesView extends CocoView
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
openPatchModal: (e) ->
|
openPatchModal: (e) ->
|
||||||
console.log 'open patch modal'
|
row = $(e.target).closest '.patch-row'
|
||||||
patch = _.find @patches.models, {id: $(e.target).data('patch-id')}
|
patch = _.find @patches.models, {id: row.data('patch-id')}
|
||||||
modal = new PatchModal(patch, @model)
|
modal = new PatchModal(patch, @model)
|
||||||
@openModalView(modal)
|
@openModalView(modal)
|
||||||
@listenTo modal, 'accepted-patch', -> @trigger 'accepted-patch'
|
@listenTo modal, 'accepted-patch', -> @trigger 'accepted-patch'
|
||||||
|
|
|
@ -28,6 +28,9 @@ module.exports = class AchievementEditView extends RootView
|
||||||
onLoaded: ->
|
onLoaded: ->
|
||||||
super()
|
super()
|
||||||
@buildTreema()
|
@buildTreema()
|
||||||
|
@listenTo @achievement, 'change', =>
|
||||||
|
@achievement.updateI18NCoverage()
|
||||||
|
@treema.set('/', @achievement.attributes)
|
||||||
|
|
||||||
buildTreema: ->
|
buildTreema: ->
|
||||||
return if @treema? or (not @achievement.loaded)
|
return if @treema? or (not @achievement.loaded)
|
||||||
|
|
|
@ -29,6 +29,9 @@ module.exports = class ArticleEditView extends RootView
|
||||||
onLoaded: ->
|
onLoaded: ->
|
||||||
super()
|
super()
|
||||||
@buildTreema()
|
@buildTreema()
|
||||||
|
@listenTo @article, 'change', =>
|
||||||
|
@article.updateI18NCoverage()
|
||||||
|
@treema.set('/', @article.attributes)
|
||||||
|
|
||||||
buildTreema: ->
|
buildTreema: ->
|
||||||
return if @treema? or (not @article.loaded)
|
return if @treema? or (not @article.loaded)
|
||||||
|
|
|
@ -11,6 +11,7 @@ RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollecti
|
||||||
CampaignAnalyticsModal = require './CampaignAnalyticsModal'
|
CampaignAnalyticsModal = require './CampaignAnalyticsModal'
|
||||||
CampaignLevelView = require './CampaignLevelView'
|
CampaignLevelView = require './CampaignLevelView'
|
||||||
SaveCampaignModal = require './SaveCampaignModal'
|
SaveCampaignModal = require './SaveCampaignModal'
|
||||||
|
PatchesView = require 'views/editor/PatchesView'
|
||||||
|
|
||||||
achievementProject = ['related', 'rewards', 'name', 'slug']
|
achievementProject = ['related', 'rewards', 'name', 'slug']
|
||||||
thangTypeProject = ['name', 'original']
|
thangTypeProject = ['name', 'original']
|
||||||
|
@ -23,6 +24,7 @@ module.exports = class CampaignEditorView extends RootView
|
||||||
events:
|
events:
|
||||||
'click #analytics-button': 'onClickAnalyticsButton'
|
'click #analytics-button': 'onClickAnalyticsButton'
|
||||||
'click #save-button': 'onClickSaveButton'
|
'click #save-button': 'onClickSaveButton'
|
||||||
|
'click #patches-button': 'onClickPatches'
|
||||||
|
|
||||||
subscriptions:
|
subscriptions:
|
||||||
'editor:campaign-analytics-modal-closed' : 'onAnalyticsModalClosed'
|
'editor:campaign-analytics-modal-closed' : 'onAnalyticsModalClosed'
|
||||||
|
@ -31,6 +33,9 @@ module.exports = class CampaignEditorView extends RootView
|
||||||
super(options)
|
super(options)
|
||||||
@campaign = new Campaign({_id:@campaignHandle})
|
@campaign = new Campaign({_id:@campaignHandle})
|
||||||
@supermodel.loadModel(@campaign, 'campaign')
|
@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.
|
# Save reference to data used by anlytics modal so it persists across modal open/closes.
|
||||||
@campaignAnalytics = {}
|
@campaignAnalytics = {}
|
||||||
|
@ -140,6 +145,11 @@ module.exports = class CampaignEditorView extends RootView
|
||||||
c.campaign = @campaign
|
c.campaign = @campaign
|
||||||
c
|
c
|
||||||
|
|
||||||
|
onClickPatches: (e) ->
|
||||||
|
@patchesView = @insertSubView(new PatchesView(@campaign), @$el.find('.patches-view'))
|
||||||
|
@patchesView.load()
|
||||||
|
@patchesView.$el.removeClass 'hidden'
|
||||||
|
|
||||||
onClickAnalyticsButton: ->
|
onClickAnalyticsButton: ->
|
||||||
@openModalView new CampaignAnalyticsModal {}, @campaignHandle, @campaignAnalytics
|
@openModalView new CampaignAnalyticsModal {}, @campaignHandle, @campaignAnalytics
|
||||||
|
|
||||||
|
@ -183,6 +193,11 @@ module.exports = class CampaignEditorView extends RootView
|
||||||
@listenTo @campaignView, 'adjacent-campaign-moved', @onAdjacentCampaignMoved
|
@listenTo @campaignView, 'adjacent-campaign-moved', @onAdjacentCampaignMoved
|
||||||
@listenTo @campaignView, 'level-clicked', @onCampaignLevelClicked
|
@listenTo @campaignView, 'level-clicked', @onCampaignLevelClicked
|
||||||
@listenTo @campaignView, 'level-double-clicked', @onCampaignLevelDoubleClicked
|
@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
|
@insertSubView @campaignView
|
||||||
|
|
||||||
onTreemaChanged: (e, nodes) =>
|
onTreemaChanged: (e, nodes) =>
|
||||||
|
|
|
@ -40,6 +40,9 @@ module.exports = class PollEditView extends RootView
|
||||||
onLoaded: ->
|
onLoaded: ->
|
||||||
super()
|
super()
|
||||||
@buildTreema()
|
@buildTreema()
|
||||||
|
@listenTo @poll, 'change', =>
|
||||||
|
@poll.updateI18NCoverage()
|
||||||
|
@treema.set('/', @poll.attributes)
|
||||||
|
|
||||||
buildTreema: ->
|
buildTreema: ->
|
||||||
return if @treema? or (not @poll.loaded)
|
return if @treema? or (not @poll.loaded)
|
||||||
|
|
|
@ -31,6 +31,7 @@ module.exports = class MyMatchesTabView extends CocoView
|
||||||
continue
|
continue
|
||||||
ids.push id unless @nameMap[id]
|
ids.push id unless @nameMap[id]
|
||||||
|
|
||||||
|
ids = _.uniq ids
|
||||||
return unless ids.length
|
return unless ids.length
|
||||||
|
|
||||||
success = (nameMap) =>
|
success = (nameMap) =>
|
||||||
|
|
|
@ -7,12 +7,18 @@ for(var i in patches) {
|
||||||
if(patch.target.collection === 'level') collection = db.levels;
|
if(patch.target.collection === 'level') collection = db.levels;
|
||||||
if(patch.target.collection === 'level_component') collection = db.level.components;
|
if(patch.target.collection === 'level_component') collection = db.level.components;
|
||||||
if(patch.target.collection === 'level_system') collection = db.level.systems;
|
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) {
|
if(collection === null) {
|
||||||
print('could not find collection', patch.target.collection);
|
print('could not find collection', patch.target.collection);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var target = collection.findOne({original:patch.target.original, name:{$exists:true}});
|
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});
|
var creator = db.users.findOne({_id:patch.creator});
|
||||||
if(target === null) {
|
if(target === null) {
|
||||||
print('No target for patch from', patch.target.collection);
|
print('No target for patch from', patch.target.collection);
|
||||||
|
@ -22,6 +28,12 @@ for(var i in patches) {
|
||||||
print(target.name, 'made by unknown person...');
|
print(target.name, 'made by unknown person...');
|
||||||
continue;
|
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
|
return @sendDatabaseError(res, err) if err
|
||||||
@sendNoContent res
|
@sendNoContent res
|
||||||
|
|
||||||
|
getNamesByIDs: (req, res) -> @getNamesByOriginals req, res, true
|
||||||
|
|
||||||
module.exports = new AchievementHandler()
|
module.exports = new AchievementHandler()
|
||||||
|
|
|
@ -100,4 +100,6 @@ CampaignHandler = class CampaignHandler extends Handler
|
||||||
docLink = "http://codecombat.com#{req.headers['x-current-path']}"
|
docLink = "http://codecombat.com#{req.headers['x-current-path']}"
|
||||||
@sendChangedHipChatMessage creator: req.user, target: doc, docLink: docLink
|
@sendChangedHipChatMessage creator: req.user, target: doc, docLink: docLink
|
||||||
|
|
||||||
|
getNamesByIDs: (req, res) -> @getNamesByOriginals req, res, true
|
||||||
|
|
||||||
module.exports = new CampaignHandler()
|
module.exports = new CampaignHandler()
|
||||||
|
|
|
@ -228,7 +228,7 @@ module.exports = class Handler
|
||||||
return @getNamesByOriginals(req, res)
|
return @getNamesByOriginals(req, res)
|
||||||
@getPropertiesFromMultipleDocuments res, User, 'name', ids
|
@getPropertiesFromMultipleDocuments res, User, 'name', ids
|
||||||
|
|
||||||
getNamesByOriginals: (req, res) ->
|
getNamesByOriginals: (req, res, nonVersioned=false) ->
|
||||||
ids = req.query.ids or req.body.ids
|
ids = req.query.ids or req.body.ids
|
||||||
ids = ids.split(',') if _.isString ids
|
ids = ids.split(',') if _.isString ids
|
||||||
ids = _.uniq ids
|
ids = _.uniq ids
|
||||||
|
@ -236,11 +236,12 @@ module.exports = class Handler
|
||||||
# Hack: levels loading thang types need the components returned as well.
|
# Hack: levels loading thang types need the components returned as well.
|
||||||
# Need a way to specify a projection for a query.
|
# Need a way to specify a projection for a query.
|
||||||
project = {name: 1, original: 1, kind: 1, components: 1}
|
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) =>
|
makeFunc = (id) =>
|
||||||
(callback) =>
|
(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) ->
|
@modelClass.findOne(criteria, project).sort(sort).exec (err, document) ->
|
||||||
return done(err) if err
|
return done(err) if err
|
||||||
callback(null, document?.toObject() or null)
|
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'
|
return @setStatus(req, res, args[0]) if req.route.method is 'put' and args[1] is 'status'
|
||||||
super(arguments...)
|
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) ->
|
setStatus: (req, res, id) ->
|
||||||
newStatus = req.body.status
|
newStatus = req.body.status
|
||||||
unless newStatus in ['rejected', 'accepted', 'withdrawn']
|
unless newStatus in ['rejected', 'accepted', 'withdrawn']
|
||||||
|
@ -37,7 +48,7 @@ PatchHandler = class PatchHandler extends Handler
|
||||||
targetHandler = require('../' + handlers[targetInfo.collection])
|
targetHandler = require('../' + handlers[targetInfo.collection])
|
||||||
targetModel = targetHandler.modelClass
|
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 }
|
sort = { 'version.major': -1, 'version.minor': -1 }
|
||||||
targetModel.findOne(query).sort(sort).exec (err, target) =>
|
targetModel.findOne(query).sort(sort).exec (err, target) =>
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
|
|
|
@ -52,4 +52,6 @@ PollHandler = class PollHandler extends Handler
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
@sendNoContent res
|
@sendNoContent res
|
||||||
|
|
||||||
|
getNamesByIDs: (req, res) -> @getNamesByOriginals req, res, true
|
||||||
|
|
||||||
module.exports = new PollHandler()
|
module.exports = new PollHandler()
|
||||||
|
|
|
@ -159,18 +159,27 @@ UserSchema.statics.statsMapping =
|
||||||
'level.component': 'stats.levelComponentEdits'
|
'level.component': 'stats.levelComponentEdits'
|
||||||
'level.system': 'stats.levelSystemEdits'
|
'level.system': 'stats.levelSystemEdits'
|
||||||
'thang.type': 'stats.thangTypeEdits'
|
'thang.type': 'stats.thangTypeEdits'
|
||||||
|
'Achievement': 'stats.achievementEdits'
|
||||||
|
'campaign': 'stats.campaignEdits'
|
||||||
|
'poll': 'stats.pollEdits'
|
||||||
translations:
|
translations:
|
||||||
article: 'stats.articleTranslationPatches'
|
article: 'stats.articleTranslationPatches'
|
||||||
level: 'stats.levelTranslationPatches'
|
level: 'stats.levelTranslationPatches'
|
||||||
'level.component': 'stats.levelComponentTranslationPatches'
|
'level.component': 'stats.levelComponentTranslationPatches'
|
||||||
'level.system': 'stats.levelSystemTranslationPatches'
|
'level.system': 'stats.levelSystemTranslationPatches'
|
||||||
'thang.type': 'stats.thangTypeTranslationPatches'
|
'thang.type': 'stats.thangTypeTranslationPatches'
|
||||||
|
'Achievement': 'stats.achievementTranslationPatches'
|
||||||
|
'campaign': 'stats.campaignTranslationPatches'
|
||||||
|
'poll': 'stats.pollTranslationPatches'
|
||||||
misc:
|
misc:
|
||||||
article: 'stats.articleMiscPatches'
|
article: 'stats.articleMiscPatches'
|
||||||
level: 'stats.levelMiscPatches'
|
level: 'stats.levelMiscPatches'
|
||||||
'level.component': 'stats.levelComponentMiscPatches'
|
'level.component': 'stats.levelComponentMiscPatches'
|
||||||
'level.system': 'stats.levelSystemMiscPatches'
|
'level.system': 'stats.levelSystemMiscPatches'
|
||||||
'thang.type': 'stats.thangTypeMiscPatches'
|
'thang.type': 'stats.thangTypeMiscPatches'
|
||||||
|
'Achievement': 'stats.achievementMiscPatches'
|
||||||
|
'campaign': 'stats.campaignMiscPatches'
|
||||||
|
'poll': 'stats.pollMiscPatches'
|
||||||
|
|
||||||
UserSchema.statics.incrementStat = (id, statName, done, inc=1) ->
|
UserSchema.statics.incrementStat = (id, statName, done, inc=1) ->
|
||||||
id = mongoose.Types.ObjectId id if _.isString id
|
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?
|
log.error err if err?
|
||||||
err = new Error "Could't find user with id '#{id}'" unless user or err
|
err = new Error "Could't find user with id '#{id}'" unless user or err
|
||||||
return done() if err?
|
return done() if err?
|
||||||
user.incrementStat statName, done, inc=1
|
user.incrementStat statName, done, inc
|
||||||
|
|
||||||
UserSchema.methods.incrementStat = (statName, done, inc=1) ->
|
UserSchema.methods.incrementStat = (statName, done, inc=1) ->
|
||||||
@set statName, (@get(statName) or 0) + inc
|
@set statName, (@get(statName) or 0) + inc
|
||||||
|
|
Reference in a new issue