From 19271fa1efc8c26900d45cfeae078f03e0e8a26a Mon Sep 17 00:00:00 2001 From: jb Date: Sat, 12 Nov 2016 09:35:34 -0800 Subject: [PATCH] Adding support for AfterFind (#2968) --- spec/CloudCode.spec.js | 132 +++++++++++++++++++++++++++++++++- src/RestQuery.js | 19 +++++ src/cloud-code/Parse.Cloud.js | 5 ++ src/triggers.js | 48 ++++++++++++- 4 files changed, 202 insertions(+), 2 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 15251ee8..8da21f86 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1306,4 +1306,134 @@ describe('beforeFind hooks', () => { done(); }); }); -}) +}); + +describe('afterFind hooks', () => { + it('should add afterFind trigger using get',(done) => { + Parse.Cloud.afterFind('MyObject', (req, res) => { + for(let i = 0 ; i < req.objects.length ; i++){ + req.objects[i].set("secretField","###"); + } + res.success(req.objects); + }); + let obj = new Parse.Object('MyObject'); + obj.set('secretField', 'SSID'); + obj.save().then(function() { + let query = new Parse.Query('MyObject'); + query.get(obj.id).then(function(result) { + expect(result.get('secretField')).toEqual('###'); + done(); + }, function(error) { + fail(error); + done(); + }); + }, function(error) { + fail(error); + done(); + }); + }); + + it('should add afterFind trigger using find',(done) => { + Parse.Cloud.afterFind('MyObject', (req, res) => { + for(let i = 0 ; i < req.objects.length ; i++){ + req.objects[i].set("secretField","###"); + } + res.success(req.objects); + }); + let obj = new Parse.Object('MyObject'); + obj.set('secretField', 'SSID'); + obj.save().then(function() { + let query = new Parse.Query('MyObject'); + query.equalTo('objectId',obj.id); + query.find().then(function(results) { + expect(results[0].get('secretField')).toEqual('###'); + done(); + }, function(error) { + fail(error); + done(); + }); + }, function(error) { + fail(error); + done(); + }); + }); + + it('should filter out results',(done) => { + Parse.Cloud.afterFind('MyObject', (req, res) => { + let filteredResults = []; + for(let i = 0 ; i < req.objects.length ; i++){ + if(req.objects[i].get("secretField")==="SSID1") { + filteredResults.push(req.objects[i]); + } + } + res.success(filteredResults); + }); + let obj0 = new Parse.Object('MyObject'); + obj0.set('secretField', 'SSID1'); + let obj1 = new Parse.Object('MyObject'); + obj1.set('secretField', 'SSID2'); + Parse.Object.saveAll([obj0, obj1]).then(function() { + let query = new Parse.Query('MyObject'); + query.find().then(function(results) { + expect(results[0].get('secretField')).toEqual('SSID1'); + expect(results.length).toEqual(1); + done(); + }, function(error) { + fail(error); + done(); + }); + }, function(error) { + fail(error); + done(); + }); + }); + + it('should handle failures',(done) => { + Parse.Cloud.afterFind('MyObject', (req, res) => { + res.error(Parse.Error.SCRIPT_FAILED, "It should fail"); + }); + let obj = new Parse.Object('MyObject'); + obj.set('secretField', 'SSID'); + obj.save().then(function() { + let query = new Parse.Query('MyObject'); + query.equalTo('objectId',obj.id); + query.find().then(function(results) { + fail("AfterFind should handle response failure correctly"); + done(); + }, function(error) { + done(); + }); + }, function(error) { + done(); + }); + }); + + it('should also work with promise',(done) => { + Parse.Cloud.afterFind('MyObject', (req, res) => { + let promise = new Parse.Promise(); + setTimeout(function(){ + for(let i = 0 ; i < req.objects.length ; i++){ + req.objects[i].set("secretField","###"); + } + promise.resolve(req.objects); + }, 1000); + return promise; + }); + let obj = new Parse.Object('MyObject'); + obj.set('secretField', 'SSID'); + obj.save().then(function() { + let query = new Parse.Query('MyObject'); + query.equalTo('objectId',obj.id); + query.find().then(function(results) { + expect(results[0].get('secretField')).toEqual('###'); + done(); + }, function(error) { + fail(error); + }); + }, function(error) { + fail(error); + }); + }); + +}); + diff --git a/src/RestQuery.js b/src/RestQuery.js index ecd9840f..1ac98ed1 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -3,6 +3,7 @@ var SchemaController = require('./Controllers/SchemaController'); var Parse = require('parse/node').Parse; +const triggers = require('./triggers'); import { default as FilesController } from './Controllers/FilesController'; @@ -122,6 +123,8 @@ RestQuery.prototype.execute = function(executeOptions) { return this.runCount(); }).then(() => { return this.handleInclude(); + }).then(() => { + return this.runAfterFindTrigger(); }).then(() => { return this.response; }); @@ -468,6 +471,22 @@ RestQuery.prototype.handleInclude = function() { return pathResponse; }; +//Returns a promise of a processed set of results +RestQuery.prototype.runAfterFindTrigger = function() { + if (!this.response) { + return; + } + // Avoid doing any setup for triggers if there is no 'afterFind' trigger for this class. + const hasAfterFindHook = triggers.triggerExists(this.className, triggers.Types.afterFind, this.config.applicationId); + if (!hasAfterFindHook) { + return Promise.resolve(); + } + // Run afterFind trigger and set the new results + return triggers.maybeRunAfterFindTrigger(triggers.Types.afterFind, this.auth, this.className,this.response.results, this.config).then((results) => { + this.response.results = results; + }); +}; + // Adds included values to the response. // Path is a list of field names. // Returns a promise for an augmented response. diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 58cb44f5..450fce65 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -50,6 +50,11 @@ ParseCloud.beforeFind = function(parseClass, handler) { triggers.addTrigger(triggers.Types.beforeFind, className, handler, Parse.applicationId); }; +ParseCloud.afterFind = function(parseClass, handler) { + const className = getClassName(parseClass); + triggers.addTrigger(triggers.Types.afterFind, className, handler, Parse.applicationId); +}; + ParseCloud._removeAllHooks = () => { triggers._unregisterAll(); } diff --git a/src/triggers.js b/src/triggers.js index 0bbce382..bc353579 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -8,7 +8,8 @@ export const Types = { afterSave: 'afterSave', beforeDelete: 'beforeDelete', afterDelete: 'afterDelete', - beforeFind: 'beforeFind' + beforeFind: 'beforeFind', + afterFind: 'afterFind' }; const baseStore = function() { @@ -185,6 +186,15 @@ export function getRequestQueryObject(triggerType, auth, query, config) { export function getResponseObject(request, resolve, reject) { return { success: function(response) { + if (request.triggerName === Types.afterFind) { + if(!response){ + response = request.objects; + } + response = response.map(object => { + return object.toJSON(); + }); + return resolve(response); + } // Use the JSON response if (response && !request.object.equals(response) && request.triggerName === Types.beforeSave) { @@ -240,6 +250,42 @@ function logTriggerErrorBeforeHook(triggerType, className, input, auth, error) { }); } +export function maybeRunAfterFindTrigger(triggerType, auth, className, objects, config) { + return new Promise((resolve, reject) => { + const trigger = getTrigger(className, triggerType, config.applicationId); + if (!trigger) { + return resolve(); + } + const request = getRequestObject(triggerType, auth, null, null, config); + const response = getResponseObject(request, + object => { + resolve(object); + }, + error => { + reject(error); + }); + logTriggerSuccessBeforeHook(triggerType, className, 'AfterFind', JSON.stringify(objects), auth); + request.objects = objects.map(object => { + //setting the class name to transform into parse object + object.className=className; + return Parse.Object.fromJSON(object); + }); + const triggerPromise = trigger(request, response); + if (triggerPromise && typeof triggerPromise.then === "function") { + return triggerPromise.then(promiseResults => { + if(promiseResults) { + resolve(promiseResults); + }else{ + return reject(new Parse.Error(Parse.Error.SCRIPT_FAILED, "AfterFind expect results to be returned in the promise")); + } + }); + } + }).then((results) => { + logTriggerAfterHook(triggerType, className, JSON.stringify(results), auth); + return results; + }); +} + export function maybeRunQueryTrigger(triggerType, className, restWhere, restOptions, config, auth) { let trigger = getTrigger(className, triggerType, config.applicationId); if (!trigger) {