codecombat/server/middleware/patches.coffee
Scott Erickson be1c1323ac Patch fixes
* Partially revert GET /db/:collection/:handle/patches, as it returned limited results for versioned docs
* Fix POST /db/:collection/:handle/patch to:
   * normalize diffs based on latest doc version
   * handle empty deltas
   * not swallow thrown errors due to _.curry
   * Set 'target.original' correctly for versioned collections
2016-09-16 16:02:56 -07:00

80 lines
2.9 KiB
CoffeeScript

utils = require '../lib/utils'
errors = require '../commons/errors'
wrap = require 'co-express'
database = require '../commons/database'
parse = require '../commons/parse'
{ postPatch } = require './patchable'
mongoose = require 'mongoose'
Patch = require '../models/Patch'
User = require '../models/User'
# TODO: Standardize model names so that this map is unnecessary
collectionNameMap = {
'achievement': 'Achievement'
'level_component': 'level.component'
'level_system': 'level.system'
'thang_type': 'thang.type'
}
module.exports.post = wrap (req, res) ->
# Based on body, figure out what collection and document this patch is for
if not req.body.target?.collection
throw new errors.UnprocessableEntity('target.collection not provided')
collection = req.body.target?.collection
modelName = collectionNameMap[collection] or collection
try
Model = mongoose.model(modelName)
catch e
if e.name is 'MissingSchemaError'
throw new errors.NotFound("#{collection} is not a known model")
else
throw e
if not Model.schema.is_patchable
throw new errors.UnprocessableEntity("#{collection} is not patchable")
# pass to logic shared with "POST /db/:collection/:handle/patch"
yield postPatch(Model, collection)(req, res)
# Allow patch submitters to withdraw their patches, or admins/artisans to accept/reject others' patches
module.exports.setStatus = wrap (req, res) ->
newStatus = req.body.status or req.body
unless newStatus in ['rejected', 'accepted', 'withdrawn']
throw new errors.UnprocessableEntity('Status must be "rejected", "accepted", or "withdrawn"')
patch = yield database.getDocFromHandle(req, Patch)
if not patch
throw new errors.NotFound('Could not find patch')
# Find the target of the patch
collection = patch.get('target.collection')
modelName = collectionNameMap[collection] or collection
Model = mongoose.model(modelName)
original = patch.get('target.original')
query = { $or: [{original}, {'_id': mongoose.Types.ObjectId(original)}] }
sort = { 'version.major': -1, 'version.minor': -1 }
target = yield Model.findOne(query).sort(sort)
if not target
throw new errors.NotFound('Could not find patch target')
# Enforce permissions
if newStatus in ['rejected', 'accepted']
unless req.user.hasPermission('artisan') or target.hasPermissionsForMethod?(req.user, 'put')
throw new errors.Forbidden('You do not have access to or own the target document.')
if newStatus is 'withdrawn'
unless req.user._id.equals patch.get('creator')
throw new errors.Forbidden('Only the patch creator can withdraw their patch.')
patch.set 'status', newStatus
# Only increment statistics upon very first accept
if patch.isNewlyAccepted()
acceptor = req.user.id
patch.set { acceptor }
yield User.incrementStat acceptor, 'stats.patchesAccepted'
yield patch.save()
target.update {$pull: {patches:patch.get('_id')}}, {}, _.noop
res.send(patch.toObject({req}))