Adds CloudCode handler for beforeFind (#2715)
* Adds CloudCode handler for beforeFind - Allows cloud code to modify a query before it is run - Works with promises for a safer environment - Supports modifiying the current query - Supports issuing new queries * Adds test for cornercase empty queries from rest * Makes sure restOptions is always definied
This commit is contained in:
@@ -1174,3 +1174,110 @@ it('beforeSave should not affect fetched pointers', done => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('beforeFind hooks', () => {
|
||||||
|
it('should add beforeFind trigger', (done) => {
|
||||||
|
Parse.Cloud.beforeFind('MyObject', (req, res) => {
|
||||||
|
let q = req.query;
|
||||||
|
expect(q instanceof Parse.Query).toBe(true);
|
||||||
|
let jsonQuery = q.toJSON();
|
||||||
|
expect(jsonQuery.where.key).toEqual('value');
|
||||||
|
expect(jsonQuery.where.some).toEqual({'$gt': 10});
|
||||||
|
expect(jsonQuery.include).toEqual('otherKey,otherValue');
|
||||||
|
expect(jsonQuery.limit).toEqual(100);
|
||||||
|
expect(jsonQuery.skip).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
let query = new Parse.Query('MyObject');
|
||||||
|
query.equalTo('key', 'value');
|
||||||
|
query.greaterThan('some', 10);
|
||||||
|
query.include('otherKey');
|
||||||
|
query.include('otherValue');
|
||||||
|
query.find().then(() => {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use modify', (done) => {
|
||||||
|
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||||
|
let q = req.query;
|
||||||
|
q.equalTo('forced', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
let obj0 = new Parse.Object('MyObject');
|
||||||
|
obj0.set('forced', false);
|
||||||
|
|
||||||
|
let obj1 = new Parse.Object('MyObject');
|
||||||
|
obj1.set('forced', true);
|
||||||
|
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||||
|
let query = new Parse.Query('MyObject');
|
||||||
|
query.equalTo('forced', false);
|
||||||
|
query.find().then((results) => {
|
||||||
|
expect(results.length).toBe(1);
|
||||||
|
let firstResult = results[0];
|
||||||
|
expect(firstResult.get('forced')).toBe(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use the modified the query', (done) => {
|
||||||
|
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||||
|
let q = req.query;
|
||||||
|
let otherQuery = new Parse.Query('MyObject');
|
||||||
|
otherQuery.equalTo('forced', true);
|
||||||
|
return Parse.Query.or(q, otherQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
let obj0 = new Parse.Object('MyObject');
|
||||||
|
obj0.set('forced', false);
|
||||||
|
|
||||||
|
let obj1 = new Parse.Object('MyObject');
|
||||||
|
obj1.set('forced', true);
|
||||||
|
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||||
|
let query = new Parse.Query('MyObject');
|
||||||
|
query.equalTo('forced', false);
|
||||||
|
query.find().then((results) => {
|
||||||
|
expect(results.length).toBe(2);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject queries', (done) => {
|
||||||
|
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||||
|
return Promise.reject('Do not run that query');
|
||||||
|
});
|
||||||
|
|
||||||
|
let query = new Parse.Query('MyObject');
|
||||||
|
query.find().then(() => {
|
||||||
|
fail('should not succeed');
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.code).toBe(1);
|
||||||
|
expect(err.message).toEqual('Do not run that query');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty where', (done) => {
|
||||||
|
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||||
|
let otherQuery = new Parse.Query('MyObject');
|
||||||
|
otherQuery.equalTo('some', true);
|
||||||
|
return Parse.Query.or(req.query, otherQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
rp.get({
|
||||||
|
url: 'http://localhost:8378/1/classes/MyObject',
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
|
},
|
||||||
|
}).then((result) => {
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ ParseCloud.afterDelete = function(parseClass, handler) {
|
|||||||
triggers.addTrigger(triggers.Types.afterDelete, className, handler, Parse.applicationId);
|
triggers.addTrigger(triggers.Types.afterDelete, className, handler, Parse.applicationId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ParseCloud.beforeFind = function(parseClass, handler) {
|
||||||
|
var className = getClassName(parseClass);
|
||||||
|
triggers.addTrigger(triggers.Types.beforeFind, className, handler, Parse.applicationId);
|
||||||
|
};
|
||||||
|
|
||||||
ParseCloud._removeAllHooks = () => {
|
ParseCloud._removeAllHooks = () => {
|
||||||
triggers._unregisterAll();
|
triggers._unregisterAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,12 @@ var triggers = require('./triggers');
|
|||||||
// Returns a promise for an object with optional keys 'results' and 'count'.
|
// Returns a promise for an object with optional keys 'results' and 'count'.
|
||||||
function find(config, auth, className, restWhere, restOptions, clientSDK) {
|
function find(config, auth, className, restWhere, restOptions, clientSDK) {
|
||||||
enforceRoleSecurity('find', className, auth);
|
enforceRoleSecurity('find', className, auth);
|
||||||
let query = new RestQuery(config, auth, className, restWhere, restOptions, clientSDK);
|
return triggers.maybeRunQueryTrigger(triggers.Types.beforeFind, className, restWhere, restOptions, config, auth).then((result) => {
|
||||||
return query.execute();
|
restWhere = result.restWhere || restWhere;
|
||||||
|
restOptions = result.restOptions || restOptions;
|
||||||
|
let query = new RestQuery(config, auth, className, restWhere, restOptions, clientSDK);
|
||||||
|
return query.execute();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// get is just like find but only queries an objectId.
|
// get is just like find but only queries an objectId.
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ export const Types = {
|
|||||||
beforeSave: 'beforeSave',
|
beforeSave: 'beforeSave',
|
||||||
afterSave: 'afterSave',
|
afterSave: 'afterSave',
|
||||||
beforeDelete: 'beforeDelete',
|
beforeDelete: 'beforeDelete',
|
||||||
afterDelete: 'afterDelete'
|
afterDelete: 'afterDelete',
|
||||||
|
beforeFind: 'beforeFind'
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseStore = function() {
|
const baseStore = function() {
|
||||||
@@ -154,6 +155,29 @@ export function getRequestObject(triggerType, auth, parseObject, originalParseOb
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRequestQueryObject(triggerType, auth, query, config) {
|
||||||
|
var request = {
|
||||||
|
triggerName: triggerType,
|
||||||
|
query: query,
|
||||||
|
master: false,
|
||||||
|
log: config.loggerController
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!auth) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
if (auth.isMaster) {
|
||||||
|
request['master'] = true;
|
||||||
|
}
|
||||||
|
if (auth.user) {
|
||||||
|
request['user'] = auth.user;
|
||||||
|
}
|
||||||
|
if (auth.installationId) {
|
||||||
|
request['installationId'] = auth.installationId;
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
// Creates the response object, and uses the request object to pass data
|
// Creates the response object, and uses the request object to pass data
|
||||||
// The API will call this with REST API formatted objects, this will
|
// The API will call this with REST API formatted objects, this will
|
||||||
// transform them to Parse.Object instances expected by Cloud Code.
|
// transform them to Parse.Object instances expected by Cloud Code.
|
||||||
@@ -216,6 +240,66 @@ function logTriggerErrorBeforeHook(triggerType, className, input, auth, error) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function maybeRunQueryTrigger(triggerType, className, restWhere, restOptions, config, auth) {
|
||||||
|
let trigger = getTrigger(className, triggerType, config.applicationId);
|
||||||
|
if (!trigger) {
|
||||||
|
return Promise.resolve({
|
||||||
|
restWhere,
|
||||||
|
restOptions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let parseQuery = new Parse.Query(className);
|
||||||
|
if (restWhere) {
|
||||||
|
parseQuery._where = restWhere;
|
||||||
|
}
|
||||||
|
if (restOptions) {
|
||||||
|
if (restOptions.include && restOptions.include.length > 0) {
|
||||||
|
parseQuery._include = restOptions.include.split(',');
|
||||||
|
}
|
||||||
|
if (restOptions.skip) {
|
||||||
|
parseQuery._skip = restOptions.skip;
|
||||||
|
}
|
||||||
|
if (restOptions.limit) {
|
||||||
|
parseQuery._limit = restOptions.limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let requestObject = getRequestQueryObject(triggerType, auth, parseQuery, config);
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
return trigger(requestObject);
|
||||||
|
}).then((result) => {
|
||||||
|
let queryResult = parseQuery;
|
||||||
|
if (result && result instanceof Parse.Query) {
|
||||||
|
queryResult = result;
|
||||||
|
}
|
||||||
|
let jsonQuery = queryResult.toJSON();
|
||||||
|
if (jsonQuery.where) {
|
||||||
|
restWhere = jsonQuery.where;
|
||||||
|
}
|
||||||
|
if (jsonQuery.limit) {
|
||||||
|
restOptions = restOptions || {};
|
||||||
|
restOptions.limit = jsonQuery.limit;
|
||||||
|
}
|
||||||
|
if (jsonQuery.skip) {
|
||||||
|
restOptions = restOptions || {};
|
||||||
|
restOptions.skip = jsonQuery.skip;
|
||||||
|
}
|
||||||
|
if (jsonQuery.include) {
|
||||||
|
restOptions = restOptions || {};
|
||||||
|
restOptions.include = jsonQuery.include;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
restWhere,
|
||||||
|
restOptions
|
||||||
|
};
|
||||||
|
}, (err) => {
|
||||||
|
if (typeof err === 'string') {
|
||||||
|
throw new Parse.Error(1, err);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// To be used as part of the promise chain when saving/deleting an object
|
// To be used as part of the promise chain when saving/deleting an object
|
||||||
// Will resolve successfully if no trigger is configured
|
// Will resolve successfully if no trigger is configured
|
||||||
|
|||||||
Reference in New Issue
Block a user