diff --git a/src/Adapters/Storage/Mongo/MongoCollection.js b/src/Adapters/Storage/Mongo/MongoCollection.js index cb721db0..66113c3d 100644 --- a/src/Adapters/Storage/Mongo/MongoCollection.js +++ b/src/Adapters/Storage/Mongo/MongoCollection.js @@ -47,6 +47,29 @@ export default class MongoCollection { return this._mongoCollection.count(query, { skip, limit, sort }); } + // Atomically finds and updates an object based on query. + // The result is the promise with an object that was in the database !AFTER! changes. + // Postgres Note: Translates directly to `UPDATE * SET * ... RETURNING *`, which will return data after the change is done. + findOneAndUpdate(query, update) { + // arguments: query, sort, update, options(optional) + // Setting `new` option to true makes it return the after document, not the before one. + return this._mongoCollection.findAndModify(query, [], update, { new: true }).then(document => { + // Value is the object where mongo returns multiple fields. + return document.value; + }) + } + + // Atomically find and delete an object based on query. + // The result is the promise with an object that was in the database before deleting. + // Postgres Note: Translates directly to `DELETE * FROM ... RETURNING *`, which will return data after delete is done. + findOneAndDelete(query) { + // arguments: query, sort + return this._mongoCollection.findAndRemove(query, []).then(document => { + // Value is the object where mongo returns multiple fields. + return document.value; + }); + } + drop() { return this._mongoCollection.drop(); } diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index a7d26245..91507ef8 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -142,51 +142,45 @@ DatabaseController.prototype.update = function(className, query, update, options var isMaster = !('acl' in options); var aclGroup = options.acl || []; var mongoUpdate, schema; - return this.loadSchema(acceptor).then((s) => { - schema = s; - if (!isMaster) { - return schema.validatePermission(className, aclGroup, 'update'); - } - return Promise.resolve(); - }).then(() => { - - return this.handleRelationUpdates(className, query.objectId, update); - }).then(() => { - return this.collection(className); - }).then((coll) => { - var mongoWhere = transform.transformWhere(schema, className, query); - if (options.acl) { - var writePerms = [ - {_wperm: {'$exists': false}} - ]; - for (var entry of options.acl) { - writePerms.push({_wperm: {'$in': [entry]}}); + return this.loadSchema(acceptor) + .then(s => { + schema = s; + if (!isMaster) { + return schema.validatePermission(className, aclGroup, 'update'); } - mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]}; - } - - mongoUpdate = transform.transformUpdate(schema, className, update); - - return coll.findAndModify(mongoWhere, {}, mongoUpdate, {}); - }).then((result) => { - if (!result.value) { - return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, - 'Object not found.')); - } - if (result.lastErrorObject.n != 1) { - return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, - 'Object not found.')); - } - - var response = {}; - var inc = mongoUpdate['$inc']; - if (inc) { - for (var key in inc) { - response[key] = (result.value[key] || 0) + inc[key]; + return Promise.resolve(); + }) + .then(() => this.handleRelationUpdates(className, query.objectId, update)) + .then(() => this.adaptiveCollection(className)) + .then(collection => { + var mongoWhere = transform.transformWhere(schema, className, query); + if (options.acl) { + var writePerms = [ + {_wperm: {'$exists': false}} + ]; + for (var entry of options.acl) { + writePerms.push({_wperm: {'$in': [entry]}}); + } + mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]}; } - } - return response; - }); + mongoUpdate = transform.transformUpdate(schema, className, update); + return collection.findOneAndUpdate(mongoWhere, mongoUpdate); + }) + .then(result => { + if (!result) { + return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, + 'Object not found.')); + } + + let response = {}; + let inc = mongoUpdate['$inc']; + if (inc) { + Object.keys(inc).forEach(key => { + response[key] = result[key]; + }); + } + return response; + }); }; // Processes relation-updating operations from a REST-format update. diff --git a/src/Controllers/UserController.js b/src/Controllers/UserController.js index 35da9a1f..019f71c1 100644 --- a/src/Controllers/UserController.js +++ b/src/Controllers/UserController.js @@ -40,49 +40,43 @@ export class UserController extends AdaptableController { verifyEmail(username, token) { - - return new Promise((resolve, reject) => { - + if (!this.shouldVerifyEmails) { // Trying to verify email when not enabled - if (!this.shouldVerifyEmails) { - reject(); - return; - } - - var database = this.config.database; - - database.collection('_User').then(coll => { + // TODO: Better error here. + return Promise.reject(); + } + + return this.config.database + .adaptiveCollection('_User') + .then(collection => { // Need direct database access because verification token is not a parse field - return coll.findAndModify({ + return collection.findOneAndUpdate({ username: username, - _email_verify_token: token, - }, null, {$set: {emailVerified: true}}, (err, doc) => { - if (err || !doc.value) { - reject(err); - } else { - resolve(doc.value); - } - }); + _email_verify_token: token + }, {$set: {emailVerified: true}}); + }) + .then(document => { + if (!document) { + return Promise.reject(); + } + return document; }); - - }); } checkResetTokenValidity(username, token) { - return new Promise((resolve, reject) => { - return this.config.database.collection('_User').then(coll => { - return coll.findOne({ - username: username, - _perishable_token: token, - }, (err, doc) => { - if (err || !doc) { - reject(err); - } else { - resolve(doc); - } - }); + return this.config.database.adaptiveCollection('_User') + .then(collection => { + return collection.find({ + username: username, + _perishable_token: token + }, { limit: 1 }); + }) + .then(results => { + if (results.length != 1) { + return Promise.reject(); + } + return results[0]; }); - }); } getUserIfNeeded(user) { @@ -130,24 +124,16 @@ export class UserController extends AdaptableController { } setPasswordResetToken(email) { - var database = this.config.database; - var token = randomString(25); - return new Promise((resolve, reject) => { - return database.collection('_User').then(coll => { + let token = randomString(25); + return this.config.database + .adaptiveCollection('_User') + .then(collection => { // Need direct database access because verification token is not a parse field - return coll.findAndModify({ - email: email, - }, null, {$set: {_perishable_token: token}}, (err, doc) => { - if (err || !doc.value) { - console.error(err); - reject(err); - } else { - doc.value._perishable_token = token; - resolve(doc.value); - } - }); + return collection.findOneAndUpdate( + { email: email}, // query + { $set: { _perishable_token: token } } // update + ); }); - }); } sendPasswordResetEmail(email) { diff --git a/src/Routers/SchemasRouter.js b/src/Routers/SchemasRouter.js index 007625f3..70b3157e 100644 --- a/src/Routers/SchemasRouter.js +++ b/src/Routers/SchemasRouter.js @@ -164,14 +164,14 @@ function deleteSchema(req) { .then(() => { // We've dropped the collection now, so delete the item from _SCHEMA // and clear the _Join collections - return req.config.database.collection('_SCHEMA') - .then(coll => coll.findAndRemove({_id: req.params.className}, [])) - .then(doc => { - if (doc.value === null) { + return req.config.database.adaptiveCollection('_SCHEMA') + .then(coll => coll.findOneAndDelete({_id: req.params.className})) + .then(document => { + if (document === null) { //tried to delete non-existent class return Promise.resolve(); } - return removeJoinTables(req.config.database, doc.value); + return removeJoinTables(req.config.database, document); }); }) .then(() => {