Cleanup update (#1590)

* destructuring in DB controller

* deleteObject in db adapter

* Turns out we can't have delete by object ID because of ACLs...

* Fix tests

* destructure acl

* Don't reject with object
This commit is contained in:
Drew
2016-04-22 14:05:21 -07:00
committed by Florent Vilmart
parent ab827e3c2f
commit 0d094767cf
10 changed files with 192 additions and 170 deletions

View File

@@ -159,6 +159,37 @@ export class MongoStorageAdapter {
.then(collection => collection.insertOne(mongoObject));
}
// Remove all objects that match the given parse query. Parse Query should be in Parse Format.
// If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined.
// If there is some other error, reject with INTERNAL_SERVER_ERROR.
// Currently accepts the acl, schemaController, validate
// for lecacy reasons, Parse Server should later integrate acl into the query. Database adapters
// shouldn't know about acl.
deleteObjectsByQuery(className, query, acl, schemaController, validate) {
return this.adaptiveCollection(className)
.then(collection => {
let mongoWhere = transform.transformWhere(
schemaController,
className,
query,
{ validate }
);
if (acl) {
mongoWhere = transform.addWriteACL(mongoWhere, acl);
}
return collection.deleteMany(mongoWhere)
})
.then(({ result }) => {
if (result.n === 0) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
}
return Promise.resolve();
}, error => {
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error');
});
}
get transform() {
return transform;
}

View File

