diff --git a/src/Adapters/Storage/Mongo/MongoCollection.js b/src/Adapters/Storage/Mongo/MongoCollection.js index 66113c3d32..84f4789931 100644 --- a/src/Adapters/Storage/Mongo/MongoCollection.js +++ b/src/Adapters/Storage/Mongo/MongoCollection.js @@ -1,4 +1,3 @@ - let mongodb = require('mongodb'); let Collection = mongodb.Collection; @@ -18,8 +17,7 @@ export default class MongoCollection { return this._rawFind(query, { skip, limit, sort }) .catch(error => { // Check for "no geoindex" error - if (error.code != 17007 || - !error.message.match(/unable to find index for .geoNear/)) { + if (error.code != 17007 || !error.message.match(/unable to find index for .geoNear/)) { throw error; } // Figure out what key needs an index @@ -59,6 +57,13 @@ export default class MongoCollection { }) } + // Atomically updates data in the database for a single (first) object that matched the query + // If there is nothing that matches the query - does insert + // Postgres Note: `INSERT ... ON CONFLICT UPDATE` that is available since 9.5. + upsertOne(query, update) { + return this._mongoCollection.update(query, update, { upsert: true }); + } + // 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. @@ -70,6 +75,10 @@ export default class MongoCollection { }); } + remove(query) { + return this._mongoCollection.remove(query); + } + drop() { return this._mongoCollection.drop(); } diff --git a/src/Controllers/HooksController.js b/src/Controllers/HooksController.js index 1e47acd782..fbc7e92024 100644 --- a/src/Controllers/HooksController.js +++ b/src/Controllers/HooksController.js @@ -8,104 +8,91 @@ import * as request from "request"; const DefaultHooksCollectionName = "_Hooks"; export class HooksController { - _applicationId: string; - _collectionPrefix: string; + _applicationId:string; + _collectionPrefix:string; _collection; - constructor(applicationId: string, collectionPrefix: string = '') { + constructor(applicationId:string, collectionPrefix:string = '') { this._applicationId = applicationId; this._collectionPrefix = collectionPrefix; } - - database() { - return DatabaseAdapter.getDatabaseConnection(this._applicationId, this._collectionPrefix); + + load() { + return this._getHooks().then(hooks => { + hooks = hooks || []; + hooks.forEach((hook) => { + this.addHookToTriggers(hook); + }); + }); } - - collection() { + + getCollection() { if (this._collection) { return Promise.resolve(this._collection) } - return this.database().rawCollection(DefaultHooksCollectionName).then((collection) => { + + let database = DatabaseAdapter.getDatabaseConnection(this._applicationId, this._collectionPrefix); + return database.adaptiveCollection(DefaultHooksCollectionName).then(collection => { this._collection = collection; return collection; }); } - + getFunction(functionName) { - return this.getOne({functionName: functionName}) + return this._getHooks({ functionName: functionName }, 1).then(results => results[0]); } - + getFunctions() { - return this.get({functionName: { $exists: true }}) + return this._getHooks({ functionName: { $exists: true } }); } - + getTrigger(className, triggerName) { - return this.getOne({className: className, triggerName: triggerName }) + return this._getHooks({ className: className, triggerName: triggerName }, 1).then(results => results[0]); } - + getTriggers() { - return this.get({className: { $exists: true }, triggerName: { $exists: true }}) + return this._getHooks({ className: { $exists: true }, triggerName: { $exists: true } }); } - + deleteFunction(functionName) { triggers.removeFunction(functionName, this._applicationId); - return this.delete({functionName: functionName}); + return this._removeHooks({ functionName: functionName }); } - + deleteTrigger(className, triggerName) { triggers.removeTrigger(triggerName, className, this._applicationId); - return this.delete({className: className, triggerName: triggerName}); - } - - delete(query) { - return this.collection().then((collection) => { - return collection.remove(query) - }).then( (res) => { - return {}; - }, 1); + return this._removeHooks({ className: className, triggerName: triggerName }); } - - getOne(query) { - return this.collection() - .then(coll => coll.findOne(query, {_id: 0})) - .then(hook => { - return hook; - }); + + _getHooks(query, limit) { + let options = limit ? { limit: limit } : undefined; + return this.getCollection().then(collection => collection.find(query, options)); } - - get(query) { - return this.collection() - .then(coll => coll.find(query, {_id: 0}).toArray()) - .then(hooks => { - return hooks; + + _removeHooks(query) { + return this.getCollection().then(collection => { + return collection.remove(query); + }).then(() => { + return {}; }); } - - getHooks() { - return this.collection() - .then(coll => coll.find({}, {_id: 0}).toArray()) - .then(hooks => { - return hooks; - }, () => ([])) - } - + saveHook(hook) { - var query; if (hook.functionName && hook.url) { - query = {functionName: hook.functionName } + query = { functionName: hook.functionName } } else if (hook.triggerName && hook.className && hook.url) { query = { className: hook.className, triggerName: hook.triggerName } } else { throw new Parse.Error(143, "invalid hook declaration"); } - return this.collection().then((collection) => { - return collection.update(query, hook, {upsert: true}) - }).then(function(res){ - return hook; - }) + return this.getCollection() + .then(collection => collection.upsertOne(query, hook)) + .then(() => { + return hook; + }); } - + addHookToTriggers(hook) { var wrappedFunction = wrapToHTTPRequest(hook); wrappedFunction.url = hook.url; @@ -114,13 +101,13 @@ export class HooksController { } else { triggers.addFunction(hook.functionName, wrappedFunction, null, this._applicationId); } - } - + } + addHook(hook) { this.addHookToTriggers(hook); return this.saveHook(hook); } - + createOrUpdateHook(aHook) { var hook; if (aHook && aHook.functionName && aHook.url) { @@ -132,19 +119,19 @@ export class HooksController { hook.className = aHook.className; hook.url = aHook.url; hook.triggerName = aHook.triggerName; - + } else { throw new Parse.Error(143, "invalid hook declaration"); - } - + } + return this.addHook(hook); }; - + createHook(aHook) { if (aHook.functionName) { return this.getFunction(aHook.functionName).then((result) => { if (result) { - throw new Parse.Error(143,`function name: ${aHook.functionName} already exits`); + throw new Parse.Error(143, `function name: ${aHook.functionName} already exits`); } else { return this.createOrUpdateHook(aHook); } @@ -152,49 +139,39 @@ export class HooksController { } else if (aHook.className && aHook.triggerName) { return this.getTrigger(aHook.className, aHook.triggerName).then((result) => { if (result) { - throw new Parse.Error(143,`class ${aHook.className} already has trigger ${aHook.triggerName}`); + throw new Parse.Error(143, `class ${aHook.className} already has trigger ${aHook.triggerName}`); } return this.createOrUpdateHook(aHook); }); } - + throw new Parse.Error(143, "invalid hook declaration"); }; - + updateHook(aHook) { if (aHook.functionName) { return this.getFunction(aHook.functionName).then((result) => { if (result) { return this.createOrUpdateHook(aHook); } - throw new Parse.Error(143,`no function named: ${aHook.functionName} is defined`); + throw new Parse.Error(143, `no function named: ${aHook.functionName} is defined`); }); } else if (aHook.className && aHook.triggerName) { return this.getTrigger(aHook.className, aHook.triggerName).then((result) => { if (result) { return this.createOrUpdateHook(aHook); } - throw new Parse.Error(143,`class ${aHook.className} does not exist`); + throw new Parse.Error(143, `class ${aHook.className} does not exist`); }); } throw new Parse.Error(143, "invalid hook declaration"); }; - - load() { - return this.getHooks().then((hooks) => { - hooks = hooks || []; - hooks.forEach((hook) => { - this.addHookToTriggers(hook); - }); - }); - } - } function wrapToHTTPRequest(hook) { - return function(req, res) { - var jsonBody = {}; - for(var i in req) { + return (req, res) => { + let jsonBody = {}; + for (var i in req) { jsonBody[i] = req[i]; } if (req.object) { @@ -205,30 +182,31 @@ function wrapToHTTPRequest(hook) { jsonBody.original = req.original.toJSON(); jsonBody.original.className = req.original.className; } - var jsonRequest = {}; - jsonRequest.headers = { - 'Content-Type': 'application/json' - } - jsonRequest.body = JSON.stringify(jsonBody); - - request.post(hook.url, jsonRequest, function(err, httpResponse, body){ + let jsonRequest = { + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(jsonBody) + }; + + request.post(hook.url, jsonRequest, function (err, httpResponse, body) { var result; if (body) { if (typeof body == "string") { try { body = JSON.parse(body); - } catch(e) { - err = {error: "Malformed response", code: -1}; + } catch (e) { + err = { error: "Malformed response", code: -1 }; } } if (!err) { result = body.success; - err = body.error; + err = body.error; } } if (err) { return res.error(err); - } else { + } else { return res.success(result); } });