@@ -195,8 +195,7 @@ function transformWhere(schema, className, restWhere, options = {validate: true}
let transformKeyOptions = {query: true};
transformKeyOptions.validate = options.validate;
for (let restKey in restWhere) {
let out = transformKeyValue(schema, className, restKey, restWhere[restKey],
transformKeyOptions);
let out = transformKeyValue(schema, className, restKey, restWhere[restKey], transformKeyOptions);
mongoWhere[out.key] = out.value;
}
return mongoWhere;
@@ -333,8 +332,7 @@ function transformUpdate(schema, className, restUpdate) {
}
for (var restKey in restUpdate) {
var out = transformKeyValue(schema, className, restKey, restUpdate[restKey],
{update: true});
var out = transformKeyValue(schema, className, restKey, restUpdate[restKey], {update: true});
// If the output value is an object with any $ keys, it's an
// operator that needs to be lifted onto the top level update

View File

@@ -87,10 +87,10 @@ DatabaseController.prototype.redirectClassNameForKey = function(className, key)
// Returns a promise that resolves to the new schema.
// This does not update this.schema, because in a situation like a
// batch request, that could confuse other users of the schema.
DatabaseController.prototype.validateObject = function(className, object, query, options) {
DatabaseController.prototype.validateObject = function(className, object, query, { acl }) {
let schema;
let isMaster = !('acl' in options);
var aclGroup = options.acl || [];
let isMaster = acl === undefined;
var aclGroup = acl || [];
return this.loadSchema().then(s => {
schema = s;
if (isMaster) {
@@ -131,14 +131,18 @@ DatabaseController.prototype.untransformObject = function(
// acl: a list of strings. If the object to be updated has an ACL,
// one of the provided strings must provide the caller with
// write permissions.
DatabaseController.prototype.update = function(className, query, update, options = {}) {
DatabaseController.prototype.update = function(className, query, update, {
acl,
many,
upsert,
} = {}) {
const originalUpdate = update;
// Make a copy of the object, so we don't mutate the incoming data.
update = deepcopy(update);
var isMaster = !('acl' in options);
var aclGroup = options.acl || [];
var isMaster = acl === undefined;
var aclGroup = acl || [];
var mongoUpdate, schema;
return this.loadSchema()
.then(s => {
@@ -152,19 +156,19 @@ DatabaseController.prototype.update = function(className, query, update, options
.then(() => this.adapter.adaptiveCollection(className))
.then(collection => {
if (!isMaster) {
query = this.addPointerPermissions(schema, className, 'update', query, aclGroup);
query = this.addPointerPermissions(schema, className, 'update', query, aclGroup);
}
if (!query) {
return Promise.resolve();
}
var mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.skipValidation});
if (options.acl) {
mongoWhere = this.transform.addWriteACL(mongoWhere, options.acl);
if (acl) {
mongoWhere = this.transform.addWriteACL(mongoWhere, acl);
}
mongoUpdate = this.transform.transformUpdate(schema, className, update, {validate: !this.skipValidation});
if (options.many) {
if (many) {
return collection.updateMany(mongoWhere, mongoUpdate);
}else if (options.upsert) {
} else if (upsert) {
return collection.upsertOne(mongoWhere, mongoUpdate);
} else {
return collection.findOneAndUpdate(mongoWhere, mongoUpdate);
@@ -203,9 +207,7 @@ function sanitizeDatabaseResult(originalObject, result) {
// Returns a promise that resolves successfully when these are
// processed.
// This mutates update.
DatabaseController.prototype.handleRelationUpdates = function(className,
objectId,
update) {
DatabaseController.prototype.handleRelationUpdates = function(className, objectId, update) {
var pending = [];
var deleteMe = [];
objectId = update.objectId || objectId;
@@ -282,51 +284,42 @@ DatabaseController.prototype.removeRelation = function(key, fromClassName, fromI
// acl: a list of strings. If the object to be updated has an ACL,
// one of the provided strings must provide the caller with
// write permissions.
DatabaseController.prototype.destroy = function(className, query, options = {}) {
var isMaster = !('acl' in options);
var aclGroup = options.acl || [];
DatabaseController.prototype.destroy = function(className, query, { acl } = {}) {
const isMaster = acl === undefined;
const aclGroup = acl || [];
var schema;
return this.loadSchema()
.then(s => {
schema = s;
.then(schemaController => {
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'delete'))
.then(() => {
if (!isMaster) {
return schema.validatePermission(className, aclGroup, 'delete');
}
return Promise.resolve();
})
.then(() => this.adapter.adaptiveCollection(className))
.then(collection => {
if (!isMaster) {
query = this.addPointerPermissions(schema, className, 'delete', query, aclGroup);
query = this.addPointerPermissions(schemaController, className, 'delete', query, aclGroup);
if (!query) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
}
}
let mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.skipValidation});
if (options.acl) {
mongoWhere = this.transform.addWriteACL(mongoWhere, options.acl);
}
return collection.deleteMany(mongoWhere);
})
.then(resp => {
//Check _Session to avoid changing password failed without any session.
// TODO: @nlutsenko Stop relying on `result.n`
if (resp.result.n === 0 && className !== "_Session") {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
}
// delete by query
return this.adapter.deleteObjectsByQuery(className, query, acl, schemaController, !this.skipValidation)
.catch(error => {
// When deleting sessions while changing passwords, don't throw an error if they don't have any sessions.
if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) {
return Promise.resolve({});
}
throw error;
});
});
});
};
// Inserts an object into the database.
// Returns a promise that resolves successfully iff the object saved.
DatabaseController.prototype.create = function(className, object, options = {}) {
DatabaseController.prototype.create = function(className, object, { acl } = {}) {
// Make a copy of the object, so we don't mutate the incoming data.
let originalObject = object;
object = deepcopy(object);
var isMaster = !('acl' in options);
var aclGroup = options.acl || [];
var isMaster = acl === undefined;
var aclGroup = acl || [];
return this.validateClassName(className)
.then(() => this.loadSchema())
@@ -570,27 +563,33 @@ DatabaseController.prototype.addNotInObjectIdsIds = function(ids = null, query)
// TODO: make userIds not needed here. The db adapter shouldn't know
// anything about users, ideally. Then, improve the format of the ACL
// arg to work like the others.
DatabaseController.prototype.find = function(className, query, options = {}) {
DatabaseController.prototype.find = function(className, query, {
skip,
limit,
acl,
sort,
count,
} = {}) {
let mongoOptions = {};
if (options.skip) {
mongoOptions.skip = options.skip;
if (skip) {
mongoOptions.skip = skip;
}
if (options.limit) {
mongoOptions.limit = options.limit;
if (limit) {
mongoOptions.limit = limit;
}
let isMaster = !('acl' in options);
let aclGroup = options.acl || [];
let isMaster = acl === undefined;
let aclGroup = acl || [];
let schema = null;
let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ?
'get' :
'find';
return this.loadSchema().then(s => {
schema = s;
if (options.sort) {
if (sort) {
mongoOptions.sort = {};
for (let key in options.sort) {
for (let key in sort) {
let mongoKey = this.transform.transformKey(schema, className, key);
mongoOptions.sort[mongoKey] = options.sort[key];
mongoOptions.sort[mongoKey] = sort[key];
}
}
@@ -604,7 +603,7 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
.then(() => this.adapter.adaptiveCollection(className))
.then(collection => {
if (!isMaster) {
query = this.addPointerPermissions(schema, className, op, query, aclGroup);
query = this.addPointerPermissions(schema, className, op, query, aclGroup);
}
if (!query) {
if (op == 'get') {
@@ -618,7 +617,7 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
if (!isMaster) {
mongoWhere = this.transform.addReadACL(mongoWhere, aclGroup);
}
if (options.count) {
if (count) {
delete mongoOptions.limit;
return collection.count(mongoWhere, mongoOptions);
} else {

View File

@@ -1,6 +1,6 @@
var request = require("request");
const send = function(method, path, body) {
var Parse = require("parse/node").Parse;
var options = {
@@ -12,7 +12,7 @@ const send = function(method, path, body) {
'Content-Type': 'application/json'
},
};
if (body) {
if (typeof body == "object") {
options.body = JSON.stringify(body);
@@ -20,7 +20,7 @@ const send = function(method, path, body) {
options.body = body;
}
}
var promise = new Parse.Promise();
request(options, function(err, response, body){
if (err) {